
最近在做一个智慧园区可视化的项目核心是把园区里的建筑楼栋、地下管廊、视频监控点位和门禁设备全部塞进一个三维地球里方便管理人员在一张图上看全局。技术选型的时候对比了好几套方案最后定下来用EarthSDK3做三维GIS开发。这个SDK最打动我的一点是它对国内GIS场景的适配度很高不用自己拼凑一大堆底层库就能把三维地球跑起来而且中文文档和示例都比较完整遇到问题翻起来不费劲。整套开发做下来从初始化地球、叠加影像地形、加载三维模型到做点击交互和相机飞行踩了不少坑也沉淀了不少能直接用的经验。这篇内容就是把这些实战过程梳理一遍给后面想用EarthSDK3做三维GIS的团队或者个人开发者一个可供参考的完整路径。如果你正打算在Web端做数字孪生、智慧城市大屏、自然资源可视化这类项目又不想从WebGL底层开始造轮子那么这篇文章很适合你。整个内容我会从选型思路、环境搭建、图层加载、交互功能到性能优化逐层展开每一步都有对应的代码示例和参数说明也会把我在实际项目中遇到的坑和排查思路一并写出来。1. 项目整体设计与选型思路1.1 需求拆解三维GIS项目到底要解决什么问题很多刚接触三维GIS的人容易陷入一个误区以为三维GIS就是在页面上显示一个会转的地球然后往上贴点模型就行。真正做过项目就会发现业务方要的不是“炫酷”而是“能用、能查、能分析”。我在这个智慧园区项目里拿到手的原始需求其实只有三条第一把园区所有空间数据统一放到一个三维场景中管理第二管理人员可以快速定位到某栋楼、某个摄像头并查看详情第三大屏上要稳定运行不能出现加载慢、浏览器崩溃这种问题。把需求翻译成技术语言就变成了四件事需要支持多源空间数据接入影像、地形、矢量、倾斜摄影、人工模型需要提供友好的相机控制和拾取交互能力需要有较高渲染性能来承载大体量模型还需要一套清晰的数据组织方式来管理不同来源的图层。这时候选择什么样的SDK直接决定了后续开发的节奏和交付质量。1.2 为什么从Cesium、Three.js里选了EarthSDK3前面先同步一个背景团队里有人之前用Three.js做过模型展示项目也有人用Cesium做过卫星轨迹可视化所以选型时并不是没有基础。我们针对EarthSDK3、Cesium、Three.js分别做了快速原型验证对比结果非常直白。Cesium的优势在于太空级数据调度和成熟的3D Tiles生态适合做全球尺度场景但它默认的地球渲染风格偏冷要调出国内GIS项目常见的视觉效果往往得写不少样式覆盖代码。Three.js自由度最高可以自己控制渲染管线但几乎所有GIS能力都得自己造轮子比如坐标系转换、瓦片加载、地形解析这些开发周期会拉得很长。EarthSDK3算是取了一个中间值。它底层的渲染能力不弱封装出来的API又很贴近国内GIS开发的习惯像“图层”“标注”“量测”这些概念基本拿到就能用不用重新理解一套逻辑。还有一个很关键的因素是它的文档和示例代码本土化做得好项目急的时候这个优势会被放大遇到问题搜一下或者翻示例就能解决不用满世界找翻译后的资料。我整理了一张当时快速验证的对比表简化后是下面这样的对比维度EarthSDK3CesiumThree.js三维地球基础能力开箱即用API语义清晰强大但偏底层需要自行集成国内GIS数据适配对WGS84、CGCS2000、EPSG:4326支持完善支持但需手动配置需自行处理业务功能封装测量、标注、可视域等有现成示例部分需从示例二次开发几乎全部自建上手成本中低文档中文友好较高高大屏项目视觉风格内置多套主题改起来方便默认风格需较多调整完全自由需要设计师配合当然这不是说EarthSDK3是万能的。在需要处理海量全球级卫星影像、或者需要深度定制渲染管线的场景下Cesium和Three.js可能更合适。但就“Web端三维GIS业务系统”这个范围来说EarthSDK3对团队的性价比确实更高。1.3 整体技术架构和数据流设计项目整体架构沿用经典的前后端分离模式。前端使用Vue 3构建业务界面三维部分由EarthSDK3负责渲染两者通过SDK对外暴露的实例通信。后端负责提供矢量和模型数据接口。数据链路大致是这样的静态地理数据建筑白模、影像瓦片、地形预先处理并上传到对象存储或GIS数据服务中前端通过图层方式加载业务数据摄像头信息、设备状态、工单记录通过HTTP接口动态获取再转成三维场景里的标注或模型绑定属性。设计这套架构时我主要考虑了两点。第一是数据和视图分离图层只负责“把数据显示出来”业务属性通过数据关联去绑定这样更换数据源时不用改动渲染层代码。第二是尽量复用SDK封装好的能力避免在业务代码里直接操作底层渲染对象降低后续维护成本。整个项目跑下来这套架构经受住了考验即使后期新增了好几类业务数据前端代码也没有出现结构性的调整。2. 环境准备与第一个三维场景2.1 搭建开发环境需要注意的细节EarthSDK3本质上是一个基于WebGL的JavaScript库所以对前端工程化环境的要求不高。Node.js版本建议使用14以上包管理器用npm或yarn都可以。项目用的Vue 3但如果你用原生HTML开发直接引入SDK的JS文件一样能跑起来。安装方式选择上如果用的是构建工具建议直接走npm安装方便做版本管理和按需引入。我在项目里使用的命令大致是npm install earth-sdk/earth然后通过import方式引入模块和样式。如果只是想快速验证功能也可以把官方提供的SDK文件下载下来在HTML里用script标签引入。两种方式我都试过build工具模式下Tree Shaking效果更好打包体积会小一些。有一点要提前确认好就是SDK的授权token。EarthSDK3通常需要注册开发者账号并创建应用获取token初始化时传入才能正常使用。朋友团队第一次跑示例时没配置token页面一直白屏排查了半天才发现是授权问题。这个虽然不算技术难点但很容易在最开始卡住建议搭建工程时先检查这一步。2.2 快速初始化一个三维地球初始化三维地球的整个过程可以拆成三步准备容器、创建实例、配置底图。第一步在页面里放一个撑满区域的div容器并给它一个id。容器的宽高必须设置不然SDK计算视口尺寸时会拿到0导致地球不渲染。这是新手最容易忽略的问题。第二步创建地球实例。示例代码如下import * as Earth from earth-sdk/earth import earth-sdk/earth/dist/style.css const viewer new Earth.Earth(earthContainer, { token: 你的应用token, scene: { backgroundColor: #0b1120 }, // 相机初始位置这里先放在北京上空 camera: { position: [116.391, 39.907, 15000], heading: 0, pitch: -60 } })第三步给地球添加一个影像底图。这里加载的是一个在线XYZ瓦片地址viewer.layers.addImageryLayer({ type: xyz, url: https://your-tile-server.com/tiles/{z}/{x}/{y}.png, minimumLevel: 3, maximumLevel: 18 })这样做完页面上就能看到一个带底图的三维地球了。注意不同小版本之间API名称可能会有细微调整。如果发现初始化方法对不上优先查阅对应版本的官方案例这是最靠谱的排查方式。2.3 初始化参数背后的一点经验初始化Earth实例时有几个参数值得花时间理解而不是直接复制官方默认配置。camera.position数组里的三个值分别是经度、纬度和相机距地高度。高度单位是米15000就是15公里这个高度下能看到整片城区适合园区项目初始视角。pitch是俯仰角-60表示相机斜向下看地面接近人站在高处往下看的效果。如果设置成-90就是正俯视的电子地图视角。scene.backgroundColor是场景背景色。默认的深蓝色适合夜景风格大屏如果你做的是日间业务系统改成偏亮的颜色会更清晰。这个参数在初始化后也可以通过viewer.scene.backgroundColor动态修改。另外还需要提醒一点earth实例的创建要放在页面挂载完成之后。如果在Vue的created或者React的构造函数里初始化容器DOM还没渲染好同样会出现白屏问题。我习惯在mounted或useEffect里初始化并加一个判断确保容器存在。3. 核心图层加载把数据放进三维球里3.1 影像底图加载在线地图服务与自建瓦片影像底图是三维GIS项目最基础的图层相当于传统GIS里的底图。EarthSDK3支持通过XYZ、WMTS、WMS等协议加载瓦片数据我这次项目同时用了两种方式在线公共瓦片服务和自建私有瓦片。在线瓦片适合项目初期快速搭建演示环境通常走XYZ协议。配置时核心是模板URL、最小层级和最大层级。模板URL里用{z}/{x}/{y}占位SDK会自动替换成当前视口所需的瓦片行列号。层级范围要和瓦片服务实际提供的层级一致设置过大或者过小都会导致瓦片请求失败或显示模糊。自建瓦片场景下使用的是离线影像。这种瓦片一般由地理处理工具提前切好存放在内网存储服务或对象存储中。配置方式和在线瓦片几乎一样只是URL改成内网地址。需要注意的坑是切瓦片时选择的坐标系必须和SDK的地球坐标系一致建议统一使用WGS84 Web墨卡托EPSG:3857。否则可能出现底图和矢量数据对不齐的情况这个问题排查起来相当耗时间。我总结了影像图层常用的几个参数参数作用我的经验值minimumLevel最小可见层级3maximumLevel最大可见层级18opacity图层透明度1或0.8contrast对比度调整1visibility是否可见true3.2 地形加载让场景“凹凸有致”纯影像底图渲染出来是平面的加载地形之后地面才会有起伏这在做山区、丘陵地带的项目时非常关键。园区的项目虽然基本是平地但为了后续扩展到周边山体场景地形能力还是提前接入了。EarthSDK3加载地形本质上是加载高程数据源通过渲染图层方式叠加到地球上。配置时只需要指定地形数据服务地址即可viewer.terrain.add({ url: https://your-terrain-server.com/terrain, visibility: true })大部分在线地形数据源都使用量化网格或者TIN格式组织。如果地形数据服务加载较慢可以通过设置地形细节层级来限制最大细节范围减少网络请求和三角面数量。注意地形数据和影像底图可能来自不同数据源一旦叠加后出现高程明显的断层或错位优先检查两个数据源的坐标系和范围是否一致这通常能解决八成以上的问题。3.3 矢量数据可视化GeoJSON与业务数据上屏三维GIS项目里矢量数据通常用来表达行政边界、道路、用地范围、管线等要素。GeoJSON是最常用的交换格式。把GeoJSON加载成三维图层并且给不同要素设置不同样式是三维GIS开发的基础能力。下面是我在项目中加载区域边界的方法viewer.layers.addGeoJsonLayer({ url: /data/region.geojson, fill: { color: rgba(0, 150, 255, 0.25), outline: true, outlineColor: #00a8ff }, clampToGround: true })关键参数有三个。fill.color控制面的填充颜色我习惯用带透明度的rgba值这样不会遮挡底图细节。outline控制是否显示边界线打开后面状区域的轮廓会非常清晰。clampToGround表示要素是否贴在地表上如果地形有起伏这个参数能让面贴合地形走而不是像纸片一样悬在半空。遇到业务数据不是GeoJSON的情况比如后端返回的是普通的JSON数组需要先转换成GeoJSON或者通过遍历数据方式手动添加标注。我一般使用一个转换函数把含经纬度字段的数据统一转成FeatureCollection再交给SDK渲染。这样做的好处是渲染层代码统一数据可以灵活切换。3.4 三维模型加载从单模型到倾斜摄影园区建筑的三维模型是项目里最核心的数据。模型格式方面我使用的有GLB单体模型和3D Tiles倾斜摄影数据两种。加载GLB单体模型比较简单关键是指定模型的放置位置、高度和缩放比例viewer.layers.addModelLayer({ url: /models/building_a.glb, position: [116.391, 39.907, 43], scale: 1, heading: 0 })position里的第三个值是模型离地高度。这个值要根据建筑底标高配置如果建筑底部被地面淹没就把高度值调高如果模型悬空就调低。现实中很多模型数据都缺乏准确的底标高这时候往往要现场调整我通常把调整步骤抽成可配置的参数方便在做模型摆放时反复微调。倾斜摄影模型一般用3D Tiles格式加载这是三维GIS项目中承载大范围精细化实景数据的主流方案viewer.layers.addTilesetLayer({ url: https://your-data-server.com/tileset.json, maximumScreenSpaceError: 16 })加载倾斜摄影数据时有一个参数极易踩坑maximumScreenSpaceError它控制模型在屏幕上的简化程度。值越大渲染性能越好但模型细节越少值越小细节越清楚但帧率会明显下降。我处理园区倾斜摄影数据时最终调到了16既保证了远看不断层近看也有足够细节。从整个图层体系来看合理的图层管理顺序是影响场景渲染效果的重要因素。影像底图放在最底层地形叠加在底图上矢量和标注放在中间层三维模型和倾斜摄影放在业务最上层。顺序错了虽然不一定报错但视觉上会出现相互遮挡的混乱情况调整起来比较痛苦。4. 业务交互功能开发让三维场景真正“能操作”4.1 相机飞行定位的多种打开方式三维GIS项目里“从当前位置飞到目标位置”是最常被业务方提及的功能。比如在大屏上点击左侧“停车场”右侧三维场景就要平滑地飞到停车场上方。EarthSDK3提供了一套相机控制API但实际开发时依然有几个细节需要处理好。基础用法是传一个目标视角viewer.camera.flyTo({ position: [116.391, 39.907, 800], heading: 0, pitch: -60, duration: 3 })duration控制飞行持续秒数我一般用3秒左右太快用户在视觉上跟不上太慢又显得反应迟钝。业务中我更常配合图层定位使用先通过图层ID找到对应图层的范围再根据范围动态计算相机落点这样无论目标物是几层楼的大建筑还是几十米的小标志都能准确框定在视野内。如果飞行时希望相机自动围绕某个目标点旋转展示可以在飞行结束后调用围绕旋转接口。这个效果在展示重点项目时很提气但要注意旋转速度不能太快否则容易让用户产生眩晕感。4.2 点击拾取与弹窗展示属性信息的三维呈现把三维场景和业务数据连起来的关键环节是点击模型或者标注时弹出对应的属性信息。比如点击一个摄像头点位能弹出“摄像头编号、所属区域、在线状态、负责人”等信息。实现这个功能我封装了一个通用点击方法viewer.on(click, (event) { const picked viewer.pick(event.windowPosition) if (picked) { const properties picked.attributes if (properties) { popup.show({ title: properties.name, content: buildContent(properties), position: picked.position }) } } })viewer.pick会从当前鼠标点击位置检测场景中是否拾取到对象返回的对象上带有之前绑定到模型或标注上的属性数据。这里最关键的开发习惯是在加载模型或标注时就要把业务属性通过统一字段绑定好比如name、type、code等。如果等点击之后再去查接口用户体验和代码逻辑都会大打折扣。弹窗组件我直接用了一个覆盖在三维画布上的普通HTML浮层通过position参数将浮层定位到模型在屏幕上的投影位置。场景旋转或缩放时浮层位置需要跟着同步可以在相机变化事件里更新浮层坐标。4.3 测量与分析能力让三维场景变得“专业”三维GIS和普通三维展示产品拉开差距的地方往往在测量和分析能力上。EarthSDK3内置的空间测量模块开箱即可用viewer.measure.enable(distance)这个接口会在场景中启用了测距模式用户点击多个点界面上就能实时显示每一段的长度和累计长度。除了测距还可以启用面积测量、高度测量、三角测量等模式。在园区管网管理场景中测距功能被用在了“估算两点间管线长度”上几乎零代码接入性价比极高。更进阶一点是做可视域分析也就是从某个观察点看出去能看见哪些区域viewer.analysis.startViewshed({ position: [116.391, 39.907, 120], direction: 0, angle: 60 })这个功能在园区安防场景中非常有用把摄像头位置作为观察点模拟监控视野范围辅助判断监控盲区。虽然三维空间分析有一定计算量但在覆盖范围适中的园区场景下运行时还是比较流畅的。4.4 动态点位与轨迹让业务数据动起来大屏可视化项目中“动起来”的数据往往比静态数据更有冲击力。我在项目里做了一个人员轨迹回放功能后端返回一系列按时间排序的坐标点前端将这些点转换成动态移动的标注或者线性轨迹。具体思路是先在地图上绘制一条轨迹线然后创建一个模型通过定时更新其position属性沿轨迹移动。移动过程中模型朝向也可以根据相邻两点方向动态计算让效果更加逼真。const movingMarker viewer.layers.createPointLayer({ image: /images/marker.png, position: trackPoints[0] }) let index 0 const timer setInterval(() { index 1 if (index trackPoints.length) { clearInterval(timer) return } movingMarker.position trackPoints[index] }, 1000)这里要注意的是轨迹数据量太大时直接逐帧更新位置会轻微卡顿。优化方式是在数据端对轨迹点做抽稀处理只保留必要的关键点位同时缩短更新间隔。实测中1000个点抽稀到200个点后视觉效果几乎不受影响但流畅度提升非常明显。5. 常见问题排查与性能优化实录5.1 白屏、授权失败与容器渲染问题三维GIS开发遇到的第一类问题往往是页面加载后一片空白或者地球不显示。从我接触到的提问来看九成以上的白屏是由下面几个原因造成的。第一是token未配置或配置错误。SDK初始化时会校验token一旦校验失败整个场景创建流程会中断。这类问题排查最容易打开浏览器控制台看网络请求如果初始化接口返回403或401基本就是授权问题。第二是容器没有宽高。这个问题前面也提过三维画布渲染依赖容器尺寸如果容器是隐藏状态或者初始宽高为0SDK拿不到有效视口尺寸地球自然不会显示。在Vue项目里如果父组件有懒加载或者v-show切换记得在切换完成后调用一次viewer.resize()。第三是数据源地址不可达。比如WS瓦片地址配置了一个不存在的域名或者自建地形数据服务没有启动场景虽然初始化成功了但视觉上只是没有一个完整的底图很容易让人误以为整个三维场景崩溃了。排查方法是用浏览器直接访问瓦片地址模板里的某一个具体瓦片URL看看能不能正常返回图片。5.2 坐标偏移最隐蔽的“数据对不上”问题坐标偏移是GIS开发中一个经典而又隐蔽的问题。表现在现象上就是底图在A位置、矢量在B位置、模型在C位置三者完全不在同一个经纬度上。发生坐标偏移的核心原因通常是数据源之间的坐标系不一致。比如影像瓦片使用WGS84 Web墨卡托投影EPSG:3857而业务矢量数据使用CGCS2000坐标系或者WGS84地理坐标EPSG:4326。Web墨卡托坐标数值本身是以米为单位的平面坐标和经纬度数值不是同一套系统直接混用必然会产生偏移。我的排查思路是按照“先确定数据坐标系再做统一转换”的顺序。建议项目一启动就定下规范所有进入场景的空间数据统一转成WGS84经纬度EPSG:4326或EPSG:3857具体看SDK内部采用哪种坐标系。业务数据在后端导出时就完成转换前端不做二次转换避免重复计算和数据不一致。注意CGCS2000和WGS84在多数城市区域内差异很小但这种小差异在大比例尺业务中会变成几米到几十米的偏差所以在政企类GIS项目中不能忽视必须作为专项问题处理。5.3 场景卡顿资源、数据和渲染的三层优化三维GIS项目跑到中后期性能优化是无法回避的话题。优化前我做的第一件事是用浏览器Performance面板记录场景在正常操作时的帧率和耗时。记录显示场景初始状态下帧率只有25左右切换到建筑密集区域时会掉到20以下交互明显有迟滞感。从GPU和网络两个维度分析了数据后我做了下面三项优化。第一项是模型资源轻量化。园区倾斜摄影原始数据如果直接加载浏览器几乎扛不住。我使用工具对模型做了纹理压缩和顶点抽稀的预处理把纹理从2048尺寸压缩到1024经过处理后加载包体减小约四成初帧加载耗时从4秒下降到2秒左右。第二项是渲染参数调整。通过调大maximumScreenSpaceError让远处模型使用更粗糙的表层渲染降低了GPU压力。同时开启视口裁剪让视野外的模型不再参与渲染计算。这两项调整对帧率提升立竿见影。第三项是数据分级加载。在项目中我把摄像头点位按区域分组结合相机高度动态控制图层的可见性。当相机拉到城市尺度时只显示所在城区的点位当镜头放大到园区内部时才显示园区全量点位。这样既保证了视觉效果又大幅减少了单帧渲染的实体数量。优化后再次记录数据帧率稳定在55以上倾角度操作也明显跟手了。整个过程验证了一个观点三维GIS性能优化不是靠单一手段就能解决的资源层、渲染层、数据层需要同时动手术。5.4 常见问题速查表把这一路踩过的坑整理成一张速查表方便后面排查问题的时候快速定位现象可能原因处理建议页面白屏token未授权或者容器无宽高检查控制台请求确认容器尺寸地球显示但无底图瓦片地址不可达或层级设置错误用浏览器直接访问瓦片检查模板URL地面平面化未加载地形数据源检查terrain.add的地址是否正确矢量与底图位置对不上坐标系不一致统一数据源坐标系推荐EPSG:4326模型半截埋入地下模型底标高设置偏低在position第三个参数加大高度点击模型无拾取结果未绑定业务属性确认加载时是否传入attributes字段场景卡顿模型面数过多、渲染参数不合理做模型轻量化调整LOD参数纹理模糊纹理分辨率过小或mipmap未配置预处理阶段提升贴图分辨率6. EarthSDK3开发中我总结的几条个人经验项目从启动到交付整个周期花了两个月左右前期踩坑多后期顺畅不少。聊聊我的体感。EarthSDK3这类三维GIS框架真正拉开开发效率差距的通常不是API掌握程度而是是否理解三维场景里“数据组织”这件事。模型、标注、图层、属性、事件这些元素之间的关系理清了后续加需求、改样式都会非常顺。反过来如果一开始只是对着官网示例做堆叠代码很容易变成一坨无法维护的混合体。另外一个心得是三维开发调试离不开“分步验证”的习惯。加一个图层就检查一次位置和样式不要所有东西一次性铺上去再去调否则出了错根本不知道是哪一层引入的。我甚至会在加载层时临时给不同图层设置不同颜色确认数据范围正确后再还原成正式样式这个方法虽然土但在排查问题的时候特别高效。最后建议团队做三维GIS项目时预留一个独立的阶段做模型处理。很多项目把时间全部排给了前端开发忽略了对原始GIS数据的轻量化和格式转换结果到了联调阶段不断被性能和兼容性问题拖住。数据处理好前端工作量至少能减少三分之一。