cesium-multi-target-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +474 -0
- package/dist/assets/renderWorker-118e8f3b.js +1 -0
- package/dist/cesium-multi-target-framework.js +4605 -0
- package/dist/cesium-multi-target-framework.umd.cjs +101 -0
- package/dist/cluster/QuadCluster.d.ts +66 -0
- package/dist/cluster/clusterClient.d.ts +44 -0
- package/dist/cluster/clusterWorker.d.ts +1 -0
- package/dist/config/types.d.ts +266 -0
- package/dist/core/EventEmitter.d.ts +9 -0
- package/dist/core/MultiTargetFramework.d.ts +184 -0
- package/dist/core/MultiTargetScene.d.ts +224 -0
- package/dist/data/types.d.ts +67 -0
- package/dist/events/bus.d.ts +336 -0
- package/dist/index.d.ts +22 -0
- package/dist/render/AirGroundReferenceRenderer.d.ts +20 -0
- package/dist/render/InstancedGltfBatch.d.ts +37 -0
- package/dist/render/InstancedSymbolRenderer.d.ts +65 -0
- package/dist/render/LowModelInstancedRenderer.d.ts +39 -0
- package/dist/render/ModelPoolRenderer.d.ts +43 -0
- package/dist/render/PointCloudRenderer.d.ts +135 -0
- package/dist/render/SelectionOverlayRenderer.d.ts +37 -0
- package/dist/render/targetVisualMetrics.d.ts +9 -0
- package/dist/site/SiteLayer.d.ts +43 -0
- package/dist/site/SiteLowModelRenderer.d.ts +12 -0
- package/dist/site/SiteSpatialIndex.d.ts +10 -0
- package/dist/site/types.d.ts +72 -0
- package/dist/track/TrackHoverPicker.d.ts +26 -0
- package/dist/track/TrackManager.d.ts +42 -0
- package/dist/track/TrackRenderer.d.ts +29 -0
- package/dist/track/types.d.ts +27 -0
- package/dist/worker/protocol.d.ts +182 -0
- package/dist/worker/renderWorker.d.ts +1 -0
- package/doc//345/244/232/347/233/256/346/240/207/344/274/230/345/214/226/344/270/216/351/242/204/346/265/213/346/270/262/346/237/223/350/257/264/346/230/216.md +186 -0
- package/doc//345/244/232/347/273/204/344/273/266/344/272/213/344/273/266/346/226/271/346/241/210.md +410 -0
- package/doc//345/257/271/345/244/226/346/216/245/345/217/243/350/257/264/346/230/216.md +519 -0
- package/doc//345/267/245/344/275/234/350/256/241/345/210/222.md +59 -0
- package/doc//346/270/262/346/237/223/344/270/232/345/212/241/351/200/273/350/276/221.md +202 -0
- package/doc//347/253/231/347/202/271/346/270/262/346/237/223/344/270/216/345/217/263/351/224/256/350/217/234/345/215/225/345/256/236/347/216/260/350/257/264/346/230/216.md +49 -0
- package/doc//350/247/206/345/217/243/351/251/261/345/212/250/346/225/260/346/215/256/346/265/201/347/250/213/344/277/256/346/224/271/350/256/241/345/210/222.md +69 -0
- package/doc//351/241/271/347/233/256/350/257/264/346/230/216/344/271/246.md +729 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# cesium-multi-target-framework
|
|
2
|
+
|
|
3
|
+
高性能 Cesium 多目标渲染框架,面向海量船舶、无人机、车辆、站点等目标的实时展示。暴露高层类 `MultiTargetFramework`,提供目标数据接入、三级 LOD 渲染、对象池、视口裁剪、运动预测、目标选择/定位、轨迹、站点层、名称牌、右键菜单和 `web-event-bus` 跨模块调用能力。
|
|
4
|
+
|
|
5
|
+
当前依赖锁定 Cesium `1.107.2`,代码兼容 Cesium `1.107.2` 到 `1.128.0` 的常见版本敏感 API。详细架构、调参和排障记录见 [项目说明书](doc/项目说明书.md),完整导出说明见 [对外接口说明](doc/对外接口说明.md)。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install cesium-multi-target-framework
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
如果宿主项目单独管理 Cesium,建议保持 Cesium 版本与本包一致:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install cesium@1.107.2
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import {
|
|
23
|
+
Viewer,
|
|
24
|
+
MultiTargetFramework,
|
|
25
|
+
getFrameworkEventBus
|
|
26
|
+
} from 'cesium-multi-target-framework'
|
|
27
|
+
|
|
28
|
+
const viewer = new Viewer('cesiumContainer', {
|
|
29
|
+
animation: false,
|
|
30
|
+
timeline: false
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const framework = new MultiTargetFramework(viewer, {
|
|
34
|
+
targetTypes: [
|
|
35
|
+
{
|
|
36
|
+
type: 'ship',
|
|
37
|
+
name: '船舶',
|
|
38
|
+
iconPath: '/icons/target-ship-mask.png',
|
|
39
|
+
defaultColor: '#25e65c',
|
|
40
|
+
smoothMove: true,
|
|
41
|
+
predictMove: false
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'uav',
|
|
45
|
+
name: '无人机',
|
|
46
|
+
iconPath: '/icons/target-uav-mask.png',
|
|
47
|
+
defaultColor: '#22d2f2',
|
|
48
|
+
smoothMove: true,
|
|
49
|
+
predictMove: true,
|
|
50
|
+
predictMinCameraHeight: 25000
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
scene: {
|
|
54
|
+
data: { commitIntervalMs: 2000, leading: true, worker: true },
|
|
55
|
+
merge: { enabled: true, maxVisible: 30000, byType: true },
|
|
56
|
+
pick: { enabled: true, hover: true }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
framework.setTargetData([
|
|
61
|
+
{
|
|
62
|
+
id: 'ship-001',
|
|
63
|
+
type: 'ship',
|
|
64
|
+
lon: 121.5,
|
|
65
|
+
lat: 31.2,
|
|
66
|
+
height: 0,
|
|
67
|
+
speedH: 8,
|
|
68
|
+
speedV: 0,
|
|
69
|
+
heading: 45,
|
|
70
|
+
renderColor: '#f81282',
|
|
71
|
+
openAnimate: true,
|
|
72
|
+
animateType: '发光',
|
|
73
|
+
animateColor: '#f81282',
|
|
74
|
+
customInfo: { name: '海巡 001' }
|
|
75
|
+
}
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
framework.scene.on('hover', (target) => {
|
|
79
|
+
console.log('hover target', target)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await framework.ready
|
|
83
|
+
await framework.locateTarget('ship-001')
|
|
84
|
+
|
|
85
|
+
// 创建实例后会自动暴露到 web-event-bus
|
|
86
|
+
const bus = getFrameworkEventBus()
|
|
87
|
+
bus.on('mtf.scene.pick', console.log)
|
|
88
|
+
await bus.invoke('mtf.framework.setTargetColor', {
|
|
89
|
+
id: 'ship-001',
|
|
90
|
+
color: '#00ffff'
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 构造参数
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
const framework = new MultiTargetFramework(viewer, options)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| 参数 | 类型 | 说明 |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `viewer` | `Cesium.Viewer` | 必填,已创建的 Cesium Viewer 实例 |
|
|
103
|
+
| `options.targetTypes` | `MultiTargetTypeConfig[]` | 必填,业务目标类型配置 |
|
|
104
|
+
| `options.scene` | `Omit<SceneConfig, "nodeTypes">` | 可选,渲染、合并、预测、拾取、名称牌等场景配置 |
|
|
105
|
+
|
|
106
|
+
`MultiTargetFramework` 构造后会同步返回实例,并异步初始化内部 Web Worker。需要作为其它组件底座时,等待 `ready`:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const framework = new MultiTargetFramework(viewer, options)
|
|
110
|
+
await framework.ready
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
也可以使用异步工厂:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
const framework = await MultiTargetFramework.create(viewer, options)
|
|
117
|
+
// 或
|
|
118
|
+
const framework2 = await createMultiTargetFramework(viewer, options)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### targetTypes
|
|
122
|
+
|
|
123
|
+
| 字段 | 说明 |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `type` | 目标类型,必须与数据中的 `type` 对应 |
|
|
126
|
+
| `name` | 类型显示名 |
|
|
127
|
+
| `targetDomain` | `surface` / `air` / `underwater`,不传时按类型名推断 |
|
|
128
|
+
| `iconPath` | 点模式图标,建议使用黑白 mask 图 |
|
|
129
|
+
| `defaultColor` | 类型默认颜色 |
|
|
130
|
+
| `highModel` / `lowModel` | 高模/低模模型资源配置 |
|
|
131
|
+
| `highModelUrl` / `lowModelUrl` | 旧字段兼容,等同模型 URL |
|
|
132
|
+
| `pointPixelSize` | 点/符号像素大小 |
|
|
133
|
+
| `pointHeadingOffset` | 点模式朝向修正,弧度 |
|
|
134
|
+
| `scale` | 模型缩放 |
|
|
135
|
+
| `modelHeadingOffset` / `modelPitchOffset` / `modelRollOffset` | 模型朝向修正,弧度 |
|
|
136
|
+
| `smoothMove` | 是否启用平滑移动 |
|
|
137
|
+
| `smoothMoveMinCameraHeight` | 相机低于/等于该高度时启用平滑 |
|
|
138
|
+
| `smoothMoveDurationMs` | 单次位置变化的平滑时长 |
|
|
139
|
+
| `predict` / `predictMove` | 是否启用运动预测,`predict` 为兼容别名 |
|
|
140
|
+
| `predictMinCameraHeight` | 相机低于/等于该高度时启用预测 |
|
|
141
|
+
| `predictSeconds` | 预测窗口秒数 |
|
|
142
|
+
| `predictFitSeconds` | 新数据到达后的航线拟合秒数 |
|
|
143
|
+
|
|
144
|
+
### scene
|
|
145
|
+
|
|
146
|
+
| 配置 | 默认 | 说明 |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `lod.pointAbove` | `150000` | 高于此相机高度走点模式 |
|
|
149
|
+
| `lod.animatedBelow` | `10000` | 低于此相机高度允许高模动画 |
|
|
150
|
+
| `pool.size` | `30000` | 通用目标池容量 |
|
|
151
|
+
| `animatedPool.size` | `64` | 高模动画池容量 |
|
|
152
|
+
| `updateFps` | `30` | 主更新频率 |
|
|
153
|
+
| `predictSeconds` | `10` | 默认预测窗口秒数 |
|
|
154
|
+
| `predictFitSeconds` | `2` | 新权威数据到达后的拟合秒数 |
|
|
155
|
+
| `fadeMs` | `350` | 显隐淡入淡出时间 |
|
|
156
|
+
| `masterHideHeight` | `150000` | 高于此高度隐藏模型内容 |
|
|
157
|
+
| `merge.enabled` | `false` | 是否启用合并 |
|
|
158
|
+
| `merge.mode` | `global` | 合并模式 |
|
|
159
|
+
| `merge.maxVisible` | `pool.size` | 最大渲染目标数 |
|
|
160
|
+
| `merge.byType` | `false` | 是否按类型分桶合并 |
|
|
161
|
+
| `pick.enabled` | `true` | 是否启用点击拾取 |
|
|
162
|
+
| `pick.pixelThreshold` | `20` | 拾取像素阈值 |
|
|
163
|
+
| `pick.hover` | `true` | 是否启用 hover |
|
|
164
|
+
| `cull.viewportCull` | `true` | 是否启用视口裁剪 |
|
|
165
|
+
| `cull.viewportBufferRatio` | `0.15` | 视口缓冲比例 |
|
|
166
|
+
| `cull.viewportBufferMinMeters` | `500` | 视口裁剪缓冲的绝对下限 |
|
|
167
|
+
| `label.enabled` | `true` | 是否显示名称牌 |
|
|
168
|
+
| `label.maxCount` | `200` | 名称牌最多显示数量 |
|
|
169
|
+
| `label.maxCameraHeight` | `5000` | 名称牌最大显示相机高度 |
|
|
170
|
+
| `frame.maxLowModelRenderItems` | `3000` | 低模后端数量上限 |
|
|
171
|
+
| `data.commitIntervalMs` | `2000` | 数据提交周期,`0` 表示立即提交 |
|
|
172
|
+
| `data.leading` | `true` | 首批数据是否立即提交 |
|
|
173
|
+
| `data.worker` | `true` | 数据处理是否使用 worker |
|
|
174
|
+
| `groundSymbols` | `false` | 点档单目标是否使用地表实例化有向符号 |
|
|
175
|
+
| `groundSymbolPixelSize` | `30` | 地表符号像素大小 |
|
|
176
|
+
|
|
177
|
+
## 数据格式
|
|
178
|
+
|
|
179
|
+
`TargetData` / `StandardTargetData` 使用 WGS84 经纬度,速度单位为 m/s,角度单位为度。
|
|
180
|
+
|
|
181
|
+
| 字段 | 必填 | 说明 |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `id` | 是 | 唯一 id |
|
|
184
|
+
| `type` | 是 | 目标类型,必须命中 `targetTypes` |
|
|
185
|
+
| `lon` / `lat` / `height` | 是 | 经度、纬度、高度 |
|
|
186
|
+
| `speedH` / `speedV` | 是 | 水平速度、垂直速度 |
|
|
187
|
+
| `heading` | 是 | 航向角,正北为 0,顺时针 |
|
|
188
|
+
| `renderColor` | 否 | 单目标渲染颜色,`#RRGGBB` 或 `#RRGGBBAA` |
|
|
189
|
+
| `openAnimate` | 否 | 是否开启动画 |
|
|
190
|
+
| `animateType` | 否 | 当前支持 `glow` / `发光` |
|
|
191
|
+
| `animateColor` | 否 | 动画颜色 |
|
|
192
|
+
| `nationality` | 否 | 国籍/归属 |
|
|
193
|
+
| `customInfo` | 否 | 自定义信息,可用于名称牌字段 |
|
|
194
|
+
| `track` | 否 | 轨迹点数组 |
|
|
195
|
+
| `secrecy` | 否 | 密级,当前透传 |
|
|
196
|
+
| `extra` | 否 | 额外业务状态 |
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
### MultiTargetFramework
|
|
201
|
+
|
|
202
|
+
| 方法 | 说明 |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| `setData(targets)` | `setTargetData` 的别名,全量替换目标数据 |
|
|
205
|
+
| `upsert(targets)` | `upsertTargetData` 的别名,增量新增或更新 |
|
|
206
|
+
| `remove(ids)` | `removeTargetData` 的别名,按 id 删除 |
|
|
207
|
+
| `addTarget(target)` | 校验并实时 upsert 单目标,返回校验后的目标 |
|
|
208
|
+
| `updateTarget(target)` | 校验并实时 upsert 单目标,返回校验后的目标 |
|
|
209
|
+
| `setTargetData(targets)` | 全量替换目标数据 |
|
|
210
|
+
| `upsertTargetData(targets)` | 增量新增或更新目标数据 |
|
|
211
|
+
| `removeTargetData(ids)` | 实时删除目标 |
|
|
212
|
+
| `applyTargetIncremental(payload)` | 应用 `{ upserts, removes }` 增量包 |
|
|
213
|
+
| `setTargetColor(id, color, options?)` | 设置目标颜色 |
|
|
214
|
+
| `setTargetGlow(id, enabled, color?, options?)` | 开关发光动画 |
|
|
215
|
+
| `hideTarget(id, options?)` | 隐藏目标 |
|
|
216
|
+
| `showTarget(id, options?)` | 显示目标 |
|
|
217
|
+
| `disappearTarget(id, options?)` | `hideTarget` 别名 |
|
|
218
|
+
| `appearTarget(id, options?)` | `showTarget` 别名 |
|
|
219
|
+
| `setNameplateConfig(config)` | 动态更新名称牌配置 |
|
|
220
|
+
| `selectTarget(id, options?)` | 选中目标,返回 `TargetSnapshot \| null` |
|
|
221
|
+
| `selectTargets(ids, options?)` | 批量选中目标,返回 `TargetSnapshot[]` |
|
|
222
|
+
| `unselectTarget(id)` | 取消单目标选中 |
|
|
223
|
+
| `clearSelection()` | 清空选中和框选 |
|
|
224
|
+
| `boxTarget(id, enabled?, options?)` | 设置黄色框选覆盖层 |
|
|
225
|
+
| `unboxTarget(id, options?)` | 取消框选 |
|
|
226
|
+
| `locateTarget(id, options?)` | 飞行定位目标,返回目标快照 |
|
|
227
|
+
| `registerContextMenuItem(item)` | 注册右键菜单项,返回注销函数 |
|
|
228
|
+
| `showContextMenu(ctx)` | 展示统一右键菜单 |
|
|
229
|
+
| `closeContextMenu()` | 关闭右键菜单 |
|
|
230
|
+
| `destroy()` | 销毁菜单 DOM、事件监听、场景和 worker |
|
|
231
|
+
|
|
232
|
+
### MultiTargetScene
|
|
233
|
+
|
|
234
|
+
高层实例的 `framework.scene` 是底层渲染引擎实例,适合需要 packed 数据、底层查询或事件监听的场景。
|
|
235
|
+
|
|
236
|
+
| 方法/事件 | 说明 |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `setData(targets)` | 全量目标数据 |
|
|
239
|
+
| `applyIncremental(payload)` | 增量目标数据 |
|
|
240
|
+
| `applyRealtimeUpsert(targets)` | 实时 upsert |
|
|
241
|
+
| `applyRealtimeRemove(ids)` | 实时删除 |
|
|
242
|
+
| `applyPacked(payload)` | 提交 typed array packed 数据 |
|
|
243
|
+
| `flushNow()` | 立即提交 staged 数据 |
|
|
244
|
+
| `pause()` / `resume()` | 暂停/恢复提交和视口刷新 |
|
|
245
|
+
| `getTargetSnapshot(id)` | 查询目标逻辑快照 |
|
|
246
|
+
| `getRenderedTargetFrame(id, result?)` | 查询当前实际渲染帧位置和航向 |
|
|
247
|
+
| `on("perf", handler)` | 性能事件 |
|
|
248
|
+
| `on("dirty", handler)` | 普通 diff 事件 |
|
|
249
|
+
| `on("packedDirty", handler)` | packed diff 事件 |
|
|
250
|
+
| `on("pick", handler)` | 点击目标事件,载荷 `PickedNodeLike \| null` |
|
|
251
|
+
| `on("hover", handler)` | hover 目标事件,载荷 `PickedNodeLike \| null` |
|
|
252
|
+
| `on("selection", handler)` | 选中集合事件 |
|
|
253
|
+
| `on("contextmenu", handler)` | 右键目标或空地事件 |
|
|
254
|
+
|
|
255
|
+
### TrackManager
|
|
256
|
+
|
|
257
|
+
可通过 `framework.tracks` 或 `framework.scene.tracks` 使用。
|
|
258
|
+
|
|
259
|
+
| 方法/事件 | 说明 |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| `on("hover", handler)` | 监听轨迹点 hover,载荷 `TrackHoverInfo \| null` |
|
|
262
|
+
| `setTracks(tracks)` | 新增或更新轨迹 |
|
|
263
|
+
| `getTracks(targetIds?)` | 查询轨迹,返回拷贝 |
|
|
264
|
+
| `showTracks(targetIds)` | 显示轨迹 |
|
|
265
|
+
| `hideTracks(targetIds)` | 隐藏轨迹 |
|
|
266
|
+
| `removeTracks(targetIds)` | 删除轨迹 |
|
|
267
|
+
| `clearTracks()` | 清空轨迹 |
|
|
268
|
+
|
|
269
|
+
### SiteLayer
|
|
270
|
+
|
|
271
|
+
站点层独立于目标系统,用于固定站点图标/低模展示。
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const siteLayer = new SiteLayer(viewer, {
|
|
275
|
+
modelSwitchHeight: 8000,
|
|
276
|
+
siteTypes: [
|
|
277
|
+
{
|
|
278
|
+
type: 'radar',
|
|
279
|
+
category: 'radar',
|
|
280
|
+
iconPath: '/icons/radar.png',
|
|
281
|
+
lowModel: { url: '/model/radar.glb' }
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
siteLayer.setData([
|
|
287
|
+
{ id: 'radar-1', type: 'radar', lon: 121, lat: 31, height: 0 }
|
|
288
|
+
])
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| 方法/事件 | 说明 |
|
|
292
|
+
| --- | --- |
|
|
293
|
+
| `setData(sites)` | 全量设置站点 |
|
|
294
|
+
| `pickAt(position, thresholdPx?)` | 按屏幕坐标拾取最近站点 |
|
|
295
|
+
| `clear()` | 清空站点 |
|
|
296
|
+
| `destroy()` | 销毁站点层 |
|
|
297
|
+
| `on("contextmenu", handler)` | 站点右键事件 |
|
|
298
|
+
| `on("pick", handler)` | 站点点击事件 |
|
|
299
|
+
| `on("hover", handler)` | 站点 hover 事件 |
|
|
300
|
+
|
|
301
|
+
## Web Event Bus 使用方式
|
|
302
|
+
|
|
303
|
+
包会依赖并复用 `web-event-bus`。创建 `MultiTargetFramework` 后会自动把 framework/tracks 的 public API 注册成可跨模块调用的 action,并桥接 scene/tracks 事件;创建 `SiteLayer` 后会自动注册 site 相关 action,并桥接站点事件。默认 action 名固定为:
|
|
304
|
+
|
|
305
|
+
```text
|
|
306
|
+
mtf.framework.<方法名>
|
|
307
|
+
mtf.tracks.<方法名>
|
|
308
|
+
mtf.site.<方法名>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
import {
|
|
313
|
+
getFrameworkEventBus
|
|
314
|
+
} from 'cesium-multi-target-framework'
|
|
315
|
+
|
|
316
|
+
const bus = getFrameworkEventBus()
|
|
317
|
+
|
|
318
|
+
bus.on('mtf.scene.hover', (payload) => {
|
|
319
|
+
console.log('hover', payload)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
await bus.invoke('mtf.framework.locateTarget', {
|
|
323
|
+
id: 'ship-001',
|
|
324
|
+
options: { range: 12000 }
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
await bus.invoke('mtf.tracks.showTracks', {
|
|
328
|
+
targetIds: ['ship-001']
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
多实例使用同一组固定 action 名:后创建的同类实例会接管对应 action;旧实例销毁时只会注销自己仍持有的 action,不会误删后来接管的实例。实例 `destroy()` 会自动解绑本实例注册的 action 和事件桥接。
|
|
333
|
+
|
|
334
|
+
## Web Event Bus 对外能力
|
|
335
|
+
|
|
336
|
+
### 事件
|
|
337
|
+
|
|
338
|
+
| Web Event Bus 事件 | 载荷 | 说明 |
|
|
339
|
+
| --- | --- | --- |
|
|
340
|
+
| `mtf.scene.perf` | `SceneEvents["perf"]` | 输入、worker、渲染慢帧等性能事件 |
|
|
341
|
+
| `mtf.scene.dirty` | `DiffResult` | 普通 diff |
|
|
342
|
+
| `mtf.scene.packedDirty` | `PackedDiffResult` | packed diff |
|
|
343
|
+
| `mtf.scene.pick` | `PickedNodeLike \| null` | 点击目标 |
|
|
344
|
+
| `mtf.scene.hover` | `PickedNodeLike \| null` | hover 目标 |
|
|
345
|
+
| `mtf.scene.selection` | `PickedNodeLike[]` | 选中集合 |
|
|
346
|
+
| `mtf.scene.contextmenu` | `SceneContextMenuPayload \| null` | 右键目标或空地 |
|
|
347
|
+
| `mtf.track.hover` | `TrackHoverInfo \| null` | 轨迹点 hover |
|
|
348
|
+
| `mtf.site.contextmenu` | `SiteContextMenuPayload` | 站点右键 |
|
|
349
|
+
| `mtf.site.pick` | `SiteData \| null` | 站点点击 |
|
|
350
|
+
| `mtf.site.hover` | `SiteData \| null` | 站点 hover |
|
|
351
|
+
|
|
352
|
+
### action
|
|
353
|
+
|
|
354
|
+
| Web Event Bus 对外能力 | 参数 | 说明 |
|
|
355
|
+
| --- | --- | --- |
|
|
356
|
+
| `mtf.framework.addTarget` | `{ target }` | 新增/更新单目标,返回校验后的目标 |
|
|
357
|
+
| `mtf.framework.updateTarget` | `{ target }` | 新增/更新单目标,返回校验后的目标 |
|
|
358
|
+
| `mtf.framework.setTargetData` | `{ targets }` | 全量替换目标数据 |
|
|
359
|
+
| `mtf.framework.upsertTargetData` | `{ targets }` | 增量新增或更新目标 |
|
|
360
|
+
| `mtf.framework.removeTargetData` | `{ ids }` | 删除目标 |
|
|
361
|
+
| `mtf.framework.applyTargetIncremental` | `{ payload }` | 应用 `{ upserts, removes }` 增量包 |
|
|
362
|
+
| `mtf.framework.setTargetColor` | `{ id, color, options? }` | 设置目标颜色 |
|
|
363
|
+
| `mtf.framework.setTargetGlow` | `{ id, enabled, color?, options? }` | 开关目标发光 |
|
|
364
|
+
| `mtf.framework.hideTarget` | `{ id, options? }` | 隐藏目标 |
|
|
365
|
+
| `mtf.framework.showTarget` | `{ id, options? }` | 显示目标 |
|
|
366
|
+
| `mtf.framework.disappearTarget` | `{ id, options? }` | 隐藏目标别名 |
|
|
367
|
+
| `mtf.framework.appearTarget` | `{ id, options? }` | 显示目标别名 |
|
|
368
|
+
| `mtf.framework.setNameplateConfig` | `{ config }` | 更新名称牌配置 |
|
|
369
|
+
| `mtf.framework.selectTarget` | `{ id, options? }` | 选中目标,返回 `TargetSnapshot \| null` |
|
|
370
|
+
| `mtf.framework.selectTargets` | `{ ids, options? }` | 批量选中目标,返回 `TargetSnapshot[]` |
|
|
371
|
+
| `mtf.framework.unselectTarget` | `{ id }` | 取消选中 |
|
|
372
|
+
| `mtf.framework.clearSelection` | 无 | 清空选中 |
|
|
373
|
+
| `mtf.framework.boxTarget` | `{ id, enabled?, options? }` | 设置框选覆盖层 |
|
|
374
|
+
| `mtf.framework.unboxTarget` | `{ id, options? }` | 取消框选 |
|
|
375
|
+
| `mtf.framework.locateTarget` | `{ id, options? }` | 飞行定位目标,返回 `TargetSnapshot \| null` |
|
|
376
|
+
| `mtf.framework.registerContextMenuItem` | `{ item }` | 注册右键菜单项,返回注销函数 |
|
|
377
|
+
| `mtf.framework.showContextMenu` | `{ context }` | 展示统一右键菜单 |
|
|
378
|
+
| `mtf.framework.closeContextMenu` | 无 | 关闭右键菜单 |
|
|
379
|
+
| `mtf.framework.destroy` | 无 | 销毁当前接管 framework action 的实例 |
|
|
380
|
+
| `mtf.tracks.setTracks` | `{ tracks }` | 新增或更新轨迹 |
|
|
381
|
+
| `mtf.tracks.getTracks` | `{ targetIds? }` 或无 | 查询轨迹 |
|
|
382
|
+
| `mtf.tracks.showTracks` | `{ targetIds }` | 显示轨迹 |
|
|
383
|
+
| `mtf.tracks.hideTracks` | `{ targetIds }` | 隐藏轨迹 |
|
|
384
|
+
| `mtf.tracks.removeTracks` | `{ targetIds }` | 删除轨迹 |
|
|
385
|
+
| `mtf.tracks.clearTracks` | 无 | 清空轨迹 |
|
|
386
|
+
| `mtf.site.setData` | `{ sites }` | 全量设置站点 |
|
|
387
|
+
| `mtf.site.clear` | 无 | 清空站点 |
|
|
388
|
+
| `mtf.site.pickAt` | `{ position, thresholdPx? }` | 按屏幕坐标拾取站点 |
|
|
389
|
+
| `mtf.site.destroy` | 无 | 销毁当前接管 site action 的站点层 |
|
|
390
|
+
|
|
391
|
+
TypeScript 类型包括 `FrameworkEventBus`、`FrameworkBusActionMap`、`FrameworkBusEventMap`、`FrameworkBusActionName`、`FrameworkEventName` 等。
|
|
392
|
+
|
|
393
|
+
## 名称牌配置
|
|
394
|
+
|
|
395
|
+
名称牌支持默认字段、按类型覆盖字段、按相机高度显示字段,以及回调逃生口。
|
|
396
|
+
|
|
397
|
+
```javascript
|
|
398
|
+
framework.setNameplateConfig({
|
|
399
|
+
enabled: true,
|
|
400
|
+
maxCount: 300,
|
|
401
|
+
fields: [
|
|
402
|
+
{ key: 'id', label: 'ID' },
|
|
403
|
+
{ key: 'speedH', label: '速度', maxCameraHeight: 6000 },
|
|
404
|
+
{ key: 'name', source: 'custom', maxCameraHeight: 3000 }
|
|
405
|
+
],
|
|
406
|
+
byType: {
|
|
407
|
+
uav: {
|
|
408
|
+
fields: [
|
|
409
|
+
{ key: 'id' },
|
|
410
|
+
{ key: 'height', label: '高度', maxCameraHeight: 8000 }
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
resolve: (ctx) => {
|
|
415
|
+
if (ctx.cameraHeight > 10000) return null
|
|
416
|
+
return [`${ctx.type}:${ctx.id}`, `数量 ${ctx.count}`]
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## 轨迹格式
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
framework.tracks.setTracks([
|
|
425
|
+
{
|
|
426
|
+
targetId: 'ship-001',
|
|
427
|
+
visible: true,
|
|
428
|
+
pointList: [
|
|
429
|
+
{ lon: 121.5, lat: 31.2, height: 0, time: Date.now(), heading: 45 }
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
])
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
`TrackRecord` 支持 `pointList` 或 `trackLine`。`trackLine` 格式为 `LINESTRING (...)` 或 `LINESTRING Z (...)`。
|
|
436
|
+
|
|
437
|
+
## 聚类工具
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
import {
|
|
441
|
+
createQuadClusterer,
|
|
442
|
+
clusterProgressive,
|
|
443
|
+
lonLatToLocalPlane,
|
|
444
|
+
createClusterWorkerClient
|
|
445
|
+
} from 'cesium-multi-target-framework'
|
|
446
|
+
|
|
447
|
+
const plane = lonLatToLocalPlane(lons, lats, count)
|
|
448
|
+
const result = clusterProgressive(plane.xs, plane.ys, count, {
|
|
449
|
+
baseCellMeters: 200,
|
|
450
|
+
maxClusters: 30000
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
const client = createClusterWorkerClient()
|
|
454
|
+
const workerResult = await client.run(lons, lats, count)
|
|
455
|
+
client.terminate()
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## 开发
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
npm install
|
|
462
|
+
npm run dev
|
|
463
|
+
npm run typecheck
|
|
464
|
+
npm run build
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
`npm run dev` 会启动 demo,默认地址为 `http://localhost:5173`。
|
|
468
|
+
|
|
469
|
+
## 更多文档
|
|
470
|
+
|
|
471
|
+
- [项目说明书](doc/项目说明书.md):完整架构、渲染链路、预测、拾取、合并和调参说明
|
|
472
|
+
- [对外接口说明](doc/对外接口说明.md):包根导出、类型和接口明细
|
|
473
|
+
- [多目标优化与预测渲染说明](doc/多目标优化与预测渲染说明.md):优化设计说明
|
|
474
|
+
- [工作计划](doc/工作计划.md):实施记录
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";const ge=self;let m={maxVisible:3e4,rootLimit:500,maxLevels:64,cameraMergeBaseHeight:1e5,cameraMergeStepHeight:1e4,cameraMergeStepRadius:100,globalViewportHeight:45e4,globalCacheIntervalMs:3e5,innerRatio:.6,viewportBufferRatio:.15,viewportBufferMinMeters:500,pointAbove:15e4,animatedBelow:1e4,predictSeconds:10,predictFitSeconds:2,targetTypes:[]},U=me(),c=me(),te=new Map,ne=new Map,oe=[],_=new Map,E=new Set,ie=new Map,re=new Map,W=new Map,N=null,x=[],O=[],G=null,L=[],Y={west:0,east:1,south:0,north:1},$=0,B=null,R=null;const K=new Set;let k=null;const D=new Map;ge.onmessage=e=>{var n,o,i,r,l;const t=e.data;if(t.type==="init"){m=t.config,ne=new Map(m.targetTypes.map(s=>[s.type,s])),oe=m.targetTypes.slice().sort((s,u)=>s.category-u.category),V({type:"ready"});return}if(t.type==="stage-packed"){t.payload.replacePending?(N=t.payload,x=[],O=[],G=null):N=t.payload,(n=t.payload.removes)!=null&&n.length&&X(t.payload.removes),V({type:"input",kind:"packed",upserts:((o=t.payload.ids)==null?void 0:o.length)??t.payload.lon.length,removes:((i=t.payload.removes)==null?void 0:i.length)??0,pendingBatches:ae()});return}if(t.type==="set-data"){G=t.targets,N=null,x=[],O=[],D.clear(),V({type:"input",kind:"set-data",upserts:t.targets.length,removes:0,pendingBatches:ae()});return}if(t.type==="incremental"){Oe(t.payload),V({type:"input",kind:"incremental",upserts:((r=t.payload.upserts)==null?void 0:r.length)??0,removes:((l=t.payload.removes)==null?void 0:l.length)??0,pendingBatches:ae()});return}if(t.type==="realtime-upsert"){const s=b();ue(t.targets);for(const u of t.targets)E.delete(u.id),_.set(u.id,u);J(!0),V({type:"realtime-done",upserts:t.targets.length,removes:0,size:c.ids.length,computeMs:b()-s});return}if(t.type==="realtime-remove"){const s=b();X(t.ids);for(const u of t.ids)_.delete(u),E.add(u);J(!0),V({type:"realtime-done",upserts:0,removes:t.ids.length,size:c.ids.length,computeMs:b()-s});return}if(t.type==="style-patch"){const s=ke(t.patches);V({type:"style-done",updated:s});return}if(t.type==="target-query"){V({type:"target-info",requestId:t.requestId,target:_e(t.id)});return}if(t.type==="commit"){Ee(t.requestId);return}if(t.type==="viewport"){Qe(t.requestId,t.bounds,t.cameraHeight,t.maxRender,t.global===!0,ht(t.cameraLon,t.cameraLat,t.horizonCullDistance));return}t.type==="destroy"&&(B!=null&&clearTimeout(B),R!=null&&clearTimeout(R),D.clear(),close())};function V(e,t){ge.postMessage(e,t??[])}function me(){return{ids:[],types:[],category:new Uint8Array(0),color:new Uint32Array(0),animation:new Uint8Array(0),animationColor:new Uint32Array(0),hidden:new Uint8Array(0),lon:new Float64Array(0),lat:new Float64Array(0),height:new Float32Array(0),heading:new Float32Array(0),speedH:new Float32Array(0),speedV:new Float32Array(0)}}function ae(){return(N?1:0)+x.length+O.length+(G?1:0)}function Oe(e){var t,n;(t=e.upserts)!=null&&t.length&&x.push(...e.upserts),(n=e.removes)!=null&&n.length&&O.push(...e.removes)}function ke(e){let t=0,n=!1;const o=new Set;for(const i of e){i.clearBefore&&i.styleKind&&!o.has(i.styleKind)&&(De(i.styleKind),o.add(i.styleKind),n=!0);const r=te.get(i.id);if(r!=null){if(i.renderColor!=null){const l=j(i.renderColor,c.color[r]);re.set(i.id,l),c.color[r]=l,i.animateColor==null&&(c.animationColor[r]=c.color[r]),n=!0}if(i.openAnimate!=null||i.animateType!=null){const l=he(i.openAnimate??c.animation[r]===1,i.animateType),s=W.get(i.id);W.set(i.id,{animation:l,animationColor:s==null?void 0:s.animationColor}),c.animation[r]=l,n=!0}if(i.animateColor!=null){const l=j(i.animateColor,c.animationColor[r]||c.color[r]),s=W.get(i.id);W.set(i.id,{animation:(s==null?void 0:s.animation)??c.animation[r],animationColor:l}),c.animationColor[r]=l,n=!0}i.hidden!=null&&(ie.set(i.id,!i.hidden),c.hidden[r]=i.hidden?1:0,n=!0),t++}}return n&&J(!0),t}function De(e){e==="color"?re.clear():e==="glow"?W.clear():e==="hidden"&&ie.clear()}function _e(e){const t=te.get(e);return t==null?null:{id:e,type:c.types[t],lon:c.lon[t],lat:c.lat[t],height:c.height[t],heading:c.heading[t],speedH:c.speedH[t],renderColor:ee(c.color[t]),openAnimate:c.animation[t]===1,animateColor:ee(c.animationColor[t]),hidden:c.hidden[t]===1}}function Ee(e){var s;const t=b(),n=U.ids.length;let o=0,i=0,r=0,l=!1;if(G)ue(G),U=ye(G),o=U.ids.length,r=n,l=!0,G=null;else if(N){const a=!!((s=N.ids)!=null&&s.length)?new Set(U.ids):void 0;if(ft(N,U),U=je(N,U),a){for(const h of U.ids)a.has(h)?i++:o++;r=Math.max(0,n+o-U.ids.length),l=!0}else i=U.ids.length;N=null}if(x.length||O.length){x.length&&ue(x),O.length&&X(O);const u=qe(U,x,O);U=u.store,o+=u.added,i+=u.updated,r+=u.removed,l=l||u.added>0||u.removed>0,x=[],O=[]}J(l||L.length===0),gt(c.ids),V({type:"commit-done",requestId:e,added:o,updated:i,removed:r,size:c.ids.length,computeMs:b()-t})}function se(e){const t=new Map;for(let n=0;n<e.length;n++)t.set(e[n],n);return t}function J(e){c=ze(U),te=se(c.ids),Ge(c);const t=e||L.length===0;if($++,t&&(k=null,R!=null&&(clearTimeout(R),R=null)),K.clear(),B!=null&&(clearTimeout(B),B=null),c.ids.length===0){L=[],Y={west:0,east:1,south:0,north:1};return}t&&(Y=ut(c.lon,c.lat)),L=t?Ye(c,Y,m.rootLimit,m.maxLevels):$e(),k||et()}function Ge(e){for(let t=0;t<e.ids.length;t++){const n=e.ids[t],o=re.get(n);o!=null&&(e.color[t]=o);const i=W.get(n);i&&(e.animation[t]=i.animation,i.animationColor!=null&&(e.animationColor[t]=i.animationColor));const r=ie.get(n);r!=null&&(e.hidden[t]=r?0:1)}}function ze(e){if(_.size===0&&E.size===0)return e;const t=se(e.ids);let n=0;for(let r=0;r<e.ids.length;r++)E.has(e.ids[r])||n++;for(const r of _.keys())!t.has(r)&&!E.has(r)&&n++;const o=We(n);let i=0;for(let r=0;r<e.ids.length;r++){const l=e.ids[r];if(E.has(l))continue;const s=_.get(l);s?pe(o,i,s):Ke(e,o,r,i),i++}for(const[r,l]of _)E.has(r)||t.has(r)||(pe(o,i,l),i++);return o}function We(e){return{ids:new Array(e),types:new Array(e),category:new Uint8Array(e),color:new Uint32Array(e),animation:new Uint8Array(e),animationColor:new Uint32Array(e),hidden:new Uint8Array(e),lon:new Float64Array(e),lat:new Float64Array(e),height:new Float32Array(e),heading:new Float32Array(e),speedH:new Float32Array(e),speedV:new Float32Array(e)}}function Ke(e,t,n,o){t.ids[o]=e.ids[n],t.types[o]=e.types[n],t.category[o]=e.category[n],t.color[o]=e.color[n],t.animation[o]=e.animation[n],t.animationColor[o]=e.animationColor[n],t.hidden[o]=e.hidden[n],t.lon[o]=e.lon[n],t.lat[o]=e.lat[n],t.height[o]=e.height[n],t.heading[o]=e.heading[n],t.speedH[o]=e.speedH[n],t.speedV[o]=e.speedV[n]}function pe(e,t,n){var r;const o=Z(n.type),i=j(n.renderColor,o.defaultColor);e.ids[t]=n.id,e.types[t]=n.type,e.category[t]=o.category,e.color[t]=i,e.animation[t]=he(n.openAnimate,n.animateType),e.animationColor[t]=j(n.animateColor??n.renderColor,i),e.hidden[t]=(r=n.extra)!=null&&r.hidden?1:0,e.lon[t]=n.lon,e.lat[t]=n.lat,e.height[t]=n.height,e.heading[t]=n.heading,e.speedH[t]=n.speedH,e.speedV[t]=n.speedV}function je(e,t){var w,v,C,A;const n=e.lon.length;if(!((w=e.ids)!=null&&w.length)&&t.ids.length===n)return{ids:t.ids,types:t.types,category:t.category,color:e.renderColor??t.color,animation:e.animate??t.animation,animationColor:e.animateColor??e.renderColor??t.animationColor,hidden:t.hidden,lon:e.lon,lat:e.lat,height:e.height??t.height,heading:e.heading??t.heading,speedH:e.speedH??t.speedH,speedV:e.speedV??t.speedV};const o=e.ids??Array.from({length:n},(p,d)=>`target_${d}`),i=e.height??new Float32Array(n),r=e.heading??new Float32Array(n),l=e.speedH??new Float32Array(n),s=e.speedV??new Float32Array(n),u=new Uint32Array(n),a=e.animate??new Uint8Array(n),h=new Uint32Array(n),F=new Uint8Array(n),y=typeof e.types=="string"?Array.from({length:n},()=>e.types):((v=e.types)==null?void 0:v.slice(0,n))??Array.from({length:n},()=>"default"),S=new Uint8Array(n);for(let p=0;p<n;p++){const d=Z(y[p]);S[p]=d.category,u[p]=((C=e.renderColor)==null?void 0:C[p])??d.defaultColor,h[p]=((A=e.animateColor)==null?void 0:A[p])??u[p]}return{ids:o.slice(),types:y,category:S,color:u,animation:a,animationColor:h,hidden:F,lon:e.lon,lat:e.lat,height:i,heading:r,speedH:l,speedV:s}}function ye(e){var v;const t=e.length,n=new Array(t),o=new Array(t),i=new Uint8Array(t),r=new Uint32Array(t),l=new Uint8Array(t),s=new Uint32Array(t),u=new Uint8Array(t),a=new Float64Array(t),h=new Float64Array(t),F=new Float32Array(t),y=new Float32Array(t),S=new Float32Array(t),w=new Float32Array(t);for(let C=0;C<t;C++){const A=e[C];n[C]=A.id,o[C]=A.type;const p=Z(A.type);i[C]=p.category,r[C]=j(A.renderColor,p.defaultColor),l[C]=he(A.openAnimate,A.animateType),s[C]=j(A.animateColor??A.renderColor,r[C]),u[C]=(v=A.extra)!=null&&v.hidden?1:0,a[C]=A.lon,h[C]=A.lat,F[C]=A.height,y[C]=A.heading,S[C]=A.speedH,w[C]=A.speedV}return{ids:n,types:o,category:i,color:r,animation:l,animationColor:s,hidden:u,lon:a,lat:h,height:F,heading:y,speedH:S,speedV:w}}function qe(e,t,n){const o=new Map,i=new Set(n);for(let a=0;a<e.ids.length;a++)o.set(e.ids[a],a);let r=0,l=0;const s=[];for(let a=0;a<e.ids.length;a++)i.has(e.ids[a])||s.push({id:e.ids[a],type:e.types[a],lon:e.lon[a],lat:e.lat[a],height:e.height[a],speedH:e.speedH[a],speedV:e.speedV[a],heading:e.heading[a],renderColor:ee(e.color[a]),openAnimate:e.animation[a]===1,animateType:e.animation[a]===1?"glow":void 0,animateColor:ee(e.animationColor[a]),extra:e.hidden[a]?{hidden:!0}:void 0});const u=new Map;for(let a=0;a<s.length;a++)u.set(s[a].id,a);for(const a of t){const h=u.get(a.id);h==null?(s.push(a),r++):(s[h]=a,l+=o.has(a.id)?1:0)}return{store:ye(s),added:r,updated:l,removed:Math.min(n.length,e.ids.length)}}function Ye(e,t,n,o){if(e.ids.length===0)return[];const i=new Array(Math.max(1,o));return i[0]=Q(e),i}function $e(){const e=L.slice(0,Math.max(1,m.maxLevels)),t=L[0];return t&&t.count.length===c.ids.length?(e[0]={ids:c.ids,lon:c.lon,lat:c.lat,height:c.height,heading:c.heading,speedH:c.speedH,speedV:c.speedV,count:t.count,category:c.category,color:c.color,animation:c.animation,animationColor:c.animationColor,hidden:c.hidden,cellDeg:0},e):(e[0]=Q(c),e)}function Q(e){const t=e.ids.length,n=new Uint32Array(t);return n.fill(1),{ids:e.ids,lon:e.lon,lat:e.lat,height:e.height,heading:e.heading,speedH:e.speedH,speedV:e.speedV,count:n,category:e.category,color:e.color,animation:e.animation,animationColor:e.animationColor,hidden:e.hidden,cellDeg:0}}function Je(e,t,n){const o=new Map,i=Math.max(1,Math.ceil(360/n)),r=Math.max(1,Math.ceil(180/n)),l=e.ids.length;for(let M=0;M<l;M++){if(e.hidden[M])continue;const f=e.category[M],I=T(Math.floor((e.lon[M]+180)/n),0,i-1),z=T(Math.floor((e.lat[M]+90)/n),0,r-1),q=(f*r+z)*i+I;let H=o.get(q);H||(H={x:I,y:z,lonSum:0,latSum:0,heightSum:0,headingSum:0,speedHSum:0,speedVSum:0,colorRSum:0,colorGSum:0,colorBSum:0,animationColorRSum:0,animationColorGSum:0,animationColorBSum:0,count:0,category:f,animation:0},o.set(q,H)),H.lonSum+=e.lon[M],H.latSum+=e.lat[M],H.heightSum+=e.height[M],H.headingSum+=e.heading[M],H.speedHSum+=e.speedH[M],H.speedVSum+=e.speedV[M],H.colorRSum+=e.color[M]>>16&255,H.colorGSum+=e.color[M]>>8&255,H.colorBSum+=e.color[M]&255,H.animationColorRSum+=e.animationColor[M]>>16&255,H.animationColorGSum+=e.animationColor[M]>>8&255,H.animationColorBSum+=e.animationColor[M]&255,H.animation=H.animation||e.animation[M],H.count++}const s=o.size,u=new Array(s),a=new Float64Array(s),h=new Float64Array(s),F=new Float32Array(s),y=new Float32Array(s),S=new Float32Array(s),w=new Float32Array(s),v=new Uint32Array(s),C=new Uint8Array(s),A=new Uint32Array(s),p=new Uint8Array(s),d=new Uint32Array(s),P=new Uint8Array(s);let g=0;for(const[M,f]of o)u[g]=`cluster:${n}:${M}`,a[g]=f.count>1?-180+(f.x+.5)*n:f.lonSum,h[g]=f.count>1?-90+(f.y+.5)*n:f.latSum,F[g]=f.heightSum/f.count,y[g]=f.headingSum/f.count,S[g]=f.speedHSum/f.count,w[g]=f.speedVSum/f.count,v[g]=f.count,C[g]=f.category,A[g]=xe(Math.round(f.colorRSum/f.count),Math.round(f.colorGSum/f.count),Math.round(f.colorBSum/f.count)),p[g]=f.animation,d[g]=xe(Math.round(f.animationColorRSum/f.count),Math.round(f.animationColorGSum/f.count),Math.round(f.animationColorBSum/f.count)),g++;return{ids:u,lon:a,lat:h,height:F,heading:y,speedH:S,speedV:w,count:v,category:C,color:A,animation:p,animationColor:d,hidden:P,cellDeg:n}}function Qe(e,t,n,o,i,r){const l=b(),s=c.ids.length;if(s===0||L.length===0){const P=b()-l;ve(e,P);return}if(i||n>=m.globalViewportHeight){Xe(e,o,l);return}const u=dt(t,m.viewportBufferRatio,m.viewportBufferMinMeters),a=Fe(n,m.maxLevels),h=Math.max(0,a-1),F=Math.max(1,Math.floor(o*m.innerRatio)),y=Math.max(0,o-F),S=Ue(t,r),w=Ue(u,r),v=Ve(n),C=Le(a),A=Le(h),p=Ze(C.level,A.level,u,F,y,o,n,r),d=Se(e,p,v,{total:s,level:a,outsideLevel:h,rawInView:S,rawInBuffer:w,rendered:p.lon.length,clusters:p.clusterCount,lastMergeRadius:ce(n,L.length),pointCount:v===0?p.lon.length:0,lowModelCount:v===1?p.lon.length:0,highModelCount:v===2?p.lon.length:0,plainPointCount:v===0?p.plainCount:0,mergedPointCount:v===0?p.clusterCount:0,mergedLowModelCount:v===1?p.clusterCount:0,mergedHighModelCount:v===2?p.clusterCount:0,forcedPointMode:!1});d.computeMs=b()-l,V({type:"render-done",packet:d},[d.lon.buffer,d.lat.buffer,d.height.buffer,d.heading.buffer,d.speedH.buffer,d.size.buffer,d.count.buffer,d.flags.buffer,d.category.buffer,d.moveDurationMs.buffer,d.color.buffer,d.animation.buffer,d.animationColor.buffer])}function Xe(e,t,n){const o=we(t);if(!o){const l=b()-n;ve(e,l);return}const i=ot(o.selection),r=Se(e,i,o.lodKind,{total:c.ids.length,level:o.level,outsideLevel:o.outsideLevel,rawInView:c.ids.length,rawInBuffer:c.ids.length,rendered:i.lon.length,clusters:o.selection.clusterCount,lastMergeRadius:o.lastMergeRadius,pointCount:i.lon.length,lowModelCount:0,highModelCount:0,plainPointCount:o.selection.plainCount,mergedPointCount:o.selection.clusterCount,mergedLowModelCount:0,mergedHighModelCount:0,forcedPointMode:!1});r.computeMs=b()-n,V({type:"render-done",packet:r},[r.lon.buffer,r.lat.buffer,r.height.buffer,r.heading.buffer,r.speedH.buffer,r.size.buffer,r.count.buffer,r.flags.buffer,r.category.buffer,r.moveDurationMs.buffer,r.color.buffer,r.animation.buffer,r.animationColor.buffer])}function Ze(e,t,n,o,i,r,l,s){const u=Ae(e,n,!0,o,s),a=Ae(t,n,!1,i,s),h=Math.min(r,u.length+a.length),F=new Float64Array(h),y=new Array(h),S=new Float64Array(h),w=new Float32Array(h),v=new Float32Array(h),C=new Float32Array(h),A=new Uint32Array(h),p=new Uint8Array(h),d=new Uint8Array(h),P=new Uint32Array(h),g=new Uint8Array(h),M=new Uint32Array(h),f=new Float32Array(h);let I=0,z=0,q=0;I=le(e,u,F,y,S,w,v,C,A,p,d,P,g,M,f,I,h,l,!0),I=le(t,a,F,y,S,w,v,C,A,p,d,P,g,M,f,I,h,l,!1);for(let H=0;H<I;H++)A[H]>1?z++:q++;return{ids:y,lon:F,lat:S,height:w,heading:v,speedH:C,count:A,flags:p,category:d,color:P,animation:g,animationColor:M,moveDurationMs:f,clusterCount:z,plainCount:q}}function et(){c.ids.length!==0&&we(m.maxVisible,!0)}function we(e,t=!1){if(c.ids.length===0||L.length===0)return null;const n=b(),o=m.globalCacheIntervalMs>0&&k!=null&&n-k.builtAt>=m.globalCacheIntervalMs;if(!t&&k&&!o)return k;const i=Ce(e);return k=i,Me(),i}function Me(){R!=null&&(clearTimeout(R),R=null),!(m.globalCacheIntervalMs<=0||c.ids.length===0)&&(R=setTimeout(()=>{if(R=null,c.ids.length===0)return;const e=b();k=Ce(m.maxVisible);const t=b()-e;Me(),V({type:"viewport-cache-ready",level:k.level,buildMs:t})},m.globalCacheIntervalMs))}function Ce(e){const t=Fe(m.globalViewportHeight,m.maxLevels),n=Math.max(0,t-1),o=tt(t),i=it(o.level,e);return{selection:nt(o.level,i,e,m.globalViewportHeight),level:t,outsideLevel:n,actualLevel:o.index,lodKind:Ve(m.globalViewportHeight),lastMergeRadius:ce(m.globalViewportHeight,L.length),builtAt:b(),generation:$}}function tt(e){const t=T(e,0,Math.max(0,m.maxLevels-1));return L[t]||(L[t]=t===0?Q(c):be(t)),{level:L[t],index:t,requested:t,scheduled:!1}}function nt(e,t,n,o){const i=Math.min(n,t.length),r=new Float64Array(i),l=new Array(i),s=new Float64Array(i),u=new Float32Array(i),a=new Float32Array(i),h=new Float32Array(i),F=new Uint32Array(i),y=new Uint8Array(i),S=new Uint8Array(i),w=new Uint32Array(i),v=new Uint8Array(i),C=new Uint32Array(i),A=new Float32Array(i),p=le(e,t,r,l,s,u,a,h,F,y,S,w,v,C,A,0,i,o,!1);let d=0,P=0;for(let g=0;g<p;g++)F[g]>1?d++:P++;return{ids:l,lon:r,lat:s,height:u,heading:a,speedH:h,count:F,flags:y,category:S,color:w,animation:v,animationColor:C,moveDurationMs:A,clusterCount:d,plainCount:P}}function ot(e){return{ids:e.ids.slice(),lon:e.lon.slice(),lat:e.lat.slice(),height:e.height.slice(),heading:e.heading.slice(),speedH:e.speedH.slice(),count:e.count.slice(),flags:e.flags.slice(),category:e.category.slice(),color:e.color.slice(),animation:e.animation.slice(),animationColor:e.animationColor.slice(),moveDurationMs:e.moveDurationMs.slice(),clusterCount:e.clusterCount,plainCount:e.plainCount}}function it(e,t){if(t<=0)return[];const n=[];for(let r=0;r<e.lon.length;r++)e.hidden[r]||n.push(r);if(n.length<=t)return n;const o=[],i=n.length/t;for(let r=0;r<t;r++)o.push(n[Math.floor(r*i)]);return o}function le(e,t,n,o,i,r,l,s,u,a,h,F,y,S,w,v,C,A,p){let d=v;const P=b();for(const g of t){if(d>=C)break;const M=oe[e.category[g]];o[d]=e.ids[g];const f=p&&mt(M,e.count[g],A),I=f?Te(pt(e,g,M),P):{lon:e.lon[g],lat:e.lat[g],height:e.height[g],heading:e.heading[g]};n[d]=I.lon,i[d]=I.lat,r[d]=I.height,l[d]=I.heading,s[d]=e.speedH[g],u[d]=e.count[g],a[d]=e.count[g]>1?1:0,p&&(a[d]|=64),f&&(a[d]|=32);const z=e.count[g]===1&&_.has(e.ids[g]);(f||M!=null&&M.smoothMove&&(A<=M.smoothMoveMinCameraHeight||z))&&(a[d]|=16),h[d]=e.category[g],F[d]=e.color[g],y[d]=e.animation[g],S[d]=e.animationColor[g],w[d]=f?80:(M==null?void 0:M.smoothMoveDurationMs)??400,d++}return d}function Ae(e,t,n,o,i){if(o<=0)return[];const r=[],l=e.lon.length;for(let a=0;a<l;a++){if(e.hidden[a]||i&&!Pe(e.lon[a],e.lat[a],i))continue;Ie(t,e.lon[a],e.lat[a])===n&&r.push(a)}if(r.length<=o)return r;const s=[],u=r.length/o;for(let a=0;a<o;a++)s.push(r[Math.floor(a*u)]);return s}function Se(e,t,n,o){const i=t.lon.length,r=new Float32Array(i);let l=0,s=0,u=0,a=0,h=0,F=0,y=0;for(let S=0;S<i;S++){r[S]=t.count[S]>1?8+Math.min(10,Math.log10(t.count[S])*4):5;const w=rt(n,t.category[S]);t.flags[S]|=w<<2;const v=t.count[S]>1;w===0?(l++,v?h++:a++):w===1?(s++,v&&F++):w===2&&(u++,v&&y++)}return o.pointCount=l,o.lowModelCount=s,o.highModelCount=u,o.plainPointCount=a,o.mergedPointCount=h,o.mergedLowModelCount=F,o.mergedHighModelCount=y,{requestId:e,computeMs:0,lon:t.lon,ids:t.ids,lat:t.lat,height:t.height,heading:t.heading,speedH:t.speedH,size:r,count:t.count,flags:t.flags,category:t.category,moveDurationMs:t.moveDurationMs,color:t.color,animation:t.animation,animationColor:t.animationColor,stats:o}}function rt(e,t){if(e!==1)return e;const n=oe[t];return n!=null&&n.forceHighModelInLowLod?2:1}function ve(e,t){const n={requestId:e,computeMs:t,lon:new Float64Array(0),ids:[],lat:new Float64Array(0),height:new Float32Array(0),heading:new Float32Array(0),speedH:new Float32Array(0),size:new Float32Array(0),count:new Uint32Array(0),flags:new Uint8Array(0),category:new Uint8Array(0),moveDurationMs:new Float32Array(0),color:new Uint32Array(0),animation:new Uint8Array(0),animationColor:new Uint32Array(0),stats:{total:c.ids.length,level:0,outsideLevel:0,rawInView:0,rawInBuffer:0,rendered:0,clusters:0,lastMergeRadius:0,pointCount:0,lowModelCount:0,highModelCount:0,plainPointCount:0,mergedPointCount:0,mergedLowModelCount:0,mergedHighModelCount:0,forcedPointMode:!1}};V({type:"render-done",packet:n})}function Fe(e,t){const n=ce(e,t);return T(Math.ceil(n/m.cameraMergeStepRadius),0,Math.max(0,t-1))}function ce(e,t){if(e<=m.cameraMergeBaseHeight)return 0;const n=e-m.cameraMergeBaseHeight;return T(Math.floor(n/m.cameraMergeStepHeight),0,Math.max(0,t-1))*m.cameraMergeStepRadius}function Le(e){const t=T(e,0,Math.max(0,m.maxLevels-1)),n=L[t];if(n)return{level:n,index:t,requested:t,scheduled:!1};st(t);const o=at(t);return{level:L[o]??Q(c),index:o,requested:t,scheduled:!0}}function at(e){for(let t=1;t<L.length;t++){const n=e-t;if(n>=0&&L[n])return n;const o=e+t;if(o<L.length&&L[o])return o}return 0}function st(e){const t=T(e,0,Math.max(0,m.maxLevels-1));t<=0||L[t]||c.ids.length===0||(K.add(t),lt(360))}function lt(e){B!=null&&(clearTimeout(B),B=null),He(e)}function He(e){if(B!=null||K.size===0)return;const t=$;B=setTimeout(()=>{B=null,ct(t)},e)}function ct(e){if(e!==$||K.size===0||c.ids.length===0)return;const t=Array.from(K).sort((n,o)=>o-n)[0];if(K.delete(t),!L[t]){const n=b();L[t]=be(t);const o=b()-n;V({type:"viewport-cache-ready",level:t,buildMs:o})}He(120)}function be(e){const t=e*m.cameraMergeStepRadius,n=Math.max(1e-6,t/111e3);return Je(c,Y,n)}function Ve(e){return e>m.pointAbove?0:e>m.animatedBelow?1:2}function Ue(e,t){let n=0;for(let o=0;o<c.lon.length;o++)c.hidden[o]||t&&!Pe(c.lon[o],c.lat[o],t)||Ie(e,c.lon[o],c.lat[o])&&n++;return n}function ut(e,t){if(e.length===0)return{west:0,east:1,south:0,north:1};let n=Number.POSITIVE_INFINITY,o=Number.NEGATIVE_INFINITY,i=Number.POSITIVE_INFINITY,r=Number.NEGATIVE_INFINITY;for(let l=0;l<e.length;l++)e[l]<n&&(n=e[l]),e[l]>o&&(o=e[l]),t[l]<i&&(i=t[l]),t[l]>r&&(r=t[l]);return{west:n,east:o,south:i,north:r}}function dt(e,t,n=0){let o=(e.east-e.west)*t,i=(e.north-e.south)*t;if(n>0){const r=(e.north+e.south)/2,l=Math.max(.01,Math.cos(r*Math.PI/180)),s=n/110540,u=n/(111320*l);i<s&&(i=s),o<u&&(o=u)}return{west:e.west-o,east:e.east+o,south:e.south-i,north:e.north+i}}function Ie(e,t,n){return t>=e.west&&t<=e.east&&n>=e.south&&n<=e.north}function ht(e,t,n){if(!(e==null||t==null||n==null||!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(n)||n<=0))return{lon:e,lat:t,distance2:n*n,cosLat:Math.cos(t*Math.PI/180)}}function Pe(e,t,n){const o=(e-n.lon)*111320*n.cosLat,i=(t-n.lat)*110540;return o*o+i*i<=n.distance2}function ue(e){for(const t of e)Be(t)}function ft(e,t){var s,u,a,h,F;const n=e.lon.length,o=e.ids??(t.ids.length===n?t.ids:void 0);if(!o)return;const i=typeof e.types=="string"?void 0:e.types,r=b(),l=se(t.ids);for(let y=0;y<n;y++){const S=o[y];if(!S)continue;const w=t.ids[y]===S?y:l.get(S)??-1,v=typeof e.types=="string"?e.types:(i==null?void 0:i[y])??(w>=0?t.types[w]:"default");Be({id:S,type:v,lon:e.lon[y],lat:e.lat[y],height:((s=e.height)==null?void 0:s[y])??(w>=0?t.height[w]:0),heading:((u=e.heading)==null?void 0:u[y])??(w>=0?t.heading[w]:0),speedH:((a=e.speedH)==null?void 0:a[y])??(w>=0?t.speedH[w]:0),speedV:((h=e.speedV)==null?void 0:h[y])??(w>=0?t.speedV[w]:0)},r)}(F=e.removes)!=null&&F.length&&X(e.removes)}function Be(e,t=b()){const n=D.get(e.id),o=n?Te(n,t):{lon:e.lon,lat:e.lat,height:e.height,heading:e.heading},i=e.type?Z(e.type):void 0;D.set(e.id,{id:e.id,updatedAtMs:t,baseLon:e.lon,baseLat:e.lat,baseHeight:e.height,speedH:e.speedH,speedV:e.speedV,heading:e.heading,renderStartLon:o.lon,renderStartLat:o.lat,renderStartHeight:o.height,renderStartHeading:o.heading,fitSeconds:Ne(i),predictSeconds:de(i)})}function X(e){for(const t of e)D.delete(t)}function gt(e){const t=new Set(e);for(const n of D.keys())t.has(n)||D.delete(n)}function mt(e,t,n){return!(e!=null&&e.predictMove)||t!==1?!1:n<=m.pointAbove&&n<=e.predictMinCameraHeight}function pt(e,t,n){const o=e.ids[t],i=D.get(o);if(i)return i;const r={id:o,updatedAtMs:b(),baseLon:e.lon[t],baseLat:e.lat[t],baseHeight:e.height[t],speedH:e.speedH[t],speedV:e.speedV[t],heading:e.heading[t],renderStartLon:e.lon[t],renderStartLat:e.lat[t],renderStartHeight:e.height[t],renderStartHeading:e.heading[t],fitSeconds:Ne(n),predictSeconds:de(n)};return D.set(o,r),r}function Te(e,t){const n=Math.max(0,(t-e.updatedAtMs)/1e3),o=Math.max(.001,e.fitSeconds);if(n<=o){const i=Ct(T(n/o,0,1)),r=Re(e,o);return{lon:fe(e.renderStartLon,r.lon,i),lat:fe(e.renderStartLat,r.lat,i),height:fe(e.renderStartHeight,r.height,i),heading:At(e.renderStartHeading,e.heading,i)}}return Re(e,n)}function Re(e,t){const n=Mt(e.baseLon,e.baseLat,e.heading,e.speedH,t);return{lon:n.lon,lat:n.lat,height:e.baseHeight+e.speedV*t,heading:e.heading}}function de(e){return Math.max(.001,(e==null?void 0:e.predictSeconds)??m.predictSeconds)}function Ne(e){return Math.max(.001,Math.min((e==null?void 0:e.predictFitSeconds)??m.predictFitSeconds,de(e)))}function Z(e){const t=ne.get(e);if(t)return t;const n=ne.get(yt(e));return n||{type:e,category:0,targetDomain:e.startsWith("air")||e.startsWith("uav")?"air":"surface",forceHighModelInLowLod:e.startsWith("air")||e.startsWith("uav"),iconPath:"/icons/target-ship-mask.png",defaultColor:wt(e),smoothMove:!1,smoothMoveMinCameraHeight:25e3,smoothMoveDurationMs:400,predictMove:!1,predictMinCameraHeight:25e3,predictSeconds:void 0,predictFitSeconds:void 0}}function yt(e){return e.startsWith("air")?"air":e.startsWith("uav")?"uav":e.startsWith("person")?"person":e.startsWith("car")?"car":"ship"}function wt(e){return e.startsWith("air")||e.startsWith("uav")?2282226:e.startsWith("person")?16768064:e.startsWith("car")?16751146:2483804}function j(e,t){if(!e)return t;const n=e.startsWith("#")?e.slice(1):e;if(n.length!==6&&n.length!==8)return t;const o=Number.parseInt(n.slice(0,6),16);return Number.isFinite(o)?o:t}function ee(e){return`#${(e&16777215).toString(16).padStart(6,"0")}`}function he(e,t){return e&&(!t||t==="glow"||t==="发光")?1:0}function xe(e,t,n){return(T(e,0,255)&255)<<16|(T(t,0,255)&255)<<8|T(n,0,255)&255}function Mt(e,t,n,o,i){const r=o*i,l=n*Math.PI/180,s=Math.cos(l)*r,u=Math.sin(l)*r,a=Math.max(1e-6,Math.cos(t*Math.PI/180));return{lon:e+u/(111e3*a),lat:t+s/111e3}}function fe(e,t,n){return e+(t-e)*n}function Ct(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function At(e,t,n){const o=((t-e)%360+540)%360-180;return e+o*n}function T(e,t,n){return Math.max(t,Math.min(n,e))}function b(){return typeof performance<"u"?performance.now():Date.now()}})();
|