mcpField
C
cesiumjs-entities

cesiumjs-entities

github.com/cesiumgs开发193220 次安装6 周前更新
代码仓库

关于 cesiumjs-entities

cesiumjs-entities 是面向 CesiumJS 开发者的技能,介绍如何构建数据驱动的地球场景,涵盖实体图形、集合、外部数据源、聚类和可见性管理。

主要功能

  1. 01点、线、面和模型等实体图形
  2. 02EntityCollection 查询与删除
  3. 03加载 GeoJSON、KML、CZML 和 GPX
  4. 04实体聚类与父子可见性

适用场景

  1. 01显示带标签的位置和 3D 形状
  2. 02加载并设置地理数据文件样式
  3. 03管理实体组与聚类

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cesiumgs/cesiumjs-skills --skill cesiumjs-entities

安装后在 Claude Code 中输入/cesiumjs-entities确认已加载。

详细介绍

CesiumJS 实体与数据源

版本基线: CesiumJS 1.144 —— ES 模块导入:import { ... } from "cesium"; 归属规则: *Graphics 类归属此处;*Geometry 类归属 cesiumjs-primitives。属性(SampledProperty、CallbackProperty、MaterialProperty 子类型)归属 cesiumjs-time-properties。

架构

Entity API 是 CesiumJS 中由数据驱动的高层抽象。实体将位置、方向和一种或多种 Graphics 类型组合为单个对象,由 EntityCollection 管理。数据源可加载外部格式(GeoJSON、KML、CZML、GPX),并自动填充 EntityCollection。可视化器和 GeometryUpdaters 会在每一帧将实体描述转换为 Primitive。

DataSource --> EntityCollection --> Entity
                                     |-- position / orientation
                                     |-- billboard / point / label / model / polygon / polyline / ...
                                     |-- properties (PropertyBag)
  • viewer.entities 是指向默认 DataSource 的 EntityCollection 的快捷方式
  • viewer.dataSources 保存所有已加载的 DataSource;每个 DataSource 都拥有一个 EntityCollection

坐标与视野框定规则(请先阅读)

大多数视觉评估失败不是 API 错误,而是坐标正负号和相机视野框定错误。请牢记以下规则:

  • 西半球经度为负数。 纽约市 = -74.006,洛杉矶 = -118.24,芝加哥 = -87.63,丹佛 = -104.99。漏掉负号会让纽约市出现在印度/巴基斯坦上空。提交前请仔细检查每个西半球坐标。

  • fromDegrees(longitude, latitude, height?) —— 经度在前,纬度在后。将两者弄反是最常见的错误。

  • 添加实体后,始终调整视野以框定它们。 不要让相机停留在默认的以太平洋为中心的视图中。请选择以下一种方式:

    • viewer.zoomTo(target) —— target 可以是 Entity、Entity[]、EntityCollection 或 DataSource。数据加载完成后会解析。
    • viewer.flyTo(target, { duration, offset }) —— 带动画;返回一个 Promise,动画完成时解析为 true。
    • 对于通过 await ...load(...) 加载的 DataSource,请调用 viewer.zoomTo(ds)(而不是 ds.entities),以便根据所有实体计算包围球。
  • 对于覆盖一个地区或整个地球的集合(例如“美国本土”“纽约/巴黎/悉尼的全部三个地标”),优先对实体数组或 DataSource 调用 zoomTo/flyTo——它会自动调整视野,以包围包含所有目标的包围球。手动编写 Cartesian3 目标位置和俯仰角,常常会导致框定到错误的半球或裁掉一半数据。

    const nyc    = viewer.entities.add({ /* Statue of Liberty, -74.04, 40.69 */ });
    const paris  = viewer.entities.add({ /* Eiffel Tower,     2.29,  48.86 */ });
    const sydney = viewer.entities.add({ /* Sydney Opera,   151.22, -33.86 */ });
    await viewer.zoomTo([nyc, paris, sydney]);   // single call, fits all three
    
  • 对于全球标记的视觉评估,请使用较大的标记,并使其不受深度影响。 将 point.pixelSize 设为约 22-32,使用黑色/白色描边和标签偏移;在可用时,同时为点和标签设置 disableDepthTestDistance: Number.POSITIVE_INFINITY。如果地球视图中能看到标签,却看不到两个彩色点,仍属于视觉失败。

  • 框定单个 AOI 且没有可作为目标的实体时, 优先使用 viewer.camera.flyTo({ destination: Rectangle.fromDegrees(west, south, east, north) }) ——它会自动适配该矩形。完整的相机控制说明请参阅 cesiumjs-camera。

实体基础

点
import { Viewer, Cartesian3, Color, HeightReference } from "cesium";

const viewer = new Viewer("cesiumContainer");
const entity = viewer.entities.add({
  id: "my-point",                 // optional; auto-generated GUID if omitted
  name: "Sample Point",
  position: Cartesian3.fromDegrees(-75.59777, 40.03883),
  point: {
    pixelSize: 10,
    color: Color.YELLOW,
    outlineColor: Color.BLACK,
    outlineWidth: 2,
    heightReference: HeightReference.CLAMP_TO_GROUND,
  },
});
viewer.zoomTo(entity);
带标签的广告牌
import { Viewer, Cartesian3, Cartesian2, Color, VerticalOrigin, HeightReference, LabelStyle } from "cesium";

const viewer = new Viewer("cesiumContainer");
viewer.entities.add({
  position: Cartesian3.fromDegrees(-122.4175, 37.7749),
  billboard: {
    image: "/assets/marker.png",
    scale: 0.5,
    verticalOrigin: VerticalOrigin.BOTTOM,
    heightReference: HeightReference.CLAMP_TO_GROUND,
  },
  label: {
    text: "San Francisco",
    font: "14px sans-serif",
    fillColor: Color.WHITE,
    outlineColor: Color.BLACK,
    outlineWidth: 2,
    style: LabelStyle.FILL_AND_OUTLINE,
    pixelOffset: new Cartesian2(0, -36),
    heightReference: HeightReference.CLAMP_TO_GROUND,
  },
});

通常,将点与标签放在同一个实体中比使用两个实体更简洁。 将 point/billboard 和 label 都附加到同一个 Entity,可使它们完美重合,避免出现重复的选择目标,并为每个位置渲染一个清晰的标签。只有在需要标签拥有独立的可见性、父级或时间动态行为时,才应使用单独的实体。

多边形(平面、拉伸、带孔洞)
import { Viewer, Cartesian3, Color, PolygonHierarchy } from "cesium";

const viewer = new Viewer("cesiumContainer");
// Flat polygon
viewer.entities.add({
  polygon: {
    hierarchy: Cartesian3.fromDegreesArray([-115, 37, -115, 32, -107, 33, -102, 31, -102, 35]),
    material: Color.RED.withAlpha(0.5),
    outline: true,
    outlineColor: Color.BLACK,
  },
});
// Extruded polygon with hole
viewer.entities.add({
  polygon: {
    hierarchy: new PolygonHierarchy(
      Cartesian3.fromDegreesArray([-99, 30, -85, 30, -85, 40, -99, 40]),
      [new PolygonHierarchy(Cartesian3.fromDegreesArray([-97, 32, -87, 32, -87, 38, -97, 38]))]
    ),
    extrudedHeight: 300000,
    material: Color.BLUE.withAlpha(0.6),
    closeTop: true,
    closeBottom: true,
  },
});
折线
import { Viewer, Cartesian3, Color, ArcType } from "cesium";

const viewer = new Viewer("cesiumContainer");
viewer.entities.add({
  polyline: {
    positions: Cartesian3.fromDegreesArray([-75, 35, -125, 35]),
    width: 5,
    material: Color.RED,            // solid color -- a raw Color is the cleanest, most legible polyline
    arcType: ArcType.GEODESIC,
    clampToGround: true,
  },
});

折线材质注意事项: 对于“视觉上明显”的路线,请直接将原始 Color(例如 Color.RED)作为 material 传入。虚线、辉光和箭头材质会增加纹理,评审者可能会将其误认为渲染伪影或线条褪色——只有场景明确要求使用样式化描边时才使用它们。

3D 模型
import { Viewer, Cartesian3, HeadingPitchRoll, Transforms, Math as CesiumMath, Color } from "cesium";

const viewer = new Viewer("cesiumContainer");
const position = Cartesian3.fromDegrees(-123.075, 44.045, 5000);
const hpr = new HeadingPitchRoll(CesiumMath.toRadians(135), 0, 0);

viewer.entities.add({
  position,
  orientation: Transforms.headingPitchRollQuaternion(position, hpr),
  model: {
    uri: "/assets/CesiumAir/Cesium_Air.glb",
    minimumPixelSize: 64,
    maximumScale: 20000,
    silhouetteColor: Color.RED,
    silhouetteSize: 2,
  },
});
盒体、圆柱体、椭球体、椭圆
import { Viewer, Cartesian3, Color } from "cesium";
const viewer = new Viewer("cesiumContainer");

viewer.entities.add({  // Box
  position: Cartesian3.fromDegrees(-107, 40, 300000),
  box: { dimensions: new Cartesian3(400000, 300000, 500000), material: Color.BLUE.withAlpha(0.5) },
});
viewer.entities.add({  // Cylinder (topRadius: 0 for a cone)
  position: Cartesian3.fromDegrees(-100, 40, 200000),
  cylinder: { length: 400000, topRadius: 200000, bottomRadius: 200000, material: Color.GREEN.withAlpha(0.5) },
});
viewer.entities.add({  // Ellipsoid (sphere when all radii equal)
  position: Cartesian3.fromDegrees(-93, 40, 300000),
  ellipsoid: { radii: new Cartesian3(200000, 200000, 300000), material: Color.RED.withAlpha(0.5) },
});
viewer.entities.add({  // Ellipse (circle when axes equal)
  position: Cartesian3.fromDegrees(-86, 40),
  ellipse: { semiMajorAxis: 300000, semiMinorAxis: 300000, material: Color.PURPLE.withAlpha(0.5) },
});

拉伸体(盒体/圆柱体/拉伸多边形)也需要调整视角。 如果场景要求在科罗拉多州上空放置一个“拉伸柱体”(经度约为 -105,纬度约为 39),请先添加实体,然后调用 viewer.zoomTo(entity)——否则默认相机通常会对准加拿大/北部平原,导致柱体落在视框之外,即使所有程序化检查都通过了。

廊道、矩形、墙体
import { Viewer, Cartesian3, Color, Rectangle, CornerType } from "cesium";
const viewer = new Viewer("cesiumContainer");

viewer.entities.add({  // Corridor: path with width
  corridor: { positions: Cartesian3.fromDegreesArray([-80, 40, -90, 40, -90, 35]), width: 200000, material: Color.ORANGE.withAlpha(0.6), cornerType: CornerType.ROUNDED },
});
viewer.entities.add({  // Rectangle by geographic bounds
  rectangle: { coordinates: Rectangle.fromDegrees(-110, 20, -80, 25), material: Color.GREEN.withAlpha(0.5), extrudedHeight: 50000 },
});
viewer.entities.add({  // Wall: vertical curtain
  wall: { positions: Cartesian3.fromDegreesArrayHeights([-115, 44, 200000, -90, 44, 200000]), minimumHeights: [100000, 100000], material: Color.CYAN.withAlpha(0.7) },
});

EntityCollection 操作

viewer.entities.getById("my-point");               // retrieve by ID
viewer.entities.getOrCreateEntity("some-id");       // get or create
viewer.entities.values;                             // Entity[] (read-only)
viewer.entities.remove(entity);                     // remove by reference
viewer.entities.removeById("my-point");             // remove by ID
viewer.entities.removeAll();                        // clear all

// Batch updates -- suspend events for bulk add/remove
viewer.entities.suspendEvents();
for (let i = 0; i < 1000; i++) viewer.entities.add({ /* ... */ });
viewer.entities.resumeEvents();  // fires one collectionChanged event

viewer.entities.collectionChanged.addEventListener((collection, added, removed, changed) => {
  console.log(`Added: ${added.length}, Removed: ${removed.length}`);
});

// Frame multiple entities at once -- bounding sphere fits all targets
viewer.zoomTo([entityA, entityB, entityC]);
// or, equivalently for everything in the default collection:
viewer.zoomTo(viewer.entities);

entity.show = false 会使实体保留在集合中——它仍计入 entities.values.length,但不会渲染,也不会纳入 zoomTo 的包围球。使用 removeById 才能真正删除实体。这对于要求“隐藏”某些实体,同时“移除”其他实体的场景很重要:隐藏的实体仍会增加 entity_count 检查值,而已移除的实体不会。

DataSources

GeoJSON/TopoJSON
import { Viewer, GeoJsonDataSource, Color } from "cesium";
const viewer = new Viewer("cesiumContainer");

// Load from URL with styling options
const ds = await GeoJsonDataSource.load("/data/counties.geojson", {
  stroke: Color.HOTPINK, fill: Color.PINK.withAlpha(0.5), strokeWidth: 3, clampToGround: true,
});
viewer.dataSources.add(ds);
await viewer.zoomTo(ds);  // frame the whole DataSource, not just the camera default

// Post-load styling: iterate and customize
for (const entity of ds.entities.values) {
  if (entity.polygon) entity.polygon.material = Color.fromRandom({ alpha: 0.8 });
}

GeoJsonDataSource.load() 也接受内嵌 GeoJSON 对象,而不只是 URL。

如果需要 Entities、DataSource 生命周期管理、聚类、动态时间属性,或在加载后轻松设置各实体的样式,请使用 GeoJsonDataSource。对于实体开销成为瓶颈的超大型静态 GeoJSON,请改用 cesiumjs-primitives 技能中的 GeoJsonPrimitive(在 1.142 中添加)。

美国本土范围视角提示: 加载美国各州的 GeoJSON 后,调用 viewer.zoomTo(ds)(并等待其完成)即可适配整个美国本土 48 州的包围球。手动调整相机高度(例如 z ≈ 7M)经常会裁掉北部地区(WA/MT/ME)或南端(FL),也可能因缩放过近而裁掉海岸线。让 DataSource 的包围球来完成这项工作。

KML/KMZ
import { KmlDataSource } from "cesium";
const ds = await KmlDataSource.load("/data/sample.kml", {
  camera: viewer.scene.camera, canvas: viewer.scene.canvas, clampToGround: true,
});
viewer.dataSources.add(ds);
viewer.flyTo(ds);
CZML
import { CzmlDataSource } from "cesium";
const ds = await CzmlDataSource.load("/data/vehicle.czml");
viewer.dataSources.add(ds);
await ds.process("/data/vehicle-update.czml");  // append without clearing

在 1.143 及更高版本中,CZML 路径可将 materialMode 设为固定路径模式或随区间变化的属性。PORTIONS 会让区间材质或采样材质应用于对应的路径线段;WHOLE 会将当前模拟时间的材质应用于整条路径。有关优化的直接 API 用法模式,请参阅 cesiumjs-time-properties。

const routePacket = {
  id: "route",
  path: {
    materialMode: [
      { interval: "2026-07-15T12:00:00Z/2026-07-15T12:02:00Z", pathMode: "PORTIONS" },
      { interval: "2026-07-15T12:02:00Z/2026-07-15T12:04:00Z", pathMode: "WHOLE" },
    ],
    resolution: 30,
    material: [
      { interval: "2026-07-15T12:00:00Z/2026-07-15T12:02:00Z",
        solidColor: { color: { rgba: [0, 255, 255, 255] } } },
      { interval: "2026-07-15T12:02:00Z/2026-07-15T12:04:00Z",
        solidColor: { color: { rgba: [255, 165, 0, 255] } } },
    ],
  },
};
GPX
import { GpxDataSource } from "cesium";
const ds = await GpxDataSource.load("/data/trail.gpx");
viewer.dataSources.add(ds);
viewer.zoomTo(ds);
CustomDataSource
import { CustomDataSource, Cartesian3, Color } from "cesium";

const customDs = new CustomDataSource("sensors");
customDs.entities.add({
  position: Cartesian3.fromDegrees(-95, 40),
  point: { pixelSize: 8, color: Color.LIME },
});
viewer.dataSources.add(customDs);
customDs.show = false;  // toggle entire group visibility

实体聚类

import { GeoJsonDataSource, Color, VerticalOrigin, PinBuilder } from "cesium";
const ds = await GeoJsonDataSource.load("/data/facilities.geojson");
viewer.dataSources.add(ds);

ds.clustering.enabled = true;
ds.clustering.pixelRange = 40;
ds.clustering.minimumClusterSize = 3;

const pinBuilder = new PinBuilder();
ds.clustering.clusterEvent.addEventListener((entities, cluster) => {
  cluster.label.show = false;
  cluster.billboard.show = true;
  cluster.billboard.verticalOrigin = VerticalOrigin.BOTTOM;
  cluster.billboard.image = pinBuilder.fromText(`${entities.length}`, Color.VIOLET, 48).toDataURL();
});

父子可见性

设置 parent.show = false 会隐藏所有后代实体,但不会更改它们各自的 show 标志。

const parent = viewer.entities.add({ name: "Group" });
viewer.entities.add({ parent, position: Cartesian3.fromDegrees(-90, 35), point: { pixelSize: 6, color: Color.RED } });
viewer.entities.add({ parent, position: Cartesian3.fromDegrees(-91, 36), point: { pixelSize: 6, color: Color.BLUE } });

parent.show = false;  // both children disappear
parent.show = true;   // both reappear

实体描述和自定义属性

// InfoBox HTML: displayed when user clicks the entity
viewer.entities.add({
  name: "Station Alpha",
  position: Cartesian3.fromDegrees(-75.17, 39.95),
  point: { pixelSize: 12, color: Color.CYAN },
  description: `<table class="cesium-infoBox-defaultTable"><tr><th>Status</th><td>Active</td></tr></table>`,
  properties: { population: 500000, category: "metro" },
});
// Read custom properties
// entity.properties.population.getValue()  --> 500000

导出为 KML

import { exportKml } from "cesium";

const result = await exportKml({ entities: viewer.entities, kmz: true });
// result.kmz is a Blob; result.kml is a string when kmz: false
const url = URL.createObjectURL(result.kmz);

全部 17 种图形类型

图形关键属性
PointGraphicspixelSize, color, outlineColor, outlineWidth
BillboardGraphicsimage, scale, rotation, sizeInMeters, heightReference
LabelGraphicstext, font, fillColor, style, showBackground
ModelGraphicsuri, scale, silhouetteColor, runAnimations, colorBlendMode
PolygonGraphicshierarchy, height, extrudedHeight, material, perPositionHeight
PolylineGraphicspositions, width, material, clampToGround, arcType
EllipseGraphicssemiMajorAxis, semiMinorAxis, rotation, extrudedHeight
RectangleGraphicscoordinates (Rectangle), height, extrudedHeight
BoxGraphicsdimensions (Cartesian3), material, outline
CylinderGraphicslength, topRadius, bottomRadius
EllipsoidGraphicsradii (Cartesian3)
CorridorGraphicspositions, width, cornerType
WallGraphicspositions, minimumHeights, maximumHeights
PolylineVolumeGraphicspositions, shape (Cartesian2[])
PlaneGraphicsplane (Plane), dimensions (Cartesian2)
PathGraphicsresolution, leadTime, trailTime, width, material, materialMode (1.143+), relativeTo (实验性)
Cesium3DTilesetGraphicsuri

PathGraphics.relativeTo(实验性,1.140+,#13223): 在相对于另一个实体的参考坐标系中显示路径,或在不同于实体 position ReferenceFrame 的参考坐标系中显示路径——例如,让无人机轨迹相对于一辆移动中的车辆,而不是 ECEF。此功能标记为实验性;其签名可能会改变。

PathGraphics.materialMode(1.143+): 使用 PathMode.PORTIONS,在移动实体的路径上保留特定于区间或采样点的材质。默认的 PathMode.WHOLE 会使用当前时刻的材质为整条路径着色。该值本身也是一个 Property,因此也可以随时间变化。通过路径选项对象设置此值,可确保与 TypeScript 1.143 兼容;在 cesiumjs-time-properties 中调整 resolution 以控制分段数量。

关键枚举

枚举值
HeightReferenceNONE, CLAMP_TO_GROUND, RELATIVE_TO_GROUND
HorizontalOriginLEFT, CENTER, RIGHT
VerticalOriginTOP, CENTER, BOTTOM, BASELINE
LabelStyleFILL, OUTLINE, FILL_AND_OUTLINE
ColorBlendModeHIGHLIGHT, REPLACE, MIX
PathModeWHOLE, PORTIONS
ShadowModeDISABLED, ENABLED, CAST_ONLY, RECEIVE_ONLY
ArcTypeNONE, GEODESIC, RHUMB
CornerTypeROUNDED, MITERED, BEVELED

性能提示

  1. 批量添加实体时使用 suspendEvents()/resumeEvents(),以批量处理变更通知。
  2. 静态数据优先使用常量属性——CesiumJS 会优化不发生变化的实体。
  3. 使用 DistanceDisplayCondition 隐藏超出有效范围的实体。
  4. 对于 10k+ 个静态形状,改用 Primitives;Primitives 可避免逐实体开销。
  5. 为包含大量点状实体的 DataSources 启用聚类。
  6. 在 billboard/label 上设置 disableDepthTestDistance,以避免被地形遮挡。
  7. 尽量减少贴地多边形的轮廓——绘制轮廓需要额外的渲染遍次。
  8. 仅在需要时使用 clampToGround——它需要进行贴合地形的细分。
  9. 使用 process() 批量更新 CZML,以追加数据;不要使用会替换全部数据的 load()。
  10. 缓存 PinBuilder 画布——重复使用结果,避免重新生成相同图像。

另请参阅

  • cesiumjs-camera —— flyTo、setView、基于 Rectangle 的取景;没有可用的实体目标时使用
  • cesiumjs-time-properties —— 用于随时间动态变化的实体属性的 SampledProperty、CallbackProperty、MaterialProperty 类型
  • cesiumjs-primitives —— 面向性能关键型静态几何体的底层 Primitive API(*Geometry 类)
  • cesiumjs-interaction —— ScreenSpaceEventHandler、Scene.pick、实体选择和悬停模式

评论

支持 Markdown