Cesium入门实战:5分钟搞定3D地球展示与坐标转换(附完整代码)
Cesium实战:从零构建3D地球与坐标转换核心指南
最近在做一个智慧城市可视化的项目,客户要求能在一个网页上流畅地展示全球范围的三维地理数据,并且能自由地切换视角、精准定位。说实话,刚开始听到这个需求,我脑子里第一个蹦出来的就是Cesium。这玩意儿在Web 3D GIS领域,尤其是地球可视化这块,基本就是“标配”了。但真上手的时候,发现网上很多教程要么太老,要么就是只讲个皮毛,关于坐标转换这块的坑,踩得我够呛。今天我就把自己从项目里趟出来的经验,结合最核心的地球展示和坐标转换逻辑,掰开揉碎了讲给你听。无论你是想快速做个Demo验证想法,还是打算在正式项目中集成Cesium,这篇文章都能帮你省下大量查文档和调试的时间。
1. 环境搭建与第一个地球
别被“3D”、“地球”这些词吓到,用Cesium在网页上展示一个可交互的地球,其实比想象中简单。我们不走复杂的环境配置,就用最直接的方式,让你在5分钟内看到效果。
1.1 获取Cesium库与访问令牌
Cesium提供了几种使用方式,对于快速入门和原型开发,我强烈推荐直接使用其CDN链接,这能避免本地文件管理的麻烦。在你的HTML文件头部引入以下资源:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>我的第一个Cesium地球</title> <!-- 引入Cesium核心JS库 --> <script src="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Cesium.js"></script> <!-- 引入Cesium默认样式 --> <link rel="stylesheet" href="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Widgets/widgets.css"> <style> /* 让地球视图充满整个浏览器窗口 */ html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <!-- 用于承载3D地球的DOM容器 --> <div id="cesiumContainer"></div> </body> </html>注意:上面的CDN链接使用了1.107版本,这是一个经过广泛测试的稳定版本。你可以访问Cesium官网查看最新稳定版并替换版本号。
接下来是关键一步:设置访问令牌。Cesium的全球影像和地形数据服务需要身份验证。你需要注册一个Cesium ion账户(免费账户有额度,用于学习和demo完全足够)。
- 访问 Cesium ion 官网并注册/登录。
- 在 Dashboard 页面,找到并点击“Access Tokens”。
- 你会看到一个默认的令牌,点击“Copy”复制它。
将令牌配置到你的JavaScript代码中,必须在创建Viewer实例之前设置:
<script> // 将你的令牌粘贴在这里 Cesium.Ion.defaultAccessToken = '你的_access_token_字符串'; // 初始化3D地球视图 const viewer = new Cesium.Viewer('cesiumContainer', { // 可选配置:隐藏一些默认控件,让界面更简洁 animation: false, // 隐藏动画控件 timeline: false, // 隐藏时间轴控件 baseLayerPicker: false, // 隐藏底图选择器 geocoder: false // 隐藏搜索框 }); // 一个简单的测试:将视角定位到北京上空 viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 1500000.0) // 经度,纬度,高度(米) }); </script>把完整的HTML文件保存,用浏览器打开。如果一切顺利,你应该能看到一个正在旋转的蓝色地球,并且镜头会快速飞向中国华北地区。恭喜,你的第一个Cesium应用已经跑起来了!
1.2 理解核心类:Viewer与Scene
代码很简单,但背后有几个核心对象在起作用。理解它们,是后续进行任何高级操作的基础。
Viewer是整个应用的入口和总控制器。你可以把它想象成一个功能齐全的“地球仪播放器”。它不仅仅包含3D场景,还集成了时间轴、图层选择、帮助按钮等一系列UI控件。我们通过new Cesium.Viewer(containerId, options)来创建它。options对象非常强大,可以用来定制几乎所有视觉和交互元素。
Scene是3D世界的容器,它管理着地球(Globe)、相机(Camera)、光源(Lighting)以及所有图形图元(Primitives)。Viewer内部自动创建了一个Scene实例,我们可以通过viewer.scene来访问。大部分对三维世界本身的修改,比如添加模型、改变地形、调整光照,都是通过Scene来完成的。
它们的关系和典型操作如下表所示:
| 对象 | 角色 | 关键属性/方法 | 常用操作场景 |
|---|---|---|---|
| Viewer | 应用总控,UI容器 | viewer.entities,viewer.camera,viewer.scene,viewer.dataSources | 添加实体(点、线、面)、控制视角、加载数据源、管理UI控件显隐 |
| Scene | 3D场景渲染核心 | scene.globe,scene.camera,scene.primitives,scene.pickPosition | 修改地球材质、启用地形、直接添加高性能图元、处理屏幕坐标拾取 |
| Globe | 地球对象 | globe.show,globe.baseColor,globe.depthTestAgainstTerrain | 隐藏/显示地球、设置海洋颜色、控制地形深度检测 |
| Camera | 观察视角 | camera.setView,camera.zoomIn,camera.flyTo | 视角跳转、飞行漫游、缩放旋转 |
一个常见的误区是分不清viewer.entities.add和viewer.scene.primitives.add的区别。简单来说,Entity API是高级的、声明式的、易于使用的接口,适合管理动态的、需要与时间关联的对象(比如移动的车辆、变化的传感器范围)。而Primitive API是底层的、性能优化的接口,适合静态的、大量重复的几何体(比如成千上万的建筑白模)。入门阶段,先用好Entity就足够了。
2. 丰富你的地球:添加数据与交互
一个光秃秃的地球显然不够。我们需要在上面放置内容。Cesium的Entity系统让这件事变得直观。
2.1 使用Entity添加点、线、面
假设我们要在纽约帝国大厦的位置添加一个红色标记点,并画一条从纽约到伦敦的航线。
// 在纽约(-74.0, 40.7)添加一个点 const empireStateBuilding = viewer.entities.add({ name: '帝国大厦', position: Cesium.Cartesian3.fromDegrees(-74.0, 40.7, 300), // 最后一个是高度 point: { pixelSize: 15, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: '帝国大厦', font: '14pt sans-serif', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 标签在点的下方 pixelOffset: new Cesium.Cartesian2(0, -20) // Y轴向下偏移20像素 } }); // 添加一条从纽约到伦敦的线 const transatlanticLine = viewer.entities.add({ name: '纽约-伦敦航线', polyline: { positions: Cesium.Cartesian3.fromDegreesArrayHeights([ -74.0, 40.7, 10000, // 纽约,高度10000米 -0.1, 51.5, 10000 // 伦敦,高度10000米 ]), width: 4, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.CYAN }) } }); // 添加一个多边形区域(例如,粗略勾勒曼哈顿岛) const manhattanPolygon = viewer.entities.add({ name: '曼哈顿区域', polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray([ -74.05, 40.70, -74.00, 40.70, -74.00, 40.80, -74.05, 40.80 ]), material: Cesium.Color.GREEN.withAlpha(0.5), // 半透明的绿色 outline: true, outlineColor: Cesium.Color.BLACK } }); // 将视角飞到纽约,并跟踪“帝国大厦”这个实体 viewer.zoomTo(empireStateBuilding); // 或者使用跟踪模式,让相机锁定实体 // viewer.trackedEntity = empireStateBuilding;运行代码,你会看到地球上的标记点和连线。Entity的配置项非常丰富,你可以轻松改变颜色、大小、贴图、动画效果等。
2.2 实现基础的视角切换
用户不可能只从一个角度看地球。流畅的视角切换是良好体验的关键。Cesium的Camera提供了多种控制方式。
1. 瞬时跳转 (setView):适合快速切换到已知的精确位置和角度。
// 跳转到指定经纬度和高度(俯视) viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 2000.0), // 北京上空2公里 orientation: { heading: Cesium.Math.toRadians(0), // 北方向为0度 pitch: Cesium.Math.toRadians(-90), // 俯仰角,-90度垂直向下看 roll: 0.0 } });2. 平滑飞行 (flyTo):这是最常用的方式,镜头会沿着一条优美的曲线飞向目标,体验极佳。
// 飞向巴黎埃菲尔铁塔 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(2.29, 48.86, 1000.0), duration: 3.0, // 飞行时间,3秒 complete: function() { console.log('飞行结束!'); }, cancel: function() { console.log('飞行被取消'); }, // 飞行过程中的视角变化 pitchAdjustHeight: -500, // 在飞行中调整俯仰角的相关高度 maximumHeight: 5000 // 飞行路径的最高海拔 });3. 程序化控制:通过直接操作相机属性,可以实现第一人称游戏式的控制。
// 获取当前相机位置和方向 const position = viewer.camera.position; const heading = viewer.camera.heading; const pitch = viewer.camera.pitch; // 让相机缓慢向右旋转(改变航向角) const rotateRight = function() { viewer.camera.rotateRight(Cesium.Math.toRadians(0.5)); // 每次旋转0.5度 }; // 每100毫秒执行一次,实现平滑旋转 setInterval(rotateRight, 100); // 停止旋转 // clearInterval(intervalId);你可以将这些飞行或跳转操作绑定到网页的按钮上,实现一个简单的场景切换面板。
<div style="position: absolute; top: 10px; left: 10px; z-index: 1000; background: white; padding: 10px; border-radius: 5px;"> <button onclick="flyToBeijing()">飞往北京</button> <button onclick="flyToNewYork()">飞往纽约</button> <button onclick="setTopView()">切换到顶视图</button> </div> <script> function flyToBeijing() { viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 500000.0), duration: 4}); } function flyToNewYork() { viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(-74.0, 40.7, 500000.0), duration: 4}); } function setTopView() { viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(0, 0, 20000000.0), orientation: {heading: 0, pitch: -Cesium.Math.PI/2, roll: 0}}); } </script>3. 坐标转换:打通2D与3D世界的桥梁
这是Cesium开发中最容易混淆,也最核心的部分。屏幕上鼠标点击的位置(2D像素坐标)如何对应到地球上的一个点(3D地理坐标)?不同格式的地理数据如何正确显示?理解Cesium的坐标系是解决这些问题的钥匙。
3.1 Cesium中的五大坐标系
Cesium内部主要使用五种坐标系,它们在不同的场景下各司其职:
- WGS84经纬度(度): 我们最熟悉的地理坐标,例如
(116.4, 39.9, 50)表示东经116.4度,北纬39.9度,海拔50米。这是一个概念性的坐标系,没有直接的Cesium对象。 - WGS84弧度(Cartographic): 这是Cesium内部进行地理计算的基础坐标系。它将经纬度转换为弧度,例如
(2.03, 0.696, 50)。对应的类是Cesium.Cartographic。 - 笛卡尔空间直角坐标系(Cartesian3): 这是Cesium渲染引擎最终使用的3D坐标系。原点在地球中心,X轴指向本初子午线与赤道交点,Z轴指向北极。一个位置表示为
(x, y, z)三个数值。对应的类是Cesium.Cartesian3。所有Entity的position、相机的位置destination,最终都需要转换成这个坐标系。 - 平面坐标系(Cartesian2): 这是二维屏幕坐标,原点在Canvas画布的左上角,单位是像素。例如鼠标点击事件获得的坐标
(250, 400)。对应的类是Cesium.Cartesian2。 - 4D笛卡尔坐标系(Cartesian4): 主要用于图形学中的齐次坐标,在一般开发中很少直接使用。
它们之间的转换关系,可以用下面这个流程图来概括其核心路径: (想象一个从屏幕点击到地理坐标的逆向推导过程)屏幕像素(Cartesian2) → 场景射线 → 地球表面/实体交点(Cartesian3) → 地理弧度(Cartographic) → 经纬度(度)
3.2 实战:从屏幕点击获取经纬度
这是一个非常常见的需求:用户点击地球上的某个位置,我们需要获取该点的经纬度信息。
// 为viewer的canvas元素添加点击事件处理器 viewer.screenSpaceEventHandler.setInputAction(function(event) { // event.position 就是点击位置的屏幕坐标 (Cartesian2) const pixelPosition = event.position; // 方法1:拾取椭球体表面坐标(忽略地形和模型) const ellipsoidPosition = viewer.scene.camera.pickEllipsoid(pixelPosition, viewer.scene.globe.ellipsoid); if (Cesium.defined(ellipsoidPosition)) { const cartographic = Cesium.Cartographic.fromCartesian(ellipsoidPosition); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const height = cartographic.height; console.log(`方法1(椭球面): 经度: ${longitude.toFixed(6)}°, 纬度: ${latitude.toFixed(6)}°, 高度: ${height.toFixed(2)}米`); } // 方法2:拾取真实场景中的坐标(包含地形和模型) const scenePosition = viewer.scene.pickPosition(pixelPosition); if (Cesium.defined(scenePosition)) { const cartographic = Cesium.Cartographic.fromCartesian(scenePosition); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const height = cartographic.height; console.log(`方法2(真实场景):经度: ${longitude.toFixed(6)}°, 纬度: ${latitude.toFixed(6)}°, 高度: ${height.toFixed(2)}米`); // 在点击位置添加一个临时点作为视觉反馈 const clickPoint = viewer.entities.add({ position: scenePosition, point: { pixelSize: 10, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2 } }); // 3秒后移除这个点 setTimeout(() => viewer.entities.remove(clickPoint), 3000); } else { console.log('点击到了空白区域(如天空)。'); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 监听左键点击事件提示:
pickEllipsoid和pickPosition的区别至关重要。如果你的场景加载了高精度地形(viewer.terrainProvider = Cesium.createWorldTerrain()),那么点击山脉时,pickEllipsoid返回的是山脉“地下”椭球面的点,而pickPosition返回的是山脉“表面”的点。在大多数涉及真实地形的应用中,应该使用pickPosition。
3.3 经纬度与Cartesian3的相互转换
这是数据导入和导出的核心。你的外部数据(如GPS轨迹、GeoJSON)很可能是经纬度格式,而Cesium渲染需要Cartesian3。
1. 经纬度(度)转 Cartesian3:
// 单个点转换 const beijingCartesian3 = Cesium.Cartesian3.fromDegrees(116.4, 39.9, 50); // 经度,纬度,高度(米) // 批量转换(高效处理大量点,如绘制线、面) const degreesArray = [116.4, 39.9, -74.0, 40.7, 2.29, 48.86]; // [lon1, lat1, lon2, lat2, ...] const positionsArray = Cesium.Cartesian3.fromDegreesArray(degreesArray); // 忽略高度 const positionsArrayWithHeight = Cesium.Cartesian3.fromDegreesArrayHeights([116.4, 39.9, 100, -74.0, 40.7, 200]); // [lon1, lat1, height1, ...]2. Cartesian3 转回经纬度(度):
// 从Cartesian3转换到Cartographic(弧度) const cartographic = Cesium.Cartographic.fromCartesian(beijingCartesian3); // 或者使用椭球体方法(本质相同) // const cartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(beijingCartesian3); // 将弧度转换为度 const longitudeDeg = Cesium.Math.toDegrees(cartographic.longitude); const latitudeDeg = Cesium.Math.toDegrees(cartographic.latitude); const heightMeters = cartographic.height; console.log(`转换结果:${longitudeDeg}, ${latitudeDeg}, ${heightMeters}`);为了方便查阅,我将常用的坐标转换函数总结如下:
| 转换方向 | 函数 | 说明 |
|---|---|---|
| 度 → Cartesian3 | Cesium.Cartesian3.fromDegrees(lon, lat, height) | 单个点,指定高度 |
| 度 → Cartesian3 (数组) | Cesium.Cartesian3.fromDegreesArray([lon1, lat1, lon2, lat2...]) | 多个点,高度为0 |
| 度 → Cartesian3 (数组带高) | Cesium.Cartesian3.fromDegreesArrayHeights([lon1, lat1, h1, lon2, lat2, h2...]) | 多个点,各自指定高度 |
| Cartesian3 → 度 | Cesium.Cartographic.fromCartesian(cartesian3)+Cesium.Math.toDegrees() | 两步走,先转弧度再转度 |
| 屏幕 → Cartesian3 (地表) | viewer.scene.pickPosition(cartesian2) | 推荐,考虑地形和模型 |
| 屏幕 → Cartesian3 (椭球) | viewer.scene.camera.pickEllipsoid(cartesian2) | 忽略地形,直接打在椭球体上 |
| Cartesian3 → 屏幕 | Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, cartesian3) | 将3D世界坐标投影到2D屏幕 |
4. 进阶整合:构建一个迷你地理标注工具
现在,我们把前面学到的所有知识整合起来,做一个有实用价值的小工具:一个允许用户点击地球添加标注,并可以列表管理这些标注的简单应用。
4.1 功能设计与数据结构
我们要实现:
- 点击地球,在点击处添加一个带标签的标记点。
- 右侧显示一个标注列表,展示每个标注的名称和坐标。
- 点击列表项,镜头可以飞向那个标注。
- 可以从列表中删除标注。
首先,在HTML中创建界面结构:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Cesium 地理标注工具</title> <script src="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Widgets/widgets.css"> <style> html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; font-family: sans-serif; } #cesiumContainer { width: 75%; height: 100%; float: left; } #sidebar { width: 25%; height: 100%; float: left; background: #f5f5f5; padding: 15px; box-sizing: border-box; overflow-y: auto; } #annotationList { list-style: none; padding: 0; } .annotation-item { background: white; margin-bottom: 10px; padding: 10px; border-radius: 5px; border-left: 4px solid #4CAF50; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .annotation-item:hover { background-color: #e8f5e9; } .coord { font-size: 0.9em; color: #666; margin-top: 5px; } .delete-btn { float: right; background: #ff5252; color: white; border: none; border-radius: 3px; padding: 3px 8px; cursor: pointer; } </style> </head> <body> <div id="cesiumContainer"></div> <div id="sidebar"> <h3>地理标注列表</h3> <p>点击地球任意位置添加标注。</p> <ul id="annotationList"></ul> </div> <script> // 主JavaScript代码将放在这里 </script> </body> </html>4.2 核心逻辑实现
在<script>标签内,我们实现完整的交互逻辑。
Cesium.Ion.defaultAccessToken = '你的_access_token'; // 替换成你的token const viewer = new Cesium.Viewer('cesiumContainer', { animation: false, timeline: false, baseLayerPicker: false }); // 存储所有标注的数组 let annotations = []; // 为每个标注生成唯一ID let nextId = 1; // 1. 点击地球添加标注 viewer.screenSpaceEventHandler.setInputAction(function(event) { const pixelPosition = event.position; const scenePosition = viewer.scene.pickPosition(pixelPosition); if (!Cesium.defined(scenePosition)) { console.log("未点击到有效位置。"); return; } // 将Cartesian3转换为经纬度 const cartographic = Cesium.Cartographic.fromCartesian(scenePosition); const lon = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6); const lat = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6); const height = cartographic.height.toFixed(2); // 创建标注对象 const annotationId = nextId++; const annotationName = `标注点 ${annotationId}`; // 在地球上添加实体 const pointEntity = viewer.entities.add({ id: `annotation_${annotationId}`, name: annotationName, position: scenePosition, point: { pixelSize: 12, color: Cesium.Color.fromRandom({alpha: 1.0}), outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: annotationName, font: '12pt sans-serif', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, pixelOffset: new Cesium.Cartesian2(0, -25), verticalOrigin: Cesium.VerticalOrigin.BOTTOM } }); // 保存到数组 const annotationData = { id: annotationId, entity: pointEntity, name: annotationName, longitude: lon, latitude: lat, height: height, cartesian3: scenePosition }; annotations.push(annotationData); // 更新侧边栏列表 updateAnnotationList(); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 2. 更新侧边栏列表的DOM function updateAnnotationList() { const listElement = document.getElementById('annotationList'); listElement.innerHTML = ''; // 清空现有列表 annotations.forEach(anno => { const li = document.createElement('li'); li.className = 'annotation-item'; li.dataset.id = anno.id; li.innerHTML = ` <strong>${anno.name}</strong> <button class="delete-btn" onclick="deleteAnnotation(${anno.id})">删除</button> <div class="coord">经度: ${anno.longitude}°</div> <div class="coord">纬度: ${anno.latitude}°</div> <div class="coord">高度: ${anno.height} 米</div> `; // 点击列表项,飞行到该标注 li.addEventListener('click', function(e) { if (!e.target.classList.contains('delete-btn')) { viewer.flyTo(anno.entity, { duration: 2.0, offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-30), 1000) }); } }); listElement.appendChild(li); }); } // 3. 删除标注函数(需要暴露给全局,供按钮调用) window.deleteAnnotation = function(id) { // 阻止事件冒泡,避免触发列表项的飞行事件 event.stopPropagation(); const index = annotations.findIndex(a => a.id === id); if (index !== -1) { // 从Cesium场景中移除实体 viewer.entities.remove(annotations[index].entity); // 从数组中移除数据 annotations.splice(index, 1); // 更新UI列表 updateAnnotationList(); } }; // 4. (可选)添加一个示例标注 function addExampleAnnotation() { // 模拟在东京点击一下 const tokyoCartesian = Cesium.Cartesian3.fromDegrees(139.76, 35.68, 100); const cartographic = Cesium.Cartographic.fromCartesian(tokyoCartesian); const lon = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6); const lat = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6); const entity = viewer.entities.add({ position: tokyoCartesian, point: { pixelSize: 15, color: Cesium.Color.GOLD }, label: { text: '示例:东京', font: '14pt sans-serif' } }); annotations.push({ id: nextId++, entity: entity, name: '示例:东京', longitude: lon, latitude: lat, height: '100.00', cartesian3: tokyoCartesian }); updateAnnotationList(); viewer.flyTo(entity); } // 页面加载后添加一个示例 setTimeout(addExampleAnnotation, 1000);这个工具虽然简单,但涵盖了Cesium开发中最常见的几个模式:事件交互、坐标转换、实体管理、UI联动。你可以在此基础上继续扩展,比如为标注添加描述信息、保存到本地存储、导入/导出GeoJSON等。
在实际项目中,当标注点成百上千时,直接使用viewer.entities可能会遇到性能瓶颈。那时就需要考虑使用CustomDataSource进行分组管理,或者对于静态点使用PrimitiveAPI 进行批量渲染优化。不过,对于大多数中小规模的应用,EntityAPI的简洁和强大已经足够应付。
