Cesium快速入门到精通系列教程九:Cesium 中高效添加和管理图标/标记的标准方式
Cesium中通过 Primitive 高效添加 点、线、多边形、圆、椭圆、球、模型 等地理要素,以下是各类地理要素的高效添加方式:
一、公告板
1. 创建 BillboardCollection 并添加到场景
const billboards = viewer.scene.primitives.add(new Cesium.BillboardCollection());
- new Cesium.BillboardCollection()
创建一个新的 公告板集合(BillboardCollection),用于管理多个公告板(Billboard)。
- viewer.scene.primitives.add(...)
将这个公告板集合添加到 Cesium 的 场景(Scene) 中,使其能够被渲染。
作用:
类似于创建一个“容器”,用于高效管理多个公告板(比如多个图标、标记点等),而不是单独添加每个公告板(单独添加会有更高的性能开销)。
2. 向 BillboardCollection 添加一个公告板
billboards.add({position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 600),image: '/Assets/nav.svg',width: 32,height: 32,scaleByDistance: new Cesium.NearFarScalar(1e3, 1.0, 2e6, 0.2)
});
- position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 600)
定义公告板的 3D 坐标位置:
113.3244, 23.1049 是经纬度(WGS84 坐标系)。
600 是高度(单位:米),表示公告板在地球表面上方 600 米处。
- image: '/Assets/nav.svg'
公告板显示的 图片路径(这里是 /Assets/nav.svg,可以是一个 SVG 或 PNG 图标)。
- width: 32, height: 32
公告板的 尺寸(宽度和高度均为 32 像素)。
- scaleByDistance: new Cesium.NearFarScalar(1e3, 1.0, 2e6, 0.2)
视距缩放优化(根据相机距离动态调整公告板大小):
当相机距离 1000 米(1e3) 时,公告板显示原始大小(1.0 倍)。
当相机距离 2000000 米(2e6) 时,公告板缩小到 0.2 倍(避免远处图标过大)。
作用:
在指定位置(广州附近,高度 600 米)添加一个 导航图标(nav.svg),并优化其显示大小(近大远小)。
3、整体作用
这段代码的 核心功能 是:
- 创建一个公告板集合(BillboardCollection),用于高效管理多个公告板。
- 向集合中添加一个公告板,指定其:
- 位置(经纬度 + 高度)。
- 显示的图片(nav.svg)。
- 尺寸(32x32 像素)。
- 视距缩放优化(近大远小,避免远处图标过大)。
4、优化点
- 使用 BillboardCollection 而不是单独添加 Billboard
批量管理多个公告板时,性能更高(减少 GPU 调用次数)。
- scaleByDistance 优化
避免远处图标过大,提升视觉效果。
- 支持 3D 位置(含高度)
不仅能在地表放置图标,还能在 3D 空间(如空中)放置。
5、扩展用法
如果需要添加多个公告板,可以循环调用 billboards.add():
const positions = [{ lon: 113.3244, lat: 23.1049, height: 600 },{ lon: 113.3254, lat: 23.1059, height: 700 }
];
positions.forEach(pos => {billboards.add({position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, pos.height),image: '/Assets/nav.svg',width: 32,height: 32});
});
性能更好的方式:
const billboards = viewer.scene.primitives.add(new Cesium.BillboardCollection({scene: viewer.scene,debugShowBoundingVolume: false // 关闭调试框,提升性能[4](@ref)})
);const positions = [{ lon: 116.40, lat: 39.91, image: "icon1.png" },{ lon: 121.47, lat: 31.23, image: "icon2.png" }
];const billboardList = positions.map(pos => {return {image: pos.image,position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat),scale: 0.8,color: Cesium.Color.WHITE.withAlpha(0.9),horizontalOrigin: Cesium.HorizontalOrigin.CENTER,verticalOrigin: Cesium.VerticalOrigin.BOTTOM};
});// 批量添加(减少渲染调用)
billboards.add(billboardList);
6、动态更新策略
直接修改属性
const billboard = billboards.get(0); // 获取第一个广告牌
billboard.scale = 1.2; // 修改缩放比例
billboard.position = Cesium.Cartesian3.fromDegrees(120.0, 30.0); // 更新位置
动态效果(旋转/闪烁)
// 通过 preRender 事件实现旋转动画[9](@ref)
viewer.scene.preRender.addEventListener(() => {const time = Date.now() * 0.001;billboard.rotation = time % (Math.PI * 2); // 持续旋转billboard.color.alpha = 0.5 + 0.5 * Math.sin(time); // 透明度闪烁
});
按需更新
// 仅当广告牌可见时更新
if (billboard.show) {billboard.scale = calculateScaleBasedOnDistance();
}
7、性能优化技巧
7.1 GPU 合并渲染
批量添加:单次 billboards.add() 提交多个广告牌,触发 GPU 实例化渲染。
纹理复用:相同图片自动合并纹理,减少 Draw Call。
7.2 距离动态控制
billboard.scaleByDistance = new Cesium.NearFarScalar(1e3, 1.0, 1e5, 0.2);
billboard.translucencyByDistance = new Cesium.NearFarScalar(1e4, 1.0, 2e5, 0.1);
近距离正常显示,远距离缩小并渐隐,降低渲染负载。
7.3 视锥体裁剪
viewer.scene.frustumCulling = true; // 默认开启,自动剔除视野外广告牌
8、内存管理机制
8.1 移除单个广告牌
billboards.remove(billboard); // 移除指定对象
8.2 批量清理
// 移除所有广告牌
billboards.removeAll();
// 或从场景中移除整个集合
viewer.scene.primitives.remove(billboards);
8.3 避免内存泄漏
// 销毁时释放资源
viewer.scene.primitives.destroyPrimitives = true;
9、总结
代码部分 | 作用 |
---|---|
new Cesium.BillboardCollection() | 创建公告板集合(高效管理多个 Billboard) |
viewer.scene.primitives.add(...) | 将集合添加到场景(使其可渲染) |
billboards.add({...}) | 添加一个公告板,指定位置、图片、尺寸和缩放优化 |
这段代码是 Cesium 中高效添加和管理 3D 图标/标记的标准方式,适用于地图、仿真、游戏等场景。
二、文本
使用 Primitive API
// 初始化Viewerconst viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),sceneMode: Cesium.SceneMode.SCENE3D});// 创建LabelCollection图元const labelCollection = viewer.scene.primitives.add(new Cesium.LabelCollection({show: true,// 启用深度测试避免被地形遮挡(需权衡性能)depthTest: false}));// 批量添加文本标签const positions = [{ lon: 116.404, lat: 39.915, text: "北京" },{ lon: 121.47, lat: 31.23, text: "上海" },// 更多位置数据...];positions.forEach(pos => {labelCollection.add({position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat),text: pos.text,font: '14px sans-serif', // 字体优化:避免过大字号fillColor: Cesium.Color.WHITE,outlineColor: Cesium.Color.BLACK,outlineWidth: 2,// 垂直对齐:文本位于坐标点下方verticalOrigin: Cesium.VerticalOrigin.BOTTOM,// 像素偏移:微调位置pixelOffset: new Cesium.Cartesian2(0, -15)});});
三. 点(Point)
使用 Primitive API(高性能,适合大量点)
const pointPrimitiveCollection = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());
pointPrimitiveCollection.add({position: Cesium.Cartesian3.fromDegrees(113.3244, 23.1049, 0),color: Cesium.Color.RED,pixelSize: 10
});
四、线(Polyline)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const polylineCollection = viewer.scene.primitives.add(new Cesium.PolylineCollection());
polylineCollection.add({positions: Cesium.Cartesian3.fromDegreesArray([113.3244, 23.1049,113.3254, 23.1059]),width: 2,material: new Cesium.ColorMaterialProperty(Cesium.Color.BLUE)
});
五、多边形(Polygon)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const polygonCollection = viewer.scene.primitives.add(new Cesium.PolygonCollection());
polygonCollection.add({hierarchy: Cesium.Cartesian3.fromDegreesArray([113.3244, 23.1049,113.3254, 23.1059,113.3264, 23.1039]),material: new Cesium.ColorMaterialProperty(Cesium.Color.GREEN.withAlpha(0.5))
});
六、圆(Circle)
使用 Primitive API(需手动计算圆周点)
// 初始化Cesium
const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),baseLayerPicker: false, // 禁用底图选择器geocoder: false, // 禁用地理编码器homeButton: false, // 禁用主页按钮infoBox: false, // 禁用信息框sceneModePicker: false, // 禁用场景模式选择器selectionIndicator: false, // 禁用选择指示器navigationHelpButton: false, // 禁用导航帮助按钮animation: false, // 禁用动画控件timeline: false, // 禁用时间轴fullscreenButton: false // 禁用全屏按钮
});// 定义圆的中心点和半径
const centerLon = 113.3244;
const centerLat = 23.1049;
const radiusInMeters = 1000;// 将圆心转换为 Cartesian3 坐标
const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat);// 计算圆的 Cartesian3 点集(近似采样)
const granularity = Cesium.Math.RADIANS_PER_DEGREE; // 采样精度(弧度/度)
const positions = [];
for (let angle = 0; angle < 360; angle += granularity) {const radians = Cesium.Math.toRadians(angle);// 计算圆周上的点(基于球面坐标)const x = radiusInMeters * Math.cos(radians);const y = radiusInMeters * Math.sin(radians);// 将局部坐标转换为全局 Cartesian3const point = Cesium.Cartesian3.fromDegrees(centerLon + x / 111320, // 经度偏移(1度≈111320米)centerLat + y / (111320 * Math.cos(Cesium.Math.toRadians(centerLat))), // 纬度偏移(考虑纬度缩放)0 // 高度(与圆心相同));positions.push(point);
}// 闭合圆(首尾相连)
positions.push(positions[0]);// 使用 Primitive API 添加圆
const primitiveCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());
primitiveCollection.add(new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolygonGeometry({polygonHierarchy: new Cesium.PolygonHierarchy(positions),perPositionHeight: false // 固定高度}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.BLUE.withAlpha(0.5) // 半透明蓝色)}}),appearance: new Cesium.PerInstanceColorAppearance({outline: true,outlineColor: Cesium.Color.RED,outlineWidth: 2})})
);// 定位相机到圆的位置
viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 5000),orientation: {heading: Cesium.Math.toRadians(0),pitch: Cesium.Math.toRadians(-30)}
});
七、椭圆(Ellipse)
使用 Primitive API(需手动计算椭圆点集)
const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),baseLayerPicker: false, // 禁用底图选择器geocoder: false, // 禁用地理编码器homeButton: false, // 禁用主页按钮infoBox: false, // 禁用信息框sceneModePicker: false, // 禁用场景模式选择器selectionIndicator: false, // 禁用选择指示器navigationHelpButton: false, // 禁用导航帮助按钮animation: false, // 禁用动画控件timeline: false, // 禁用时间轴fullscreenButton: false // 禁用全屏按钮
});// 定义椭圆的中心点、半长轴、半短轴和旋转角度const centerLon = 113.3244;const centerLat = 23.1049;const semiMajorAxis = 2000; // 半长轴(米)const semiMinorAxis = 1000; // 半短轴(米)const rotation = Cesium.Math.toRadians(45); // 旋转角度(弧度)// 将椭圆中心转换为 Cartesian3 坐标const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat);// 计算椭圆的 Cartesian3 点集(近似采样)const granularity = Cesium.Math.RADIANS_PER_DEGREE; // 采样精度(弧度/度)const positions = [];for (let angle = 0; angle < 360; angle += granularity) {const radians = Cesium.Math.toRadians(angle);// 计算椭圆上的点(基于参数方程)const x = semiMajorAxis * Math.cos(radians);const y = semiMinorAxis * Math.sin(radians);// 旋转椭圆const rotatedX = x * Math.cos(rotation) - y * Math.sin(rotation);const rotatedY = x * Math.sin(rotation) + y * Math.cos(rotation);// 将局部坐标转换为全局 Cartesian3const point = Cesium.Cartesian3.fromDegrees(centerLon + rotatedX / 111320, // 经度偏移(1度≈111320米)centerLat + rotatedY / (111320 * Math.cos(Cesium.Math.toRadians(centerLat))), // 纬度偏移(考虑纬度缩放)0 // 高度(与椭圆中心相同));positions.push(point);}// 闭合椭圆(首尾相连)positions.push(positions[0]);// 使用 Primitive API 添加椭圆const primitiveCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());primitiveCollection.add(new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolygonGeometry({polygonHierarchy: new Cesium.PolygonHierarchy(positions),perPositionHeight: false // 固定高度}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.BLUE.withAlpha(0.5) // 半透明蓝色)}}),appearance: new Cesium.PerInstanceColorAppearance({outline: true,outlineColor: Cesium.Color.RED,outlineWidth: 2})}));// 定位相机到椭圆位置viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 5000),orientation: {heading: Cesium.Math.toRadians(0),pitch: Cesium.Math.toRadians(-30)}});
八、球(Sphere)
使用 Primitive API(Primitive 方式需手动构建 Geometry)
const sphereCollection = viewer.scene.primitives.add(new Cesium.PrimitiveCollection());
sphereCollection.add(new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.EllipsoidGeometry({vertexFormat: Cesium.VertexFormat.POSITION_AND_NORMAL,radii: new Cesium.Cartesian3(100, 100, 100)})}),appearance: new Cesium.PerInstanceColorAppearance()})
);
九、3D 模型(Model)
使用 Primitive API(Primitive 方式需手动加载模型)
性能优化建议
场景 | 推荐方式 | 原因 |
---|---|---|
少量要素 | Entity API | 代码简洁,开发效率高 |
大量要素(>1000) | Primitive API | 性能更高,减少 CPU-GPU 通信开销 |
动态更新(如轨迹动画) | Entity API | 支持更简单的属性动画 |
自定义渲染(如特殊着色器) | Primitive API | 可深度定制渲染逻辑 |
总结
要素类型 | 推荐 API | 示例代码 |
---|---|---|
点 | Entity 或 PointPrimitiveCollection | viewer.entities.add({ point: {...} }) |
线 | Entity 或 PolylineCollection | viewer.entities.add({ polyline: {...} }) |
多边形 | Entity 或 PolygonCollection | viewer.entities.add({ polygon: {...} }) |
圆/椭圆 | Entity API(更简单) | viewer.entities.add({ circle: {...} }) |
球 | Entity API(更简单) | viewer.entities.add({ ellipsoid: {...} }) |
模型 | Entity API(更简单) | viewer.entities.add({ model: {...} }) |