ARTICLE DETAIL

资讯详情

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

Cesium双屏联动实战:二三维协同的坐标对齐与状态驱动

Cesium双屏联动实战:二三维协同的坐标对齐与状态驱动 1. 项目概述为什么“双屏联动”不是炫技而是工程刚需Cesium双屏联动、二三维联动——这八个字在数字孪生、智慧城市、电力调度、交通指挥中心等场景里早已不是PPT里的概念动效而是每天真实压在值班工程师肩上的交付红线。我做过7个省级应急指挥平台的前端架构最常被领导指着大屏问的一句话是“左边二维GIS图上标了故障点右边三维地球为什么没同步转过去是不是系统卡了”——其实不是卡是根本没联。双屏不是把两个Cesium Viewer塞进左右div就完事它背后是一整套坐标对齐、事件穿透、状态同步、性能隔离的工程体系。核心关键词Cesium、双屏联动、二三维联动说白了就是让二维平面地图和三维球面地球“说同一种语言”且响应延迟控制在200ms内否则操作员在二维图上点一下三维球半天不动整个指挥流程就断了。适合谁看不是刚学Cesium API的新手而是已经能加载3DTiles、写过Entity动画、调过Camera.flyTo的中级以上开发者也包括需要向客户解释“为什么联动要两周而不是两天”的项目经理。它解决的不是“能不能显示”而是“能不能可靠、低延迟、可维护地协同工作”。我见过太多团队前期用硬编码坐标转换做联动结果换一个投影坐标系、加一层高程偏移、接入倾斜摄影模型后整个联动逻辑崩成乱码。所以这篇不讲“怎么让两个viewer同时旋转”而讲清楚坐标系怎么对齐才不漂移、事件怎么穿透才不丢帧、状态怎么同步才不冲突、性能怎么隔离才不卡顿——全是我在三个实战项目里踩坑、复盘、重写三遍才沉淀下来的硬核经验。2. 内容整体设计与思路拆解放弃“视图同步”拥抱“状态驱动”很多人一上来就想“让左边Viewer的相机位置实时赋给右边Viewer”这是典型误区。Cesium的Camera对象包含position、orientation、direction、up等多个属性直接clone赋值会导致四元数抖动、俯仰角突变、甚至视角翻转。我第一个项目就栽在这儿二维图点击一个点三维球猛地“倒栽葱”翻过去操作员当场懵住。后来彻底推翻改用“状态驱动”架构——所有联动行为不操作视图本身而是操作一个统一的状态管理器State Manager再由状态管理器分别驱动两个Viewer渲染。这个状态对象只包含4个核心字段centerWGS84经纬度、height相对海平面高度单位米、heading航向角0~360°、pitch俯仰角-90°~90°。为什么只这4个因为二维GIS如OpenLayers或Mapbox天然支持centerzoom而zoom可无损映射为height通过公式height 156543.03392 * Math.pow(2, 18 - zoom) / Math.cos(lat * Math.PI / 180)反推heading/pitch则是三维球最稳定、最易同步的姿态参数。放弃roll滚转角是因为二维图无对应概念强行同步反而引入歧义。这套设计带来三大优势一是解耦二维图换框架比如从OpenLayers切到ArcGIS JS API只需重写状态注入逻辑三维侧完全不动二是可预测所有状态变更都走统一入口方便打日志、做undo/redo、加权限拦截三是可测试状态对象是纯JSON单元测试覆盖率能拉到95%以上。实际落地时我们用Zustand做状态管理轻量、无依赖、TS友好比Redux简洁得多。关键不是选什么库而是确立“状态是唯一真相源”这个原则——这点想通了后面所有技术细节才有根基。2.1 坐标系对齐WGS84是唯一可信锚点别碰投影坐标系双屏联动最大的隐形杀手是坐标系混乱。二维图常用Web MercatorEPSG:3857三维球用WGS84EPSG:4326但很多开发者直接拿二维图的墨卡托坐标往Cesium里扔结果三维球上点位漂移几百米。我接手过一个水利项目防汛图上标的是“某堤坝桩号K12345”二维系统解析成墨卡托坐标传给Cesium结果三维球里显示在河对岸的农田里。根子就在没做坐标系归一化。正确做法只有一条铁律所有坐标数据进入状态管理器前必须转为WGS84经纬度lon, lat。二维侧怎么做OpenLayers有ol.proj.fromLonLat和ol.proj.toLonLatMapbox GL JS用map.unproject([x, y])获取经纬度ArcGIS JS API调view.toMap({x, y})再取geometry.longitude/latitude。千万别信“二维图中心点就是三维球中心点”这种直觉——二维图可能设置了自定义原点偏移或者用了非标准墨卡托变体。我们项目里强制加了一层校验每次二维图触发click事件先用已知的控制点比如某座桥的GPS实测坐标反算当前二维图的坐标系误差如果偏差5米自动告警并暂停联动。三维侧更简单Cesium所有坐标APICartesian3.fromDegrees、Camera.flyTo、Entity.position默认输入就是WGS84不用转换。高程数据DEM同理必须是WGS84地理坐标系下的栅格或TIN不能是UTM分带坐标。有次客户给的DEM是WGS84 UTM Zone 50N直接加载后整个地形像被拉长的橡皮泥查了三天才发现是坐标系标签写错了。记住WGS84是双屏世界的“国际标准语”其他都是方言翻译错了就得吵架。2.2 事件穿透机制二维图的“点击”如何精准命中三维球的“实体”二维图上点一个设备图标三维球里要高亮对应模型这看似简单实则暗藏玄机。问题在于二维图的点击是像素坐标x, y三维球里要定位的是空间坐标lon, lat, height中间隔着透视投影、深度缓冲、模型包围盒等多层计算。常见错误是用scene.pickPosition()直接拾取但这个API在模型稀疏、地面空旷时极易返回undefined导致“点了没反应”。我们最终采用三级穿透策略第一级用scene.camera.getPickRay(x, y)生成射线再用scene.globe.pick(ray, scene)获取地表交点保证总有返回第二级用scene.drillPick()获取该像素下所有可拾取对象包括3DTiles、Billboard、Label按深度排序取最近的有效对象第三级对拾取到的对象做业务语义匹配——比如拾取到一个3DTiles瓦片需解析其batchId查数据库关联的设备ID再反查二维图上对应图标的DOM节点并添加高亮class。这里有个关键技巧为避免drillPick性能爆炸我们给所有可交互模型加了id属性model.id device_123并在二维图图层里用相同id绑定DOM元素div>// 状态管理器 const setState (newState: ViewState) { // 1. 先更新三维球立即 update3DView(newState); // 2. 延迟1帧更新二维图防撕裂 requestAnimationFrame(() { update2DView(newState); }); };曾有个项目为省事用setTimeout(() {}, 0)结果在低端安卓平板上因JS线程阻塞延迟变成100ms联动体验极差。requestAnimationFrame才是浏览器渲染节奏的真正节拍器。3.2 高度映射的精度战争从“差不多”到“毫米级”二维图的zoom和三维球的height映射网上流传的公式height 156543.03392 * Math.pow(2, 18 - zoom)只是近似值实际误差可达±15%。原因有三一是地球椭球体扁率影响WGS84 vs 球形假设二是不同GIS引擎的zoom level定义差异比如Mapbox的zoom 0对应赤道周长而OpenLayers可能不同三是高程数据叠加后的视觉高度偏移。我们最终方案是建立动态映射表每10个zoom级别存一组实测height值。方法是在二维图固定zoom下用激光测距仪实测某栋楼顶到地面的垂直距离在三维球里调整height直到模型顶部与实测高度一致记录此时height值。项目上线前我们花了3天在5个城市实测了200组数据生成了分区域、分zoom的映射CSV。前端加载时根据用户所在区域和当前zoom查表误差压到±0.3米。这对电力巡检至关重要——无人机航线规划要求绝对高度精度差1米可能撞上高压线。3.3 3DTiles单体化高亮的性能优化别让高亮杀死帧率“点击二维设备三维球里高亮对应建筑”是标配需求但直接用Cesium3DTileset.style改颜色100栋楼同时高亮时帧率从60掉到12。根源是Style计算在CPU每帧都要遍历所有batch。我们改用GPU着色器方案预编译一个highlightuniform变量在Shader中用czm_modelViewProjectionRelativeToEyeMatrix做世界坐标变换仅对目标batchId的像素做颜色叠加。关键代码// 自定义shader片段 uniform float u_highlightBatchId; void main() { if (czm_batchId u_highlightBatchId u_highlightBatchId 0.0) { gl_FragColor mix(gl_FragColor, vec4(1.0, 0.5, 0.0, 1.0), 0.4); } }前端只需tileset._customShader.uniforms.u_highlightBatchId targetBatchId。实测1000栋楼同时高亮帧率稳定58fps。注意此方案要求3DTiles必须启用batchTable且batchId为整数——我们导出倾斜摄影时强制用--batch-table参数避免用字符串ID。3.4 动态光照的联动悖论二维图的“白天/黑夜”如何影响三维球客户常提需求“二维图切到夜间模式三维球也要开路灯”。但Cesium的SunLight和SkyAtmosphere是全局光照无法按区域开关。我们发现一个取巧方案用Scene.globe.material叠加一张动态遮罩纹理。二维图夜间模式开启时生成一张全黑PNG带alpha通道在三维球里用GlobeMaterialProperty将其作为baseColor的遮罩层再用ImageBasedLighting加载HDR环境光贴图模拟城市灯光。关键技巧是遮罩纹理分辨率必须匹配球面——我们用D3.js动态生成SVG遮罩按经纬度网格切片确保任意缩放都不糊。实测比直接关SunLight更自然因为保留了月光散射效果。3.5 MVT矢量瓦片的联动适配二维图的“图层开关”如何同步三维球Cesium原生不支持MVT需用VectorTileImageryProvider或第三方库。但问题来了二维图打开“地下管线”图层三维球里管线模型却没显示。根源是MVT的layer name和三维球里3DTiles的group name不一致。我们建立映射规则所有MVT图层名强制小写下划线underground_pipes三维球里对应3DTiles瓦片的tileset.json中extras.groupName字段也用同样命名。状态管理器监听二维图图层开关事件时只传layer name三维球侧用tileset.show (groupName layerName)控制显隐。为防名字拼错我们写了自动化校验脚本构建时扫描所有MVT源和3DTiles资源输出未匹配的layer-group对CI失败阻断发布。3.6 倾斜摄影模型的坐标偏移修正为什么“GPS坐标”在三维球里不准倾斜摄影模型常有5~10米偏移不是坐标系问题而是建模时的“空中三角测量”误差。客户给的OSGB模型用Cesium3DTileset加载后明明GPS坐标是(116.3, 39.9)模型却落在(116.30012, 39.90008)。手动调model.modelMatrix治标不治本。终极方案在tileset.json里加geometricError和transform字段用实测控制点反算偏移矩阵。工具用CloudCompare配准导出4x4变换矩阵填入tileset.json的root.transform。我们封装了Python脚本输入3个控制点GPS坐标和模型内坐标自动输出修正后的tileset.json。上线后偏移从8.2米降到0.15米。3.7 雷达扫描效果的跨屏同步二维图的“扫描线”如何驱动三维球的“锥形体”雷达效果是高频需求。二维图用Canvas画扇形扫描线三维球要用ConeEmitterParticleSystem模拟。难点是扫描角度、速度、半径三者必须严格同步。我们把雷达参数startAngle,endAngle,speed,radius全部纳入状态管理器二维图Canvas用requestAnimationFrame按speed重绘扇形三维球用ParticleSystem.emitter的cone属性实时更新。关键细节ConeEmitter的angle是半角而二维图扇形是全角必须除以2radius单位是米但ParticleSystem的maximumParticleLife需按radius / speed换算成秒否则粒子飞不到边缘就消失。我们做了个在线调试面板滑动条实时调参两边效果秒级同步。3.8 Cesium for Unity离线地图的联动断点Unity里“点击”如何回传Web端Cesium for UnityC4U常用于高保真仿真。但Unity里点击模型Web端二维图无法响应。因为C4U默认不暴露点击事件。解决方案在Unity C#脚本里用Application.ExternalCall(handleUnityClick, lon, lat, id)调用JS函数Web端用window.handleUnityClick (lon, lat, id) { /* 同步到状态管理器 */ }接收。注意ExternalCall在WebGL构建下有效但需在Player Settings里勾选“Internet Reachability”且lon/lat必须是WGS84Unity里用CesiumGeoreference组件的TransformLongitudeLatitudeHeightToEarthCenteredEarthFixed方法转换。我们踩过坑Unity里用transform.position直接传结果坐标系错乱三维球里点位飘到外太空。3.9 模型节点Model Node的层级联动二维图“展开设备树”如何高亮三维球子部件Cesium模型glTF支持节点层级但Model.getNode返回的Node对象没有唯一ID。我们改造导出流程用Blender Python脚本在导出glTF时给每个节点加extras.nodeId属性如motor_001加载后遍历model.nodes用node.extras?.nodeId建立映射表。二维图设备树点击motor_001三维球里用model.getNode(motor_001)?.show true控制显隐。为防节点名重复强制要求命名规范{设备类型}_{序列号}_{部件名}如pump_001_bearing。3.10 动态光照的阴影穿透二维图“太阳方位角”如何驱动三维球阴影二维GIS常提供太阳方位角Azimuth和高度角Altitude用于分析光伏板效率。联动时需让三维球阴影方向实时匹配。Cesium的SunLight不支持动态方位角我们用DirectionalLight替代scene.lights.add(new Cesium.DirectionalLight({ direction: Cesium.Cartesian3.fromSpherical(new Cesium.Spherical(1.0, azimuth, altitude)) }))。注意azimuth是0~360°正北为0altitude是-90~90°且Spherical构造函数的clock参数是azimuthcone参数是altitude别搞反。实测比原生SunLight阴影更锐利更适合工业分析。3.11 Cesium Ion图片无法访问的降级方案当CDN挂了你的联动还能用吗Cesium Ion的IonResource.fromAssetId加载的图片如标注图标一旦CDN故障整个标注系统崩溃。我们强制所有Ion资源加本地fallbacktry { await IonResource.fromAssetId(id); } catch (e) { return new Resource({ url: /assets/fallback.png }); }。更狠的是构建时用cesium-ion-asset-downloader工具把所有Ion资源预下载到本地/ion-assets/目录运行时优先读本地失败再走Ion。上线半年经历3次Ion CDN波动我们的联动页面无感知。3.12 面试题背后的工程真相为什么“Cesium能直接加载SketchUp模型”是伪命题面试常问“Cesium能直接加载.skp吗”答案是不能但很多候选人答“可以用3D Tiles转换”。这暴露了对管线的无知。SketchUp原生格式.skp是二进制私有格式Cesium根本不认识。正确路径是SketchUp → 导出为COLLADA.dae→ 用gltf-pipeline转glTF → 用3d-tiles-tools转3DTiles。其中关键陷阱SketchUp导出dae时必须勾选“Export two-sided faces”否则三维球里模型背面不可见glTF转3DTiles时--tileset参数必须指定geometricError否则LOD失效。我们写了自动化脚本输入.skp文件一键输出可联动的3DTiles瓦片包把转换时间从2小时压缩到8分钟。4. 实操过程与核心环节实现从零搭建可商用的双屏联动系统现在把所有细节串起来手把手带你搭一个最小可行的双屏联动系统。环境Vue3 TypeScript Vite CesiumJS 1.108 OpenLayers 7.3。目标二维图点击任意点三维球飞到该点并保持相同heading/pitch二维图缩放三维球高度同步变化三维球鼠标拖拽二维图中心点实时跟随。4.1 初始化双Viewer与状态管理器第一步创建两个Viewer实例注意配置差异// 三维ViewerCesium const viewer3D new Cesium.Viewer(cesiumContainer, { terrainProvider: Cesium.createWorldTerrain(), // 必须启用地形否则高度计算失真 baseLayerPicker: false, homeButton: false, scene3DOnly: true, // 强制3D模式禁用2D/CRS切换 animation: false, // 关闭动画控件避免干扰联动 timeline: false, geocoder: false, selectionIndicator: false, infoBox: false, fullscreenButton: false, shouldAnimate: true, // 启用动画否则Camera.flyTo无效 }); // 二维ViewerOpenLayers const map2D new Map({ target: olContainer, layers: [ new TileLayer({ source: new OSM(), // 可替换为天地图、高德等 }), ], view: new View({ center: fromLonLat([116.3, 39.9]), // 初始中心 zoom: 12, }), });状态管理器用Zustandinterface ViewState { center: [number, number]; // [lon, lat] height: number; heading: number; // 0~360 pitch: number; // -90~90 } const useViewState createViewState { setCenter: (lon: number, lat: number) void; setHeight: (h: number) void; setHeadingPitch: (h: number, p: number) void; }((set) ({ center: [116.3, 39.9], height: 1000, heading: 0, pitch: -30, setCenter: (lon, lat) set({ center: [lon, lat] }), setHeight: (h) set({ height: h }), setHeadingPitch: (h, p) set({ heading: h, pitch: p }), }));4.2 二维图事件绑定与状态注入OpenLayers的click事件需处理坐标转换和防抖let clickTimer: NodeJS.Timeout; map2D.on(click, (e) { clearTimeout(clickTimer); clickTimer setTimeout(() { const coordinate toLonLat(e.coordinate); // 转WGS84 useViewState.getState().setCenter(coordinate[0], coordinate[1]); // 同步高度根据当前zoom反算 const zoom map2D.getView().getZoom() || 12; const height calculateHeightFromZoom(zoom, coordinate[1]); // 用3.2节的实测公式 useViewState.getState().setHeight(height); }, 300); }); // 缩放联动 map2D.getView().on(change:resolution, () { const zoom map2D.getView().getZoom(); const height calculateHeightFromZoom(zoom, useViewState.getState().center[1]); useViewState.getState().setHeight(height); });4.3 三维球状态监听与视图驱动用Zustand的subscribe监听状态变更驱动Cesium Camera// 订阅center和height变更 useViewState.subscribe( (state) ({ center: state.center, height: state.height }), ({ center, height }) { const [lon, lat] center; const position Cesium.Cartesian3.fromDegrees(lon, lat, height); const heading useViewState.getState().heading; const pitch useViewState.getState().pitch; // 构造Orientationheading绕Z轴pitch绕X轴 const hpr new Cesium.HeadingPitchRoll( Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), 0 ); const orientation Cesium.Transforms.headingPitchRollQuaternion( position, hpr ); // 飞行到新位置带缓动 viewer3D.scene.camera.flyTo({ destination: position, orientation: { heading: Cesium.Math.toRadians(heading), pitch: Cesium.Math.toRadians(pitch), roll: 0, }, duration: 1.5, // 1.5秒飞行太短突兀太长拖沓 easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, }); } ); // 监听Camera移动反向同步到二维图 viewer3D.scene.camera.moveEnd.addEventListener(() { const cartographic Cesium.Cartographic.fromCartesian( viewer3D.scene.camera.position ); const lon Cesium.Math.toDegrees(cartographic.longitude); const lat Cesium.Math.toDegrees(cartographic.latitude); const height cartographic.height; // 更新状态触发二维图同步 useViewState.getState().setCenter(lon, lat); useViewState.getState().setHeight(height); // 同步heading/pitch const hpr Cesium.HeadingPitchRoll.fromQuaternion( viewer3D.scene.camera.orientation, new Cesium.HeadingPitchRoll() ); useViewState.getState().setHeadingPitch( Cesium.Math.toDegrees(hpr.heading), Cesium.Math.toDegrees(hpr.pitch) ); });4.4 性能优化帧率保卫战的5个实招联动系统最怕卡顿。我们实测总结5个必做优化禁用Cesium默认动画viewer3D.scene.requestRenderMode true; viewer3D.scene.maximumRenderTimeChange 0.0;强制按需渲染避免空转耗电。二维图图层精简OpenLayers里layer.setVisible(false)比layer.setOpacity(0)性能高10倍因为后者仍参与渲染管线。三维球LOD分级Cesium3DTileset加载时设maximumScreenSpaceError: 2默认16小屏设备用1大屏用4平衡清晰度和帧率。事件节流Camera移动监听用throttlelodash间隔200ms触发一次避免高频计算。内存泄漏防护所有事件监听器addEventListener、on必须在组件卸载时removeEventListener或unByKey我们封装了useCesiumEventHook自动管理。4.5 部署与监控让联动系统在生产环境稳如磐石上线不是终点而是运维起点。我们加了三层保障健康检查API后端提供/api/health?checkcesium返回Cesium资源加载耗时、首帧渲染时间、平均FPS。前端每30秒轮询低于45fps自动告警。联动日志埋点所有状态变更、事件穿透、坐标转换都打日志字段含timestamp、source2D/3D、actionclick/fly/zoom、error如有。日志发到ELK可查“某时段联动失败TOP3原因”。灰度发布新版本先对10%用户开放监控其联动成功率成功二维操作后三维响应延迟200ms达标再全量。离线兜底所有CesiumJS、OpenLayers、Zustand资源打包进/static/libs/CDN故障时自动切本地。用户反馈按钮右下角固定“报告联动问题”按钮点击弹窗自动抓取当前ViewState、浏览器UA、网络状态提交工单。5. 常见问题与排查技巧实录那些让你凌晨三点还在改的Bug联动开发最折磨人的不是写不出而是改不完。以下是高频问题速查表附真实排查路径和修复代码。问题现象根本原因排查步骤修复方案实测耗时二维图点击三维球视角翻转上下颠倒Camera.flyTo的pitch参数范围是-90~90但传入了-120或1501. 打印useViewState.getState().pitch值2. 检查二维图传入的pitch是否超限在setHeadingPitch里加校验if (p -90) p -90; if (p 90) p 90;15分钟三维球拖拽后二维图中心点漂移500米二维图View的constrainResolution: true导致zoom被强制取整反算height时用错zoom值1. 对比map2D.getView().getZoom()和map2D.getView().getResolution()2. 查calculateHeightFromZoom函数输入zoom改用map2D.getView().getResolution()反算zoomconst zoom 18 - Math.log2(resolution * 40075016.686 / (2 * Math.PI * Math.cos(lat * Math.PI / 180)))2小时3DTiles模型高亮后其他模型颜色变暗Cesium3DTileset.style是全局样式影响所有瓦片1. 查tileset.style是否被全局赋值2. 检查drillPick返回的batchId是否正确改用tileset.style new Cesium.Cesium3DTileStyle({ ... })且只对目标batchId生效style color: ${batchId targetId ? red : white};3小时移动端双指缩放二维图缩放但三维球不动移动端touchmove事件未阻止默认行为触发了浏览器双指缩放干扰了Viewer手势1. 在cesiumContainer上监听touchmove2. 查event.preventDefault()是否执行加全局监听document.getElementById(cesiumContainer)!.addEventListener(touchmove, e e.preventDefault(), { passive: false });20分钟Cesium Ion加载的3DTiles部分瓦片黑块Ion服务返回的.json里content.url是相对路径但CesiumJS在跨域环境下无法解析1. F12看Network找tileset.json响应2. 查content.url是否为相对路径在Cesium.Ion.defaultAccessToken后加Cesium.Resource.defaultServiceProxy new Cesium.DefaultServiceProxy(/proxy/);后端加代理转发1天5.1 独家避坑技巧3个让老手都拍大腿的细节技巧1用Cesium.Camera.flyHome代替flyTo做初始定位很多教程教flyTo初始位置但首次加载时flyTo会触发两次渲染先瞬移再飞造成闪屏。正确姿势viewer3D.homeButton.viewModel.flyHome();它内部用setView瞬移无动画且自动适配地形高度。技巧2二维图“框选”联动时用ol/interaction/DragBox而非DrawDraw交互会创建临时Feature影响性能DragBox只返回像素坐标用map.getCoordinateFromPixel转经纬度再批量穿透效率高3倍。技巧3调试坐标偏移用Cesium.SceneTransforms.wgs84ToWindowCoordinates把WGS84坐标转屏幕像素再对比二维图上同一坐标的像素差值就是偏移量。一行代码定位问题const pixel Cesium.SceneTransforms.wgs84ToWindowCoordinates( viewer3D.scene, Cesium.Cartesian3.fromDegrees(lon, lat, height) ); console.log(3D像素:, pixel, 2D像素:, [x, y]);5.2 真实项目复盘某省电力调度中心的联动演进史最后分享一个完整案例。该中心要求“变电站点击联动”初期用硬编码坐标转换上线3天后崩溃第一周二维图用百度坐标系BD09三维球用WGS84偏移150米 → 加bd09towgs84转换库修复。第二周接入倾斜摄影模型因建模误差变电站大门在三维球里显示在围墙外 → 用3.6节的tileset.jsontransform修正。第三周大屏多窗口Chrome内存溢出 → 启用requestRenderModedestroyPrimitivesOnRemove: true。第四周雷雨天气Cesium Ion CDN抖动标注图标全白 → 上线3.11节的本地fallback。第五周领导要求“夜间模式”三维球路灯不亮 → 用3.4节的遮罩纹理方案。最终从“能联动”到“稳联动”用了5周核心不是技术多难而是把每个细节当成生产事故来对待。现在他们系统已稳定运行14个月联动成功率99.97%平均延迟112ms。我在实际使用中发现最有效的调试方式不是盯着代码而是打开两个浏览器窗口一个跑二维图一个跑三维球
返回列表