去年接了个三维可视化项目,需求看起来很简单:在Cesium里把全国几个样板河段的管理点位标出来,点击看实时水位、监控视频和巡河记录。结果第一版做完,自己都看不下去——底图用的在线影像在部分地区分辨率拉胯,标注点一多就卡顿,更别提点击弹窗这种交互了。后来把天地图作为底图接进来,又把标记这一整套从Entity换成Primitive架构重新梳理一遍,才算真正能用在业务上。
这篇就把Cesium里用天地图做底图、实现业务标记的完整过程写出来,从底图选型、坐标系适配、标记体系搭建到性能优化和单体化联动都过一遍。适合已经在用Cesium做项目、但觉得标记功能“能用但不好用”的人,也适合刚接触Cesium、想直接抄一套能落地的标记方案的开发。文章里涉及的代码都是可以在项目中直接改改用的,不是Demo级别的玩具。
1. 天地图那张底图,到底比在线影像强在哪
先说选型。现在很多人一上来就用Cesium官方默认的在线影像服务,毕竟零配置、打开就能跑。但在实际业务里这么做经常出问题:国外影像源在国内访问不稳定,加载速度忽快忽慢;遇到城市级高精度查看需求时,某些区域影像更新慢,甚至能看到明显的拼接痕迹。更关键的是,国内业务对地图服务的合规性要求很高,项目验收的时候你得能说清楚底图是从哪个权威渠道来的。
天地图的优势就在这几点上体现得比较明显。
首先是权威性。天地图是国家地理信息公共服务平台,底图数据来自各级测绘地理信息部门,坐标体系用的是CGCS2000(中国2000国家大地坐标系)。做国内的项目,拿天地图当底图,数据出处说得清,合规问题不用操心。
其次是免费额度对中小项目很够用。天地图开放平台提供多种瓦片服务,个人和企业只要注册个账号申请Token(就是大家常说的tk),就能拿到标准WMTS服务地址。一般中小型项目每天几万次的瓦片请求量完全在免费范围内。
再者,天地图在国内的访问速度和更新频率,是很多商业图源比不了的。高程地形服务、影像服务、矢量服务都有专门的瓦片地址,按需加载,配置起来不绕。
不过要注意一点,天地图本身提供的是瓦片服务,不是现成的Cesium图层。你得在Cesium里把它的WMTS接口拼进自己的底图体系。在动手之前,先去天地图官网注册账号,在控制台里创建一个应用,拿到tk密钥。这一步不用写代码,但很多人都卡在“不知道去哪找tk”,其实就在官网开放平台的“控制台—应用管理”里,创建应用后自动生成。
还有一点值得提前说清楚:天地图在线服务需要联网访问,如果你的项目部署在纯内网环境,得先准备好离线瓦片方案。常见做法是拿天地图的瓦片工具把所需区域的底图切片缓存到本地,或用GeoServer重新发布。这块涉及面比较大,这章先不展开,项目确实跑在内网的话,建议单独用一套离线切片方案。
2. 底图接入实操:影像、注记、地形一次配齐
天地图的WMTS服务地址是标准REST风格,Cesium官方有对应的封装类,不需要写底层的瓦片解析逻辑。下面直接给出我项目里验证过的核心配置。
2.1 影像和注记图层的标准配置
先在开发环境里装好Cesium依赖。我是按官方推荐方式引入整个Cesium包:
npm install cesium代码里建议直接把Cesium的静态资源通过CESIUM_BASE_URL指到自己的目录,或者用vite-plugin-cesium这类插件处理,避免资源路径报错。基础配置完成后,接入天地图影像图层的核心代码如下:
const viewer = new Cesium.Viewer('cesiumContainer', { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, infoBox: false }); const TIANDITU_TOKEN = '你的天地图tk'; // 影像底图 const imgProvider = new Cesium.UrlTemplateImageryProvider({ url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS' + '&REQUEST=GetTile&VERSION=1.0.0' + '&LAYER=img&STYLE=default&TILEMATRIXSET=w' + '&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}' + '&tk=' + TIANDITU_TOKEN, subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], maximumLevel: 18 }); // 影像注记(叠加在影像底图上的中文地名、道路名) const ciaProvider = new Cesium.UrlTemplateImageryProvider({ url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS' + '&REQUEST=GetTile&VERSION=1.0.0' + '&LAYER=cia&STYLE=default&TILEMATRIXSET=w' + '&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}' + '&tk=' + TIANDITU_TOKEN, subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], maximumLevel: 18 }); viewer.imageryLayers.addImageryProvider(imgProvider); viewer.imageryLayers.addImageryProvider(ciaProvider);这里有个很多人没注意的细节:img_w是影像地址,cia_w是影像对应的中文注记层。只加影像不加注记,地图上全是卫星图但没有地名标注,业务人员根本没法用;两个图层要一起加,并且注记层叠加顺序在影像层之后,Cesium的addImageryProvider默认就满足这个顺序。
如果项目需要的是“电子地图风格”而不是卫星影像,把img_w替换成vec_w,cia_w替换成cva_w,其他参数不用动:
// 矢量底图 const vecProvider = new Cesium.UrlTemplateImageryProvider({ url: 'https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS' + '&REQUEST=GetTile&VERSION=1.0.0' + '&LAYER=vec&STYLE=default&TILEMATRIXSET=w' + '&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}' + '&tk=' + TIANDITU_TOKEN, subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], maximumLevel: 18 }); // 矢量注记 const cvaProvider = new Cesium.UrlTemplateImageryProvider({ url: 'https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS' + '&REQUEST=GetTile&VERSION=1.0.0' + '&LAYER=cva&STYLE=default&TILEMATRIXSET=w' + '&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}' + '&tk=' + TIANDITU_TOKEN, subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], maximumLevel: 18 });2.2 地形服务要不要接
很多Cesium项目用默认的Ellipsoid地形,表面是光滑的球面,叠加3dtiles或高程分析时效果会差很多。天地图的高程地形服务可以缓解这个问题,官方提供的是一个WMS风格的接口,目前主要有两种接法:
一种是通过Cesium.ArcGisMapServerImageryProvider加载天地图的地形服务(部分地区支持);另一种是用Cesium.CesiumTerrainProvider配合自定义地形瓦片。实测下来,更稳的方案其实是直接用Cesium官方地形或基于天地图高程数据生成的Terrain瓦片。如果你不想在“接地形”上花太多时间,又希望倾斜模型和白模贴合地面,可以先用官方地形顶着,再在建模阶段做底部压平。
我自己的做法是,底图用天地图,地形用Cesium官方Terrain或离线地形瓦片。天地图地形服务在部分区域的效果确实不错,但接口稳定性和国际网络环境下还是有波动,生产环境优先保稳定,地形数据能用即可。
2.3 坐标系那点事:CGCS2000和WGS84要不要转换
天地图用的是CGCS2000坐标系,Cesium内部用的是WGS84。很多新手一听“两个坐标系”就慌了,以为要做复杂转换。实操中,这两个坐标系在绝大多数业务场景下差值在厘米到分米级,Cesium做可视化、量算、标绘根本感知不到差异。直接当WGS84用,经纬度坐标照常填Cesium.Cartesian3.fromDegrees(lng, lat, height),坐标值不用做任何修正。
唯一要注意的是:如果项目里接了来自不同部门的数据,有的数据用的是地方坐标系或者GCJ02(火星坐标系),那才需要专门的转换工具类。GCJ02的偏移在几十到几百米量级,不能忽略,通常用开源工具库把GCJ02转成WGS84再喂给Cesium。天地图的坐标拾取工具返回的是CGCS2000经纬度,可以直接用,不用二次处理。
3. 标记落地:Entity方便,Primitive保命
底图问题解决后,就到了标题里最核心的“标记”。
Cesium里做标记,最简单的是用Entity加BillboardGraphics或PointGraphics。Entity上手快,代码直观,对少量标记点(几十到一两百个)性能也够。项目初期我就用这种方式,把每个监测点渲染成一个圆形图标加一个名称标签:
function addSimpleMarker(viewer, lng, lat, name, time) { const entity = viewer.entities.add({ id: `monitor_${lng}_${lat}`, position: Cesium.Cartesian3.fromDegrees(lng, lat, 0), billboard: { image: '/markers/monitor.png', width: 32, height: 32, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY }, label: { text: name, font: '14px sans-serif', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, pixelOffset: new Cesium.Cartesian2(0, -36), disableDepthTestDistance: Number.POSITIVE_INFINITY }, properties: { lastUpdate: time } }); return entity; }用Entity做标记有几个好处:数据模型自带属性存储(properties)、点击拾取事件天然支持、增删改查都非常方便,适合快速迭代。但一旦标记量级上去,问题就来了。
我在项目里加载的业务点位大约3000个,包含河长制公示牌、雨量站、水位站、排污口等。用Entity全量加载后,相机一旋转,明显掉帧,在某些配置较低的业务电脑上,旋转地球甚至直接卡顿好几秒。原因不复杂:每个Entity都是一等公民,内部会创建独立的渲染状态、DrawCommand,几千个DrawCommand同时存在,GPU和CPU压力都很大。
解决方案就是换用Primitive+BillboardCollection。它把多个Billboard合批到一次绘制调用里,渲染性能比Entity高一个量级。下面是我封装的一组轻量标记工具:
class MarkerLayer { constructor(viewer) { this.viewer = viewer; this.billboards = new Cesium.BillboardCollection(); this.viewer.scene.primitives.add(this.billboards); } addMarkers(markerList) { markerList.forEach((item) => { const position = Cesium.Cartesian3.fromDegrees(item.lng, item.lat, 0); this.billboards.add({ position: position, image: item.icon || '/markers/default.png', width: item.width || 32, height: item.height || 32, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 禁止被地形/模型遮挡,确保标记始终可见 disableDepthTestDistance: Number.POSITIVE_INFINITY, // 自定义属性,拾取时用 id: item.id, attributes: item.attrs || {} }); }); } removeAll() { this.billboards.removeAll(); } }使用方式:
const markers = new MarkerLayer(viewer); markers.addMarkers([ { id: 'station_001', lng: 116.40, lat: 39.90, icon: '/markers/raindrop.png', attrs: { type: '雨量站' } }, { id: 'station_002', lng: 116.41, lat: 39.91, icon: '/markers/waterlevel.png', attrs: { type: '水位站' } } ]);BillboardCollection里的每个Billboard渲染时走的是一条合并的绘制路径,同样3000个点,帧率能稳定在50~60fps。这是我在这个项目里最值得的一次重构。
如果你连图片都不需要,只求最多数量的点标记,用PointPrimitiveCollection还能再省一点显存,性能更进一步。它只渲染圆形或方形色点,样式比较基础,适合“只看分布态势”的场景。
4. 标记和3dtiles单体化联动:点击白模弹窗
标记不只是“点一下看到气泡”这么简单。我在这个项目里遇上的真实需求是:倾斜摄影和人工白模已经建好,业务希望点击模型上的楼层或设备,弹出对应监测数据。这就涉及“3dtiles单体化”。
Cesium加载3dtiles本身没什么难度:
const tileset = await Cesium.Cesium3DTileset.fromUrl('/data/3dtiles/river_daye/tileset.json'); viewer.scene.primitives.add(tileset);麻烦的是“点击某个建筑”,你得知道点中的是哪一栋、有没有业务数据。3dtiles里每个Building有batchId,需要模型数据在生成时把业务属性写入batchTable。这通常依赖建模团队在数据生产阶段完成。拿到数据后,前端可以用Scene.pick来命中:
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction((movement) => { const picked = viewer.scene.pick(movement.position); if (Cesium.defined(picked) && picked.primitive instanceof Cesium.Cesium3DTileset) { const feature = picked.primitive.getFeature(picked.batchId); const propNames = feature.getPropertyNames(); const props = {}; propNames.forEach((name) => { props[name] = feature.getProperty(name); }); // 根据属性字段SpringId或BldCode匹配业务标记数据 const bizData = findMarkerByCode(props.BldCode); if (bizData) { openMarkerInfoWindow(bizData, movement.position); } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);单体化的核心不是代码,是数据约定。在项目启动阶段就要和建模团队对齐字段命名,比如建筑编码统一叫bld_code,业务编码统一用monitor_code,别一个用BldCode一个用建筑编号,前端没法写通用逻辑。我们项目踩过这个坑,后面所有单体化模型数据命名都写进验收标准里了。
这块和标记系统的配合点在哪?选中建筑后弹出的信息窗,本质上就是标记点的信息卡片。你可以直接复用标记点的信息窗组件,把feature属性塞进去展示水位、水质、责任单位等字段,用户看到的交互是一致的。
5. 天地图瓦片加载的稳定性:滚动崩溃和瓦片错乱的排查
网上不少人反馈“Cesium 3D地球滚动出现崩溃”。这个词条我也被搜索到过好几次,结合个人经验大致说一下。
天地图WMTS服务对并发请求数是有限制的,同一时间请求过多会直接拒绝服务,表现就是瓦片加载一半突然变灰,再滚动还会卡死。这不是Cesium本身的问题,是瓦片源限流。解决办法有几个:
- 开启Cesium的请求调度:Cesium默认有最大Request限制,但有时你手动创建多个Primitive会绕过调度器。确保所有影像请求走的是
ImageryLayer体系,而不要用fetch自己去拼瓦片URL。 - 控制视口内可见图层数量:如果底图、注记、业务切片、轨迹切片全部同时可见,瓦片请求数量会急剧膨胀。业务图层尽量在低缩放级别隐藏、高缩放级别显示,减少不必要的请求。
- 瓦片域名轮询:天地图给你提供
t0到t7八个子域名,用UrlTemplateImageryProvider的subdomains参数全部配置上,浏览器会分散请求,避免单个域名的并发限制。
说到崩溃,还有一种情况是标记点太多且每个标记用了独立的Entity、又开了disableDepthTestDistance: Infinity,在旋转相机时还会触发大量深度测试计算,低端设备上表现为“卡死”。这就是上一章说的,改用Primitive批量标记能直接规避掉。
瓦片错乱的另一个常见原因是缓存污染。浏览器或Cesium自带的TileCache缓存了旧瓦片,而天地图更新了影像版本,新旧版本混在一起显示会产生割裂。排查时直接在Cesium.Viewer构造参数里设置imageryLayer的缓存规则,或清理浏览器缓存对比验证。
还有一个隐蔽问题:你用UrlTemplateImageryProvider时忘了加minimumLevel和maximumLevel,Cesium会在高缩放级别请求天地图不存在的瓦片,返回异常数据,导致部分区域白屏。天地图影像最高到18级,所以maximumLevel: 18这个参数必须写。
6. 坐标拾取、精度校验和数据预处理
标记位置数据从哪来?这直接关系到业务能不能用。这里结合“天地图坐标拾取”来聊。
天地图官网提供了在线坐标拾取工具,打开后在地图上点一下就能看到经纬度坐标和对应的级别。很多非GIS背景的业务人员都用这个工具来提供点位数据,拿到的是CGCS2000坐标,直接落到Cesium里没问题。但这种方式精度只到小数点后4位左右(约10米),对河流断面、排污口这种需要精确位置的对象不够用。
项目里我推荐的做法是:在Cesium画布上加一个“勘察标绘模式”,让业务人员直接在三维地球上点选位置生成标记,而不是线下用坐标拾取工具抄数据再录系统。实现起来不复杂:
const pickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); function enablePickMode(callback) { pickHandler.setInputAction((movement) => { // 用椭球表面拾取,忽略已有的标记和模型 const ray = viewer.camera.getPickRay(movement.position); const cartesian = viewer.scene.globe.pick(ray, viewer.scene); if (cartesian) { const carto = Cesium.Cartographic.fromCartesian(cartesian); const lng = Cesium.Math.toDegrees(carto.longitude); const lat = Cesium.Math.toDegrees(carto.latitude); callback({ lng, lat, height: carto.height }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }这样采到的点位是真正贴合地形的三维坐标,高度也有值,后面叠加水位标尺、淹没分析都方便。
再说精度校验。外部系统导出的POI数据经常有坐标漂移的情况,尤其是GCJ02坐标直接拿来用,位置会偏移几百米。比对方法很简单:拿一个硬件GPS终端去现场采几个已知点,把这几个点在Cesium里和天地图影像对照,偏差明显的就说明数据坐标系不对,需要写个转换工具批量清洗。
我给团队定过一个规矩:所有从外部系统导入的点位,必须经过“坐标转换—剔除异常—现场抽检”三道关卡才能入库,否则做出来的标记系统就是“看着有数据,到现场根本不在地儿上”。
7. 一套完整的业务标记方案:结合实际项目串起来
为了更好理解,我把前面零零散散的内容组装成一个模拟场景:河长制巡河系统。需求是加载河道的倾斜摄影、显示各级河长公示牌和监测站点,点击标记能看到实时水位和巡河记录。
第一步,底图配置。加载天地图影像和注记,外加Cesium官方地形。
第二步,加载倾斜摄影模型:
const tileset = await Cesium.Cesium3DTileset.fromUrl('/data/3dtiles/river_3d/tileset.json'); viewer.scene.primitives.add(tileset); try { await viewer.scene.viewer.flyTo(tileset); } catch (e) { // 首次加载相机飞到某个默认河段视角 }第三步,加载标记数据。业务数据是后端接口返回的JSON数组,包含点位名称、类型、经纬度、状态等。用MarkerLayer批量添加,不同类型用不同图标:雨量站蓝色水滴、水位站绿色标尺、公示牌黄色底牌。然后对BillboardCollection注册点击事件:
const markerLayer = new MarkerLayer(viewer); markerLayer.addMarkers(bizPoints); const clickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); clickHandler.setInputAction((movement) => { const picked = viewer.scene.pick(movement.position); if (Cesium.defined(picked) && picked.primitive instanceof Cesium.BillboardCollection) { const attrs = picked.id; // attrs里是addMarkers时传的attributes openInfoWindow(attrs, movement.position); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);第四步,信息窗。Cesium自带的信息框比较简陋,我通常是直接在HTML里划一个绝对定位的div,点击标记时把该点位的详情渲染进去,再用Cesium.SceneTransforms.worldToWindowCoordinates把三维坐标转成屏幕坐标来定位弹窗:
function openInfoWindow(attrs, screenPos) { infoPanel.style.left = screenPos.x + 12 + 'px'; infoPanel.style.top = screenPos.y + 12 + 'px'; infoPanel.style.display = 'block'; infoPanel.innerHTML = buildHtmlByType(attrs); }这就是一个能上生产的最小完整方案。
8. 进阶方向:标注聚合、热力图、鹰眼和单体化的组合
标记系统跑通之后,还会有各种进阶需求找上门。这里挑几个高频场景说一下。
8.1 海量点聚合
当标记数量过万,直接渲染还是会有压力。天地图热词里出现了“百度地图同一图层大量数据标记效果”,本质就是聚类聚合。Cesium侧有对应的做法:在GeoJSON数据源上开启聚类:
const dataSource = await Cesium.GeoJsonDataSource.load('/data/points_10w.json'); const clustering = dataSource.clustering; clustering.enabled = true; clustering.pixelRange = 35; clustering.minimumClusterSize = 3; clustering.clusterEvent.addEventListener((clusteredEntities, cluster) => { cluster.label.text = clusteredEntities.length.toString(); cluster.billboard.image = '/markers/cluster.png'; });这种方式和前面的BillboardCollection思路互补:数据少用Primitive集合,数据特别大用数据源聚类。
8.2 标记热力图
热力图可以和标记联动。比如排污口监测数据分布,用色块表达浓度高低。通常做法是:先把标记点位的值转成屏幕坐标,在Canvas上绘制热力底图,然后作为图片叠加到Cesium的Rectangle上。第三方的heatmap.js配合Cesium转换函数可以做到:
// 伪代码示意 const heatCanvas = renderHeatMap(heatData); // 生成canvas viewer.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees(west, south, east, north), material: heatCanvas, // 关闭深度测试,保证热力显示在模型上层 } });这个方案在河湖污染扩散趋势展示里很实用。
8.3 鹰眼图联动
“Cesium鹰眼”本质是一个缩略版地图同步视图。在主Cesium实例的camera.changed事件里,更新缩略地球的相机位置;反过来,在缩略地球上点击拖动,同步控制主视图。很多项目中,鹰眼图直接用一个Canvas画主视图覆盖范围的矩形框就行,不需要再加载一套完整天地图。
8.4 单体化与标记的深度结合
单体化不只是“点了弹窗”。我在做巡检业务时,把单体化的楼层标记和业务流关联了起来:点击楼栋单体化模型→弹出该栋楼的标记列表→点击某个标记→视角平滑飞到标记点位并打开摄像头画面。这套互动在数字孪生、智慧园区项目里是标配。
实现核心在于“定位飞行”的体验:
function flyToMarker(entity) { const pos = entity.position.getValue(viewer.clock.currentTime); viewer.camera.flyTo({ destination: pos, orientation: { heading: Cesium.Math.toRadians(20), pitch: Cesium.Math.toRadians(-45), roll: 0 }, duration: 1.5 }); }9. 项目里的几个“如果不注意必踩”的细节
说几个在开发中反复被问到的坑,集中排除一遍。
第一个是天地图tk泄漏风险。tk写在前端,等于公开了,别人拿到就能用你的配额。稍微大一点的项目,建议后端做一层代理,前端请求同源接口,由后端转发天地图WMTS并把tk放在服务端配置里。这样既保护了配额,又能统一做缓存和限流。
第二个是标记图标切图规范。别小看图标尺寸和锚点。我见过图标画得很大,但视觉重心和经纬度位置偏差很远,放大后点不准。建议所有标记图标统一为32x32或48x48,锚点固定在底部中心(verticalOrigin: BOTTOM),并且图标主体视觉重心校准在锚点上方2~4像素处,这样最符合手指/鼠标点击习惯。
第三个是高空视野下的标记符号缩放。默认Billboard在缩放时字号和图标尺寸不变,导致拉高视野后挤成一团。可结合实际需求,在camera.changed事件里根据相机高度动态调整scaleByDistance或切换聚合图层:
billboard.scaleByDistance = new Cesium.NearFarScalar(1000, 1.0, 50000, 0.4);第四个是设备和浏览器兼容性。Cesium开发多数时候跑在Chrome上没问题,但业务现场可能用老旧Windows机器上的浏览器,WebGL版本低,会白屏或卡顿。项目交付时我会额外注意:目标浏览器必须支持WebGL2,GPU加速没被禁用;低端机器上尽量关掉抗锯齿、降低分辨率系数;天地图影像层和模型数量做取舍,不支持一键全开。
第五个是关于“三维地球滚动出现崩溃”这类问题的调试思路。不要急着改代码,先把浏览器控制台的报错、网络请求失败、WebGL警告一起截图记录,再对比正常环境的表现。很多时候是某台机器的显卡驱动问题,不是代码Bug。遇到这种问题,优先降级Cesium的contextOptions,关闭一些高级渲染特性测试,看是否复现。
第六个是移动端适配。热词里有“天地图移动端uniapp能用吗”,如果你打算在移动端内嵌Cesium,需要确认天地图服务是否允许在移动App内访问。用WebView加载H5页面问题不大,但要注意触摸交互和性能,且天地图的tk对Referer有校验,移动端请求时Referer可能为空,需要后端代理配合。
10. 我最后想说的两句话
做Cesium开发久了会发现,真正拉开项目差距的往往不是炫酷效果,而是底图稳不稳、标记准不准、数据能不能和业务对上。天地图给了你一个合规、稳定、免费的底子,剩下的就是把标记这个基本功做扎实。Entity爽一时,Primitive稳一世,单体化数据协议早定早省心。
回到标题“02-Cesium-实现天地图标记”,如果你要做的是简单Demo,照着第2章和第3章的代码就能出效果。如果你要做的是能上生产的业务系统,至少要把第7章的完整串法和第9章的避坑清单过一遍。踩过这一轮,你手上的Cesium项目基本就不会再被“底图和标记”拖后腿了。