高德地图采用中国国家测绘局制订的地理信息系统的坐标系GCJ02,即火星坐标系,他是在wgs84坐标系的基础上进行一次加密。因此cesium在加载高德地图服务底图时,会存在偏差和纠偏的问题。是否纠偏,根据具体的业务需求。本文通过引用第三方纠偏库,同时支持cesium加载高德底图GCJ02坐标系及纠偏后的WGS84坐标系。
1. 引入第三方纠偏库
gcoord(geographic coordinates)是一个处理地理坐标转换的JS库,用来修正百度地图、高德地图及其它互联网地图坐标系不统一的问题。
1.1 安装
npm i gcoord
1.2 引入
import gcoord from 'gcoord';
2. 自定义高德纠偏切片方案
2.1 WebMercatorTilingScheme类
该类是cesium自带的EPSG:3857切片方案,因为高德的加密偏移问题,需要基于该类进行扩展,自定义高德纠偏切片方案。其中的原理,这里不去解释,后续有时间,另起专栏讲讲webgis的底层原理。
AmapMercatorTilingScheme.js
/*
* @Description:
* @Author: maizi
* @Date: 2023-04-03 17:36:41
* @LastEditTime: 2023-04-03 21:02:36
* @LastEditors: maizi
*/
import gcoord from 'gcoord';
class AmapMercatorTilingScheme extends Cesium.WebMercatorTilingScheme {
constructor(options) {
super(options)
let projection = new Cesium.WebMercatorProjection()
this._projection.project = function(cartographic, result) {
//WGS84转GCJ02坐标
result = gcoord.transform([
Cesium.Math.toDegrees(cartographic.longitude),
Cesium.Math.toDegrees(cartographic.latitude)
], gcoord.WGS84, gcoord.GCJ02)
result = projection.project(
new Cesium.Cartographic(
Cesium.Math.toRadians(result[0]),
Cesium.Math.toRadians(result[1])
)
)
return new Cesium.Cartesian2(result.x, result.y)
}
this._projection.unproject = function(cartesian, result) {
let cartographic = projection.unproject(cartesian)
//GCJ02转WGS84坐标
result = gcoord.transform([
Cesium.Math.toDegrees(cartographic.longitude),
Cesium.Math.toDegrees(cartographic.latitude)
], gcoord.GCJ02, gcoord.WGS84)
return new Cesium.Cartographic(
Cesium.Math.toRadians(result[0]),
Cesium.Math.toRadians(result[1])
)
}
}
}
export default AmapMercatorTilingScheme
2.2 UrlTemplateImageryProvider类
该类允许用户通过url模板实现瓦片底图的加载,基于该类进行扩展,使用上述的切片方案自定义高德底图提供器。
AmapImageryProvider .js
/*
* @Description:
* @Author: maizi
* @Date: 2023-04-03 17:36:41
* @LastEditTime: 2023-04-03 21:02:36
* @LastEditors: maizi
*/
import AmapMercatorTilingScheme from './AmapMercatorTilingScheme'
//影像(无标注)
const IMG_URL = 'https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}'
//电子(无标注)
const VEC_URL = 'https://wprd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7<ype=3&x={x}&y={y}&z={z}'
//道路(无标注)
const ROAD_URL = 'https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8<ype=11&x={x}&y={y}&z={z}'
//电子(有标注)
const VEC_LABEL_URL = 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
//道路(有标注)
const ROAD_LABEL_URL = 'http://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
class AmapImageryProvider extends Cesium.UrlTemplateImageryProvider {
constructor(options = {}) {
switch(options.type) {
case 'img_w':
options['url'] = IMG_URL
break
case 'vec_w':
options['url'] = VEC_URL
break
case 'road_w':
options['url'] = ROAD_URL
break
case 'vec_label_w':
options['url'] = VEC_LABEL_URL
break
case 'road_label_w':
options['url'] = ROAD_LABEL_URL
break
}
if (!options.subdomains || !options.subdomains.length) {
options['subdomains'] = ['1', '2', '3', '4']
}
if (options.crs === 'WGS84') {
options['tilingScheme'] = new AmapMercatorTilingScheme()
}
super(options)
}
}
export default AmapImageryProvider
3. 完整示例代码
GdLayer.vue
<!--
* @Description:
* @Author: maizi
* @Date: 2023-04-03 11:34:28
* @LastEditTime: 2023-04-03 20:23:51
* @LastEditors: maizi
-->
<template>
<div id="container">
<div class="pane_container">
<el-checkbox v-model="checked" class="my-check">纠偏</el-checkbox>
<el-button size="small" @click="changeMap(0)">影像地图</el-button>
<el-button size="small" @click="changeMap(1)">电子地图(无标注)</el-button>
<el-button size="small" @click="changeMap(2)">街道图(无标注)</el-button>
<el-button size="small" @click="changeMap(3)">电子地图(有标注)</el-button>
<el-button size="small" @click="changeMap(4)">街道图(有标注)</el-button>
</div>
</div>
</template>
<script>
import * as MapWorks from './js/MapWorks'
export default {
name: 'GdLayer',
data(){
return {
checked: false
}
},
mounted() {
this.init();
},
beforeDestroy(){
//实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
MapWorks.destroy();
},
methods:{
init(){
let container = document.getElementById("container");
MapWorks.initMap(container)
MapWorks.changeBaseMap(0)
let coords = [104.0634012222290039, 30.6598806381225586, 500]
let hpr = {
heading: 0,
pitch:-90.0,
roll: 0
}
MapWorks.setView(coords, hpr)
},
changeMap(index){
MapWorks.changeBaseMap(index, this.checked)
}
}
}
</script>
<style lang="scss" scoped>
#container{
width: 100%;
height: 100%;
background: rgba(7, 12, 19, 1);
overflow: hidden;
background-size: 40px 40px, 40px 40px;
background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
.pane_container{
margin-top: 12px;
margin-left: 12px;
position: absolute;
padding: 10px 15px;
border-radius: 4px;
border: 1px solid rgba(128, 128, 128, 0.5);
color: #ffffff;
background: rgba(0, 0, 0, 0.4);
box-shadow: 0 3px 14px rgb(128 128 128 / 50%);
z-index: 2;
.my-check{
margin-right: 12px;
}
}
}
</style>
MapWorks.js
/*
* @Description:
* @Author: maizi
* @Date: 2023-04-03 17:34:21
* @LastEditTime: 2023-04-03 20:24:37
* @LastEditors: maizi
*/
import AmapImageryProvider from './AmapImageryProvider'
// 初始视图定位在中国
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);
let viewer = null;
function initMap(container) {
viewer = new Cesium.Viewer(container, {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
scene3DOnly: true,
orderIndependentTranslucency: false,
contextOptions: {
webgl: {
alpha: true
}
}
})
viewer._cesiumWidget._creditContainer.style.display = 'none'
viewer.scene.fxaa = true
viewer.scene.postProcessStages.fxaa.enabled = true
if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
// 判断是否支持图像渲染像素化处理
viewer.resolutionScale = window.devicePixelRatio
}
// 移除默认影像
removeAll()
// 地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true
// 背景色
viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
}
function addGdLayer(options) {
const layerProvider = new AmapImageryProvider(options);
viewer.imageryLayers.addImageryProvider(layerProvider);
}
function changeBaseMap(type,enabled) {
removeAll()
switch(type) {
case 0: //影像(无标注)
addGdLayer({
type: 'img_w',
crs: enabled ? 'WGS84' : null
})
break
case 1: //电子(无标注)
addGdLayer({
type: 'vec_w',
crs: enabled ? 'WGS84' : null
})
break
case 2: //道路(无标注)
addGdLayer({
type: 'road_w',
crs: enabled ? 'WGS84' : null
})
break
case 3: //电子(有标注)
addGdLayer({
type: 'vec_label_w',
crs: enabled ? 'WGS84' : null
})
break
case 4: //道路(有标注)
addGdLayer({
type: 'road_label_w',
crs: enabled ? 'WGS84' : null
})
break
}
}
function removeAll() {
viewer.imageryLayers.removeAll();
}
function destroy() {
viewer.entities.removeAll();
viewer.imageryLayers.removeAll();
viewer.destroy();
}
function setView(coords,hpr) {
viewer.scene.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(coords[0], coords[1], coords[2]),
orientation: {
heading: Cesium.Math.toRadians(hpr.heading),
pitch: Cesium.Math.toRadians(hpr.pitch),
roll: Cesium.Math.toRadians(hpr.roll),
}
});
}
export {
initMap,
addGdLayer,
changeBaseMap,
setView,
removeAll,
destroy
}