ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cesium加载天地图做标记:坐标转换与性能优化实战

Cesium加载天地图做标记:坐标转换与性能优化实战 Cesium项目里折腾天地图算是国内做三维GIS几乎绕不开的一道坎。之前写过一篇Cesium基础的入门踩坑记录这篇接着往下走专门聊“天地图标记”这件事。先说我个人的结论Cesium默认的在线影像源在国界、地名这些细节上跟咱们实际业务里常用的天地图比起来差了不是一星半点。所以把天地图接进来再用Entity API做标记是很多三维项目上线前的标准动作。这篇东西适合谁看一种是刚把Cesium跑起来、准备做点正经功能的新手另一种是用过Leaflet或OpenLayers做二维地图、现在要转三维的老手。我会从天地图瓦片加载、坐标转换、标记点绘制、线面绘制一路讲到3DTiles的标记和常见的崩溃排查。所有代码我都用当前能跑的稳定写法直接在Cesium 1.9x以上版本实测过你可以直接复制到自己的项目里改。1. 天地图接入前的设计与踩坑预判1.1 为什么非要用天地图很多刚接触Cesium的朋友会有个疑问Cesium不是自带了一套全球影像吗为什么还要费劲去接天地图这里有几个非常现实的原因。第一是合规性。Cesium自带的底图服务在境内做商用项目时数据来源和审图号是个大问题很多政企项目审计过不了。天地图是国家地理信息公共服务平台在资质和数据合规性上省心得多。第二是数据新鲜度。Cesium默认影像源在某些地区的数据更新非常滞后城镇细节、道路信息跟实际情况对不上。天地图在国内的数据更新频率和精度做项目时更有保障。第三是地名标注。天地图有专门的中文注记层村、镇、街道这些地名信息比Cesium默认底图完整得多。如果你做的是跟民生、城管、应急相关的项目这一点直接决定演示效果。1.2 接入前必须想清楚的三件事在动手写代码之前建议你先想清楚这三件事能省掉后面很多返工的时间。第一步要想清楚的是坐标系。Cesium原生用的是WGS84经纬度坐标系而天地图的瓦片是按照Web墨卡托EPSG:3857来切片的。好在天地图官方提供的服务已经做了适配瓦片请求时可以直接用经纬度来组织真正需要手动做坐标转换的场景集中在标绘数据回传和与其他系统对接时。这个问题我会在第三章详细说。第二步是搞清楚天地图的调用凭证。天地图的瓦片服务需要tk参数token去天地图官网注册开发者账号创建应用后就能拿到。注意个人开发者和企业开发者能申请到的服务配额不一样做商用项目建议直接申请企业认证不然并发一高瓦片加载就会变慢。第三步是规划好你项目里到底要加载哪些图层。天地图提供的常用服务有影像底图img_w、影像注记cia_w、矢量底图vec_w、矢量注记cva_w共四个。这个命名规律记好后面写代码时直接用。1.3 做标记功能的整体技术选型标记这个事在Cesium里其实有三条技术路线。第一条路线是用Entity API这也是本篇的重点。Cesium的Entity体系封装得非常好点、线、面、文字标签都有现成的类代码写起来跟搭积木一样对新手友好动态增删也方便。缺点是大量Entity同时存在时性能会下降不过几百上千个标记的场景完全没问题。第二条路线是Primitive API性能更高适合成千上万个标记的超大数据量场景但API非常底层写起来繁琐调试成本高。第三条路线是干脆用3DTiles把标记数据做成三维瓦片适合海量点数据或者需要跟倾斜摄影模型叠加的场景但生产流程复杂需要额外的数据加工工具。我自己的习惯是项目前期和中期用Entity到了后期真有性能瓶颈了再针对热点数据做Primitive优化。做标记功能简单直接永远是第一原则。2. 天地图接入实操从零搭建可运行代码2.1 申请天地图开发者密钥并确认服务地址天地图的token申请流程不复杂但有几个细节值得注意。打开天地图官网注册账号后进入“控制台”选择“创建应用”。应用类型一定要根据你的实际使用场景来填是个人的选个人是企业的选企业。我在实际项目里发现一个有意思的点同一个token在不同时间段请求瓦片响应速度是有差异的高峰时段明显会变慢。所以如果你要做的是演示系统建议在代码里做一层瓦片缓存机制或者提前把常用区域的瓦片预加载缓存下来。这个我后面会展开说。拿到token后天地图官方给出的瓦片服务地址一般是这种格式http://t0.tianditu.gov.cn/img_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERimgSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk你的密钥注意这里有个坑天地图官方文档里给的示例地址往往是http开头的但你的项目如果部署在https环境下直接引用http地址会触发浏览器的混合内容拦截瓦片加载不出来。解决办法是看你的项目环境全站https就把地图服务地址改成https开头天地图现在支持https访问。域名方面t0可以直接用t0到t7轮询做负载均衡用也可以全用t0。2.2 Cesium中加载天地图影像和注记层在Cesium里加载天地图思路跟加载其他第三方瓦片服务一样用ImageryProvider的子类。天地图的WMTS服务比较标准但你直接写WMTS的Provider会涉及一堆坐标转换和参数映射麻烦不说还容易出错。更干净的做法是直接用UrlTemplateImageryProvider把瓦片地址里的坐标参数映射好就行。先看影像底图层的加载代码const tianduToken 你的天地图密钥; // 天地图影像底图 const imgProvider new Cesium.UrlTemplateImageryProvider({ url: https://t0.tianditu.gov.cn/img_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERimgSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk tianduToken, subdomains: [t0, t1, t2, t3, t4, t5, t6, t7], maximumLevel: 18, tilingScheme: new Cesium.WebMercatorTilingScheme({ numberOfLevelZeroTilesX: 1, numberOfLevelZeroTilesY: 1 }) });这里有个重点tilingScheme必须指定为WebMercatorTilingScheme因为天地图的瓦片切分规则就是标准的Web墨卡托金字塔。如果不指定Cesium会默认用GeographicTilingScheme经纬度网格加载出来的瓦片位置会完全错乱地图是花的。再看注记层。注记层就是叠加在影像上的地名、道路名、边界名这些文字信息。注意加载顺序必须先加载影像层再加载注记层注记层在上面const cvaProvider new Cesium.UrlTemplateImageryProvider({ url: https://t0.tianditu.gov.cn/cia_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERciaSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk tianduToken, subdomains: [t0, t1, t2, t3, t4, t5, t6, t7], maximumLevel: 18, tilingScheme: new Cesium.WebMercatorTilingScheme({ numberOfLevelZeroTilesX: 1, numberOfLevelZeroTilesY: 1 }) }); // 将两个图层依次添加到场景中 const viewer new Cesium.Viewer(cesiumContainer, { baseLayer: false, // 关闭Cesium默认底图 }); viewer.imageryLayers.addImageryProvider(imgProvider); viewer.imageryLayers.addImageryProvider(cvaProvider);这个baseLayer: false是个关键的坑。如果你不关掉默认的影像图层天地图的影像会和Cesium默认的影像叠加在一起两层地图错位交叉画面非常乱。关闭之后场景里完全从零开始干净利落。2.3 初始化Viewer时的几个建议配置加载天地图这一步跑通之后Viewer的初始化参数还有几个建议你顺手配好都是实战中总结出来的。一是去掉无用的UI控件。Cesium默认会显示时间轴、动画控件、Home按钮、场景模式切换器这些在做业务系统时基本用不到还会占据屏幕空间。建议直接关掉const viewer new Cesium.Viewer(cesiumContainer, { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false });二是开启地形。如果你只是做二维影像标绘默认的椭球地形就够了。但如果你后面要叠加3DTiles倾斜摄影模型建议接一个全球地形服务这样相机视角和模型贴合度会好很多const terrainProvider await Cesium.createWorldTerrainAsync({ requestVertexNormals: true, requestWaterMask: true }); viewer.terrainProvider terrainProvider;三是设置默认视角。把自己项目最关注的区域设为相机初始位置这样用户打开页面第一时间看到的就是“有用的地方”而不是一张全球图viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 10000.0) });这段代码的意思是把相机放到北京这个位置高度10公里视角大约能覆盖整个城区。具体经纬度按你的项目需求改就好。3. 坐标系统转换天地图标记不偏移的关键3.1 WGS84、Web墨卡托、Cesium内部坐标的三角关系做标记之前必须先搞清楚三个坐标系统的关系否则做出来的标记一定会漂移。天地图对外服务用的坐标是WGS84经纬度但它的瓦片切片规则基于Web墨卡托投影。也就是说你要定位一个点用的是经纬度坐标比如北京的116.39, 39.9但它落到哪个瓦片上是按墨卡托计算出来的。Cesium在加载图片瓦片时会根据你指定的tilingScheme自动完成这个换算这就是为什么上一章一定要把WebMercatorTilingScheme写对。Cesium内部还有一个概念叫Cartesian3它是三维笛卡尔坐标单位是米原点在地心。你在Cesium里做的所有三维计算包括相机控制、模型摆放、碰撞检测用的都是这个坐标系。所以实际使用中你的数据流是点击屏幕拿到的屏幕坐标转成经纬度再转成Cartesian3去创建标记。标记完成后经纬度是你在业务里的“真身”Cartesian3是Cesium里临时用来渲染的。3.2 Entity标记到底用经纬度还是笛卡尔坐标很多新手会直接在Entity里写position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)。这个写法没错但不优雅。Cesium的Entity体系做了很多便捷封装更推荐直接给一个Cesium.Cartesian3但坐标值来源用经纬度转换。这样代码最直观后续要批量处理数据也方便const position Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100.0); viewer.entities.add({ position: position, point: { pixelSize: 12, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } });上面这段代码在指定位置画了一个红色圆点加白色描边大小为12像素。heightReference这个属性非常实用后面展开讲。3.3 坐标拾取与反算的完整方案做标记功能绕不开的一件事就是“用户点了地图上的某个位置我要知道这儿的经纬度”。这个反算逻辑在Cesium里很直接但还是有几个小坑。Cesium提供了viewer.screenSpaceEventHandler来处理鼠标事件。用ScreenSpaceEventType.LEFT_CLICK监听左键点击然后调用viewer.camera.pickEllipsoid实现坐标反算const handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction((click) { const cartesian viewer.camera.pickEllipsoid(click.position, viewer.scene.globe.ellipsoid); if (cartesian) { const cartographic Cesium.Cartographic.fromCartesian(cartesian); const lat Cesium.Math.toDegrees(cartographic.latitude); const lng Cesium.Math.toDegrees(cartographic.longitude); const height cartographic.height; console.log(经度: ${lng.toFixed(6)}, 纬度: ${lat.toFixed(6)}, 高度: ${height.toFixed(2)}米); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);这里pickEllipsoid的返回值是相机射线与地球椭球面的交点。如果你的场景开启了地形点击山体表面时这个函数返回的仍然是椭球面的点和实际地形表面会有一个高度差。要获取真实地表位置应该用viewer.scene.pickPosition配合SceneTransforms来做。但这个函数依赖深度缓冲区需要在初始化时开启viewer.scene.globe.depthTestAgainstTerrain true。如果遇到pickPosition返回undefined的坑第一反应就是检查这个depthTestAgainstTerrain有没有打开。还有一个可能导致pickPosition失效的场景就是加载了3DTiles模型后模型区域坐标匹配不对相机射线跟模型的碰撞检测失效。排查思路是先关闭3DTiles看看是否能正常拾取能的话就基本确定是模型本身的问题。3.4 关于天地图坐标拾取的一个补充说明热度词里有一个很具体的关键词天地图坐标拾取。这里补充一点我的理解。天地图官网上提供了一个坐标拾取工具可以在地图上直接点击查看某个位置的经纬度坐标。这个工具适合用来“人工采点”比如你提前把重点标记位置的经纬度记下来写死在代码里。但如果是面向用户的交互式标注系统更好的方案是直接在Cesium里做拾取就是我上面展示的EventHandler方案。这样用户点地图、系统记坐标、生成标记整个流程在同一个页面里闭环不用来回切换工具。另外在使用天地图官网坐标拾取工具时要注意它默认显示的是经纬度但有时候也会显示成度分秒格式。如果后续要把这些坐标喂给Cesium记得转成十进制度数。这个转换其实不用写代码心里记住1度等于60分、1分等于60秒就行。4. 标记功能实战点、线、面、文字标记4.1 点标记圆点、图标、广告牌的三级进阶点标记是最常见的标记类型。Cesium里实现点标记有三种方式分别对应不同的场景需求。基础方式是point属性就是上一节看到的圆点适合做海量数据的散点图比如人口分布、设备位置。优点是渲染性能好代码简单。进阶方式是用billboard属性加载图标图片适合做带有业务语义的标记点比如消防站、医院、告警位置viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0), billboard: { image: ./marker.png, width: 40, height: 40, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } });这里verticalOrigin要设为BOTTOM因为图标图片的底部尖端是实际地理位置如果不设置图标会以整个图片的中心点对齐到地球上视觉效果就会偏高或偏移。再进阶一步用label属性在标记点旁边添加文字说明。点图标文字组合是业务系统里最常见的标记形态const entity viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0), point: { pixelSize: 12, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: 北京市中心, font: 14px sans-serif, fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE, pixelOffset: new Cesium.Cartesian2(0, -24), heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } });pixelOffset控制文字相对标记点的偏移量设成(0, -24)表示文字显示在点上方24像素处这个值可以根据字号大小适当调整避免文字压着图标。4.2 线标记动态流动线与轨迹回放线标记在应急调度、车辆轨迹、管线展示这些场景里用得很多。Cesium里画线用polyline属性const positions Cesium.Cartesian3.fromDegreesArray([ 116.39, 39.9, 116.41, 39.91, 116.43, 39.89, 116.45, 39.92 ]); viewer.entities.add({ polyline: { positions: positions, width: 4, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.CYAN }) } });fromDegreesArray接收的是一个扁平数组偶数位是经度奇数位是纬度。注意顺序先经度后纬度写反了线会跑到海上去这种错误一眼还看不出来得把相机视角拉高才看得到。PolylineGlowMaterialProperty会给线条加上发光效果视觉上更好看。如果你的线条是轨迹类动态数据可以考虑用PolylineArrowMaterialProperty加箭头方向。做轨迹回放动画时思路是动态更新polyline.positions的值。具体做法是定时器或requestAnimationFrame里按时间顺序不断往数组里追加新的坐标点同时用viewer.entities.remove(entity)清掉上一帧的线重新画一条新的。实测下来每秒更新10-20次线长在几千个点以内性能完全能接受。4.3 面标记区域框选与多边形标绘面标记在国土、规划、拆迁评估这类项目里很常用。Cesium里画多边形用polygon属性const polygonPositions Cesium.Cartesian3.fromDegreesArray([ 116.38, 39.88, 116.42, 39.88, 116.42, 39.92, 116.38, 39.92 ]); const polygonEntity viewer.entities.add({ polygon: { hierarchy: new Cesium.PolygonHierarchy(polygonPositions), material: Cesium.Color.RED.withAlpha(0.4), outline: true, outlineColor: Cesium.Color.WHITE, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } });material用的是半透明的红色withAlpha(0.4)既能看清下面的底图又能让框选区域一目了然。outline控制是否显示边界线。关于heightReference如果设为CLAMP_TO_GROUND多边形的边界会贴着地表适合在地形起伏明显的山区做区域标绘。反过来如果设为NONE并指定一个具体的高度值多边形就会悬浮在空中适合做立体的警戒区或飞行航线保护区。这里分享一个实测过的坑当经纬度坐标点很多比如几百上千个时fromDegreesArray一次性计算会比较慢页面会卡一下。建议把坐标数组拆成小批次分帧处理或者用Web Worker做异步计算。4.4 动态增删标记的三种模式在做标记系统时我总结出三种常用的增删标记模式分别应对不同场景。第一种是“永久标记”只要页面不刷新就一直在适合做数据展示类系统。用一个数组把创建的entity对象存起来后续所有操作高亮、移动、删除都能通过这个数组索引到const markers []; function addMarker(lng, lat) { const entity viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(lng, lat, 0), point: { pixelSize: 12, color: Cesium.Color.RED } }); markers.push(entity); return entity; } function removeMarker(entity) { viewer.entities.remove(entity); const index markers.indexOf(entity); if (index ! -1) { markers.splice(index, 1); } }第二种是“临时标记”比如用户正在绘制一条路径时路径上的点需要随时删除重画。做法是先创建临时图层绘制完毕后清空再整体加到正式图层const tempLayer new Cesium.CustomDataSource(temp); viewer.dataSources.add(tempLayer); function clearTemp() { tempLayer.entities.removeAll(); }第三种是“控制器模式”通过监听键盘事件控制当前选中标记的高亮和移动。高亮的实质是修改Entity的point.color和point.pixelSize属性。把当前选中entity引用保存在全局变量里操作生效时先还原上一个entity的样式再改变当前entity的样式。5. 3DTiles模型标记与性能优化5.1 在3DTiles模型上叠加标记的两种思路项目做到后期往往要跟倾斜摄影模型、BIM模型打交道。3DTiles是Cesium加载大规模三维模型的标准格式热度词里很多问题也都集中在3DTiles上。关于在3DTiles模型上做标记有两种主要的实现思路。第一种思路直接把标记Entity叠在模型上。做法是先加载3DTiles模型再在模型对应的经纬度位置添加标记。这里有一个核心注意点3DTiles模型自身的坐标是基于本地坐标系的它摆放在地球上的位置由模型的modelMatrix决定。你做标记时用的经纬度必须和模型摆放的经纬度能对应上否则标记和模型会严重分离。第二个思路把标记本身做成3DTiles。具体做法是用Cesium ion或者第三方工具把一批带有地理坐标的标注点数据比如GeoJSON、CSV切片成3DTiles点云然后在Cesium中加载。这种做法适合标记点特别多的情况比如几万个设备状态点Entity API扛不住就得走这条路线。5.2 3D Tiles标记在旋转视角时的崩溃问题排查热度词里有一条我印象很深cesium 3d地球滚动出现崩溃。这个问题在加载3DTiles后做旋转、缩放操作时很常见我来分享一下自己的排查经验。先说一个最常见的崩溃原因纹理内存溢出。倾斜摄影模型动辄几个GB的纹理数据当相机快速旋转、视野内同时加载大量瓦片时显存瞬间就爆了浏览器标签页直接崩溃。排查方法是打开浏览器的任务管理器看GPU进程内存占用是否异常高。解决办法有两个一是给模型做纹理压缩比如转成KTX2格式二是限制Cesium的瓦片缓存大小viewer.scene.context.cacheSize 100;第二个崩溃原因是数据源加载了不该加载的东西。比如你把所有3DTiles的maximumScreenSpaceError设得太小导致模型在很远的距离就开始加载高精度的瓦片内存和带宽直接双挤爆。合理的做法是根据项目需求设置合适的值const tileset await Cesium.Cesium3DTileset.fromUrl(http://your-server/3dtiles/tileset.json); tileset.maximumScreenSpaceError 16; viewer.scene.primitives.add(tileset);maximumScreenSpaceError这个值的意思当模型在屏幕上占用的像素误差小于这个值时就不再加载更高精度的瓦片。值越大远距离看到的模型越粗糙但性能越好。做室外大场景16是一个比较均衡的起点。第三个崩溃原因是图层叠加顺序问题。3DTiles模型里包含了地面的高程信息而天地图影像又是平面纹理。如果天地图被叠加在模型之上视觉上模型会被“压扁”旋转时出现严重的画面撕裂感严重时甚至触发渲染管线异常。正确的图层结构是天地图影像垫底3DTiles模型上浮。5.3 模型节点操作给标记定位带来的启发热度词里还有一条“cesium模型节点”联想到一个实际场景单体化。所谓单体化就是把倾斜摄影模型里的一栋栋建筑从一整块模型里单独分离出来然后给每个建筑挂属性数据。单体化之后你要在某一栋楼上做标记就得知道这栋楼对应的模型节点和位置。这个跟3DTiles本身的基础操作也相关。3DTiles的瓦片数据内部是分层的每一层可以关掉或打开。如果模型加载后想单独显示某栋楼可以先把整个模型隐藏再只显示匹配的节点。不过跟标记直接相关的是这样一层关系每个3DTiles瓦片其实是有边界范围的这个范围用经纬度表示。通过读取瓦片的boundingVolume可以拿到模型覆盖的地理范围进而推算某个标记点是否在这个范围内。这为做“标记自动贴楼”提供了底层坐标依据。5.4 标记点位稠密时的性能优化策略当标记点数量达到几千到上万个时Entity API的渲染性能会明显下降。实测经验是一两千个简单点标记还能保持流畅超过五千个就开始掉帧了。第一板斧是开启viewer.scene.debugShowFramesPerSecond眼测当前交互帧数确认是不是标记点导致的卡顿别盲目优化。第二板斧是用集合批量绘制。Cesium里有PointPrimitiveCollection这种集合类型它的渲染效率远高于独立Entityconst pointCollection viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection()); for (const item of data) { pointCollection.add({ position: Cesium.Cartesian3.fromDegrees(item.lng, item.lat, 0), color: Cesium.Color.RED, pixelSize: 10 }); }注意集合里的点不支持动态修改样式需要更新时得先remove掉再重新add。如果样式需要频繁变化建议还是用Entity 数据驱动的方案。第三板斧是视锥裁剪。把离相机太远的标记自动隐藏或者让标记的显示精度随距离分级。Cesium的Entity体系里有distanceDisplayCondition属性可以控制Entity在某个距离范围内才显示point: { pixelSize: 12, color: Cesium.Color.RED, distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 5000) }这个设置表示距离相机5公里范围内的点显示5公里外的直接隐藏大大减少渲染负担。实测下来在标记点数量超过5000时这是一个简单高效、强烈建议开启的配置。6. 常见问题排查与实战经验补充6.1 瓦片加载不出来或显示错乱的排查清单天地图瓦片加载这件事说简单也简单说恶心也恶心。这里整理一份排查清单按顺序走大多数问题都能解决。第一步看控制台有没有网络请求报错。如果瓦片地址请求直接404或403优先检查token有没有拼对是不是复制的时候丢了字符。我写过一个小建议把瓦片URL直接复制到浏览器地址栏用真实的坐标参数访问一次能打开就说明地址没问题。第二步检查瓦片地址是不是被浏览器拦截了。打开控制台看有没有「Mixed Content」的报错。有的话把地图服务地址从http改成https代码里已经演示过了。第三步确认tilingScheme配置。瓦片花屏、错位、重叠十有八九是WebMercatorTilingScheme没写对。还有一种隐蔽的错误是你同时加载了影像层和注记层两层的tilingScheme不一致导致两层地图对不齐。两个Provider必须用完全相同的tilingScheme。第四步检查maximumLevel设置。天地图有些区域的瓦片只精细到一定层级报maximumLevel: 18时加载高于18层级瓦片会失败报404。这个是正常现象不代表代码有问题。第五步如果是在内网部署排查内外网隔离策略。有些单位的网管会拦截对境外或特定域名的请求天地图的瓦片服务域名如果被加入了防火墙黑名单一样加载不出来。这时候要么联系网管加白名单要么做个瓦片代理服务器把天地图的瓦片数据预先下载到内网再通过内网接口提供瓦片服务。6.2 坐标偏差与高程异常的处理经验做天地图标记时坐标偏差是最让人头疼的问题之一。总结了几个高频场景。场景一标记点位置跟底图偏差了几十米到几百米。优先怀疑坐标系的锅。天地图的矢量服务vec_w和影像服务img_w都是WGS84但如果你拿到的业务数据是GCJ02坐标比如从微信小程序或某些国产地图SDK拿到的直接喂给Cesium必然偏。需要先做GCJ02到WGS84的坐标转换。这个转换公式网上很多核心是一组近似迭代这里不给代码了搜索“火星坐标转换WGS84”就能找到现成工具库。场景二标记点飘在空中或者陷入地下。这个现象通常是高度问题。如果你的位置数据里带了一个负数或者不合理的海拔值标记就会陷到地下。解决办法是把高度强制设为0并开启heightReference: Cesium.HeightReference.CLAMP_TO_GROUND。加了这句之后标记会紧紧贴住地表无论是平地还是山坡展示效果都比较自然。场景三3DTiles模型与标记点位整体偏移。这种偏差往往是模型制作时坐标系转换出了问题比如模型原始数据是地方坐标系在转成3DTiles时没有正确地转换到WGS84。排查思路是想办法拿到模型在制作软件里的原始坐标和切片时输入的坐标做交叉验证确定偏差方向和高低。6.3 编辑器与工程化配置中的几个顺手技巧热度词里有一条“vscode使用svn标记文件”说明不少人在工程化管理版本时也会遇到困惑。虽然不是Cesium的直接知识点但既然大家关注也顺便说下跟项目开发相关的几个工程化技巧。首先Cesium项目通常建议用Vite或Webpack做构建。不管用哪个都要记得把CESIUM_BASE_URL配置好否则Cesium的静态资源Worker脚本、图片、字体加载不出来。有同事遇到过场景加载正常但一直报错的情况最后发现是资源路径没配对。其次如果你的团队用SVN做版本管理建议把Cesium相关的第三方构建产物统一放到一个独立的目录里并在.svnignore里排除掉。原因很简单这些文件体积大、改动频繁而且不是你们团队自己维护的代码不需要进版本库。每次拉取代码时用脚本从npm或CDN自动恢复这些依赖比直接把几十MB的文件塞进SVN要省心得多。6.4 关于ArcGIS调用天地图与其它工具的补充说明热度词里还出现了几组跟ArcGIS相关的词arcgis加载在线天地图的安装步骤详解、arcgis pro如何连线天地图、arcgis如何调用天地图。这说明一个现象天地图的对接需求不只是Cesium这边有ArcMap和ArcGIS Pro用户也在折腾。ArcGIS系列加载天地图的思路大多是走WMTS服务。在ArcMap里用“添加WMTS服务”功能输入天地图的WMTS地址按向导配置好坐标系就能叠加显示。需要注意的是ArcMap对天地图的墨卡托投影支持比较依赖自带投影库如果版本较老可能需要手动定义坐标系参数。但Cesium这边不一样Cesium的UrlTemplateImageryProvider直接给你一个“手动构造URL”的通道自由度极高。天地图的WMTS地址在Cesium里不需要复杂的投影定义只要你把tilingScheme写对一切都能够正常渲染。这也是我推荐大家用Cesium做天地图项目的一个重要原因。6.5 一个容易忽视的问题移动端适配热度词里有一条关于uniapp和天地图的提问“天地图移动端uniapp能用吗能多端适配吗”。这个问题其实从侧面反映了一个趋势现在很多Cesium项目已经不只是跑在PC大屏上还要兼容手机和平板。Cesium官方并不直接支持uniapp但uniapp可以在web-view里内嵌一个H5页面Cesium跑在H5页面里从功能角度完全可行。只是要注意从原生端传坐标到H5时用的是uniapp的postMessage接口双方需要约定好数据协议。移动端的性能问题是更大的坎。手机GPU能力和内存比PC差一大截加载3DTiles模型时容易卡顿甚至闪退。我的建议是移动端项目不要一上来就加载精细倾斜摄影先做层级压缩和抽稀等用户放大到需要的区域再动态加载高精度瓦片。标记数量也要控制上千个点的场景建议直接改用PointPrimitiveCollection。7. 写在最后的一些个人体会天地图标记这个功能单独拆开看每个点都不难但真正落地一个项目时会碰到的坑比想象中多得多。我从第一次接天地图到现在踩过最狠的坑就是坐标系写错导致整个标记系统在演示当天发现位置全偏了最后紧急写了个坐标转换脚本才救回来。所以这篇里花了大量篇幅讲坐标系不是闲笔是真金白银换来的教训。如果你正准备开始做Cesium项目我的建议很简单先做一个最简易的页面把天地图影像和注记加载出来再画一个标记点试试走通“加载地图-拾取坐标-画点画线”这条主链路再往上面叠业务逻辑。千万不要一上来就堆功能Cesium的调试成本很高一旦多层叠加出问题排查难度会指数级上升。另外分享一个适合日常开发的小技巧。用viewer.entities时养成把创建的entity分组管理的习惯。不同业务类型的标记放在不同的CustomDataSource里后续做显隐控制就非常方便。用代码管理数据源比在页面上逐个找再隐藏效率高得多。这个习惯我是在第二次做项目时养成的之后每次开发都受益。天地图标记只是Cesium应用里比较小的一块拼图。真正有用的是背后那一整套坐标系理解、图层组织、性能调优的方法论。这套东西吃透了以后接任何第三方地图、做任何类型的标绘功能都是类似套路不会抓瞎。这次就先聊到这下篇可以接着说说3DTiles单体化的实现思路感兴趣的可以留意后续更新。
返回列表