Cesium快速入门到精通系列教程九:Cesium 中高效添加和管理图标/标记的标准方式

发布于:2025-06-18 ⋅ 阅读:(13) ⋅ 点赞:(0)

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、整体作用​​

这段代码的 ​​核心功能​​ 是:

  1. ​​创建一个公告板集合​​(BillboardCollection),用于高效管理多个公告板。
  2. ​​向集合中添加一个公告板​​,指定其:
  • ​​位置​​(经纬度 + 高度)。
  • ​​显示的图片​​(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

  // 初始化Viewer
  const 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);
    // 将局部坐标转换为全局 Cartesian3
    const 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);
            // 将局部坐标转换为全局 Cartesian3
            const 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: {...} })

网站公告

今日签到

点亮在社区的每一天
去签到