cesium加载百度地图并纠偏

百度地图使用的是百度地图自定义的BD09坐标系,是在火星坐标系的基础上二次加密得到。因此cesium在加载百度地图服务底图时,也存在偏差和纠偏的问题。是否纠偏,根据具体的业务需求。本文通过引用第三方纠偏库,同时支持cesium加载百度BD09坐标系及纠偏后的WGS84坐标系。

1. 引入第三方纠偏库

同上篇 cesium加载高德地图并纠偏-CSDN博客

2. 自定义百度纠偏切片方案

百度地图的切片方式和web墨卡托的切片方式不同,需要将百度切片方式转换为web墨卡托的切片方式,具体实现原理较为复杂,这里不去解释,后续有时间,在webgis的底层原理里进行分析。

2.1 自定义百度地图墨卡托投影类

BaiduMercatorProjection.js


const MC_BAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0]
const LL_BAND = [75, 60, 45, 30, 15, 0]
const MC2LL = [
  [
    1.410526172116255e-8,
    8.98305509648872e-6,
    -1.9939833816331,
    2.009824383106796e2,
    -1.872403703815547e2,
    91.6087516669843,
    -23.38765649603339,
    2.57121317296198,
    -0.03801003308653,
    1.73379812e7
  ],
  [
    -7.435856389565537e-9,
    8.983055097726239e-6,
    -0.78625201886289,
    96.32687599759846,
    -1.85204757529826,
    -59.36935905485877,
    47.40033549296737,
    -16.50741931063887,
    2.28786674699375,
    1.026014486e7
  ],
  [
    -3.030883460898826e-8,
    8.98305509983578e-6,
    0.30071316287616,
    59.74293618442277,
    7.357984074871,
    -25.38371002664745,
    13.45380521110908,
    -3.29883767235584,
    0.32710905363475,
    6.85681737e6
  ],
  [
    -1.981981304930552e-8,
    8.983055099779535e-6,
    0.03278182852591,
    40.31678527705744,
    0.65659298677277,
    -4.44255534477492,
    0.85341911805263,
    0.12923347998204,
    -0.04625736007561,
    4.48277706e6
  ],
  [
    3.09191371068437e-9,
    8.983055096812155e-6,
    0.00006995724062,
    23.10934304144901,
    -0.00023663490511,
    -0.6321817810242,
    -0.00663494467273,
    0.03430082397953,
    -0.00466043876332,
    2.5551644e6
  ],
  [
    2.890871144776878e-9,
    8.983055095805407e-6,
    -0.00000003068298,
    7.47137025468032,
    -0.00000353937994,
    -0.02145144861037,
    -0.00001234426596,
    0.00010322952773,
    -0.00000323890364,
    8.260885e5
  ]
]
const LL2MC = [
  [
    -0.0015702102444,
    1.113207020616939e5,
    1.704480524535203e15,
    -1.033898737604234e16,
    2.611266785660388e16,
    -3.51496691766537e16,
    2.659570071840392e16,
    -1.072501245418824e16,
    1.800819912950474e15,
    82.5
  ],
  [
    8.277824516172526e-4,
    1.113207020463578e5,
    6.477955746671608e8,
    -4.082003173641316e9,
    1.077490566351142e10,
    -1.517187553151559e10,
    1.205306533862167e10,
    -5.124939663577472e9,
    9.133119359512032e8,
    67.5
  ],
  [
    0.00337398766765,
    1.113207020202162e5,
    4.481351045890365e6,
    -2.339375119931662e7,
    7.968221547186455e7,
    -1.159649932797253e8,
    9.723671115602145e7,
    -4.366194633752821e7,
    8.477230501135234e6,
    52.5
  ],
  [
    0.00220636496208,
    1.113207020209128e5,
    5.175186112841131e4,
    3.796837749470245e6,
    9.920137397791013e5,
    -1.22195221711287e6,
    1.340652697009075e6,
    -6.209436990984312e5,
    1.444169293806241e5,
    37.5
  ],
  [
    -3.441963504368392e-4,
    1.113207020576856e5,
    2.782353980772752e2,
    2.485758690035394e6,
    6.070750963243378e3,
    5.482118345352118e4,
    9.540606633304236e3,
    -2.71055326746645e3,
    1.405483844121726e3,
    22.5
  ],
  [
    -3.218135878613132e-4,
    1.113207020701615e5,
    0.00369383431289,
    8.237256402795718e5,
    0.46104986909093,
    2.351343141331292e3,
    1.58060784298199,
    8.77738589078284,
    0.37238884252424,
    7.45
  ]
]

class BaiduMercatorProjection {
  constructor() {
    this.isWgs84 = false
  }
 
  convertMC2LL(point) {
    if (!point) {
      return { lng: 0, lat: 0 }
    }
    let lnglat = {}
    if (this.isWgs84) {
      lnglat.lng = (point.lng / 20037508.34) * 180
      let mmy = (point.lat / 20037508.34) * 180
      lnglat.lat =
        (180 / Math.PI) *
        (2 * Math.atan(Math.exp((mmy * Math.PI) / 180)) - Math.PI / 2)
      return {
        lng: lnglat['lng'].toFixed(6),
        lat: lnglat['lat'].toFixed(6)
      }
    }

    let temp = {
      lng: Math.abs(point['lng']),
      lat: Math.abs(point['lat'])
    }

    let factor = undefined
    for (let i = 0; i < MC_BAND.length; i++) {
      if (temp['lat'] >= MC_BAND[i]) {
        factor = MC2LL[i]
        break
      }
    }
    lnglat = this.convertor(point, factor)
    return {
      lng: lnglat['lng'].toFixed(6),
      lat: lnglat['lat'].toFixed(6)
    }
  }

  convertLL2MC(point) {
    if (!point) {
      return { lng: 0, lat: 0 }
    }
    if (
      point['lng'] > 180 ||
      point['lng'] < -180 ||
      point['lat'] > 90 ||
      point['lat'] < -90
    ) {
      return point
    }

    if (this.isWgs84) {
      let mercator = {}
      let earthRad = 6378137.0
      mercator.lng = ((point.lng * Math.PI) / 180) * earthRad
      let a = (point.lat * Math.PI) / 180
      mercator.lat =
        (earthRad / 2) * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)))

      return {
        lng: parseFloat(mercator['lng'].toFixed(2)),
        lat: parseFloat(mercator['lat'].toFixed(2))
      }
    }

    point['lng'] = this.getLoop(point['lng'], -180, 180)
    point['lat'] = this.getRange(point['lat'], -74, 74)
    let temp = { lng: point['lng'], lat: point['lat'] }
    let factor = undefined
    for (let i = 0; i < LL_BAND.length; i++) {
      if (temp['lat'] >= LL_BAND[i]) {
        factor = LL2MC[i]
        break
      }
    }
    if (!factor) {
      for (let i = 0; i < LL_BAND.length; i++) {
        if (temp['lat'] <= -LL_BAND[i]) {
          factor = LL2MC[i]
          break
        }
      }
    }
    let mc = this.convertor(point, factor)
    return {
      lng: parseFloat(mc['lng'].toFixed(2)),
      lat: parseFloat(mc['lat'].toFixed(2))
    }
  }


  convertor(fromPoint, factor) {
    if (!fromPoint || !factor) {
      return { lng: 0, lat: 0 }
    }
    let x = factor[0] + factor[1] * Math.abs(fromPoint['lng'])
    let temp = Math.abs(fromPoint['lat']) / factor[9]
    let y =
      factor[2] +
      factor[3] * temp +
      factor[4] * temp * temp +
      factor[5] * temp * temp * temp +
      factor[6] * temp * temp * temp * temp +
      factor[7] * temp * temp * temp * temp * temp +
      factor[8] * temp * temp * temp * temp * temp * temp
    x *= fromPoint['lng'] < 0 ? -1 : 1
    y *= fromPoint['lat'] < 0 ? -1 : 1
    return {
      lng: x,
      lat: y
    }
  }

  getRange(v, a, b) {
    if (a != null) {
      v = Math.max(v, a)
    }
    if (b != null) {
      v = Math.min(v, b)
    }
    return v
  }

  getLoop(v, a, b) {
    while (v > b) {
      v -= b - a
    }
    while (v < a) {
      v += b - a
    }
    return v
  }


  lngLatToMercator(point) {
    return this.convertLL2MC(point)
  }

  mercatorToLngLat(point) {
    return this.convertMC2LL(point)
  }
}

export default BaiduMercatorProjection

2.2 自定义百度地图切片方案

BaiduMercatorTilingScheme.js

import BaiduMercatorProjection from './BaiduMercatorProjection'
import gcoord from 'gcoord';


class BaiduMercatorTilingScheme extends Cesium.WebMercatorTilingScheme {
  constructor(options) {
    super(options)
    let projection = new BaiduMercatorProjection()
    this._projection.project = function(cartographic, result) {
      result = result || {}
      // wgs84坐标系转BD09坐标系
      result = gcoord.transform([
        Cesium.Math.toDegrees(cartographic.longitude),
         Cesium.Math.toDegrees(cartographic.latitude)
      ], gcoord.WGS84, gcoord.BD09)


      result[0] = Math.min(result[0], 180)
      result[0] = Math.max(result[0], -180)
      result[1] = Math.min(result[1], 74.000022)
      result[1] = Math.max(result[1], -71.988531)


      result = projection.lngLatToMercator({
        lng: result[0],
        lat: result[1]
      })
      return new Cesium.Cartesian2(result.lng, result.lat)
    }
    this._projection.unproject = function(cartesian, result) {
      result = result || {}
      result = projection.mercatorToLngLat({
        lng: cartesian.x,
        lat: cartesian.y
      })
      // BD09坐标系转wgs84坐标系
      result = gcoord.transform([
        result.lng,
        result.lat
      ], gcoord.BD09, gcoord.WGS84)


      return new Cesium.Cartographic(
        Cesium.Math.toRadians(result[0]),
        Cesium.Math.toRadians(result[1])
      )
    }
    this.resolutions = options.resolutions || []
  }


  tileXYToNativeRectangle(x, y, level, result) {
    const tileWidth = this.resolutions[level]
    const west = x * tileWidth
    const east = (x + 1) * tileWidth
    const north = ((y = -y) + 1) * tileWidth
    const south = y * tileWidth


    if (!Cesium.defined(result)) {
      return new Cesium.Rectangle(west, south, east, north)
    }


    result.west = west
    result.south = south
    result.east = east
    result.north = north
    return result
  }


  positionToTileXY(position, level, result) {
    const rectangle = this._rectangle
    if (!Cesium.Rectangle.contains(rectangle, position)) {
      return undefined
    }
    const projection = this._projection
    const webMercatorPosition = projection.project(position)
    if (!Cesium.defined(webMercatorPosition)) {
      return undefined
    }
    const tileWidth = this.resolutions[level]
    const xTileCoordinate = Math.floor(webMercatorPosition.x / tileWidth)
    const yTileCoordinate = -Math.floor(webMercatorPosition.y / tileWidth)
    if (!Cesium.defined(result)) {
      return new Cesium.Cartesian2(xTileCoordinate, yTileCoordinate)
    }
    result.x = xTileCoordinate
    result.y = yTileCoordinate
    return result
  }
}


export default BaiduMercatorTilingScheme

2.3 自定义百度地图提供器

BaiduImageryProvider.js

import BaiduMercatorTilingScheme from './BaiduMercatorTilingScheme'
// 影像底图
const IMG_URL = 'http:maponline{s}.bdimg.com/starpic/u=x={x};y={y};z={z};v=009;type=sate&qt=satepc&fm=46&app=webearth2&v=009'
// 影像标注
const IMG_LABEL_URL = 'http:maponline{s}.bdimg.com/tile/?x={x}&y={y}&z={z}&qt=vtile&styles=sl&showtext=1&scaler=2&v=083'
// 电子底图无标注
const VEC_URL ='http:maponline{s}.bdimg.com/tile/?x={x}&y={y}&z={z}&qt=vtile&styles=pl&showtext=0&scaler=1&v=083'
// 电子地图有标注
const VEC_LABEL_URL ='http:maponline{s}.bdimg.com/tile/?x={x}&y={y}&z={z}&qt=vtile&styles=pl&showtext=1&scaler=2&v=083'

class BaiduImageryProvider {
  constructor(options = {}) {
    switch(options.type) {
      case 'img_w':
        this._url = IMG_URL
        break
      case 'img_label_w':
        this._url = IMG_LABEL_URL
        break
      case 'vec_w':
        this._url = VEC_URL
        break
      case 'vec_label_w':
        this._url = VEC_LABEL_URL
        break
    }
    this._tileWidth = 256
    this._tileHeight = 256
    this._maximumLevel = 18
    this._crs = options.crs || 'BD09'
    if (options.crs === 'WGS84') {
      let resolutions = []
      for (let i = 0; i < 19; i++) {
        resolutions[i] = 256 * Math.pow(2, 18 - i)
      }
      this._tilingScheme = new BaiduMercatorTilingScheme({
        resolutions,
        rectangleSouthwestInMeters: new Cesium.Cartesian2(
          -20037726.37,
          -12474104.17
        ),
        rectangleNortheastInMeters: new Cesium.Cartesian2(
          20037726.37,
          12474104.17
        )
      })
    } else {
      this._tilingScheme = new Cesium.WebMercatorTilingScheme({
        rectangleSouthwestInMeters: new Cesium.Cartesian2(-33554054, -33746824),
        rectangleNortheastInMeters: new Cesium.Cartesian2(33554054, 33746824)
      })
    }
    this._rectangle = this._tilingScheme.rectangle
    this._credit = undefined
    this._style = options.style || 'normal'
  }

  get url() {
    return this._url
  }

  get token() {
    return this._token
  }

  get tileWidth() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'tileWidth must not be called before the imagery provider is ready.'
      )
    }
    return this._tileWidth
  }

  get tileHeight() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'tileHeight must not be called before the imagery provider is ready.'
      )
    }
    return this._tileHeight
  }

  get maximumLevel() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'maximumLevel must not be called before the imagery provider is ready.'
      )
    }
    return this._maximumLevel
  }

  get minimumLevel() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'minimumLevel must not be called before the imagery provider is ready.'
      )
    }
    return 0
  }

  get tilingScheme() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'tilingScheme must not be called before the imagery provider is ready.'
      )
    }
    return this._tilingScheme
  }

  get rectangle() {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'rectangle must not be called before the imagery provider is ready.'
      )
    }
    return this._rectangle
  }

  get ready() {
    return !!this._url
  }

  get credit() {
    return this._credit
  }

  get hasAlphaChannel() {
    return true
  }

  getTileCredits(x, y, level) {}

  requestImage(x, y, level) {
    if (!this.ready) {
      throw new Cesium.DeveloperError(
        'requestImage must not be called before the imagery provider is ready.'
      )
    }
    let xTiles = this._tilingScheme.getNumberOfXTilesAtLevel(level)
    let yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level)
    let url = this._url
      .replace('{z}', level)
      .replace('{s}', String(1))
    if (this._crs === 'WGS84') {
      url = url.replace('{x}', String(x)).replace('{y}', String(-y))
    } else {
      url = url
        .replace('{x}', String(x - xTiles / 2))
        .replace('{y}', String(yTiles / 2 - y - 1))
    }
    return Cesium.ImageryProvider.loadImage(this, url)
  }
}

export default BaiduImageryProvider

3. 完整示例代码

MapWorks.js

/*
 * @Description: 
 * @Author: maizi
 * @Date: 2023-04-03 17:34:21
 * @LastEditTime: 2023-04-04 13:55:20
 * @LastEditors: maizi
 */
import BaiduImageryProvider from './BaiduImageryProvider'
// 初始视图定位在中国
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 addBdLayer(options) {
  const layerProvider = new BaiduImageryProvider(options);
  viewer.imageryLayers.addImageryProvider(layerProvider);
}
function changeBaseMap(type,enabled) {
  removeAll()
  switch(type) {
    case 0: //影像地图
      addBdLayer({
        type: 'img_w',
        crs: enabled ? 'WGS84' : null
      })
      addBdLayer({
        type: 'img_label_w',
        crs: enabled ? 'WGS84' : null
      })
      break
    case 1: //电子地图无标注
      addBdLayer({
        type: 'vec_w',
        crs: enabled ? 'WGS84' : null
      })
      break
    case 2: //电子地图有标注
      addBdLayer({
        type: 'vec_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,
  addBdLayer,
  changeBaseMap,
  setView,
  removeAll,
  destroy
}

 BdLayer.vue

<!--
 * @Description: 
 * @Author: maizi
 * @Date: 2023-04-03 11:34:28
 * @LastEditTime: 2023-04-04 15:41:15
 * @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>
    </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>

4 运行结果

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值