关于 cesiumjs-entities
cesiumjs-entities 是面向 CesiumJS 开发者的技能,介绍如何构建数据驱动的地球场景,涵盖实体图形、集合、外部数据源、聚类和可见性管理。
主要功能
- 01点、线、面和模型等实体图形
- 02EntityCollection 查询与删除
- 03加载 GeoJSON、KML、CZML 和 GPX
- 04实体聚类与父子可见性
适用场景
- 01显示带标签的位置和 3D 形状
- 02加载并设置地理数据文件样式
- 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 种图形类型
| 图形 | 关键属性 |
|---|---|
PointGraphics | pixelSize, color, outlineColor, outlineWidth |
BillboardGraphics | image, scale, rotation, sizeInMeters, heightReference |
LabelGraphics | text, font, fillColor, style, showBackground |
ModelGraphics | uri, scale, silhouetteColor, runAnimations, colorBlendMode |
PolygonGraphics | hierarchy, height, extrudedHeight, material, perPositionHeight |
PolylineGraphics | positions, width, material, clampToGround, arcType |
EllipseGraphics | semiMajorAxis, semiMinorAxis, rotation, extrudedHeight |
RectangleGraphics | coordinates (Rectangle), height, extrudedHeight |
BoxGraphics | dimensions (Cartesian3), material, outline |
CylinderGraphics | length, topRadius, bottomRadius |
EllipsoidGraphics | radii (Cartesian3) |
CorridorGraphics | positions, width, cornerType |
WallGraphics | positions, minimumHeights, maximumHeights |
PolylineVolumeGraphics | positions, shape (Cartesian2[]) |
PlaneGraphics | plane (Plane), dimensions (Cartesian2) |
PathGraphics | resolution, leadTime, trailTime, width, material, materialMode (1.143+), relativeTo (实验性) |
Cesium3DTilesetGraphics | uri |
PathGraphics.relativeTo(实验性,1.140+,#13223): 在相对于另一个实体的参考坐标系中显示路径,或在不同于实体positionReferenceFrame的参考坐标系中显示路径——例如,让无人机轨迹相对于一辆移动中的车辆,而不是 ECEF。此功能标记为实验性;其签名可能会改变。
PathGraphics.materialMode(1.143+): 使用PathMode.PORTIONS,在移动实体的路径上保留特定于区间或采样点的材质。默认的PathMode.WHOLE会使用当前时刻的材质为整条路径着色。该值本身也是一个Property,因此也可以随时间变化。通过路径选项对象设置此值,可确保与 TypeScript 1.143 兼容;在cesiumjs-time-properties中调整resolution以控制分段数量。
关键枚举
| 枚举 | 值 |
|---|---|
HeightReference | NONE, CLAMP_TO_GROUND, RELATIVE_TO_GROUND |
HorizontalOrigin | LEFT, CENTER, RIGHT |
VerticalOrigin | TOP, CENTER, BOTTOM, BASELINE |
LabelStyle | FILL, OUTLINE, FILL_AND_OUTLINE |
ColorBlendMode | HIGHLIGHT, REPLACE, MIX |
PathMode | WHOLE, PORTIONS |
ShadowMode | DISABLED, ENABLED, CAST_ONLY, RECEIVE_ONLY |
ArcType | NONE, GEODESIC, RHUMB |
CornerType | ROUNDED, MITERED, BEVELED |
性能提示
- 批量添加实体时使用
suspendEvents()/resumeEvents(),以批量处理变更通知。 - 静态数据优先使用常量属性——CesiumJS 会优化不发生变化的实体。
- 使用
DistanceDisplayCondition隐藏超出有效范围的实体。 - 对于 10k+ 个静态形状,改用 Primitives;Primitives 可避免逐实体开销。
- 为包含大量点状实体的 DataSources 启用聚类。
- 在 billboard/label 上设置
disableDepthTestDistance,以避免被地形遮挡。 - 尽量减少贴地多边形的轮廓——绘制轮廓需要额外的渲染遍次。
- 仅在需要时使用
clampToGround——它需要进行贴合地形的细分。 - 使用
process()批量更新 CZML,以追加数据;不要使用会替换全部数据的load()。 - 缓存 PinBuilder 画布——重复使用结果,避免重新生成相同图像。
另请参阅
- cesiumjs-camera ——
flyTo、setView、基于Rectangle的取景;没有可用的实体目标时使用 - cesiumjs-time-properties —— 用于随时间动态变化的实体属性的 SampledProperty、CallbackProperty、MaterialProperty 类型
- cesiumjs-primitives —— 面向性能关键型静态几何体的底层 Primitive API(
*Geometry类) - cesiumjs-interaction —— ScreenSpaceEventHandler、Scene.pick、实体选择和悬停模式