cesium-multi-target-framework
v0.3.14
Published
高性能 Cesium 多目标渲染框架:对象池 + 八叉树 + 分帧调度 + 运动预测 + 三级 LOD 渲染
Maintainers
Readme
cesium-multi-target-framework
高性能 Cesium 多目标渲染框架,面向海量船舶、无人机、车辆、站点等目标的实时展示。暴露高层类 MultiTargetFramework,提供目标数据接入、三级 LOD 渲染、对象池、视口裁剪、运动预测、目标选择/定位、轨迹、站点层、名称牌、右键菜单和 web-event-bus 跨模块调用能力。
当前依赖锁定 Cesium 1.107.2,代码兼容 Cesium 1.107.2 到 1.128.0 的常见版本敏感 API。详细架构、调参和排障记录见 项目说明书,完整导出说明见 对外接口说明。
如何使用
最小接入流程:
- 安装包和 Cesium。
- 把业务图标、模型放到宿主项目静态资源目录,例如
/icons、/model。 - 创建
Cesium.Viewer。 - 用
targetTypes声明每类目标的type、图标、颜色、模型、运动策略。 - 调用
setTargetData写入全量目标,后续用upsertTargetData/removeTargetData做增量。 - 按需使用
scene.on(...)、tracks、SiteLayer、web-event-bus接入交互。
import {
Viewer,
MultiTargetFramework,
TARGET_DOMAINS
} from 'cesium-multi-target-framework'
import 'cesium/Build/Cesium/Widgets/widgets.css'
const viewer = new Viewer('cesiumContainer', {
animation: false,
timeline: false
})
const framework = new MultiTargetFramework(viewer, {
visualIcons: [
{
key: 'ais',
iconPath: '/icons/target-ais-mask.png',
pointHeadingOffset: 180
}
],
targetTypes: [
{
type: 'ship-other',
name: '其他船舶',
targetDomain: TARGET_DOMAINS.SURFACE,
iconPath: '/icons/target-ship-mask.png',
defaultColor: '#25e65c',
lowModel: { url: '/model/其他船舶.gltf', scale: 100, headingOffset: -90 },
highModel: { url: '/model/其他船舶.gltf', scale: 100, headingOffset: -90 },
highModelPoolSize: 30,
physicalSize: { length: 100, width: 20, height: 20 },
smoothMove: true,
predictMove: false
},
{
type: 'air-uav',
name: '无人机',
targetDomain: TARGET_DOMAINS.AIR,
iconPath: '/icons/mtf/uav-radar.png',
defaultColor: '#00FF00',
lowModel: { url: '/model/mtf/uav-low.gltf', scale: 0.3, headingOffset: -90 },
highModel: { url: '/model/mtf/uav-high.gltf', scale: 0.3, headingOffset: -90 },
smoothMove: true,
predictMove: true,
deriveMotionFromPosition: true,
predictRounds: 1,
predictRoundSeconds: 10
}
],
scene: {
data: { commitIntervalMs: 0, leading: true, worker: true },
merge: { enabled: true, maxVisible: 10000, cameraMergeBaseHeight: 500, byType: true },
pick: { enabled: true, hover: true },
bearingLine: {
enabled: true,
durationMinutes: 1,
lineType: 'dashed',
width: 2,
color: '#35d7ff',
colorsByType: {
'air-uav': '#39ff88',
'air-bird': '#ffcf33',
'air-civilian-aircraft': '#4aa3ff'
},
opacity: 0.85,
maxCameraHeight: 12000
}
}
})
await framework.ready
framework.setTargetData([
{
id: 'ship-001',
type: 'ship',
lon: 121.5,
lat: 31.2,
height: 0,
speedH: 8,
speedV: 0,
heading: 45,
updateTime: Date.now(),
length: 120,
width: 28,
physicalHeight: 18
}
])
// visualKey 只覆盖图标,不改变目标 type、模型和 LOD 逻辑。
framework.setTargetVisualKey('ship-001', 'ais')
framework.setTargetVisualKey('ship-001', null)
framework.upsertTargetData([
{
id: 'uav-001',
type: 'air-uav',
lon: 121.58,
lat: 31.25,
height: 1200,
speedH: 45,
speedV: 0,
heading: 80
}
])
framework.scene.on('pick', (target) => {
console.log('pick target', target)
})目标数据必须满足:
| 字段 | 说明 |
| --- | --- |
| id | 全局唯一目标 id |
| type | 必须命中 targetTypes[].type |
| lon / lat / height | WGS84 经纬度和高度 |
| speedH / speedV | 水平/垂直速度,单位 m/s |
| heading | 航向角,正北为 0,顺时针 |
| bowHeading | 可选艏向角;模型、图标和跟随方向优先使用该值,缺失时回退到 heading |
| updateTime | 可选,点位获取时间,Unix 毫秒时间戳;对应类型开启 deriveMotionFromPosition 时用于前端本地计算航向和速度 |
| length / width / physicalHeight | 可选,目标真实长、宽、高,单位米;不传时使用 targetTypes[].physicalSize 或域级默认尺寸 |
| visualKey | 可选,命中 visualIcons[].key 时只覆盖目标图标;不改变 type、高低模和业务分类 |
常用调用:
| 目标 | API |
| --- | --- |
| 全量替换 | framework.setTargetData(targets) |
| 增量新增/更新 | framework.upsertTargetData(targets) |
| 删除目标 | framework.removeTargetData(ids) |
| 定位目标 | await framework.locateTarget(id, options) |
| 设置颜色 | framework.setTargetColor(id, color) |
| 开关发光 | framework.setTargetGlow(id, enabled, color?) |
| 覆盖图标 | framework.setTargetVisualKey(id, visualKey);传 null 恢复默认图标 |
| 显示轨迹 | framework.tracks.setTracks(records)、framework.tracks.showTracks(ids) |
| 画业务线 | framework.drawLine(options)、framework.updateLine(id, options)、framework.removeLine(id) |
| 画航道 | framework.createChannel(options)、framework.updateChannel(id, options)、framework.removeChannel(id) |
| 画图片 | framework.drawImage(options)、framework.updateImage(id, options)、framework.removeImage(id) |
| 航向指示器 | framework.showHeadingIndicator(targetId, options)、framework.hideHeadingIndicator(targetId)、framework.clearHeadingIndicators() |
| 画图形 | framework.drawShape(options)、framework.startDrawingOverlay(options)、framework.editShape(id)、framework.clearShapes() |
| 通用模型 | framework.modelManager.addModel(options)、framework.modelManager.updateModel(id, options)、framework.modelManager.removeModel(id) |
| 站点层 | new SiteLayer(viewer, options) |
手动画线示例:
线宽规则:
lineType: 'dashed'使用 WebGL/CesiumPolylineCollection,适合虚线、轻量叠加线。浏览器 WebGL 线段宽度通常只支持 1px,很多环境会把更大的线宽降级为 1px。lineType: 'solid'且width不填或width <= 1时,也使用 WebGL 线,线宽是屏幕像素。lineType: 'solid'且width > 1时,width表示实际米数,框架使用地理几何宽线绘制。相机拉远时屏幕上会自然变细,适合河道、管控区边界等需要真实宽度的业务线。- 若需要标准“宽实线 + 中心虚线”的航道样式,优先使用
createChannel,见下方航道示例。
const lineId = framework.drawLine({
// 可主动指定 id;不传时框架生成一个 id 并作为返回值
id: 'river-main-line',
points: [
{ lon: 121.5, lat: 31.2, height: 0 },
{ lon: 121.58, lat: 31.26, height: 0 }
],
// solid + width > 1 表示实际宽度:120 米
width: 120,
color: '#ffdd55',
opacity: 0.9,
lineType: 'solid'
})
framework.updateLine(lineId, {
points: [
{ lon: 121.5, lat: 31.2, height: 0 },
{ lon: 121.62, lat: 31.3, height: 0 }
],
// dashed 仍是 WebGL 虚线,宽线能力受浏览器限制
width: 1,
lineType: 'dashed'
})
// 按 id 删除一条线
framework.removeLine(lineId)
// 删除所有业务线
framework.clearLines()手动画图片示例:
const imageId = framework.drawImage({
// 可主动指定 id;不传时框架生成一个 id 并作为返回值
id: 'ship-follow-image',
imagePath: '/icons/mtf/aircraft.png',
coordinate: { lon: 121.5, lat: 31.2 },
// 图片渲染高度,单位米;不传默认 0
height: 120,
// 可在最终高度上额外抬高;跟随目标且不传 height 时适合用来避免贴地
heightOffset: 1,
followTarget: true,
targetId: 'ship_001',
rotateWithTarget: true, // 是否随目标航向旋转;true 时图片会跟着 targetId 的 heading 一起转
// 默认图片上方为 0 度;这里额外顺时针旋转 15 度
initialRotation: 15,
// 未配置米制宽高时,按原图像素换算成默认米制尺寸
scale: 0.5,
// 需要透视大小时打开米制尺寸;镜头拉远会变小、拉近会变大
sizeInMeters: true,
sizeMeters: 1200
})
framework.drawImage({
id: 'screen-ratio-image',
imagePath: '/icons/mtf/ring-scale.svg',
coordinate: { lon: 121.55, lat: 31.24, height: 0 },
// 开启透视平面下的屏幕定比;图片宽度约等于屏幕短边的 8%
scaleByScreen: true,
maxScreenPercent: 8
})
framework.updateImage(imageId, {
followTarget: false,
coordinate: { lon: 121.6, lat: 31.3, height: 0 },
rotateWithTarget: false // 关闭随目标航向旋转,图片保持 initialRotation 固定朝向
})
// 按 id 删除一张图
framework.removeImage(imageId)
// 删除所有图片覆盖物
framework.clearImages()内置航向指示器示例:
// 打开目标航向指示器:外圈默认占屏幕短边 70%,内圈自动为外圈的 82%
framework.showHeadingIndicator('ship_001')
// 主动配置外圈定比大小;内圈仍按外圈 * 0.82
framework.showHeadingIndicator('ship_001', {
maxScreenPercent: 80,
heightOffset: 1
})
// 按目标关闭
framework.hideHeadingIndicator('ship_001')
// 关闭全部航向指示器
framework.clearHeadingIndicators()航向指示器说明:
- 框架内置外圈和内圈两张图,随包分发,不需要业务提供图片路径。
- 外圈固定正北方向,不随目标旋转。
- 内圈跟随目标,并随目标航向旋转。
maxScreenPercent表示外圈宽度占屏幕短边百分比;默认70。innerRatio表示内圈相对外圈的比例;默认0.82。- 航向指示器默认跟随目标当前高度,并在目标高度基础上抬高
1m;可通过heightOffset调整抬高量。只有显式传入height时才使用固定绝对高度。 - 指示器复用图片覆盖物系统,因此仍是带透视的地图平面图,不是 billboard。
航道示例:
// 创建航道:自动绘制宽实线 + 中心虚线,返回含 id 的航道对象
const channel = framework.createChannel({
id: 'route-main',
show: true,
points: [
{ lon: 110.41, lat: 19.97, height: 0 },
{ lon: 110.38, lat: 20.08 }
],
widthMeters: 50,
color: '#1677ff',
dashColor: '#eafcff'
})
// 按 id 更新:可改显示开关、路径点、宽度、颜色等
framework.updateChannel(channel.id, {
show: false
})
framework.updateChannel(channel.id, {
show: true,
points: [
{ lon: 110.41, lat: 19.97 },
{ lon: 110.39, lat: 20.02 },
{ lon: 110.38, lat: 20.08 }
],
widthMeters: 80
})
// 按 id 删除 / 查询 / 清空
framework.removeChannel(channel.id)
framework.getChannel('route-main')
framework.clearChannels()航道说明:
- 框架按一条航道自动维护两条业务线:地理宽实线 + 中心 WebGL 虚线,样式与 demo 海口河道一致。
show: true且路径点不少于 2 个时绘制;否则隐藏,但不删除航道记录。points中height未传时默认0。widthMeters表示航道实际宽度,单位米;默认50。color为宽实线颜色,默认#1677ff;dashColor为中心虚线颜色,默认#eafcff。wideOpacity/dashOpacity分别控制宽实线与虚线透明度,默认0.7/0.95。- 不传
id时框架自动生成mtf-channel-N;返回值ChannelRecord含当前完整配置。 clearLines()会同时清空航道;若只想删航道,请用clearChannels()或removeChannel(id)。
手动画图形示例:
framework.drawShape({
id: 'control-circle',
type: 'circle',
center: { lon: 121.5, lat: 31.2, height: 0 },
radiusMeters: 3000,
fillColor: '#35d7ff',
fillOpacity: 0.2,
outlineColor: '#ffffff',
outlineWidth: 2
})
framework.startDrawingOverlay({
type: 'polygon',
fillColor: '#ffdf5a',
fillOpacity: 0.24,
outlineColor: '#ffffff',
outlineWidth: 2,
// 自定义形状:点击多个路径点;鼠标靠近起点 14px 内会吸附,再点击闭合;
// 也可以在非起点位置快速双击,自动按当前路径闭合
snapPixels: 14,
// 默认 true:绘制完成后立刻进入编辑态
enterEditAfterComplete: true,
onStart: (result) => {
// 点击绘制按钮后即生成 id;完成前它是绘制草稿 id
console.log('shape start', result.id)
},
onChange: (result) => {
// 新增点、拖动点、在线上插入点等路径变化都会触发
console.log('shape change', result.changeReason, result.wkt, result.pathPoints)
},
onComplete: (result) => {
console.log('shape done', result.id, result.type, result.wkt, result.pathPoints)
}
})
// 矩形/圆:第一次点击中心点,移动鼠标预览外轮廓,第二次点击完成
framework.startDrawingOverlay({ type: 'rectangle' })
framework.startDrawingOverlay({ type: 'circle' })
framework.startDrawingOverlay({
type: 'line',
outlineColor: '#ffffff',
outlineWidth: 1,
lineType: 'solid',
onComplete: (result) => {
// result.type === 'line'
// result.wkt returns LINESTRING (...)
console.log('line done', result.id, result.wkt, result.pathPoints)
}
})
// 按 id 进入编辑状态。矩形可拖中心点和边点;圆可拖中心点和半径点。
// 编辑时鼠标在路径点上是移动态;鼠标在路径线上是加点态,点击可插入新路径点。
// 矩形/圆一旦在线上插入新路径点,就会自动转换为 polygon;圆转换时按 36 段生成路径点。
framework.editShape('control-circle')
framework.cancelEditingOverlay()
framework.clearShapes()startDrawingOverlay 生命周期回调:
| 回调 | 时机 |
| --- | --- |
| onStart(result) | 调用 startDrawingOverlay 后立即触发,此时已经有 id,但还是绘制草稿 |
| onChange(result) | 路径发生实际变化时触发,例如新增点、插入点、拖动点 |
| onComplete(result) | 绘制完成后触发,id 从这时开始成为正式图形 id |
| onCancel(result) | 绘制取消时触发 |
| onEditStart(result) | 绘制完成自动进入编辑态时触发 |
| onEditEnd(result) | 退出编辑态时触发 |
result 字段说明:
| 字段 | 含义 |
| --- | --- |
| id | 图形 id,可传给 editShape(id)、updateShape(id, options)、removeShape(id) |
| type | 图形类型:rectangle、circle、polygon |
| status | 当前状态:drawing、editing、complete、cancelled |
| changeReason | 本次变化原因,例如 start、add-point、insert-point、move-point、complete、cancel |
| wkt | 当前最终图形的 WKT。三类图形都以 POLYGON 返回;圆会按绘制采样点近似成面 |
| pathPoints | 全部外轮廓路径点数组,不包含额外闭合重复点 |
| points | pathPoints 的兼容别名,同样表示全部外轮廓路径点 |
| controlPoints | 绘制/编辑用控制点。矩形是中心点和边点,圆是中心点和半径点,自定义图形是用户点击的路径点 |
| center | 矩形/圆的中心点;自定义图形通常没有 |
| radiusMeters | 圆半径,单位米;非圆通常没有 |
图片参数说明:
imagePath:图片路径,支持项目 public 下路径或可访问 URL。coordinate/position:固定坐标,lon/lat/height。height:图片渲染高度,单位米,默认0。固定坐标时可替代coordinate.height;跟随目标时不传则使用目标当前高度,显式传入后会覆盖目标高度。heightOffset:在最终渲染高度上额外增加的米数,默认0。例如跟随目标且不传height时,heightOffset: 1表示目标高度 +1m。followTarget:是否跟随目标;为true时优先使用targetId当前渲染位置。targetId:跟随的目标 id。rotateWithTarget:是否随目标航向旋转。initialRotation:初始旋转角度,单位度,默认图片上方为0度。scale:未主动指定米制尺寸时的默认倍率,默认1;框架会按图片原始像素换算成米制平面尺寸。sizeInMeters/perspective:是否强调使用米制透视尺寸;当前默认图片覆盖物本身就是地图平面贴图,宽高按真实米数绘制,镜头拉远会变小、拉近会变大。sizeMeters:米制透视尺寸下的最大边长,保持原图宽高比。widthMeters/heightMeters:米制透视尺寸下的指定宽高;只传一个时按原图宽高比补另一个。scaleByScreen:是否在透视平面基础上按屏幕比例控制图片宽度;默认false。开启后仍然是地图平面贴图,不会切成 billboard。相机缩放导致目标尺寸变化时,会先等待100ms稳定,再用0.5s线性过渡到新的尺寸;过渡期间如果相机继续移动,会暂停当前过渡并等待下一次稳定后重新执行,避免滚轮缩放时即时变大/变小造成抖动。maxScreenPercent:开启scaleByScreen后,图片宽度占屏幕短边的百分比,例如8表示约 8%。maxScreenRatio:和maxScreenPercent等价的小数写法,例如0.08。
渲染模式说明:
- 默认模式不是 billboard,而是和点模式地面符号同类的 WebGL 平面贴图:图片躺在当前位置的地表切平面上,使用图片 alpha 裁剪透明区域,支持固定坐标、跟随目标、跟随目标航向旋转。
scaleByScreen: true仍然使用这套 WebGL 平面贴图,只是按相机距离把屏幕比例换算成米制宽度,并在相机缩放稳定后平滑过渡;因此图案有地图透视和遮挡关系,同时宽度会大致保持屏幕定比。
主 demo 在海口附近内置了四种图片覆盖物测试:
- 固定地面圆环:固定坐标,不跟随目标,使用
sizeInMeters + sizeMeters体现透视大小。 - 跟随目标圆环:跟随
demo_ship_0,不随航向旋转,使用米制透视尺寸。 - 跟随转向圆环:跟随
demo_ship_0,并随目标heading旋转,使用米制透视尺寸。 - 屏幕定比圆环:在“测试”面板点击“屏幕定比图”按钮绘制/清除,两层图片跟随目标当前高度并抬高
1m;外圈罗盘固定正北,内圈跟随摆动 mock 船并随船航向旋转,外圈/内圈均带 30 度刻度。
主 demo 还在海口附近内置了一条示例航道 haikou-river-channel,通过 createChannel 绘制宽实线 + 中心虚线。
Examples
包内包含可运行的 examples 目录,并会随 npm 包发布。它覆盖基础目标、低空五类型、增量更新/一轮预测、轨迹站点事件总线等场景。
源码仓库运行:
npm install
npm run build
cd examples
npm install
npm run dev已安装 npm 包后运行:
cd node_modules/cesium-multi-target-framework/examples
npm install
npm run dev如果把示例复制到业务项目,把 examples/src/main.js 里的 ../../dist/cesium-multi-target-framework.js 改成包名 cesium-multi-target-framework,并复制 examples/public 下的静态资源。
AI 快速索引
如果你是 AI 或第一次接手本仓库,优先读这些文件:
| 入口 | 用途 |
| --- | --- |
| src/core/MultiTargetFramework.ts | 对外高层 API、参数校验、事件总线绑定入口 |
| src/core/MultiTargetScene.ts | 场景渲染主流程、LOD、拾取、名称牌、菜单 |
| src/config/types.ts | 全部配置类型、默认值、目标类型归一化 |
| src/worker/renderWorker.ts | Worker 数据提交、视口裁剪、LOD 选择、预测 |
| src/render/ModelPoolRenderer.ts | 高低模、对象池、模型资源解析 |
| demo/main.ts | 主 demo:目标类型、mock 数据接入、交互按钮 |
| demo/mockBackend.ts | demo mock 接口适配,含低空目标枚举映射 |
| demo/mtfTargetTypes.ts | demo 低空目标资源路径和后端枚举到 type 的映射 |
常见任务定位:
| 要改什么 | 优先改哪里 |
| --- | --- |
| 新增目标类型 | targetTypes 配置;demo 先改 demo/main.ts 和 demo/mtfTargetTypes.ts |
| 新增模型/图标 | 放到 demo/public/model、demo/public/icons,再在 targetTypes 中引用 |
| 调整高低模切换 | scene.lod.pointAbove、scene.lod.animatedBelow、frame.maxLowModelRenderItems |
| 调整运动预测 | 类型级 predictMove、deriveMotionFromPosition、predictSpeedScale、predictRounds、predictRoundSeconds |
| 调整裁剪/合并 | scene.cull、scene.horizonCull、scene.merge |
| 查性能瓶颈 | 先看 demo 面板日志,再看 perf 事件和 renderWorker.ts |
Demo 低空目标类型
主 demo 已集成从 low-uav/public/icons/mtf 与 low-uav/public/model/mtf 摘取的低空目标资源:
| 后端枚举 | demo type | 名称 | 图标 | 模型 |
| --- | --- | --- | --- | --- |
| 0 | air-unknown | 未识别飞行物 | /icons/mtf/unknown-aerial.png | /model/mtf/未知飞行器.gltf |
| 1 | air-uav | 雷达无人机 | /icons/mtf/uav-radar.png | /model/mtf/uav-low.gltf、/model/mtf/uav-high.gltf |
| 2 | air-bird | 鸟 | /icons/mtf/bird.png | /model/mtf/飞鸟.gltf |
| 6 | air-fpv | 高机动无人机 | /icons/mtf/uav-radar.png | /model/mtf/uav-low.gltf、/model/mtf/uav-high.gltf |
| 7 | air-civilian-aircraft | 民航飞机 | /icons/mtf/aircraft.png | /model/mtf/民航飞机.gltf |
资源文件位于 demo/public,Vite demo 运行时会按根路径访问。新增低空类型时,先把后端枚举写入 demo/mtfTargetTypes.ts,再在 demo/main.ts 的 targetTypes 中补模型、图标、颜色和预测策略。
框架精华
框架的核心思路是把海量目标渲染拆成“数据进入、后台计算、裁剪筛选、复用渲染、动画衔接、事件调度”几条稳定流水线:大规模计算尽量放到 Worker 和分帧流程中,主线程只消费已经裁好的渲染包,并通过可配置策略在精度、性能和业务表达之间做平衡。
多维度的数据裁剪
同时使用视口 bounds、平视距离/扇形裁剪、LOD、合并抽稀、类型/ID 显隐和名称牌距离限制,尽量让后续渲染只处理真正需要显示的目标。
多线程无阻塞 Worker 计算
目标数据提交、视口筛选、合并抽稀、预测位置计算都在 Web Worker 中完成,避免大批量数据处理直接阻塞 Cesium 主线程。
对象池复用
目标 billboard、发光效果、名称牌、模型和站点渲染对象都走池化复用,减少频繁创建/销毁 Cesium primitive 带来的卡顿和内存抖动。
大计算分帧处理
全量数据提交、全局缓存、层级构建和视口更新按帧调度与节流,避免一次性把扫描、构建和渲染更新压到同一帧。
动画补间操作
目标平滑移动、预测运动、高低模切换、显隐淡入淡出和名称牌缩放都通过补间衔接,让数据跳变不会直接表现成画面跳变。
数据驱动
业务只需要持续写入目标、轨迹、站点和样式数据,框架根据当前相机、LOD、预测和裁剪配置自动生成最终渲染状态。
多扩展配置
目标类型、模型资源、LOD、合并、裁剪、预测、名称牌、站点层、拾取和右键菜单都可配置,便于不同业务场景按性能和显示需求调参。
事件总线调度
内置 web-event-bus action 和事件桥接,跨组件可以直接调用目标、轨迹、站点和菜单能力,降低业务模块之间的耦合。
安装
npm install cesium-multi-target-framework如果宿主项目单独管理 Cesium,建议保持 Cesium 版本与本包一致:
npm install [email protected]快速开始
import {
Viewer,
MultiTargetFramework,
getFrameworkEventBus
} from 'cesium-multi-target-framework'
const viewer = new Viewer('cesiumContainer', {
animation: false,
timeline: false
})
const framework = new MultiTargetFramework(viewer, {
targetTypes: [
{
type: 'ship',
name: '船舶',
iconPath: '/icons/target-ship-mask.png',
defaultColor: '#25e65c',
smoothMove: true,
predictMove: false
},
{
type: 'uav',
name: '无人机',
iconPath: '/icons/target-uav-mask.png',
defaultColor: '#22d2f2',
smoothMove: true,
predictMove: true,
deriveMotionFromPosition: true,
predictMinCameraHeight: 25000,
predictSpeedScale: 0.6,
predictFitSeconds: 2,
predictRounds: 1,
predictRoundSeconds: 10
}
],
scene: {
data: { commitIntervalMs: 2000, leading: true, worker: true },
merge: { enabled: true, maxVisible: 10000, cameraMergeBaseHeight: 500, byType: true },
pick: { enabled: true, hover: true },
label: {
maxCount: 50,
maxCameraHeight: 5000,
fields: [
{ key: 'id', label: 'ID' },
{ key: 'homePort', source: 'custom', label: '所属渔港', fallback: '-' }
]
}
}
})
framework.setTargetData([
{
id: 'ship-001',
type: 'ship',
lon: 121.5,
lat: 31.2,
height: 0,
speedH: 8,
speedV: 0,
heading: 45,
renderColor: '#f81282',
openAnimate: true,
animateType: '发光',
animateColor: '#f81282',
customInfo: {
name: '海巡 001',
homePort: '舟山沈家门渔港'
}
}
])
framework.scene.on('hover', (target) => {
console.log('hover target', target)
})
await framework.ready
await framework.locateTarget('ship-001')
// 创建实例后会自动暴露到 web-event-bus
const bus = getFrameworkEventBus()
bus.on('mtf.scene.pick', console.log)
await bus.invoke('mtf.framework.setTargetColor', {
id: 'ship-001',
color: '#00ffff'
})构造参数
const framework = new MultiTargetFramework(viewer, options)| 参数 | 类型 | 说明 |
| --- | --- | --- |
| viewer | Cesium.Viewer | 必填,已创建的 Cesium Viewer 实例 |
| options.targetTypes | MultiTargetTypeConfig[] | 必填,业务目标类型配置 |
| options.scene | Omit<SceneConfig, "nodeTypes"> | 可选,渲染、合并、预测、拾取、名称牌等场景配置 |
MultiTargetFramework 构造后会同步返回实例,并异步初始化内部 Web Worker。需要作为其它组件底座时,等待 ready:
const framework = new MultiTargetFramework(viewer, options)
await framework.ready也可以使用异步工厂:
const framework = await MultiTargetFramework.create(viewer, options)
// 或
const framework2 = await createMultiTargetFramework(viewer, options)targetTypes
| 字段 | 说明 |
| --- | --- |
| type | 目标类型,必须与数据中的 type 对应 |
| name | 类型显示名 |
| targetDomain | surface / air / underwater,不传时按类型名推断 |
| iconPath | 点模式图标;兼容旧版,默认作为可被 renderColor 染色的基底层 |
| baseIconPath | 可选,显式指定基底层图标;不传时使用 iconPath |
| decorIconPath | 可选,装饰层图标,绘制在基底层之上并保持原始颜色;不传时不进入装饰层批次 |
| defaultColor | 类型默认颜色 |
| highModel / lowModel | 高模/低模模型资源配置 |
| highModelPoolSize | 该类型高模预热数量和可用高模上限;默认 20,超出后降级到低模 |
| highModelUrl / lowModelUrl | 旧字段兼容,等同模型 URL |
| physicalSize | 类型默认真实尺寸,单位米,形如 { length, width, height };目标未传 length / width / physicalHeight 时使用该默认值 |
| pointPixelSize | 点/符号像素大小 |
| pointHeadingOffset | 点模式朝向修正,弧度 |
| scale | 模型缩放 |
| modelHeadingOffset / modelPitchOffset / modelRollOffset | 模型朝向修正,弧度 |
| smoothMove | 是否启用平滑移动 |
| smoothMoveMinCameraHeight | 相机低于/等于该高度时启用平滑 |
| smoothMoveDurationMs | 单次位置变化的平滑时长 |
| predict / predictMove | 是否启用运动预测,predict 为兼容别名 |
| deriveMotionFromPosition | 是否忽略该类型目标的后端航向/速度,改用同一目标相邻两次权威点位和 updateTime 在 worker 内本地计算 heading / speedH / speedV;默认 false,只在初始化配置时生效 |
| predictMinCameraHeight | 相机低于/等于该高度时启用预测 |
| predictSpeedScale | 预测速度倍率;默认使用 scene 配置,0.6 表示按 60% 速度外推 |
| predictSeconds | 预测窗口秒数 |
| predictFitSeconds | 新数据到达后回到最新预测轨迹的误差抹平秒数,默认使用 scene 配置 |
| predictRounds | 每次权威数据到达后最多执行几轮预测;不传为无限 |
| predictRoundSeconds | 单轮预测持续时间;不传时使用 predictSeconds |
scene
| 配置 | 默认 | 说明 |
| --- | --- | --- |
| lod.pointAbove | 150000 | 高于此相机高度走点模式 |
| lod.animatedBelow | 10000 | 低于此相机高度允许高模动画 |
| pool.size | 30000 | 通用目标池容量 |
| animatedPool.size | 200 | 所有类型同时渲染高模的全局上限;超出后降级到低模 |
| animatedPool.surfaceTypeSize | 20 | 未配置 highModelPoolSize 的地表/水面类型默认预热数量 |
| animatedPool.airTypeSize | 20 | 未配置 highModelPoolSize 的空中类型默认预热数量 |
| animatedPool.prewarmPerFrame | 2 | 每帧最多启动的高模预热任务数 |
| updateFps | 30 | 主更新频率 |
| predictionFpsByDomain | 跟随 updateFps | 按目标域设置 Worker 预测终点频率,例如 { surface: 2, air: 10, underwater: 2 } |
| predictSeconds | 10 | 默认预测窗口秒数 |
| predictFitSeconds | 2 | 新权威数据到达后回到最新预测轨迹的误差抹平秒数 |
| predictSpeedScale | 1 | 默认预测速度倍率;类型级 predictSpeedScale 可覆盖 |
| predictRounds | Infinity | 每次权威数据到达后最多执行几轮预测;1 表示只预测一轮 |
| predictRoundSeconds | predictSeconds | 单轮预测持续时间;一轮结束且新数据未到时停止继续外推 |
| fadeMs | 350 | 显隐淡入淡出时间 |
| masterHideHeight | 150000 | 高于此高度隐藏模型内容 |
| merge.enabled | false | 是否启用合并 |
| merge.mode | global | 合并模式 |
| merge.maxVisible | pool.size | 最大渲染目标数 |
| merge.byType | false | 是否按类型分桶合并 |
| pick.enabled | true | 是否启用点击拾取 |
| pick.pixelThreshold | 20 | 拾取像素阈值 |
| pick.hover | true | 是否启用 hover |
| bearingLine.enabled | false | 是否在低模/高模单目标前方显示航向指示线 |
| bearingLine.durationMinutes | 1 | 航向线长度,按 speedH * durationMinutes * 60 计算,可配置 |
| bearingLine.lineType | dashed | 航向线类型:dashed 虚线、solid 实线 |
| bearingLine.width | 2 | 航向线宽度,会按当前 WebGL 能力做安全裁剪 |
| bearingLine.color | #35d7ff | 航向线默认颜色 |
| bearingLine.colorsByType | 未配置 | 按目标 type 覆盖航向线颜色,例如 { "air-uav": "#39ff88" };未命中时使用 bearingLine.color |
| bearingLine.opacity | 0.85 | 航向线透明度 |
| bearingLine.arrow | true | 是否在线尾显示箭头 |
| bearingLine.arrowLengthMeters | 70 | 箭头头部固定长度,单位米,不随航向线长度变化 |
| bearingLine.arrowWidthMeters | 70 | 箭头头部实际张口宽度,单位米;默认 70 米 |
| bearingLine.arrowWidthMultiplier | 2 | 兼容旧配置;未配置 arrowWidthMeters 时按主线线宽倍率计算 |
| bearingLine.minCameraHeight | 0 | 相机低于该高度时不显示;默认不限制 |
| bearingLine.maxCameraHeight | Infinity | 相机高于该高度时不显示;默认不限制 |
| bearingLine.targetTypes | 不限 | 只给指定目标类型显示航向线 |
| cull.viewportCull | true | 是否启用视口裁剪 |
| cull.viewportBufferRatio | 0.5 | 视口缓冲比例 |
| cull.flatPitchViewportBufferRatio | 0.12 | 平视/近地平线视角下使用的较小视口缓冲比例 |
| cull.flatPitchThreshold | -25 | 相机 pitch 高于/等于该角度时使用平视缓冲比例,默认来自 DEFAULT_FLAT_PITCH_THRESHOLD_DEGREES |
| cull.viewportBufferMinMeters | 500 | 视口裁剪缓冲的绝对下限 |
| horizonCull.enabled | true | 平视/近地平线视角下启用距离和方向裁剪 |
| horizonCull.forwardAngle | 160 | 前方扇形裁剪角度,近处目标保底保留 |
| horizonCull.nearKeepDistance | 3000 | 近处保底半径,单位米 |
| label.enabled | true | 是否显示名称牌 |
| label.maxCount | 50 | 名称牌最多显示数量 |
| label.maxCameraHeight | 5000 | 名称牌最大显示相机高度 |
| frame.maxLowModelRenderItems | 3000 | 低模后端数量上限 |
| frame.maxModelRenderItems | animatedPool.size | 旧配置兼容;未配置 animatedPool.size 时作为全局高模上限 |
| data.commitIntervalMs | 2000 | 数据提交周期,0 表示立即提交 |
| data.leading | true | 首批数据是否立即提交 |
| data.worker | true | 数据处理是否使用 worker |
| data.staleTimeoutMs | 600000 | 默认目标离线剔除时间;地面/水下默认 10 分钟 |
| data.staleTimeoutByDomainMs.air | 60000 | 空中目标离线剔除时间,默认 1 分钟 |
| data.staleTimeoutByDomainMs.surface | 600000 | 地面目标离线剔除时间,默认 10 分钟;设为 0 可关闭该类超时剔除 |
| groundSymbols | true | 点档单目标是否使用地表实例化有向符号 |
| groundSymbolPixelSize | 30 | 地表符号像素大小 |
| physicalSizeIconFallback | false | low/high LOD 下当前类型没有对应模型时,是否用 iconPath 图标按真实尺寸兜底渲染;开启后只按 length 计算整体缩放倍数,宽高同倍率,避免图标被拉伸变形 |
| defaultPhysicalSizeByDomain | 内置默认 | 按 surface / air / underwater 配置默认真实尺寸;内置默认分别为 20x5x4、3x3x1、20x5x4 米 |
数据格式
TargetData / StandardTargetData 使用 WGS84 经纬度,速度单位为米/秒(m/s),角度单位为度。
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| id | 是 | 唯一 id |
| type | 是 | 目标类型,必须命中 targetTypes |
| lon / lat / height | 是 | 经度、纬度、高度 |
| speedH / speedV | 是 | 水平速度、垂直速度,单位米/秒(m/s);speedV 向上为正 |
| heading | 是 | 航向角,正北为 0,顺时针 |
| bowHeading | 否 | 艏向角,正北为 0,顺时针;只影响渲染方向,不参与运动预测 |
| updateTime | 否 | 点位获取时间,Unix 毫秒时间戳;对应类型开启 deriveMotionFromPosition 时用于前端本地计算航向和速度 |
| renderColor | 否 | 单目标渲染颜色,#RRGGBB 或 #RRGGBBAA |
| visualKey | 否 | 已注册的图标 key;命中 visualIcons[].key 时只覆盖图标,不改变目标 type、模型和 LOD |
| length / width / physicalHeight | 否 | 目标真实长、宽、高,单位米;优先级高于类型级 physicalSize 和域级默认尺寸 |
| openAnimate | 否 | 是否开启动画 |
| animateType | 否 | 当前支持 glow / 发光 |
| animateColor | 否 | 动画颜色 |
| nationality | 否 | 国籍/归属 |
| customInfo | 否 | 自定义信息,可用于名称牌字段 |
| track | 否 | 轨迹点数组 |
| secrecy | 否 | 密级,当前透传 |
| extra | 否 | 额外业务状态 |
PackedTargetData 支持同名 typed array 字段:bowHeading?: Float32Array、updateTime?: Float64Array、length?: Float32Array、width?: Float32Array、physicalHeight?: Float32Array,以及字符串数组 visualKeys?: string[]。bowHeading 中使用 NaN 表示该目标没有艏向;其他字段与对象数据语义一致,适合大批量 packed 数据。
low/high 模型 LOD 默认使用模型配置里的 scale,由使用者自行控制显示大小。只有模型配置显式设置 useRealLength: true,且目标数据传了 length 时,才会按 目标 length / 类型 physicalSize.length 对模型做同倍率缩放。模型缺失时同一尺寸还会用于图标兜底,普通远距离点图标仍保持固定像素大小,不会增加大规模点渲染成本。
启用 useRealLength: true 后,如果模型配置的 scale 与 physicalSize.length 基本一致,框架会把它识别为“按 1m 基准模型配置”的实长模型:加载 glTF/GLB 时先计算模型包围盒最长边,若最长边不在 0.8m ~ 1.3m 范围内,会自动乘以 1 / 最长边 做一次归一化,再叠加目标实际 length 缩放。没有开启 useRealLength 或目标没有传 length 时,不会触发这套真实船长和归一化逻辑。
开启真实船长:
{
physicalSize: { length: 100, width: 20, height: 20 },
highModel: {
url: '/model/集装箱货船.gltf',
scale: 100,
headingOffset: -90,
useRealLength: true
}
}关闭真实船长:不配置 useRealLength,或显式写 useRealLength: false。此时目标数据里的 length 不会改变模型大小,模型只按配置的 scale 显示。
{
highModel: {
url: '/model/集装箱货船.gltf',
scale: 100,
useRealLength: false
}
}自定义图标覆盖
目标默认按 targetTypes[].iconPath 显示图标;iconPath 会作为可染色基底层,仍兼容旧配置。需要装饰层时可配置 decorIconPath,装饰层保持原始颜色,不受目标 renderColor 影响。业务只需要少量目标显示特殊图标时,不建议新增目标类型,也不建议在目标数据里直接传 URL;推荐先在框架配置中注册 visualIcons,再通过目标数据或 API 写入 visualKey。
const framework = new MultiTargetFramework(viewer, {
visualIcons: [
{
key: 'ais',
iconPath: '/icons/target-ais-mask.png',
decorIconPath: '/icons/target-ais-decor.png',
pointHeadingOffset: 180
},
{
key: 'fishing-boat',
iconPath: '/icons/fishing-boat-mask.png',
pointHeadingOffset: 180
}
],
targetTypes: [
{
type: 'ship',
iconPath: '/icons/target-ship-mask.png',
decorIconPath: '/icons/target-ship-decor.png',
defaultColor: '#25e65c'
}
]
})
framework.upsertTargetData([
{
id: 'ship-001',
type: 'ship',
lon: 121.5,
lat: 31.2,
height: 0,
speedH: 8,
speedV: 0,
heading: 45,
visualKey: 'ais'
}
])
// 支持单个 id 或 id 数组,适合批量测试/业务批量切换。
framework.setTargetVisualKey('ship-001', 'fishing-boat')
framework.setTargetVisualKey(['ship-001', 'ship-002'], 'ais')
framework.setTargetVisualKey('ship-001', null)性能说明:
visualKey进入 worker 后会解析成稳定的数字iconIndex,渲染循环只消费数字索引。setTargetVisualKey(id, key)默认只更新命中的目标 id,并失效相关渲染缓存,不需要重新 upsert 完整目标。visualKey只影响图标图集索引;目标type、高低模选择、业务分类、名称牌类型判断仍使用原始type。- 高空合并时会按
type + iconIndex分桶,避免默认船图标和 AIS 图标被合成同一个代表点后丢失视觉差异。
API
MultiTargetFramework
| 方法 | 说明 |
| --- | --- |
| setData(targets) | setTargetData 的别名,全量替换目标数据 |
| upsert(targets) | upsertTargetData 的别名,增量新增或更新 |
| remove(ids) | removeTargetData 的别名,按 id 删除 |
| addTarget(target) | 校验并实时 upsert 单目标,返回校验后的目标 |
| updateTarget(target) | 校验并实时 upsert 单目标,返回校验后的目标 |
| setTargetData(targets) | 全量替换目标数据 |
| upsertTargetData(targets) | 增量新增或更新目标数据 |
| removeTargetData(ids) | 实时删除目标 |
| applyTargetIncremental(payload) | 应用 { upserts, removes } 增量包 |
| setTargetColor(id, color, options?) | 设置目标颜色 |
| setTargetGlow(id, enabled, color?, options?) | 开关发光动画 |
| setTargetVisualKey(id, visualKey?, options?) | 设置目标图标覆盖;id 支持字符串或字符串数组,visualKey 传 null/空值恢复类型默认图标 |
| hideTarget(id, options?) | 隐藏目标 |
| showTarget(id, options?) | 显示目标 |
| setTargetVisibility({ visible, ids?, types? }) | 强制显示/隐藏目标;不传 ids/types 时作用于全局 |
| setNameplateVisibility({ visible, ids?, types? }) | 强制显示/隐藏名称牌;不传 ids/types 时作用于全局 |
| setBearingLineVisibility(visible) | 打开/关闭目标航向指向线;默认关闭 |
| setPointLayerVisibility(visible) | 打开/关闭点图层和地表实例化符号层 |
| disappearTarget(id, options?) | hideTarget 别名 |
| appearTarget(id, options?) | showTarget 别名 |
| setNameplateConfig(config) | 动态更新名称牌配置 |
| selectTarget(id, options?) | 选中目标,返回 TargetSnapshot \| null |
| selectTargets(ids, options?) | 批量选中目标,返回 TargetSnapshot[] |
| unselectTarget(id) | 取消单目标选中 |
| clearSelection() | 清空选中和框选 |
| boxTarget(id, enabled?, options?) | 设置黄色框选覆盖层 |
| unboxTarget(id, options?) | 取消框选 |
| locateTarget(id, options?) | 飞行定位目标,返回目标快照 |
| registerContextMenuItem(item) | 注册右键菜单项,返回注销函数 |
| showContextMenu(ctx) | 展示统一右键菜单 |
| closeContextMenu() | 关闭右键菜单 |
| destroy() | 销毁菜单 DOM、事件监听、场景和 worker |
ModelManager
framework.modelManager 管理不跟随多目标 LOD 数据生命周期的业务模型。位置单位为 WGS84 度/米,heading、pitch、roll 使用度。
const model = await framework.modelManager.addModel({
id: 'port-drilling-rig',
url: '/models/钻井.gltf',
category: 'normal',
longitude: 110.329,
latitude: 20.086,
height: 0,
heading: 20,
scale: 500,
runAnimations: true
})
framework.modelManager.updateModel(model.id, {
longitude: 110.331,
heading: 45,
pitch: 2
})
framework.modelManager.removeModel(model.id)| 方法 | 说明 |
| --- | --- |
| addModel(options) | 异步添加并返回 ManagedModel;同 id 已存在时直接返回已有模型 |
| getModel(id) | 查询模型快照;普通模型的 model 字段是原生 Cesium.Model |
| updateModel(id, options) | 按 id 更新经纬高、航向/俯仰/翻滚、缩放、显隐或动画状态 |
| removeModel(id) | 按 id 删除,返回是否找到并删除 |
| clearModels(category?) | 清空全部模型,或只清空 normal / instanced 分类 |
normal:默认分类。每个 id 对应独立Cesium.Model,支持 glTF/GLB 完整材质、蒙皮和动画。instanced:同文件名共享一个 GPU 实例批次,适合大量静态模型;不执行 glTF 动画和蒙皮。- 资源键取 URL 的文件名并忽略大小写。仍有同名模型存在时,新 id 即使传入不同目录的同名 URL,也复用第一次注册的 URL;业务侧应避免不同模型使用相同文件名。
MultiTargetScene
高层实例的 framework.scene 是底层渲染引擎实例,适合需要 packed 数据、底层查询或事件监听的场景。
| 方法/事件 | 说明 |
| --- | --- |
| setData(targets) | 全量目标数据 |
| applyIncremental(payload) | 增量目标数据 |
| applyRealtimeUpsert(targets) | 实时 upsert |
| applyRealtimeRemove(ids) | 实时删除 |
| applyPacked(payload) | 提交 typed array packed 数据 |
| flushNow() | 立即提交 staged 数据 |
| pause() / resume() | 暂停/恢复提交和视口刷新 |
| getTargetSnapshot(id) | 查询目标逻辑快照 |
| getRenderedTargetFrame(id, result?) | 查询当前实际渲染帧位置和航向 |
| on("perf", handler) | 性能事件 |
| on("dirty", handler) | 普通 diff 事件 |
| on("packedDirty", handler) | packed diff 事件 |
| on("pick", handler) | 点击目标事件,载荷 PickedNodeLike \| null |
| on("hover", handler) | hover 目标事件,载荷 PickedNodeLike \| null |
| on("select", handler) | 目标选中事件,载荷 { id, target, targets } |
| on("unselect", handler) | 目标取消选中事件,载荷 { id, target?, targets } |
| on("selection", handler) | 选中集合事件 |
| on("contextmenu", handler) | 右键目标或空地事件 |
TrackManager
可通过 framework.tracks 或 framework.scene.tracks 使用。
| 方法/事件 | 说明 |
| --- | --- |
| on("hover", handler) | 监听轨迹点 hover,载荷 TrackHoverInfo \| null |
| setTracks(tracks) | 新增或更新轨迹 |
| getTracks(targetIds?) | 查询轨迹,返回拷贝 |
| showTracks(targetIds) | 显示轨迹 |
| hideTracks(targetIds) | 隐藏轨迹 |
| removeTracks(targetIds) | 删除轨迹 |
| clearTracks() | 清空轨迹 |
| setPointLabelOptions(options) | 配置轨迹点标签文案/样式/数量上限 |
| showPointLabels(targetIds) | 按目标 id 打开轨迹点标签 |
| hidePointLabels(targetIds) | 按目标 id 关闭轨迹点标签 |
| clearPointLabels() | 一键关闭全部轨迹点标签 |
SiteLayer
站点层独立于目标系统,用于固定站点图标/低模展示。
const siteLayer = new SiteLayer(viewer, {
modelSwitchHeight: 8000,
mergeDistanceMeters: 100,
defaultCoverageRangeMeters: 10000,
defaultInstallationHeight: 30,
defaultPitchDeg: 0,
defaultFieldOfViewDeg: 60,
siteTypes: [
{
type: 'radar',
category: 'radar',
iconPath: '/icons/radar.png',
coverageRangeMeters: 18000,
installationHeight: 35,
pitchDeg: 0.15,
fieldOfViewDeg: 70,
lowModel: { url: '/model/radar.glb' }
}
]
})
siteLayer.setData([
{
id: 'radar-1',
type: 'radar',
lon: 121,
lat: 31,
height: 0,
heading: 90,
coverageRangeMeters: 12000,
installationHeight: 30,
pitchDeg: 0.15,
fieldOfViewDeg: 60
}
])站点覆盖范围默认 10km,可在 SiteLayerOptions.defaultCoverageRangeMeters、SiteTypeConfig.coverageRangeMeters 或单个 SiteData.coverageRangeMeters 覆盖。installationHeight 默认为 30m,表示设备安装高度;pitchDeg 表示相对水平面的向下俯仰角,fieldOfViewDeg 表示水平视场角宽度。指向扇形使用 heading +/- fieldOfViewDeg / 2 展开,扇形长度优先按 installationHeight / tan(abs(pitchDeg)) 计算,并限制在覆盖半径内;pitchDeg 为空或接近 0 时直接使用覆盖半径。
站点覆盖圆、指向扇形和中心指向线默认不渲染。需要业务主动打开时调用:
siteLayer.setCoverageVisibility({ visible: true })
// 或分别控制
siteLayer.setCoverageVisibility({ showCoverage: true, showDirectionSector: false })
// 或只打开某个站点的覆盖范围/指向扇形
siteLayer.setCoverageVisibility({ ids: ['site_radar_haikou_c'], visible: true, showCoverage: true })
siteLayer.setCoverageVisibility({ ids: ['site_radar_haikou_c'], visible: true, showDirectionSector: true })
// 调整站点 heading、fieldOfViewDeg、pitchDeg 等数据时,覆盖/指向几何默认 150ms 补间
siteLayer.setData(nextSites, { animationMs: 150 })默认 mergeDistanceMeters 为 100m。视口内站点距离小于该阈值时会合并成一个站点集合:底部显示圆环,不同类型按环绕 slot 展示;同一集合内多个同类型站点只显示一个该类型图标,并在图标右上角显示数量角标。集合右键仍触发 contextmenu,payload 保留 site 代表站点,同时增加 sites、slotSites、clusterId、isCluster。命中集合时,site.lon / site.lat / site.height 是集合 slot 的实际渲染位置,便于业务直接画选中态;原始代表站点可从 site.originalSite 读取。
| 方法/事件 | 说明 |
| --- | --- |
| setData(sites, options?) | 全量设置站点;options.animationMs 控制覆盖/指向几何补间,默认 150 |
| setVisibility({ visible, ids?, types? }) | 强制显示/隐藏站点;不传 ids/types 时作用于全局 |
| setCoverageVisibility({ visible?, ids?, types?, showCoverage?, showDirectionSector?, animationMs? }) | 打开/关闭站点覆盖范围、指向扇形和中心指向线;可按站点 id/type 过滤;默认 150ms 补间 |
| pickAt(position, thresholdPx?) | 按屏幕坐标拾取最近站点 |
| clear() | 清空站点 |
| destroy() | 销毁站点层 |
| on("contextmenu", handler) | 站点右键事件 |
| on("pick", handler) | 站点点击事件 |
| on("hover", handler) | 站点 hover 事件 |
| on("select", handler) | 站点选中事件 |
| on("unselect", handler) | 站点取消选中事件 |
Web Event Bus 使用方式
包会依赖并复用 web-event-bus。创建 MultiTargetFramework 后会自动把 framework/tracks 的 public API 注册成可跨模块调用的 action,并桥接 scene/tracks 事件;创建 SiteLayer 后会自动注册 site 相关 action,并桥接站点事件。默认 action 名固定为:
mtf.framework.<方法名>
mtf.tracks.<方法名>
mtf.site.<方法名>import {
getFrameworkEventBus
} from 'cesium-multi-target-framework'
const bus = getFrameworkEventBus()
bus.on('mtf.scene.hover', (payload) => {
console.log('hover', payload)
})
await bus.invoke('mtf.framework.locateTarget', {
id: 'ship-001',
options: { range: 12000 }
})
await bus.invoke('mtf.tracks.showTracks', {
targetIds: ['ship-001']
})
await bus.invoke('mtf.tracks.setPointLabelOptions', {
template: '{time}\\n{speed}m/s',
maxLabels: 1000,
maxLabelsPerTarget: 100
})
await bus.invoke('mtf.tracks.showPointLabels', {
targetIds: ['ship-001']
})
await bus.invoke('mtf.framework.drawImage', {
imagePath: '/icons/mtf/aircraft.png',
followTarget: true,
targetId: 'ship-001',
rotateWithTarget: true, // 是否随目标航向旋转;true 时图片会跟着 targetId 的 heading 一起转
initialRotation: 0
})多实例使用同一组固定 action 名:后创建的同类实例会接管对应 action;旧实例销毁时只会注销自己仍持有的 action,不会误删后来接管的实例。实例 destroy() 会自动解绑本实例注册的 action 和事件桥接。
Web Event Bus 对外能力
事件
| Web Event Bus 事件 | 载荷 | 说明 |
| --- | --- | --- |
| mtf.scene.perf | SceneEvents["perf"] | 输入、worker、渲染慢帧等性能事件 |
| mtf.scene.dirty | DiffResult | 普通 diff |
| mtf.scene.packedDirty | PackedDiffResult | packed diff |
| mtf.scene.pick | PickedNodeLike \| null | 点击目标 |
| mtf.scene.hover | PickedNodeLike \| null | hover 目标 |
| mtf.scene.select | TargetSelectEvent | 目标选中 |
| mtf.scene.unselect | TargetUnselectEvent | 目标取消选中 |
| mtf.scene.selection | PickedNodeLike[] | 选中集合 |
| mtf.scene.contextmenu | SceneContextMenuPayload \| null | 右键目标或空地 |
| mtf.track.hover | TrackHoverInfo \| null | 轨迹点 hover |
| mtf.site.contextmenu | SiteContextMenuPayload | 站点右键 |
| mtf.site.pick | PickedSiteData \| null | 站点点击;集合命中时坐标为集合渲染位置 |
| mtf.site.hover | PickedSiteData \| null | 站点 hover;集合命中时坐标为集合渲染位置 |
| mtf.site.select | SiteSelectEvent | 站点选中 |
| mtf.site.unselect | SiteUnselectEvent | 站点取消选中 |
action
| Web Event Bus 对外能力 | 参数 | 说明 |
| --- | --- | --- |
| mtf.framework.setData | { targets } | setTargetData 别名 |
| mtf.framework.upsert | { targets } | upsertTargetData 别名 |
| mtf.framework.remove | { ids } | removeTargetData 别名 |
| mtf.framework.addTarget | { target } | 新增/更新单目标,返回校验后的目标 |
| mtf.framework.updateTarget | { target } | 新增/更新单目标,返回校验后的目标 |
| mtf.framework.setTargetData | { targets } | 全量替换目标数据 |
| mtf.framework.upsertTargetData | { targets } | 增量新增或更新目标 |
| mtf.framework.removeTargetData | { ids } | 删除目标 |
| mtf.framework.applyTargetIncremental | { payload } | 应用 { upserts, removes } 增量包 |
| mtf.framework.setTargetColor | { id, color, options? } | 设置目标颜色 |
| mtf.framework.setTargetGlow | { id, enabled, color?, options? } | 开关目标发光 |
| mtf.framework.setTargetVisualKey | { id, visualKey?, options? } | 设置目标图标覆盖;visualKey 传 null/空值恢复默认图标 |
| mtf.framework.hideTarget | { id, options? } | 隐藏目标 |
| mtf.framework.showTarget | { id, options? } | 显示目标 |
| mtf.framework.setTargetVisibility | { visible, ids?, types? } | 强制显示/隐藏目标 |
| mtf.framework.setNameplateVisibility | { visible, ids?, types? } | 强制显示/隐藏名称牌 |
| mtf.framework.setBearingLineVisibility | { visible } | 打开/关闭目标航向指向线 |
| mtf.framework.setPointLayerVisibility | { visible } | 打开/关闭点图层和地表实例化符号层 |
| mtf.framework.disappearTarget | { id, options? } | 隐藏目标别名 |
| mtf.framework.appearTarget | { id, options? } | 显示目标别名 |
| mtf.framework.setNameplateConfig | { config } | 更新名称牌配置 |
| mtf.framework.selectTarget | { id, options? } | 选中目标,返回 TargetSnapshot \| null |
| mtf.framework.selectTargets | { ids, options? } | 批量选中目标,返回 TargetSnapshot[] |
| mtf.framework.unselectTarget | { id } | 取消选中 |
| mtf.framework.clearSelection | 无 | 清空选中 |
| mtf.framework.boxTarget | { id, enabled?, options? } | 设置框选覆盖层 |
| mtf.framework.unboxTarget | { id, options? } | 取消框选 |
| mtf.framework.locateTarget | { id, options? } | 飞行定位目标,返回 TargetSnapshot \| null |
| mtf.framework.registerContextMenuItem | { item } | 注册右键菜单项,返回注销函数 |
| mtf.framework.showContextMenu | { context } | 展示统一右键菜单 |
| mtf.framework.closeContextMenu | 无 | 关闭右键菜单 |
| mtf.framework.drawLine | DrawLineOptions | 画业务线,返回线 id |
| mtf.framework.updateLine | { id, options } | 更新业务线 |
| mtf.framework.removeLine | { id } | 删除业务线 |
| mtf.framework.clearLines | 无 | 清空业务线;同时清空航道 |
| mtf.framework.drawImage | DrawImageOptions | 画图片覆盖物,返回图片 id |
| mtf.framework.updateImage | { id, options } | 更新图片覆盖物 |
| mtf.framework.removeImage | { id } | 删除图片覆盖物 |
| mtf.framework.clearImages | 无 | 清空图片覆盖物 |
| mtf.framework.showHeadingIndicator | { targetId, options? } | 按目标 id 打开内置航向指示器 |
| mtf.framework.hideHeadingIndicator | { targetId } | 按目标 id 关闭航向指示器 |
| mtf.framework.clearHeadingIndicators | 无 | 关闭全部航向指示器 |
| mtf.framework.createChannel | CreateChannelOptions | 创建航道,返回 ChannelRecord |
| mtf.framework.updateChannel | { id, options } | 按 id 更新航道,返回更新后的 ChannelRecord \| undefined |
| mtf.framework.removeChannel | { id } | 按 id 删除航道,返回是否删除成功 |
| mtf.framework.clearChannels | 无 | 清空全部航道 |
| mtf.framework.getChannel | { id } | 按 id 查询航道,返回 ChannelRecord \| undefined |
| mtf.framework.drawShape | DrawShapeOptions | 画矩形、圆或自定义图形,返回图形 id |
| mtf.framework.updateShape | { id, options } | 更新图形覆盖物 |
| mtf.framework.removeShape | { id } | 删除图形覆盖物 |
| mtf.framework.clearShapes | 无 | 清空图形覆盖物 |
| mtf.framework.editShape | { id } | 按 id 进入图形编辑状态 |
| mtf.framework.startDrawingOverlay | StartOverlayDrawOptions | 进入交互绘制模式 |
| mtf.framework.cancelDrawingOverlay | 无 | 取消当前交互绘制 |
| mtf.framework.cancelEditingOverlay | 无 | 退出当前图形编辑状态 |
| mtf.framework.destroy | 无 | 销毁当前接管 framework action 的实例 |
| mtf.tracks.setTracks | { tracks } | 新增或更新轨迹 |
| mtf.tracks.getTracks | { targetIds? } 或无 | 查询轨迹 |
| mtf.tracks.showTracks | { targetIds } | 显示轨迹 |
| mtf.tracks.hideTracks | { targetIds } | 隐藏轨迹 |
| mtf.tracks.removeTracks | { targetIds } | 删除轨迹 |
| mtf.tracks.clearTracks | 无 | 清空轨迹 |
| mtf.tracks.setPointLabelOptions | TrackPointLabelOptions | 配置轨迹点标签;bus 推荐使用 template |
| mtf.tracks.showPointLabels | { targetIds } | 按目标 id 打开轨迹点标签 |
| mtf.tracks.hidePointLabels | { targetIds } | 按目标 id 关闭轨迹点标签 |
| mtf.tracks.clearPointLabels | 无 | 一键关闭全部轨迹点标签 |
| mtf.site.setData | { sites, options? } | 全量设置站点 |
| mtf.site.setVisibility | { visible, ids?, types? } | 强制显示/隐藏站点 |
| mtf.site.setCoverageVisibility | { visible?, ids?, types?, showCoverage?, showDirectionSector?, animationMs? } | 打开/关闭站点覆盖范围、指向扇形和中心指向线 |
| mtf.site.clear | 无 | 清空站点 |
| mtf.site.pickAt | { position, thresholdPx? } | 按屏幕坐标拾取站点,返回 PickedSiteData \| null |
| mtf.site.destroy | 无 | 销毁当前接管 site action 的站点层 |
TypeScript 类型包括 FrameworkEventBus、FrameworkBusActionMap、FrameworkBusEventMap、FrameworkBusActionName、FrameworkEventName 等。
名称牌配置
名称牌就是目标旁边悬浮的小信息框。它可以显示目标 id、速度、高度、业务名称等字段,也可以按目标类型显示不同内容。
最常用的是 fields:每一项表示名称牌里的一行。
| 字段 | 说明 |
| --- | --- |
| key | 要显示哪个字段名。默认从目标对象顶层取值,例如 target.id、target.speedH、target.height |
| label | 显示名。不写时只显示值;写了会显示成 label: value |
| source | 取值来源。默认 standard 表示从目标顶层取;custom 表示从 target.customInfo 取 |
| fallback | 取不到值时显示什么。不配置时该行为空就不显示 |
| minCameraHeight | 相机高度低于该值时不显示这一行 |
| maxCameraHeight | 相机高度高于该值时不显示这一行 |
名称牌整体配置常用字段:
| 字段 | 说明 |
| --- | --- |
| enabled | 是否启用名称牌 |
| maxCount | 最多显示多少个名称牌,防止屏幕被铺满 |
| maxCameraHeight | 相机高于该高度时整体不显示名称牌 |
| maxDistance | 目标距离相机超过该距离时不显示 |
| height | 名称牌锚点高度,不配时跟随目标当前渲染高度 |
| showFieldLabels | 是否显示 label:,默认 true |
| fields | 默认字段列表,适用于所有类型 |
| byType | 按目标 type 精确覆盖配置 |
| resolve | 高级自定义函数;返回字符串/字符串数组/null/undefined |
基础例子
假设目标数据是:
{
id: 'uav-001',
type: 'air-uav',
lon: 121.5,
lat: 31.2,
height: 1200,
speedH: 35,
speedV: 0,
heading: 80,
customInfo: {
name: '无人机 A',
source: '雷达'
}
}配置:
framework.setNameplateConfig({
enabled: true,
maxCount: 50,
maxCameraHeight: 10000,
fields: [
{ key: 'id', label: 'ID' },
{ key: 'speedH', label: '速度', fallback: '0', maxCameraHeight: 6000 },
{ key: 'name', label: '名称', source: 'custom', maxCameraHeight: 3000 }
]
})相机高度小于等于 3000 米时显示:
ID: uav-001
速度: 35
名称: 无人机 A相机高度在 3000 到 6000 米之间时,name 这一行会隐藏:
ID: uav-001
速度: 35相机高度超过 6000 米时,speedH 这一行也会隐藏:
ID: uav-001相机高度超过 10000 米时,整个名称牌隐藏。
按类型覆盖
byType 用目标数据里的 type 精确匹配。比如你的无人机类型是 air-uav,这里就必须写 air-uav,不能写 uav。
framework.setNameplateConfig({
fields: [
{ key: 'id', label: 'ID' },
{ key: 'speedH', label: '速度' }
],
byType: {
'air-uav': {
fields: [
{ key: 'id', label: 'ID' },
{ key: 'height', label: '高度', maxCameraHeight: 8000 },
{ key: 'source', label: '来源', source: 'custom' }
]
},
ship: {
fields: [
{ key: 'id', label: 'ID' },
{ key: 'nationality', label: '国籍', fallback: '-' }
]
}
}
})规则是:
- 没命中
byType时,使用外层fields。 - 命中
byType[type]时,使用该类型自己的fields。 byType的 key 必须等于目标数据里的type,例如air-uav、air-bird、ship-other。byType[type].maxCameraHeight可以单独控制该类型名称牌的最大显示高度。
resolve 高级自定义
resolve(ctx) 可以完全接管名称牌内容。它适合做复杂逻辑,比如聚合目标、不同高度显示不同文案、格式化单位。
framework.setNameplateConfig({
resolve: (ctx) => {
if (ctx.cameraHeight > 10000) return null
if (ctx.count > 1) return `聚合目标 x${ctx.count}`
return [
`${ctx.type}:${ctx.id}`,
`高度 ${ctx.data?.height ?? '-'} m`
]
}
})返回值含义:
| 返回值 | 效果 |
| --- | --- |
| string | 显示一行 |
| string[] | 显示多行 |
| null | 不显示名称牌 |
| undefined | 不接管,继续使用 fields / byType 默认逻辑 |
ctx 常用字段:
| 字段 | 说明 |
| --- | --- |
| ctx.id | 目标 id |
| ctx.type | 目标类型 |
| ctx.data | 原始目标数据,可能是 undefined |
| ctx.cameraHeight | 当前相机高度,单位米 |
| ctx.distance | 目标到相机距离,可能为空 |
| ctx.count | 聚合数量;普通单目标通常是 1 |
优先级:resolve 先执行。只要它返回的不是 undefined,框架就不会再走 fields / byType。
完整配置片段
framework.setNameplateConfig({
enabled: true,
maxCount: 50,
height: 120,
showFieldLabels: true,
fields: [
{ key: 'id', label: 'ID' },
{ key: 'speedH', label: '速度', fallback: '0', maxCameraHeight: 6000 },
{ key: 'homePort', source: 'custom', label: '所属渔港', fallback: '-', maxCameraHeight: 3000 }
],
byType: {
ship: {
fields: [
{ key: 'id', label: 'ID' },
{ key: 'homePort', source: 'custom', label: '所属渔港', fallback: '-' },
{ key: 'nationality', label: '国籍', fallback: '-' }
]
},
'air-uav': {
fields: [
{ key: 'id', label: 'ID' },
{ key: 'height', label: '高度', maxCameraHeight: 8000 }
]
}
},
resolve: (ctx) => {
if (ctx.cameraHeight > 10000) return null
return [`${ctx.type}:${ctx.id}`, `数量 ${ctx.count}`]
}
})
framework.setNameplateVisibility({ visible: false, types: ['air-uav'] })
framework.setTargetVisibility({ visible: false, ids: ['ship-001'] })
siteLayer.setVisibility({ visible: false, types: ['radar'] })轨迹格式
framework.tracks.setTracks([
{
targetId: 'ship-001',
visible: true,
pointList: [
{ lon: 121.5, lat: 31.2, height: 0, time: Date.now(), heading: 45 }
]
}
])TrackRecord 支持 pointList 或 trackLine。trackLine 格式为 LINESTRING (...) 或 LINESTRING Z (...)。
轨迹点标签默认关闭,可按目标 id 打开。标签使用 billboard + atlas 画布渲染,默认只渲染当前轨迹 LOD 下的点,并受 maxLabels / maxLabelsPerTarget 限制。template 支持 {targetId}、{pointIndex}、{time}、{speed}、{heading}、{source}、{lon}、{lat}、{height}。
framework.tracks.setPointLabelOptions({
template: '{time}\\n速度:{speed}m/s',
maxLabels: 1000,
maxLabelsPerTarget: 100
})
framework.tracks.showPointLabels(['ship-001'])
framework.tracks.hidePointLabels(['ship-001'])
framework.tracks.clearPointLabels()聚类工具
import {
createQuadClusterer,
clusterProgressive,
lonLatToLocalPlane,
createClusterWorkerClient
} from 'cesium-multi-target-framework'
const plane = lonLatToLocalPlane(lons, lats, count)
const result = clusterProgressive(plane.xs, plane.ys, count, {
baseCellMeters: 200,
maxClusters: 30000
})
const client = createClusterWorkerClient()
const workerResult = await client.run(lons, lats, count)
client.terminate()开发
npm install
npm run dev
npm run typecheck
npm run buildnpm run dev 会启动 demo,默认地址为 http://localhost:5173。
更多文档
- 项目说明书:完整架构、渲染链路、预测、拾取、合并和调参说明
- 对外接口说明:包根导出、类型和接口明细
- 多目标优化与预测渲染说明:优化设计说明
- 工作计划:实施记录
