什么是 Entity
Entity 是 Cesium 提供的高层业务对象系统。
它的作用是:用更接近业务的方式描述场景中的对象,比如点、线、面、图标、文字、模型、轨迹等。
Entity = 数据描述 + 显示样式 + 时间动态
比如一个无人机,可以被描述成:
- 当前位置
- 图标
- 名称标签
- 飞行轨迹
- 模型
- 随时间变化的位置
这些都可以放在一个 Entity 里。
Entity 和 Primitive 的关系
Entity 本身不是最终的底层渲染对象。
真实渲染链路是:
Entity
↓
Visualizer
↓
Primitive
↓
Scene
↓
GPU
也就是说:
Entity 负责好用
Primitive 负责渲染
Entity 更适合业务开发,Primitive 更适合性能优化和大规模静态数据。
Entity 在 Viewer 中的位置
Entity 通常通过 viewer.entities 管理。
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
point: {
pixelSize: 10,
color: Cesium.Color.RED
}
})
结构关系:
Viewer
├─ entities
│ ├─ Entity
│ ├─ Entity
│ └─ Entity
└─ scene
└─ primitives
viewer.entities 本质上是一个 EntityCollection。
Entity 的关键字段
每个 Entity 除了具体图形(point / billboard / polyline 等),还有一些和图形无关的通用字段:
| 字段 | 说明 |
|---|---|
id | 唯一标识,业务里常直接用业务 id |
name | 名称,会显示在 InfoBox 标题里 |
description | HTML 描述,点击 Entity 后弹出 InfoBox |
position | 空间位置,可以是常量也可以是 Property |
orientation | 方向(四元数),影响模型 / 几何体姿态 |
parent | 父 Entity,子节点跟随父节点变换 |
show | 是否显示 |
availability | 时间可用范围 |
配合 description 可以直接出弹窗:
viewer.entities.add({
id: 'device-001',
name: '设备 001',
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0),
description: `
<h3>设备 001</h3>
<p>状态:在线</p>
<p>电量:92%</p>
`,
billboard: { image: '/device.png' }
})
Cesium 默认会在点击时显示 InfoBox 和 SelectionIndicator。如果业务自己做了弹窗,可以在 Viewer 创建时关掉:
const viewer = new Cesium.Viewer('cesium', {
infoBox: false,
selectionIndicator: false
})
Entity 可以表示什么
一个 Entity 可以同时包含多个图形属性。
| 属性 | 表现 |
|---|---|
position | 空间位置 |
point | 点 |
billboard | 图标 |
label | 文字 |
polyline | 线 |
polygon | 面 |
ellipse | 椭圆 / 圆 |
rectangle | 矩形 |
box | 盒子 |
cylinder | 圆柱 / 圆锥 |
ellipsoid | 椭球 |
model | glTF / glb 模型 |
path | 轨迹 |
例如,一个 Entity 可以同时有图标和文字:
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
billboard: {
image: '/marker.png',
scale: 1
},
label: {
text: '设备 A',
font: '16px sans-serif',
pixelOffset: new Cesium.Cartesian2(0, -30)
}
})
创建点
viewer.entities.add({
id: 'point-1',
name: '测试点',
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2
}
})
常用属性:
| 属性 | 说明 |
|---|---|
pixelSize | 点大小,单位像素 |
color | 点颜色 |
outlineColor | 边框颜色 |
outlineWidth | 边框宽度 |
heightReference | 高度参考 |
disableDepthTestDistance | 距离多远后关闭深度测试 |
创建图标 Billboard
billboard 常用于地图标记点、设备图标、告警图标。
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
billboard: {
image: '/marker.png',
width: 32,
height: 32,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
}
})
常用属性:
| 属性 | 说明 |
|---|---|
image | 图标地址 |
width / height | 图标大小 |
scale | 缩放 |
pixelOffset | 屏幕像素偏移 |
verticalOrigin | 垂直对齐方式 |
horizontalOrigin | 水平对齐方式 |
heightReference | 高度参考 |
创建文字 Label
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
label: {
text: '北京',
font: '16px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -30)
}
})
常见用途:
- 设备名称
- 地名标注
- 测点编号
- 区域名称
- 告警信息
创建线 Polyline
viewer.entities.add({
name: '测试线',
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.4, 39.9,
116.5, 39.85
]),
width: 4,
material: Cesium.Color.CYAN
}
})
如果需要带高度:
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArrayHeights([
116.3, 39.8, 100,
116.4, 39.9, 200,
116.5, 39.85, 150
]),
width: 4,
material: Cesium.Color.RED
}
})
贴地线:
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.4, 39.9
]),
width: 4,
material: Cesium.Color.YELLOW,
clampToGround: true
}
})
Polyline 常用 material
material 不止是颜色,还可以传材质 Property:
| 材质 | 效果 |
|---|---|
Color | 纯色 |
PolylineOutlineMaterialProperty | 带描边的线 |
PolylineDashMaterialProperty | 虚线 |
PolylineArrowMaterialProperty | 箭头线,常用于方向 |
PolylineGlowMaterialProperty | 流光 / 发光 |
polyline: {
positions: ...,
width: 6,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.CYAN,
dashLength: 16
})
}
箭头线常用于方向标记:
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED)
创建面 Polygon
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.5, 39.8,
116.5, 40.0,
116.3, 40.0
]),
material: Cesium.Color.RED.withAlpha(0.4),
outline: true,
outlineColor: Cesium.Color.WHITE
}
})
贴地面:
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.5, 39.8,
116.5, 40.0,
116.3, 40.0
]),
material: Cesium.Color.GREEN.withAlpha(0.4),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
}
})
注意:贴地效果依赖地形和渲染能力,复杂面或大量面可能需要考虑 GroundPrimitive。
拉伸成 3D 体块:
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.5, 39.8,
116.5, 40.0,
116.3, 40.0
]),
extrudedHeight: 5000,
material: Cesium.Color.ORANGE.withAlpha(0.6),
outline: true,
outlineColor: Cesium.Color.BLACK
}
})
带洞的多边形(外环 + 内环):
viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArray([
116.3, 39.8,
116.5, 39.8,
116.5, 40.0,
116.3, 40.0
]),
[
new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArray([
116.35, 39.85,
116.45, 39.85,
116.45, 39.95,
116.35, 39.95
])
)
]
),
material: Cesium.Color.RED.withAlpha(0.4)
}
})
创建模型 Model
Entity 可以直接加载 glTF / glb 模型。
viewer.entities.add({
name: '无人机',
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 300),
model: {
uri: '/models/drone.glb',
scale: 1,
minimumPixelSize: 64,
maximumScale: 200
}
})
常用属性:
| 属性 | 说明 |
|---|---|
uri | 模型地址 |
scale | 缩放 |
minimumPixelSize | 最小显示像素 |
maximumScale | 最大缩放 |
heightReference | 高度参考 |
silhouetteColor | 轮廓颜色 |
silhouetteSize | 轮廓宽度 |
如果需要大量模型或更底层控制,可以使用 Cesium.Model.fromGltfAsync() 加到 scene.primitives。
Box / Cylinder / Ellipsoid
除了点、线、面、模型,Entity 还支持几种常见几何体。
盒子:
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 50),
box: {
dimensions: new Cesium.Cartesian3(100, 100, 100),
material: Cesium.Color.BLUE.withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.BLACK
}
})
圆柱 / 圆锥(顶半径为 0 时变圆锥):
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0),
cylinder: {
length: 200,
topRadius: 0,
bottomRadius: 50,
material: Cesium.Color.YELLOW.withAlpha(0.5)
}
})
球 / 椭球:
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
ellipsoid: {
radii: new Cesium.Cartesian3(80, 80, 80),
material: Cesium.Color.RED.withAlpha(0.5)
}
})
这些图形的姿态由 Entity 的 orientation 控制(见后文)。
path 轨迹
path 常用于显示动态对象的历史轨迹。
const entity = viewer.entities.add({
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start,
stop
})
]),
position: sampledPosition,
path: {
resolution: 1,
material: Cesium.Color.YELLOW,
width: 3
}
})
path 通常和 SampledPositionProperty 一起使用。
Entity 的核心:Property
Entity 最重要的特点是:很多属性都可以是 Property。
普通写法:
point: {
color: Cesium.Color.RED
}
动态写法:
point: {
color: new Cesium.CallbackProperty(() => {
return Cesium.Color.RED.withAlpha(Math.random())
}, false)
}
可以理解为:
普通值 = 固定不变
Property = 可以随时间变化
当你写 color: Cesium.Color.RED 时,Cesium 内部会自动把它包装成 ConstantProperty。所以平时不需要手动 new ConstantProperty(),只在显式构造常量属性时才用得到。
常见 Property:
| Property | 作用 |
|---|---|
ConstantProperty | 常量属性 |
CallbackProperty | 每帧动态计算 |
SampledPositionProperty | 按时间采样的位置 |
TimeIntervalCollectionProperty | 不同时间段不同值 |
CompositeProperty | 组合多个时间属性 |
ReferenceProperty | 引用其它 Entity 属性 |
CallbackProperty
CallbackProperty 适合做实时变化的属性。
例如动态半径:
let radius = 100
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0),
ellipse: {
semiMajorAxis: new Cesium.CallbackProperty(() => {
radius += 1
return radius
}, false),
semiMinorAxis: new Cesium.CallbackProperty(() => {
return radius
}, false),
material: Cesium.Color.RED.withAlpha(0.3)
}
})
注意:CallbackProperty 会被频繁调用,不要在里面做接口请求、复杂计算或大量对象创建。
SampledPositionProperty
SampledPositionProperty 用来描述一个对象随时间变化的位置,常用于轨迹、回放、仿真。
const property = new Cesium.SampledPositionProperty()
const start = Cesium.JulianDate.now()
const time1 = Cesium.JulianDate.addSeconds(start, 0, new Cesium.JulianDate())
const time2 = Cesium.JulianDate.addSeconds(start, 10, new Cesium.JulianDate())
property.addSample(
time1,
Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100)
)
property.addSample(
time2,
Cesium.Cartesian3.fromDegrees(116.40, 39.91, 150)
)
const entity = viewer.entities.add({
position: property,
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW
},
path: {
material: Cesium.Color.CYAN,
width: 3
}
})
viewer.clock.startTime = start.clone()
viewer.clock.currentTime = start.clone()
viewer.clock.shouldAnimate = true
viewer.trackedEntity = entity
这就是 Entity 很适合做动态轨迹的原因。
orientation 和模型朝向
模型 / 几何体 Entity 常常需要朝向运动方向。VelocityOrientationProperty 会根据 position 自动算出朝向:
entity.orientation = new Cesium.VelocityOrientationProperty(entity.position)
适合:
- 车辆沿轨迹行驶
- 无人机航线飞行
- 船舶轨迹
如果只是固定朝向,可以用 HeadingPitchRoll 转四元数:
const position = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)
const hpr = new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(90), 0, 0
)
entity.position = position
entity.orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr)
availability
availability 用来控制 Entity 在什么时间范围内可见。
entity.availability = new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start,
stop
})
])
常见于:
- 轨迹回放
- 历史车辆
- 无人机飞行任务
- 时间轴动态数据
trackedEntity
trackedEntity 可以让相机跟随某个 Entity。
viewer.trackedEntity = entity
常见场景:
- 跟随无人机
- 跟随车辆
- 跟随人员
- 飞行轨迹回放
取消跟随:
viewer.trackedEntity = undefined
flyTo 单次飞行
viewer.flyTo(entity) 是一次性飞过去,不会跟随:
viewer.flyTo(entity)
也支持 EntityCollection 和 DataSource:
viewer.flyTo(dataSource)
和 trackedEntity 的区别:
flyTo = 飞一次,到位后停止
trackedEntity = 一直跟着 Entity 移动
selectedEntity
viewer.selectedEntity 表示当前选中的 Entity,会同步显示 SelectionIndicator 和 InfoBox:
viewer.selectedEntity = entity
业务上经常这样用:
handler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position)
if (picked && picked.id instanceof Cesium.Entity) {
viewer.selectedEntity = picked.id
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
清除选中:
viewer.selectedEntity = undefined
Entity 拾取
Entity 可以通过 scene.pick 拾取。
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position)
if (!Cesium.defined(picked)) {
return
}
const entity = picked.id
if (entity instanceof Cesium.Entity) {
console.log(entity.id, entity.name)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
因为 Entity 的 id 可以自己设置,所以非常适合和业务数据关联。
viewer.entities.add({
id: 'device-001',
name: '设备 001',
position,
billboard: {
image: '/device.png'
}
})
如果多个 Entity 在同一像素位置重叠,scene.pick 只会拿到最上面那个。需要拿到所有命中可以用 drillPick:
const picks = viewer.scene.drillPick(movement.position)
picks.forEach((p) => {
if (p.id instanceof Cesium.Entity) {
console.log(p.id.id)
}
})
drillPick 比 pick 重,不要每帧调用。
EntityCollection
viewer.entities 是一个 EntityCollection,常用方法如下:
| 方法 | 说明 |
|---|---|
add() | 添加 Entity |
remove() | 删除指定 Entity |
removeById() | 按 id 删除 |
removeAll() | 删除全部 Entity |
getById() | 按 id 获取 |
contains() | 判断是否包含 |
values | 获取全部 Entity |
示例:
const entity = viewer.entities.getById('device-001')
if (Cesium.defined(entity)) {
viewer.entities.remove(entity)
}
删除全部 Entity:
viewer.entities.removeAll()
注意:removeAll() 只会清理 viewer.entities,不会删除 scene.primitives 里的 3D Tiles、Primitive、Model,也不会清理 viewer.dataSources 里的 Entity。
监听 Entity 增删变化:
viewer.entities.collectionChanged.addEventListener(
(collection, added, removed, changed) => {
console.log('added', added.length)
console.log('removed', removed.length)
console.log('changed', changed.length)
}
)
可以用来做"统计当前可见 Entity 数量"、"业务数据和场景对象同步"等。
show 控制显示隐藏
Entity 本身有 show 属性。
entity.show = false
也可以控制某个图形属性:
entity.billboard.show = false
entity.label.show = false
如果需要按业务分组显示,可以自己维护数组或 Map:
const deviceEntities = new Map()
deviceEntities.set(device.id, entity)
高度模式
Entity 的点、图标、文字、面、模型等经常会用到高度参考。
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
常见值:
| 值 | 含义 |
|---|---|
HeightReference.NONE | 使用坐标中的高度 |
HeightReference.CLAMP_TO_GROUND | 贴地(地形或 3D Tiles,按场景默认) |
HeightReference.RELATIVE_TO_GROUND | 相对地面高度 |
HeightReference.CLAMP_TO_TERRAIN | 只贴地形 |
HeightReference.RELATIVE_TO_TERRAIN | 只相对地形 |
HeightReference.CLAMP_TO_3D_TILE | 贴在 3D Tiles 表面 |
HeightReference.RELATIVE_TO_3D_TILE | 相对 3D Tiles 表面 |
注意:
NONE使用坐标里的高度,通常接近 HAE。CLAMP_TO_GROUND依赖地形或地表。RELATIVE_TO_GROUND表示在地面基础上抬高。- 贴 3D Tiles 的几种模式要在场景里加载了
Cesium3DTileset才有效。
按距离控制显示
Entity 的图标、点、文字、模型有几个按距离生效的属性,常用于"远了看不到"或"远了变小":
| 属性 | 作用 |
|---|---|
distanceDisplayCondition | 距离区间内才显示 |
scaleByDistance | 按距离缩放 |
translucencyByDistance | 按距离改变透明度 |
pixelOffsetScaleByDistance | 按距离缩放偏移量 |
billboard: {
image: '/device.png',
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 50000),
scaleByDistance: new Cesium.NearFarScalar(1000, 1.5, 50000, 0.5),
translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 50000, 0.2)
}
"近距离显示文字、远距离只剩图标"这种业务效果,就是用 label 和 billboard 分别设置不同的 distanceDisplayCondition 实现的。
DataSource 和 Entity
除了直接使用 viewer.entities,Cesium 还可以通过 DataSource 加载一组 Entity。
常见 DataSource:
| DataSource | 用途 |
|---|---|
GeoJsonDataSource | 加载 GeoJSON |
CzmlDataSource | 加载 CZML 动态数据 |
KmlDataSource | 加载 KML |
CustomDataSource | 自定义 Entity 分组 |
例如加载 GeoJSON:
const dataSource = await Cesium.GeoJsonDataSource.load('/data/area.geojson', {
clampToGround: true
})
viewer.dataSources.add(dataSource)
viewer.flyTo(dataSource)
自定义分组:
const dataSource = new Cesium.CustomDataSource('devices')
dataSource.entities.add({
id: 'device-001',
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),
point: {
pixelSize: 10,
color: Cesium.Color.RED
}
})
viewer.dataSources.add(dataSource)
如果一类业务数据需要整体显示、隐藏、删除,CustomDataSource 比直接塞到 viewer.entities 更好管理。
Entity 性能边界
Entity 很方便,但不是无限适合海量数据。
大致经验:
| 数据规模 | 建议 |
|---|---|
| 几十、几百个业务对象 | Entity 很合适 |
| 几千个对象 | 需要注意样式、动态属性和更新频率 |
| 上万点位 | 优先考虑 Primitive Collection |
| 十万级以上 | 使用 Primitive、3D Tiles、点云或服务端切片 |
Entity 卡顿常见原因:
- 数量太多
- 每个 Entity 都有 label / billboard / path
- 大量
CallbackProperty - 每帧频繁增删 Entity
- 每帧创建新对象
- 没有做视野范围过滤
Entity 和 Primitive 怎么选
| 场景 | 推荐 |
|---|---|
| 业务点、设备、车辆、无人机 | Entity |
| 需要点击、名称、业务 id | Entity |
| 需要轨迹回放、时间动态 | Entity + Property |
| 加载 GeoJSON、CZML、KML | DataSource + Entity |
| 海量静态点 | PointPrimitiveCollection |
| 海量图标 | BillboardCollection |
| 海量静态面 | Primitive |
| 城市模型、倾斜摄影 | Cesium3DTileset |
一句话:
先用 Entity 把业务跑通,性能不够再下沉到 Primitive。
常见问题
-
Entity 加了但看不到
常见原因是坐标写错、高度太低、相机没飞过去、颜色透明度为 0,或者
show被设置成了false。 -
点位和底图错位
Cesium 默认使用 WGS84。如果底图是高德、腾讯、百度等国内坐标系,可能需要处理 GCJ02 / BD09 和 WGS84 的转换。
-
贴地对象不贴地
贴地效果依赖地形、深度和具体图形类型。没有真实地形时,贴的是光滑椭球,不是真实地面。
-
CallbackProperty导致卡顿CallbackProperty会频繁执行。不要在里面请求接口,也不要每次都创建大量新对象。 -
大量 label 很卡
Label 本身比较重。大量文字建议做聚合、视野过滤、层级显示,或者换成
LabelCollection。 -
每次刷新数据都
removeAll()再add()这种做法简单但容易卡顿。更好的方式是按 id 更新已有 Entity,只新增、删除变化的部分。
-
Entity 的位置怎么更新?
静态更新可以直接改:
entity.position = Cesium.Cartesian3.fromDegrees(116.4, 39.91, 100)连续动态位置更适合用
SampledPositionProperty或CallbackProperty。 -
为什么
viewer.entities.removeAll()没删掉 3D Tiles?因为 3D Tiles 加在
viewer.scene.primitives,不属于viewer.entities。
小结
Entity 是 Cesium 中最常用、最适合业务开发的对象系统。
它的核心价值是:
- 写法简单
- 适合点线面、图标、文字、模型
- 支持时间动态
- 支持拾取和业务 id
- 能和 DataSource 配合管理数据
实际开发中可以这样选:
业务对象少、中等规模、有动态需求 -> Entity
海量静态渲染、性能压力大 -> Primitive
城市级三维模型、点云、倾斜摄影 -> 3D Tiles