---
title: Cesium与CAD图叠加显示
source: https://vjmap.com/guide/cesiummap.html
---

# Cesium与CAD图叠加显示

   Cesium是一个非常优秀的三维地球GIS引擎, 开源且免费, 能够加载各种符合标准的地图图层，瓦片图、矢量图等都支持。在实际中经常会遇到需要将CAD的图纸叠加到cesium上面，与地表地形叠加显示查看。那如何实现在Cesium中实现与CAD的DWG图叠加显示分析呢？

#  技术分析

 业内一般的做法步骤为:

- (1) 在AutoCAD中把DWG图导出为DXF明码格式文件
- (2) 利用第三方工具如GDAL，把dxf转为shp等GIS数据格式
- (3) 利用开源的gis服务，如geoserver或mapserver把shp文件发布成gis服务
- (4) 在cesium中加载gis服务的瓦片图层

上面的方案的优点很明显，基本上利用开源的方案能把流程跑通。但在实际项目中缺点也很明显，主要是在步骤 (1)(2), 因为dwg是私有格式，通过dwg转出成dxf再转化成shp文件时，会丢失图中的很多数据，CAD数据类型较为丰富，支持点、块符号、线、面、多段线、椭圆、块、文字等多种数据类型，而转换到GIS中，只转换为点、线、面、注记等类型，这使得CAD图形数据不能很好的满足GIS的要求，如：CAD中的Text数据类型，直接转换后只转换为GIS中的Point。通过这种转换去绘制，会导致和原始CAD图形绘制不太一样， 同时CAD里面有线型、字体也会导致绘制上的差异性。

  唯杰地图vjmap完全兼容AutoCAD格式的DWG文件，无需通过转换可直接发布成gis的WMS格式，可有效解决上面问题。实现步骤为:

- (1) 上传DWG图形，利用唯杰地图发布为WMS图层
- (2) 在cesium中加载wms瓦片图层

# 实现

先上效果图，（红色的线为cad图层)

![cesiumcad.gif](https://vjmap.com/blogimages/cesiumcad.gif)



## 具体步骤:

- 确定CAD图的坐标系

  在打开的CAD图中，随便找了图中的一个点坐标，坐标为”614802.89177, 3257472.36997”

   那怎么确定这个坐标是哪个坐标系呢？ 如果已经知道坐标能不能倒推坐标系呢？
   

   可以通过 [通过CAD图中的坐标确定坐标系](/guide/cadepsg.html) 这个工具。也可以用如下方法，这里我们只讨论高斯克吕格投影。

   假设已经知道数据的投影是高斯克吕格，怎样判定是6°带还是3°带？怎样判定中央经线是多少呢？

   如果坐标范围是6位，我们可以判定坐标系不带带号的。如果Y值范围在333000m~667000m，可能是3°分带法；如果Y值范围在166 000m~834000m，一定是6°分带法。如果取值范围重合的部分有可能3°分带法也可能在6°分带法。

   如果坐标范围是8位，我们可以判定坐标系是以带号命名，并且根据带号可以知道是3°带还是6°带。我们国家的经度范围大致在73°33′E~105°05′E，所以如果是以带号命名，带号在13~23带之间的，说明是6°分带法；带号在25~45带之间的，说明是3°分带法。

  在这个图中，坐标为”614802“为6位，大概率能确定为省去了带系的3°分带坐标。并且根据图中图框中坐标系写的为2000坐标系，同时地点为重庆附近。而重庆的经度差不多是以106开头。我们去epsg官网查询得知，该坐标系应该是"EPSG:4544".

  ![image-20220801192221302](https://vjmap.com/blogimages/image-20220801192221302.png)

- 上传打开CAD图

  在[唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) 上传CAD图形。

- 在cesium增加cad图层

   ~~~javascript
   let imageryProvider= new Cesium.WebMapTileServiceImageryProvider({
   			url: "http://t0.tianditu.gov.cn/img_w/wmts?tk=3346bb6ad29b5013c5952cf1117b80e9",
   			layer: "img",
   			style: "default",
   			tileMatrixSetID: "w",
   			format: "tiles",
   			maximumLevel: 14,
   		});
   
   		let viewer = new Cesium.Viewer('cesiumContainer', {
   			imageryProvider: imageryProvider,
   			contextOptions: {
   				webgl: {
   					alpha: true
   				}
   			},
   			selectionIndicator: false,
   			animation: false, //是否显示动画控件
   			baseLayerPicker: false, //是否显示图层选择控件
   			geocoder: false, //是否显示地名查找控件
   			timeline: false, //是否显示时间线控件
   			sceneModePicker: false, //是否显示投影方式控件
   			navigationHelpButton: false, //是否显示帮助信息控件
   			infoBox: false, //是否显示点击要素之后显示的信息
   			fullscreenButton: false,
   			shouldAnimate: true //动画播放
   		});
   
   		var layers = viewer.scene.imageryLayers;
   
   		layers.addImageryProvider(
   			new Cesium.WebMapServiceImageryProvider({
   			    // 通过vjmap的wms加载cad图
   				url: `https://vjmap.com/server/api/v1/map/cmd/wms/
   sys_cadcesium/v1?mapbounds=&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:4326&transparent=true&width=256&height=256&layers=se92105f73&crs=EPSG:4544&fourParameter=&token=your token`,
   			})
   		);
   ~~~

上面的代码好像简单明了，可实际中发现，在cesium中，cad可以和天地图影像叠加。但在放大的过程中，发现wms生成的瓦片有错开的问题。如下图所示:

![image-20220801193313571](https://vjmap.com/blogimages/image-20220801193313571.png)

## 问题分析

在cesium中，是WGS84坐标系，而CAD图是4544坐标系。两个不同的椭球体之间转换，在WMS中瓦片生成是根据坐标转换生成的瓦片数据，而由于坐标转换是非线性的，导致生成的图片内容不能完全对上。所以有错开的问题。

那怎么解决呢？

最终的方案是：

- 先通过四参数，把CAD与墨卡托3857的转换参数求出来

- 再墨卡托3857转wgs4326

  通过这样的转换，数据转换是线性转换了，就不会有错开的问题了。

  具体代码如下:

  ```javascript
  // --Cesium中加载CAD图(WMS图层)--
  // 地图服务对象
  let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
  // 打开地图
  let mapId = "sys_cadcesium";
  let res = await svc.openMap({
      mapid: mapId, // 地图ID
      mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
      style: vjmap.openMapDarkStyle() // div为深色背景颜色时，这里也传深色背景样式
  })
  if (res.error) {
      // 如果打开出错
      message.error(res.error)
  }
  let layer = res.layer;//图层样式名
  
  let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
  let boundsArray = mapBounds.toPointArray();
  
  // 得到坐标转换后的墨卡托点
  let mktPoints = await svc.cmdTransform("EPSG:4544", "EPSG:3857", boundsArray.map(a => vjmap.geoPoint(a)));
  // cad上面的点坐标
  let cadPoints = [...boundsArray];
  // 通过坐标参数求出四参数
  let fourparam = vjmap.coordTransfromGetFourParamter(
      mktPoints.map(a => vjmap.geoPoint(a)),
      cadPoints.map(a => vjmap.geoPoint(a)),
      true
  );
  
  if (typeof Cesium !== "object") {
      // 如果没有环境
      await vjmap.addScript([{
          src: "js/Cesium/Cesium.js"
      }, {
          src: "js/Cesium/Widgets/widgets.css"
      }])
  }
  
  let imageryProvider= new Cesium.WebMapTileServiceImageryProvider({
      url: "https://t0.tianditu.gov.cn/img_w/wmts?tk=3346bb6ad29b5013c5952cf1117b80e9",
      layer: "img",
      style: "default",
      tileMatrixSetID: "w",
      format: "tiles",
      maximumLevel: 14,
  });
  
  let viewer = new Cesium.Viewer('map', {
      imageryProvider: imageryProvider,
      contextOptions: {
          webgl: {
              alpha: true
          }
      },
      selectionIndicator: false,
      animation: false, //是否显示动画控件
      baseLayerPicker: false, //是否显示图层选择控件
      geocoder: false, //是否显示地名查找控件
      timeline: false, //是否显示时间线控件
      sceneModePicker: false, //是否显示投影方式控件
      navigationHelpButton: false, //是否显示帮助信息控件
      infoBox: false, //是否显示点击要素之后显示的信息
      fullscreenButton: false,
      shouldAnimate: true //动画播放
  });
  
  
  var layers = viewer.scene.imageryLayers;
  
  // 加一个高德注记图层
  layers.addImageryProvider(
      new Cesium.UrlTemplateImageryProvider({
          url: "https://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8",
      })
  );
  
  
  // 增加cad的wms图层
  let wmsUrl = svc.wmsTileUrl({
      mapid: mapId, // 地图id
      layers: layer, // 图层名称
      bbox: '', // bbox这里不需要传，cesium会自动加上
      srs: "EPSG:4326", // cesium地图是wgs84
      crs: "EPSG:3857", // 先把wgs84转3857
      fourParameter: [fourparam.dx,fourparam.dy,fourparam.scale,fourparam.rotate] // 转成 3857后，再用四参数去转成cad坐标
  })
  layers.addImageryProvider(
      new Cesium.WebMapServiceImageryProvider({
          url: wmsUrl,
      })
  );
  
  
  // cad图坐标转web wgs84坐标
  const cadToWebCoordinate = point => {
      // 先用四参数转成墨卡托3857，再墨卡托3857转wgs84
      // 这里需要用四参数反算，因为上面的四参数是3857转cad的，这里是反过来，要cad转3857，所以需要反算
      let ptMkt = vjmap.coordTransfromByInvFourParamter(vjmap.geoPoint(point), fourparam);
      return vjmap.Projection.mercator2LngLat(ptMkt);// 再墨卡托3857转经纬度
  }
  // 转web wgs84坐标转cad图坐标
  const webTocadCoordinate = point => {
      // 先wgs84转墨卡托3857,再用四参数转成cad
      let ptMkt = vjmap.Projection.lngLat2Mercator(vjmap.geoPoint(point));
      return vjmap.coordTransfromByFourParamter(ptMkt, fourparam);
  }
  // 根据cad图的中心点，计算wgs84的中心点坐标
  let cadCenter = mapBounds.center();
  let webCenter = cadToWebCoordinate(cadCenter);
  //设置初始位置
  viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(webCenter[0], webCenter[1], 30000)
  });
  
  // 如果需要在地图上查询cad的实体坐标，可通过svc.rectQueryFeature来实现，需要传入两个cad的点坐标范围
  // 可以通过 webTocadCoordinate 接口把wgs84的坐标转成 cad 的坐标去查询.
  
  ```

  出于对数据保密的需要，我们对CAD图进行了脱敏处理，叠加的效果如下

  ![image-20220801194426286](https://vjmap.com/blogimages/image-20220801194426286.png)

访问 [https://vjmap.com/demo/#/demo/map/web/07cesiumCadWmsLayer](https://vjmap.com/demo/#/demo/map/web/07cesiumCadWmsLayer) ，查看效果和代码
