device-position-data-and-rendering-primitives.md 59 KB

调研 —— 点位下设备的位置数据来源与三维呈现原语

票: 01 — 调研 —— 点位下设备的位置数据来源与三维呈现原语 日期: 2026-09-18 范围: 只查事实(能做什么、数据在哪),不给建议、不做设计。

方法与只读声明: 后端 E:\pipe-ner-server 全程只读阅读,未修改任何后端文件;按 AGENTS.md 未运行任何 build / dev / serve / 浏览器调试 / 截图命令。前端与 node_modules/cesium 亦只读。地图 map.md 的 ## Notes 现状事实 1–8 未重复推导,只做独立复核与补充。

路径简写(全文沿用):

  • SYS/ = E:\pipe-ner-server\pipe-network-service\zksy-system\src\main\java\com\zksy\
  • ADMIN/ = E:\pipe-ner-server\pipe-network-service\zksy-admin\src\main\java\com\zksy\
  • FE/ = E:\City-Lifeline-Cockpit-TvT\src\modules\cockpit\
  • CES/ = E:\City-Lifeline-Cockpit-TvT\node_modules\cesium\Source\Cesium.d.ts
  • ENG/ = E:\City-Lifeline-Cockpit-TvT\node_modules\@cesium\engine\Source\

Bottom line

  1. 不能一次拿到。 后端 20 个 /api/cockpit/v1/ 端点里,没有任何一个能在「某监测点下所有设备」这一粒度上同时给出经纬度 + 坐标校验 + 状态。缺口不在响应层包装,而在数据模型:设备一旦按点位归集,坐标就在 CockpitMonitoringPointDeviceSnapshot 上被丢掉了。唯一带完整设备坐标的来源(/equipments/{deviceCode}/archive)是逐台取。
  2. deviceCodes 已交付且可用,但前端零消费。 它只在 map-snapshot 的 monitoringPoints[].deviceCodes 回传,含离线设备、不受分页影响、无状态过滤,空值是 [] 而非 null。前端 FE/ 乃至整个 src/ 里 deviceCodes 零引用。
  3. 点位与设备各有一份独立的经纬度,且后端没有任何相对位置信息。 equipment_point_rel 是纯关联表,无偏移/高度/朝向/井杆字段;全后端模块 grep 相关词 0 命中。「设备坐标是否等于点位坐标」是数据问题,schema 不保证。
  4. Cesium 1.145.0 上三者都能被单独拾取,只有 PointPrimitive 不能贴地。 BillboardCollection / PointPrimitiveCollection / LabelCollection 三者 GPU 侧都是批渲染(draw call 不随点数线性增长);Entity 的额外代价在 JS 对象与每帧 Visualizer update。Entity 的 point.heightReference 是内部拿 billboard 冒充实现的。
  5. 仓库内没有任何设备级 glTF/GLB 模型资产。 45 个 3D 文件全是建筑/车辆/船只/卫星/人体/道具/倾斜摄影瓦片;唯一「设备形状」的 glTF 只以 oss.icegl.cn 远程 URL 存在于插件 demo 代码里。设备点位目前完全依赖 8 个几何 SVG 形状 + tokens.css 色键烘焙成 billboard 图片。SHAPES 里没有任何设备形状。

1. 数据来源:现有一张图相关端点里,哪一个能一次给出「某监测点下所有设备的经纬度 + 坐标校验 + 状态」

1.1 端点全景(20 个只读端点)

ADMIN/web/controller/cockpit/ 下 grep @GetMapping 共 29 处、覆盖 20 个不同路径(部分控制器多端点)。契约手册的端点清单在 docs/cockpit/README.md:27-61,其中「一张图与详情」一节在 :50-61。

与设备坐标相关的候选只有 5 个(其余是概览/基础设施/预警处置,不含设备级坐标):

端点 控制器位置 能否一次给出「点位下所有设备的经纬度 + 校验 + 状态」
GET /api/cockpit/v1/monitoring-points/{code} ADMIN/web/controller/cockpit/CockpitMonitoringPointDetailController.java:37 不能 —— 有设备清单 + 状态,无任何经纬度
GET /api/cockpit/v1/map-snapshot ADMIN/.../CockpitMapSnapshotController.java:33 不能 —— 点位有坐标 + deviceCodes,但没有设备明细;带坐标的只有未关联设备
GET /api/cockpit/v1/monitoring/distribution ADMIN/.../CockpitMonitoringDistributionController.java:33 不能 —— 点位 marker 无设备清单,设备 marker 只含未关联设备
GET /api/cockpit/v1/monitoring/situation ADMIN/.../CockpitMonitoringSituationController.java:32 不能 —— abnormalEquipment[] 有设备坐标 + 校验 + 状态,但只含异常设备、且无 monitoringPointCode
GET /api/cockpit/v1/equipments/{deviceCode}/archive ADMIN/.../CockpitEquipmentArchiveController.java:37 能拿到,但逐台一次(N+1)

1.2 逐端点源码核对

① /monitoring-points/{monitoringPointCode} —— 缺经纬度,缺口在数据模型

  • 响应类 SYS/cockpit/point/CockpitMonitoringPointDetailResponse.java:7-19,devices 字段声明在 :17,类型 List<CockpitMonitoringPointDeviceCard>。
  • 卡片类 SYS/cockpit/point/CockpitMonitoringPointDeviceCard.java:5-16,共 11 个字段:deviceCode :6、deviceName :7、deviceTypeName :8、online :9、alarm :10、warning :11、workOrderInProgress :12、statusReasons :13、statusTime :14、deviceStatus :15、statusLabel :16。无 longitude / latitude / coordinateValidation。
  • 卡片不是直接从数据源构造的:SYS/cockpit/point/CockpitMonitoringPointStatusResolver.java:34-48 的 cards() 从 CockpitMonitoringPointDeviceSnapshot 逐个映射,而快照类 SYS/cockpit/point/CockpitMonitoringPointDeviceSnapshot.java:6-14 同样只有那 9 个字段(无坐标)。→ 坐标在更上游的模型里就已经不存在了,不只是响应层少写了一个 getter。
  • 点级坐标校验是有的:CockpitMonitoringPointDetailResponse.java:18 的 coordinateValidation,由 SYS/cockpit/point/CockpitMonitoringPointDetailService.java:43-44 调 CockpitCoordinateValidator.validate(detail.getLongitude(), detail.getLatitude(), detail.isCoordinateVerified()) 得到;输入来自 SYS/cockpit/point/CockpitMonitoringPointDetail.java:9-11 的 longitude / latitude / coordinateVerified。校验器在 SYS/cockpit/foundation/CockpitCoordinateValidator.java:11-32(产出 state / reason / renderable / crs,见 SYS/cockpit/foundation/CockpitCoordinateValidation.java:3-19)。
  • 注意一个易踩的点:该响应不返回监测点自身的 longitude / latitude(CockpitMonitoringPointDetailResponse.java:8-19 无这两个字段),只回校验结论。点位坐标得从 map-snapshot 的点位摘要拿。
  • 演示页佐证:docs/cockpit/monitoring-point-detail-demo.html:89-92 的 device() 工厂只造 deviceCode/deviceName/deviceTypeName/deviceStatus/statusLabel/statusReasons/statusTime,:111 的 coordinateValidation 是点级的一个对象,不在任何 device 里。
  • 适配器只有空实现:SYS/cockpit/point/EmptyCockpitMonitoringPointDetailAdapter.java:8-10 的 load() 直接 return null;接口 SYS/cockpit/point/CockpitMonitoringPointDetailAdapter.java:3-4。全后端 grep CockpitMonitoringPointDetailAdapter 只命中接口 + 空实现 + 两个测试的 mock → 真实数据适配器尚未落地(与 docs/cockpit/README.md:152 把「数据库级联调」列为未执行项一致)。

② /map-snapshot —— 点位有坐标、有 deviceCodes,但没有设备明细

  • 服务 SYS/cockpit/map/CockpitMapSnapshotService.java:75-102:monitoringPoints(...) + unassociatedEquipment(...) 两条列表,total = points.size() + equipment.size()(:79),分页窗口横跨这一条总序列(:80-87)。
  • 点位摘要 SYS/cockpit/map/CockpitMonitoringPointSummary.java:10-21 有 longitude :19、latitude :20、coordinateValidation :21,但没有 devices[]。
  • 未关联设备摘要 SYS/cockpit/map/CockpitUnassociatedEquipmentSummary.java:8-16 有 longitude :14、latitude :15、coordinateValidation :16;但它只含未关联设备 —— CockpitMapSnapshotService.java:278-288 用 associatedDeviceCodes(snapshot)(:291-302,取整份快照里所有点位下的 deviceCode)把已关联设备排除掉。
  • 响应类 SYS/cockpit/map/CockpitMapSnapshotResponse.java:15-18:monitoringPoints / unassociatedEquipment / coordinateValidation / mapFilter。
  • 演示页 docs/cockpit/map-snapshot-demo.html:64-70 与之一致:点位对象只有 pointCode/pointName/region/industry/typeCode/typeName/pointStatus/statusReasons/deviceCodes/longitude/latitude/coordinateValidation,没有设备明细。

③ /monitoring/distribution —— 点位 marker 连设备清单都没有

  • MonitoringPointMarker 在 SYS/cockpit/monitoring/CockpitMonitoringDistributionResponse.java:274-311:pointCode/pointName/region/industry/deviceType/pointStatus/statusReasons/longitude/latitude/coordinateValidation —— 无 devices[]、无 deviceCodes。
  • 构造处 SYS/cockpit/monitoring/CockpitMonitoringDistributionService.java:293-306;点位校验在 :269-274,不可渲染则计数并 continue(:271-274,invalidCoordinateCount++)。
  • UnassociatedEquipmentMarker 在 CockpitMonitoringDistributionResponse.java:313-350,有 longitude :321 / latitude :322 / coordinateValidation :323;构造处 CockpitMonitoringDistributionService.java:359-373,校验在 :333-338。
  • 已关联设备的坐标在快照模型里就没有:SYS/cockpit/monitoring/CockpitMonitoringDistributionSnapshot.java:107 的 SourceMonitoringPoint.devices 类型是 List<CockpitMonitoringPointDeviceSnapshot>(又是那个无坐标的类),而 SourceSmartDevice(同文件 :161-256)才有 longitude :166 / latitude :167 / coordinateVerified :168 以及 online/alarm/warning/workOrderInProgress/statusReasons/statusTime。
  • 类注释自己写明了这个契约取向:CockpitMonitoringDistributionSnapshot.java:14-15 —— "Associated devices are described through their monitoring point; the point's typeCode is the shared smart-device type of its devices."

④ /monitoring/situation —— 有设备坐标,但只含异常设备、且无点位归属

  • SYS/cockpit/monitoring/CockpitMonitoringSituationResponse.java:183-232 的 AbnormalEquipment:deviceCode :184、deviceName :185、deviceType :186、industry :187、region :188、abnormalType :189、status :190、statusReasons :191、longitude :192、latitude :193、coordinateValidation :194。
  • 源模型 SYS/cockpit/monitoring/CockpitMonitoringSituationSnapshot.java:93-167 的 SourceMetricDevice 也有 longitude :101 / latitude :102 / coordinateVerified :103。
  • 但列表名就是 abnormalEquipment(响应 :21),且没有任何 monitoringPointCode 字段 → 无法按监测点归集。

⑤ /equipments/{deviceCode}/archive —— 有坐标,逐台

  • SYS/cockpit/equipment/CockpitEquipmentArchive.java:26-27 的 getLongitude() / getLatitude() 委托给快照 SYS/cockpit/equipment/CockpitEquipmentArchiveSnapshot.java:16-18(longitude / latitude / equipmentLocation)。
  • 演示页 docs/cockpit/equipment-archive-demo.html 同端点的样例载荷一致。

1.3 后端领域模型里设备到底有没有坐标

有,但是在「已关联」这条路上被丢掉了。

有坐标的三处快照源模型:

  • SYS/cockpit/map/CockpitMapSnapshot.java:100-127 的 SourceSmartDevice:longitude :105、latitude :106、coordinateVerified :107。
  • SYS/cockpit/monitoring/CockpitMonitoringDistributionSnapshot.java:161-256 的 SourceSmartDevice:除坐标外还带状态(online :169、alarm :170、warning :171、workOrderInProgress :172、statusReasons :173、statusTime :174)。
  • SYS/cockpit/monitoring/CockpitMonitoringSituationSnapshot.java:93-167 的 SourceMetricDevice:坐标 + 指标状态。

点位侧的快照源模型也都有坐标:

  • SYS/cockpit/map/CockpitMapSnapshot.java:67-98 的 SourceMonitoringPoint:longitude :72、latitude :73、coordinateVerified :74、devices :75。
  • CockpitMonitoringDistributionSnapshot.java:99-159 的 SourceMonitoringPoint:longitude :104、latitude :105、coordinateVerified :106、devices :107。

结构性问题(本票最核心的发现):两条 SourceMonitoringPoint.devices(CockpitMapSnapshot.java:75、CockpitMonitoringDistributionSnapshot.java:107)的类型都是 List<CockpitMonitoringPointDeviceSnapshot> —— 正是那个没有经纬度的类。也就是说:设备坐标挂在 SourceSmartDevice 上;一旦设备按点位归集,就换成 CockpitMonitoringPointDeviceSnapshot,坐标在这一步被丢弃。响应层的 CockpitMonitoringPointDeviceCard 只是继承了这个缺失。

数据库层:SYS/base/domain/EquipmentBase.java:65-72 有 longitude :66 / latitude :69 / equipmentLocation :72(equipment_base 表)。

1.4 若不能,缺哪个字段、挂在哪个响应类上、要动的类名与端点

缺的字段: 设备级 longitude / latitude / coordinateValidation(或等价的 coordinateVerified 布尔)。

要动的类(按依赖顺序,均为事实陈述的「改动面」,不是设计建议):

  1. SYS/cockpit/point/CockpitMonitoringPointDeviceSnapshot.java —— 加 longitude / latitude / coordinateVerified。
  2. SYS/cockpit/point/CockpitMonitoringPointDeviceCard.java —— 加 longitude / latitude / coordinateValidation。
  3. SYS/cockpit/point/CockpitMonitoringPointStatusResolver.java:34-48 的 cards() —— 透传新字段(它是唯一的卡片构造点)。
  4. 若同时要覆盖 /map-snapshot 与 /monitoring/distribution:两处 SourceMonitoringPoint.devices 的填充方(适配器实现)也要给坐标;另有两处显式 new CockpitMonitoringPointDeviceSnapshot(...) 需要补参数 —— SYS/cockpit/monitoring/CockpitMonitoringDistributionService.java:343-352 与 :469-473(effectiveDevice)。
  5. 校验语义可直接复用 SYS/cockpit/foundation/CockpitCoordinateValidator.java:11-32(与点位级同一套 state/reason/renderable/crs 词表)。

端点:不需要新增。 /monitoring-points/{monitoringPointCode} 已经回 devices[](CockpitMonitoringPointDetailController.java:37),补上字段后即为「一次拿全」。这正是票 09 预期要去推的那张后端工单。

另外两个可用的补充信道(都不能替代坐标):

  • 设备编码 → 点位编码的映射已交付:WebSocket cockpitDeviceStatusDelta 帧带 monitoringPointCode(ADMIN/web/websocket/DeviceStatusRedisListener.java:127、:187-194,:209 的 monitoringPointCode(deviceCode) 实现;口径见 docs/cockpit/README.md:109 的 equipment_base.equipment_code → equipment_point_rel → monitor_point.point_code)。帧里只有状态,没有坐标。 DEVICE_CODE/device_code 别名见 docs/cockpit/README.md:10。
  • mapFilter.deviceCodes 在 map-snapshot / distribution / situation / events/* / overview/monitoring 等端点回显(docs/cockpit/README.md:69),是编码集合,不含坐标。

1.5 后端交付状态与既有说明(Gogs 侧)

  • 前端仓 .scratch/cockpit-shell-panel-layout/handoff/backend-contract-gaps.md:14-15 记录 BT-1 = Gogs #59、BT-2 = Gogs #60,均已于 2026-09-18 交付并关闭;:64-87 是 BT-1 的完整工单草案(其验收标准 :82 写明「同一 snapshotAt 下与 /monitoring-points/{code} 的 devices[] 一致」)。
  • #59 交付的是 deviceCodes(编码清单),不是坐标 —— 证据是后端 CockpitMonitoringPointSummary.java:14 只有 List<String> deviceCodes,:76-79 的 MapFilter 亦然。故票 09 第 2 条设想的「是不是 #59 已经带了坐标而前端适配层漏读」可以排除:前端没有漏读,后端确实没给。
  • 后端 docs/cockpit/ 下 26 个 demo 页 + README.md 已通读相关部分;没有任何一页展示设备级坐标(monitoring-point-detail-demo.html:89-92、map-snapshot-demo.html:64-70、equipment-archive-demo.html 为准)。
  • 前端仓 grep Gogs|gogs → 0 命中(Gogs 链接只存在于前端仓的 .scratch/ 手写文档里,见上)。

2. deviceCodes 的语义

字段位置: SYS/cockpit/map/CockpitMonitoringPointSummary.java:14(private final List<String> deviceCodes),getter :45。构造参数 :24。

回传端点:只有一个。 CockpitMonitoringPointSummary 只被 SYS/cockpit/map/CockpitMapSnapshotResponse.java:15 序列化 → 只有 GET /api/cockpit/v1/map-snapshot 的 monitoringPoints[].deviceCodes。

  • /monitoring/distribution 的 MonitoringPointMarker(CockpitMonitoringDistributionResponse.java:274-311)没有这个字段。
  • 同名但不同物:mapFilter.deviceCodes(CockpitMapSnapshotResponse.java:64、CockpitMonitoringDistributionResponse.java:232、CockpitMonitoringSituationResponse.java:250、CockpitCurrentEventsResponse.java:104、CockpitMultidimensionalAnalysisResponse.java:115、CockpitHistoryAnalysisResponse.java:154、CockpitHandlingEfficiencyResponse.java:134、CockpitMonitoringOverviewResponse.java:201)是另一层语义(生效筛选范围回显),不要与逐点 deviceCodes 混为一谈。

构造口径(唯一构造点): SYS/cockpit/map/CockpitMapSnapshotService.java:246-253

246: List<String> deviceCodes = devices.stream()
247:         .filter(Objects::nonNull)
248:         .map(CockpitMonitoringPointDeviceSnapshot::getDeviceCode)
249:         .filter(Objects::nonNull)
250:         .map(String::trim)
251:         .filter(code -> !code.isEmpty())
252:         .distinct()
253:         .collect(Collectors.toList());

逐条回答票面 4 问:

问题 结论 证据
在哪些端点回传 只有 GET /api/cockpit/v1/map-snapshot 的 monitoringPoints[].deviceCodes CockpitMapSnapshotResponse.java:15;CockpitMonitoringDistributionResponse.java:274-311 无此字段
是否含离线设备 含。 构造链上没有任何 online / status 过滤 CockpitMapSnapshotService.java:246-253(只滤 null/空/去重);设备来源是整份 source.getDevices()(:242-244)
是否受分页影响 不受。 deviceCodes 在生成 summary 时算完(:246-253 → 构造 :255-268),分页只切 summary 对象(:79-87)。点位被切到哪一页,它都带该点位全量的 deviceCodes :79-87、:246-268
是否受状态过滤影响 不受。 map-snapshot 连 pointStatus 入参都没有 入参签名 CockpitMapSnapshotService.java:53-54(page/pageSize/snapshotAt/industry/region/deviceType);docs/cockpit/README.md:52
受什么筛选影响 受 industry / region / deviceType 影响,且是通过点位受影响:monitoringPoints() 先过滤点位(:229-236),再对存活点位算 deviceCodes。点位被筛掉 → 它的 deviceCodes 一并消失 :229-236、:231-233(matchesFilters(industry, region, deviceType, source.getTypeCode(), source.getRegion(), types))
空值语义 空数组 [],不是 null。 source.getDevices() == null 时先退化成 Collections.emptyList() :242-244;演示页 docs/cockpit/map-snapshot-demo.html:64 与 :70 正是 deviceCodes:[]
顺序 源顺序去重,未排序。 用的是 Stream distinct()(保持首次出现顺序) :252

mapFilter.deviceCodes 的独立口径(易混,值得记一笔):

  • CockpitMapSnapshotService.java:321-346:由「存活点位的 deviceCodes ∪ unassociatedEquipment 的 deviceCode」组成,sorted(...) 升序(:344-345、:348-352);:56-57 的类注释与 docs/cockpit/README.md:69 都写「过滤后全量、分页前、升序」。
  • 一处口径细节:unassociatedEquipment 的排除集用的是 associatedDeviceCodes(snapshot),即未过滤的整份快照(:291-302);而 mapFilter 只遍历过滤后的 points + equipment(:327-343)。所以被 region/industry 筛掉的点位,其关联设备的编码既不在 points 里、也不在 equipment 里 → 该编码不出现在 mapFilter.deviceCodes。这是既有实现的客观行为,本票只记录。

前端消费现状:零。 FE/ 与整个 E:\City-Lifeline-Cockpit-TvT\src grep deviceCodes → 0 命中。FE/domain/mapSummary.js:26-44 的 toMonitoringPoint() 只读 longitude/latitude/coordinateValidation/pointCode/pointName/pointStatus/statusReasons,不读 deviceCodes;文件头 :4-7 列的后端形状注释里也没有它。→ 契约已交付但前端尚未接线(这正是票 05「设备点位图层的触发与生命周期」要处理的入口)。


3. 设备与点位的空间关系

3.1 两张表各有独立的经纬度列

表 实体类 坐标字段 位置描述 其它
monitor_point(关键监控点位表) SYS/base/domain/MonitorPoint.java,@TableName(value = "monitor_point", schema = "app_user") :23 longitude :46、latitude :49 location 位置描述 :43 pointId :28、pointCode :31、pointName :34、networkId 关联管网设施ID :37、pointType :40、installationDate :53、status :56、delFlag :77
equipment_base(设备基础信息表) SYS/base/domain/EquipmentBase.java,@TableName("equipment_base") :23 longitude :66、latitude :69 equipmentLocation 设备位置描述 :72 equipmentId :28、equipmentCode :31、equipmentName :34、equipmentTypeId :53、installTime :76、district :46、等

DDL 佐证:pipe-network-service/sql/iot_equipment_management.sql:63-83(monitor_point,longitude DECIMAL(15,10) :70、latitude :71、location VARCHAR(512) :69、network_id :67)。

3.2 关联表是纯关联,没有任何相对位置信息

SYS/base/domain/EquipmentPointRel.java:@TableName(value = "equipment_point_rel", schema = "app_user") :21,字段只有 relId :27、equipmentId :30、pointId :33、createTime :37、delFlag :41。

DDL:sql/iot_equipment_management.sql:88-97 —— rel_id / equipment_id / point_id / create_time,主键 + 两个索引,无其它列。

→ 没有「同井 / 同杆 / 相对点位 / 安装高度 / 朝向 / 方位」这类信息。

查证范围(明确「查不到」):

  • 后端 pipe-network-service/ 全模块 grep 同井|同杆|杆塔|relativePosition|offsetX|offsetY|mountHeight|installHeight|poleCode|wellCode → 0 命中。
  • pipe-network-service/sql/ 全目录 grep offset|height|azimuth|heading|elevation|altitude|relative → 0 命中。
  • 设备侧最接近「位置」的自由文本只有 EquipmentBase.equipmentLocation :72("设备位置描述")与 MonitorPoint.location :43("位置描述")——都是给人读的字符串,不是可计算的相对几何。

3.3 「设备的经纬度与所属监测点经纬度是否同一个值」

后端没有任何地方保证或校验它们相等,因此这是数据问题,不是契约问题:

  • 两张表各存一份独立的 longitude/latitude(§3.1),没有触发器、没有服务端校验、没有外键约束把二者绑定。
  • 后端只在一处显式声明了「关联设备的空间承载对象是它的监测点」这一契约取向:SYS/cockpit/monitoring/CockpitMonitoringDistributionSnapshot.java:14-15 的类注释 —— "Associated devices are described through their monitoring point."
  • 结构性推论(本票能给出的最确定的表述):只要走「已关联设备」这条路,响应里根本取不到设备自己的坐标(§1.3),所以「是否同一个值」在当前契约下无法从任一 cockpit 端点观测到。要回答它,必须先补齐 §1.4 的字段,或逐台调 /equipments/{deviceCode}/archive 与点位摘要做比对。
  • 前端纪律与此互相印证:CONTEXT.md 的 Avoid_ 禁止「人工坐标偏移 / 把聚合点当作真实监测点」(票 09 正文 :20 已引),即不许前端自己造设备位置。

4. 呈现原语(Cesium 1.145.0)

4.1 版本与证据位置

  • 实装版本 1.145.0:E:\City-Lifeline-Cockpit-TvT\node_modules\cesium\package.json 的 version 字段(另有 node_modules/cesium/package.json 里 @cesium/engine: ^26.3.0 的运行时实现包)。
  • 随包 d.ts 只有一个:CES/ = node_modules/cesium/Source/Cesium.d.ts(51,152 行)。node_modules/cesium/Build/Cesium/Cesium.d.ts 不存在(Test-Path = False),package.json 的 types 指向 ./Source/Cesium.d.ts。
  • 注意:随包 d.ts 是被删减过的,全文 grep @performance → 0 命中。原版 JSDoc 的 Performance: 段落只在官方 ref-doc 网页上。故本节的「开销」结论同时引用三处:
    1. 随包 d.ts(API 存在性与签名,行号可复现);
    2. 随包实现源码 ENG/ = node_modules/@cesium/engine/Source/(批渲染与拾取的实现机制,行号可复现);
    3. 官方 ref-doc(Performance: 段落的语义,给 URL)。
  • 官方文档 URL(版本对应 1.145 源码行):
    • https://cesium.com/learn/cesiumjs/ref-doc/Billboard.html
    • https://cesium.com/learn/cesiumjs/ref-doc/BillboardCollection.html
    • https://cesium.com/learn/cesiumjs/ref-doc/PointPrimitiveCollection.html
    • https://cesium.com/learn/cesiumjs/ref-doc/PointPrimitive.html
    • https://cesium.com/learn/cesiumjs/ref-doc/Label.html / LabelCollection.html
    • https://cesium.com/learn/cesiumjs/ref-doc/Scene.html#pick

4.2 三者的批渲染与单点开销(机制可证,数值无官方口径)

Entity + billboard/label graphics BillboardCollection PointPrimitiveCollection
声明位置 CES:21784 export class Entity {(文档 :21778-21783「aggregate multiple forms of visualization into a single high-level object」) CES:27414 export class BillboardCollection { CES:42804 export class PointPrimitiveCollection {
元素类 不是元素:Entity.billboard CES:21836、label :21864、point :21889、properties :21905、id :21801(注意 Entity.id 是 string) Billboard CES:27064 PointPrimitive CES:42667
GPU 侧实际渲染 同一批 collection:BillboardVisualizer(CES:19715-19721「maps Entity#billboard to a Billboard」)、LabelVisualizer(CES:23375-23382)、PointVisualizer(CES:24089-24095);DataSourceDisplay.defaultVisualizersCallback() CES:21295 默认挂上三者 整集合一次 instanced draw:ENG/Scene/BillboardCollection.js:2089 command.count = 6;、:2090 command.instanceCount = billboardsLength;;instanced ⇒ 恒 1 个 VA(ENG/Renderer/VertexArrayFacade.js:344) ENG/Scene/PointPrimitiveCollection.js:1154 command.primitiveType = PrimitiveType.POINTS;、:496 注释 // 1 vertex per pointPrimitive、不传 indexBuffer ⇒ 1 个 VA ⇒ 一次 draw
draw call 数(默认混合模式) 同上(取决于它落到哪个 collection) OPAQUE_AND_TRANSLUCENT 时 2 个命令:ENG/Scene/BillboardCollection.js:2063 const totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; 同理按 blend pass 分:ENG/Scene/PointPrimitiveCollection.js:1155-1156(Pass.OPAQUE / Pass.TRANSLUCENT)
官方 Performance: 口径 无 Entity 级条目 见下 见下
每元素更新开销 每 Entity 一套 JS graphics 对象 + 每帧 Visualizer update(BillboardVisualizer.update d.ts :19728、LabelVisualizer.update :23389、PointVisualizer.update :24102);标签还有字形生成/缓存(ENG/Scene/LabelCollection.js:96 createGlyphCanvas) 逐 billboard 写入 CPU→GPU traffic,且与改了几个属性无关;改动多时「清空重建」可能更省 add/remove 期望常数时间,但会整块重写顶点缓冲(O(n) + CPU→GPU 开销);大批量应先 add 完再 update
可调的性能杠杆 — blendOption(全不透明或全半透明可提升最多 2x)d.ts :27472;同标识符的图片自动共享纹理 d.ts :27390-27391 blendOption(同 2x)d.ts :42852;add 的官方建议见下
附带事实 DataSourceDisplay.defaultVisualizersCallback() CES:21295 默认挂上三个 Visualizer;EntityCluster CES:21984 负责懒建集合(clusterBillboards :22017 / clusterLabels :22021 / clusterPoints :22025) BillboardCollection 构造器需要 scene 才能贴地或对地球做深度测试(CES:27406) —

出处原文(官方 ref-doc,逐字要点):

  • Billboard 页 Performance: —— "Reading a property, e.g., Billboard#show, is constant time. Assigning to a property is constant time but results in CPU to GPU traffic when BillboardCollection#update is called. The per-billboard traffic is the same regardless of how many properties were updated. If most billboards in a collection need to be updated, it may be more efficient to clear the collection with BillboardCollection#removeAll and add new billboards instead of modifying each one."(https://cesium.com/learn/cesiumjs/ref-doc/Billboard.html)
  • PointPrimitiveCollection 页 Performance: —— "For best performance, prefer a few collections, each with many points, to many collections with only a few points each. Organize collections so that points with the same update frequency are in the same collection."(https://cesium.com/learn/cesiumjs/ref-doc/PointPrimitiveCollection.html)
  • PointPrimitiveCollection#add 页 Performance: —— "Calling add is expected constant time. However, the collection's vertex buffer is rewritten - an O(n) operation that also incurs CPU to GPU overhead. For best performance, add as many pointPrimitives as possible before calling update."
  • PointPrimitiveCollection#remove 页 —— 同理 O(n) 重写;官方明确「只想临时隐藏点时,用 PointPrimitive#show 比 remove + re-add 更省」。
  • LabelCollection 自己没有绘制路径,它建在两个 BillboardCollection 上:ENG/Scene/LabelCollection.js:630(_backgroundBillboardCollection)、:643(_glyphBillboardCollection)、:274(每个字形一个 billboard)、:1049-1050(两者各自 update(frameState))。→ 文本标签的 GPU 成本 ≈ 字形数 × billboard,但仍落在 billboard 批里(Label 的 GPU 侧没有独立通道)。

诚实标注(本票不编数): Cesium 官方文档与随包源码给出的都是复杂度与机制(1 次 draw / O(n) 重写 / 逐元素 traffic 恒定),没有「N 个点多少毫秒」这类数值,随包 d.ts 里连 @performance 段落都被删了。故本票不给量级数字;量的判断属于票 02(特效开销预算)与本图 08(原型)的走查。

4.3 能否单独拾取(不与监测点标记抢)

结论:三者都能逐元素拾取,且拾取互不干扰;但「谁被拾到」由遮挡关系与拾取矩形决定,必须显式处理重叠。

  • 每个元素都有自己的 id,这就是拾取时回给你的东西:
    • Billboard.id: any CES:27286,option 文档 CES:26994「A user-defined object to return when the billboard is picked with Scene#pick」
    • Label.id: any CES:38568(文档 :38566)
    • PointPrimitive.id: any CES:42755(文档 :42753「the user-defined value returned when the point is picked」)
    • Entity.id: string CES:21801(类型不同:Entity 路径下拾取回来的 id 是 Entity 本身,见下)
  • 一次 draw call 下仍能逐实例拾取:pick color 是逐元素的 → ENG/Scene/BillboardCollection.js:1096-1098(batchTable 或顶点属性)、ENG/Scene/PointPrimitiveCollection.js:556 + :568 AttributeCompression.encodeRGB8(pickColor) + :1169 command.pickId = "v_pickColor"。
  • 拾取在离屏 pick framebuffer 里做,结果进 Set 去重:ENG/Scene/PickFramebuffer.js:56-98(:61 const objects = new Set();)→ 互不干扰。
  • API 签名与语义:
    • Scene.pick(windowPosition, width?, height?): any | undefined CES:44947;文档 CES:44928-44930「contains the first (top) primitive in the scene at a particular window coordinate」;默认拾取矩形 3×3(CES:44943-44944)。
    • Scene.drillPick(windowPosition, limit?, width?, height?): any[] CES:45054;文档 CES:45042-45045「all primitives at a particular window coordinate ... ordered by their visual order in the scene (front to back)」。
    • Scene.pickAsync CES:44986;Scene.pickPosition CES:45040。
  • pickedObject 的形状固定为 { primitive, id, ... }:ENG/Renderer/Context.js:1679-1682 的官方示例即 context.createPickId({ primitive: this, id: this.id });渲染侧 ENG/Scene/PickFramebuffer.js:78 const object = context.getObjectByPickColor(pickColor);。
  • Entity 路径下 id 就是 Entity:ENG/DataSources/BillboardVisualizer.js:127-128(billboard = cluster.getBillboard(entity); billboard.id = entity;)、ENG/DataSources/LabelVisualizer.js:138、ENG/DataSources/PointVisualizer.js:148。

「抢不抢」的实际裁决点(仓库现状,这是票 06 要面对的既成事实):

FE/services/cesiumEngine.js:404-431 的 pickPointMarkerAt():

417: export function pickPointMarkerAt(viewer, windowX, windowY) {
418:     if (!viewer || !viewer.scene || !viewer.canvas || typeof viewer.scene.pick !== 'function') return null
420:     const rect = viewer.canvas.getBoundingClientRect()
421:     const picked = viewer.scene.pick(new Cartesian2(windowX - rect.left, windowY - rect.top))
422:     const entity = picked && picked.id
423:     const properties = entity && entity.properties
424:     if (!properties || !properties.pointCode) return null
  • 用的是 scene.pick(单个,最上层),没有传 width/height(走默认 3×3),没有用 drillPick(整个 src/ grep drillPick → 0 命中)。
  • 判据是 实体属性里有 pointCode(:424),注释 :408-409 明说「只认点位标记数据源上的实体 —— 拾到地形、影像、3D Tiles 或空白一律回 null」。
  • 因此:一个只有 deviceCode 属性、没有 pointCode 的设备实体,在现有这条链路上会被判为 null(不抢);反过来说,它也无法被现有链路拾到 —— 要支持设备拾取,就得扩充这条判据或另开一个入口(票 06 的范围)。
  • 重叠时的行为是确定的:scene.pick 只回最上层那一个(:44928-44930),同像素下的另一个拾不到;要区分就必须换 drillPick。这是「同一监测点下多台设备坐标重叠」时必须先裁决的事实(对应本图 ## Not yet specified 第 1 条)。
  • 一个相关开关:Scene#pickTranslucentDepth(ENG/d.ts 中 pickPosition 的文档 CES:45033「Set Scene#pickTranslucentDepth to true to include the depth of translucent primitives; otherwise, this essentially picks through translucent primitives」)。该文档挂在 pickPosition 上,不要外推到 pick;但半透明特效与拾取的关系需要票 02/06 专门处理。

仓库现状补充: src/ grep BillboardCollection|PointPrimitiveCollection|LabelCollection → 0 命中。点位目前只用 Entity + billboard graphic(FE/services/cesiumEngine.js:319-362 的 syncPointMarkers(),:335-357 的 source.entities.add({...});数据源常量 :267 POINT_MARKER_SOURCE,pointMarkerSource() :286-301)。DOM 手势层在 FE/services/pointPick.js(:12 CLICK_MAX_DRAG_PX = 4、:20 isClickGesture、:35 attachPointPick、:52 onPick),只做「位移 ≤ 4px 算点选」判定并输出窗口坐标,不 preventDefault、不抢 pointer capture。接线在 FE/services/mapService.js:380(attachPointPick(record.host, ({x,y}) => pickPointAt(x,y)))与 :796-806(:799 调 pickPointMarkerAt)。

4.4 能否贴地(heightReference)

支持矩阵(随包 d.ts,行号可复现):

目标 heightReference 证据
Entity.billboard → BillboardGraphics ✅ option CES:19517、属性 :19617、option 文档 :19494
Entity.label → LabelGraphics ✅ option :23196、属性 :23314
Entity.point → PointGraphics ✅ 但见下方陷阱 option :24003、属性 :24036
BillboardCollection 的 Billboard ✅ option CES:27027、属性 :27078、option 文档 :27002
LabelCollection 的 Label ✅ option :38343、属性 :38376
PointPrimitiveCollection 的 PointPrimitive ❌ 没有 PointPrimitive 全类 CES:42667-42755:show/position/scaleByDistance/translucencyByDistance/pixelSize/color/outlineColor/outlineWidth/distanceDisplayCondition :42746/disableDepthTestDistance :42751/id :42755/splitDirection —— 无 heightReference

HeightReference 枚举 CES:36770(文档 :36767-36769「Represents the position relative to the terrain.」):NONE = 0 :36774、CLAMP_TO_GROUND = 1 :36778、RELATIVE_TO_GROUND = 2 :36782、CLAMP_TO_TERRAIN = 3 :36786、RELATIVE_TO_TERRAIN = 4 :36790、CLAMP_TO_3D_TILE = 5 :36794。

实现侧交叉验证(ENG/ 源码里 grep heightReference 的命中数): Scene/PointPrimitive.js → 0、Scene/PointPrimitiveCollection.js → 0;而 Scene/Billboard.js → 19、Scene/BillboardCollection.js → 8、Scene/Label.js → 12、Scene/LabelCollection.js → 8。

关键陷阱(Entity 的 point.heightReference 是「借 billboard 冒充」): ENG/DataSources/PointVisualizer.js:117-130

119:     if (heightReference !== HeightReference.NONE && !defined(billboard)) {
120:       if (defined(pointPrimitive)) {
121:         returnPrimitive(item, entity, cluster);
122:         pointPrimitive = undefined;
123:       }
124:
125:       billboard = cluster.getBillboard(entity);
126:       billboard.id = entity;
127:       billboard.image = undefined;

:230 billboard.heightReference = heightReference;;:138-149 在 heightReference === NONE 时才回落到真正的 cluster.getPoint(entity)。

→ 想用 PointPrimitiveCollection 实现贴地是做不到的(原语本身没有这个属性);Entity 的 point 一旦要贴地,引擎内部就把它换成一个无图 billboard。这个结论在随包实现源码与 Build/CesiumUnminified/Cesium.js:197909-197927 中一致。

构造器要求(贴地/深度测试的前提): BillboardCollection 的 options.scene 文档 CES:27406「Must be passed in for billboards that use the height reference property or will be depth tested against the globe」;LabelCollection 同款 CES:38647。PointPrimitiveCollection 的构造器 CES:42805-42810 只有 modelMatrix / debugShowBoundingVolume / blendOption / show,没有 scene —— 与「不支持贴地」自洽。Entity 路径下这个 scene 由 ENG/DataSources/EntityCluster.js:661-665(new CollectionConstructor({ scene: this._scene }))保证。

贴地相关的抗裁剪参数: BillboardCollection.coarseDepthTestDistance CES:27491 / threePointDepthTestDistance :27503;LabelCollection.threePointDepthTestDistance CES:38737;文档说明 threePointDepthTestDistance 管的是「billboard/label 的 heightReference 为 CLAMP_TO_GROUND/CLAMP_TO_TERRAIN 时,在相机距离内改用三点深度测试,保证任一点可见即整块可见」(CES:27412-27413、:38653)。PointPrimitiveCollection 没有这两个参数。

4.5 disableDepthTestDistance 与 distanceDisplayCondition 的可用性

目标 distanceDisplayCondition disableDepthTestDistance
BillboardGraphics ✅ option CES:19528 / 属性 :19691 ✅ option :19529 / 属性 :19696
LabelGraphics ✅ option :23203 / 属性 :23355 ✅ option :23204 / 属性 :23360
PointGraphics ✅ option :24009 / 属性 :24065 ✅ option :24010 / 属性 :24070
Billboard ✅ option :27038 / 属性 :27275 ✅ option :27039 / 属性 :27282
Label ✅ option :38350 / 属性 :38558 ✅ option :38351 / 属性 :38564
PointPrimitive ✅ 属性 CES:42746 ✅ 属性 CES:42751
集合级 — BillboardCollection 的 coarseDepthTestDistance :27491 / threePointDepthTestDistance :27503

PointPrimitive 这两项可用但不在 d.ts 的 add() option 模板里(add(options?: any) CES:42883),实现里确实生效:ENG/Scene/PointPrimitive.js:49-53(disableDepthTestDistance 校验)、:60-103(distanceDisplayCondition 校验/克隆)、:398-440(两个 accessor)。

语义(Billboard 版,CES:27277-27281):「When set to undefined or 0, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied.」Label CES:38560-38563、PointPrimitive :42748-42751、BillboardGraphics :19693-19694 同义。集合级 coarseDepthTestDistance 的文档 CES:27482-27490 说明它只在 disableDepthTestDistance 允许深度测试时才生效。

仓库现状(既成事实,设备点位要与它一致或显式分歧): FE/services/cesiumEngine.js:335-357 的 billboard 同时写了

344:                 heightReference: HeightReference.CLAMP_TO_GROUND,
346:                 disableDepthTestDistance: Number.POSITIVE_INFINITY,

(:310-311 注释说明动机:有 DEM,否则点位浮在山体上方或埋进去。)device 点位若要视觉一致,这两个取值就是现成的对照基准。


5. 设备级资产清单

5.1 glTF / GLB 等 3D 模型资产:无设备级模型

全仓 glob **/*.{glb,gltf,fbx,obj,b3dm,i3dm,pnts,cmpt,3dtiles,ktx2,dae,drc} 命中 45 个文件,全部在 public/plugins/**(dist/plugins/** 是构建镜像,node_modules/draco3d/bunny.drc 是依赖自带)。src/、tools/、preview/、pluginMaker/、docs/、.scratch/ 零命中;src/assets/ 目录不存在(Test-Path = False)。

public/ 下命中(路径 | 字节)——均为建筑/交通/星球/人体/道具:

  • public/plugins/basic/htmls/model/model.gltf | 550544 —— 扩展名骗人:实为 GLB 二进制(头是 glTF magic + JSON chunk),含 KHR_draco_mesh_compression,generator Khronos glTF Blender I/O v4.0.44,节点名 Bottom;不是设备。
  • public/plugins/basic/materials/raytracedDiamond/diamond.glb | 9960
  • public/plugins/digitalCity/model/{coneAnchorB 18132, spiral-middle 1460, tornado 4256}、digitalCity/volumetricFluidFire/demo-scene.glb | 29448
  • public/plugins/digitalPark/innovationHubAr/model/InnovationHubAR.glb | 3331380;digitalPark/model/{arctic_tooltip 178284, laboratoryBuild 200616, vr_sculpture_park 155416}
  • public/plugins/earthSample/model/lowpolyPlanet/{airplane.gltf 21598, cloud.gltf 11376, planet.gltf 727753}
  • public/plugins/eCommerce/model/{box 52140, pack 81068}
  • public/plugins/floor/models/topoBase/baseModel{A,B,C,D,E,F,G,H,L}.glb | 4132 – 95928
  • public/plugins/geokit/case-real-3/model/{chuan.glb 1806572(船), weixing.glb 360988(卫星)}
  • public/plugins/geokit/tiles/level_*_tile_*.b3dm × 9 | 3333 – 190547
  • public/plugins/hunyuan3D/model/icegl-bm.glb | 305548
  • public/plugins/industry4/model/lambo.glb | 1230764(跑车)
  • public/plugins/medical/model/brainStorm/brain.glb | 408744
  • public/plugins/UIdemo/model/arrowWhite2.5d.glb | 155056
  • public/plugins/visualArts/biineBee/models/bee.glb | 1814280;visualArts/model/{LeePerrySmith.glb 404976(人头), room-transformed.glb 428436}
  • public/plugins/water/model/staticWater.glb | 58412

唯一「设备形状」的 glTF 只以远程 URL 存在于插件 demo 代码,仓库无本地副本:

  • src/plugins/zoneFreeScene/pages/freeRefiningIndustry.vue:90,100,102,104,106,110,112,114 —— gasTank.glb / oilHeater.glb / tower.glb / oilTank.glb / machine.glb / tank2.glb / gtUnit.glb / flaringDevice.glb,全部 https://oss.icegl.cn/p/modelServer/models/2低像素炼油/...
  • src/plugins/zoneFreeScene/pages/freeShipSea.vue:67,69,71,73,75,77 —— 灯塔B.glb / 货轮B.glb / 游轮A.glb / 风电机B.glb / 风电机A.glb / 浮标A.glb(同域名)

易误判项:src/plugins/industry4/common/device.ts + components/device.vue 名字里有 device,但读全文是 UnrealBloom / LED 发光着色器 demo,用的模型是 lambo.glb,不是设备资产。

5.2 3D Tiles / city massing:与设备模型完全两条线

仓库内 tileset json 只有 2 个:

  1. public/plugins/geokit/tiles/tileset.json(12913 B)—— 插件 demo 的倾斜摄影瓦片:asset.version 1.0、gltfUpAxis: "Z"、geometricError 500.0,配 9 个 b3dm。引用点:src/plugins/geokit/pages/case-3dtiles.vue:27、src/plugins/simpleGIS/components/tilesCom.vue:44、src/plugins/simpleGIS/components/obliquePhoto.vue:41。
  2. .scratch/cockpit-shell-panel-layout/verify/fixtures/tiles/tileset.json(739 B)—— 走查夹具,只有包围盒、无 content,自述见 :3-7。

驾驶舱加载链(现状参考):FE/domain/mapLayers.js:81-90(planTilesets())→ FE/services/cesiumEngine.js:20-21(import Cesium3DTileset / Cesium3DTileStyle)、:204-223(applyTilesets(),:214 await Cesium3DTileset.fromUrl(...))、:180-197(readTilesetDiagnostics())、:235-255(applyViewModeLayers() 用 Cesium3DTileStyle 调 alpha);入口注释 FE/services/mapService.js:110 与 :896。

票 34 的 city massing 资产本体不在仓库,是 repo 外生成物:生成器 tools/city-massing/build-town.mjs(:19-20 产物 <DATA_DIR>/{town.glb,tileset.json,meta.json};:25-27 GIS = 'D:/GIS/GIS'、DATA_DIR;:484 写 town.glb;:503-517 写 tileset.json);回验 tools/city-massing/verify-town.mjs(:29 读 town.glb、:149-150 读 tileset transform);说明 tools/city-massing/README.md:48-60。运行时靠 .env.local:11({"prefix":"/gis/city","dir":"D:/GIS/GIS/cityMassing"})与 public/cockpit/config.json:42-47 的 "tilesets":[{"url":"/gis/city/tileset.json"}] 挂载(两者都被 .gitignore 排除)。票据 .scratch/cockpit-shell-panel-layout/issues/34-lightweight-city-massing.md:23,46,73。

→ 仓库里 0 个 city massing 资产文件,且它是「示意建筑体块」,与设备级模型无关。

5.3 驾驶舱的 marker / legend 图标:运行时以内联 SVG 生成,不是 canvas 绘制

  • FE/domain/icons.js:12-57 —— 内联 SVG 字符串图标集;渲染在 FE/components/CockpitIcon.vue:8-18,:31 iconFor(props.name)。
  • FE/domain/pointStatus.js:42-59 —— SHAPES 内联 SVG 片段(12×12 viewBox,仅 currentColor);:217-223 markerSvg() 打成 data:image/svg+xml;charset=utf-8,...。
  • FE/services/cesiumEngine.js:335-347 —— 作为 billboard.image 挂到 Cesium 实体,:341-342 width: 18, height: 18。
  • FE/services/mapService.js:639-651 —— toneColors() 从 .cockpit-shell 的 getComputedStyle 读 --lv1..--lv4/--neutral(:647 styles.getPropertyValue('--'+tone);:631-637 有同值兜底色);:670-676 调 syncPointMarkers(..., (shape,color) => markerSvg(shape,color,18))。

修正一处常见假设: FE/ 下 grep OffscreenCanvas|drawImage|toDataURL|createElement('canvas')|devicePixelRatio 在绘制语义上 0 命中(canvas 的命中全指 Cesium 自己的 viewer.canvas)。所以没有离屏 canvas 光栅化;是纯 SVG data-URL,尺寸由 billboard.width/height = 18 像素决定,与 devicePixelRatio 无关。

5.4 静态图标文件(与设备无关)

  • public/logo.png | 33245、src/images/icon.png | 5586、public/plugins/simpleGIS/image/icon.png | 1440、public/cockpit/aerial.webp | 517078(FE/components/MapCanvas.vue:12 用作中央一张图的占位底图)。
  • 全仓唯一「地图点位图标」现成样例:public/plugins/geokit/case-real-3/icons/satellite.svg | 1124、ship.svg | 600(24×24,圆底 + 白色 glyph),被 src/plugins/geokit/pages/case-real-3.vue:51-53 的 <GeoIcon icon="…/satellite.svg" :size="16" :point="weiXingPosition" /> 使用。注意这是 three.js / Tres 的 @icegl/geokit 组件(实现只在 node_modules),不是 Cesium 链路,只可作视觉参照。
  • 无关 demo 资产:public/plugins/industry4/icon/c1..c9.webp(车漆皮肤按钮,src/plugins/industry4/components/su7/UIcarSkin.vue:12,25)、industry4/preview/deviceLight*.png(案例封面,src/plugins/industry4/config.js:22-23)、earthSample 地球贴图、operationTool/preview/*.png 中文预览图、各插件 preview/*.png 封面。
  • alarm / status / equip 命名的设备状态图标:查不到。(图片名匹配 icon|marker|device|point|equip|alarm|status|legend 与 点|标|图例,范围 public/、public/plugins/、src/。)
  • FE/ 递归搜索 *.png|*.svg|*.jpg|*.webp|*.gif|*.ico → 0 个图片文件。驾驶舱点位不依赖任何图片图标。

5.5 FE/domain/icons.js 的形状清单(全 67 行)

共 6 个图标,每项为 { viewBox, inner },inner 是内联 SVG 标记字符串,一律 currentColor(无任何色值),无 canvas、无外部图片文件、无 devicePixelRatio 逻辑:

键名 行 viewBox 内容
brand :14 0 0 32 32 同心环 + 十字轴线
layers :23 0 0 16 16 图层叠片
points :30 0 0 16 16 双圆环 + 实心点(通用几何标记,非设备形状)
event :37 0 0 16 16 三角 + 感叹号
resource :44 0 0 16 16 库房 / 处置力量
blank :51 0 0 48 32 空框 + 折线(面板占位)

导出:ICON_NAMES :59、isKnownIcon :61、iconFor :65。尺寸由调用方给:FE/components/CockpitIcon.vue:27 的 size 默认 '1em'(:11-12 绑到 width/height)。文件头 :4-7 自述「先用内联 SVG 字符串而不是图片文件……正式稿由设计公司交付」。

5.6 FE/domain/pointStatus.js 的 SHAPES(全 223 行)

状态词表 POINT_STATUS :27-34(6 个,原样后端字符串):ALARM :28、WARNING :29、WORK_ORDER :30、NORMAL :31、OFFLINE :32、EMPTY :33。

形状注册表 SHAPES :42-59,8 个键(verbatim):

形状名 行 定义
triangle :44 实心三角
diamond :46 实心菱形
square :48 实心方块(rx="1")
circle :50 实心圆
ring :52 空心圆环(stroke-width="2")
hollowSquare :54 空心方块
hollowDiamond :56 空心菱形
cross :58 十字

头部注释 :37-40:内联 SVG 片段、12×12 视框、只用 currentColor、不含任何颜色值;同一份片段被地图图例与地图标记共用。

状态 → 视觉映射 POINT_STATUS_VISUALS :78-85:

状态 tone shape label priority 行
ALARM lv1 triangle 报警 1 :79
WARNING lv2 diamond 预警 2 :80
WORK_ORDER lv3 square 工单处置 3 :81
NORMAL lv4 circle 正常 4 :82
OFFLINE neutral ring 离线 5 :83
EMPTY neutral hollowSquare 无设备 6 :84
(UNKNOWN_VISUAL) neutral hollowDiamond 状态未发布 7 :88
(UNASSOCIATED_VISUAL) neutral cross 无关联设备 8 :91

其它:DEFAULT_SHAPE = 'circle' :61、isKnownShape :63、shapeSvg :68、visualForStatus :94、pointVisual :105、planPointMarkers :121、summarizeMarkerStatuses :147、legendVisuals :157、pointLegend :172、legendMatchesMarkers :184、markerSvg :217。planPointMarkers() 的 marker 对象带 longitude :138 / latitude :139(:101-102 注释解释「这里不读坐标,能不能上图是 domain/coordinate.js 的事」)。

是否设备形状?——否。 8 个全部是通用几何标记(实心/空心 的圆-环-方-菱形 + 三角 + 十字),没有 box / camera / valve / pump / 设备箱 之类的设备 glyph。连「无设备」(EMPTY)用的也是几何形状 hollowSquare(:84)。

5.7 FE/styles/

目录里只有 1 个文件 FE/styles/tokens.css(2870 B,71 行)。点位 / 设备状态语义色(全部锚在 .cockpit-shell :15):

自定义属性 行 值
--lv1 :34 #ff4d5e
--lv2 :35 #ffa63d
--lv3 :36 #4fd8ff
--lv4 :37 #35e39a
--neutral :38 #6d92ae

:31-33 注释明确「语义色:事件四级与点位状态共用一套调色板(ADR 0006 / 0008)」。其它 token:--u :17、--bg-0 :20、--panel-top/-bot/-line/-line-hi :21-24、--inset :25、--cyan/--cyan-d :28-29、--text/--text-2/--muted :41-43、--fs-caption/--fs-body/--fs-title/--fs-heading :50-53、--space-1..3 :56-58、--column-w :62、--z-map/--z-columns/--z-overlay :65-67、--shadow :70。

配套:事件四级色键 FE/domain/statusTones.js:14(TONES)、:19-24(LEVEL_TAGS);FE/components/StatusTag.vue:86-90 用 var(--lv1..--neutral) 落到 --tag-color。

5.8 tools/

递归只有 3 个文件:tools/city-massing/build-town.mjs(23899 B)、tools/city-massing/verify-town.mjs(9246 B)、tools/city-massing/README.md(4076 B)。

  • build-town.mjs = 城市体块模型生成器(:19-20 用法与产物、:25-27 读本机 D:/GIS/GIS 的 OSM buildings/roads/waterways + resa.json + terrain-grid.json、:387-390 写 glTF JSON、:484 writeGlb(...'town.glb'...)、:503-517 写 tileset.json)。
  • verify-town.mjs = 回验脚本(不生成资产)。
  • 没有任何生成 marker 图片或下载模型的脚本:tools/ 下 grep marker|icon|\.png|\.svg|\.jpg|download|fetch\( → 0 命中。

5.9 设备级模型资产的一句话裁决

No —— 仓库内不存在任何现成的设备级 glTF/GLB 模型资产。 45 个 3D 文件的文件名与(小 JSON 及二进制头的)内容全部指向建筑、车辆、船只、卫星、星球、人体、道具与倾斜摄影瓦片;唯一「设备形状」的 glTF(gasTank / oilTank / machine / tower / flaringDevice / 风电机 / 灯塔 / 浮标)只以 https://oss.icegl.cn/... 远程 URL 写在 src/plugins/zoneFreeScene/pages/*.vue 的 demo 里,本地无副本。设备点位当前完全依赖 SVG 形状(FE/domain/pointStatus.js:42-59 的 8 个几何形状)+ FE/styles/tokens.css:34-38 的色键,烘焙成 data:image/svg+xml billboard 图片(FE/domain/pointStatus.js:217-223、FE/services/cesiumEngine.js:335-347)。驾驶舱内也没有任何 glTF 实体加载代码(FE/ 内 .glb|.gltf|GLTF|GLB|Model. 仅 1 处注释命中 FE/domain/mapLayers.js:73)。


6. 明确「查不到」的清单与查证范围

查不到的东西 查过的地方与模式
后端任何「一次给出点位下全部设备经纬度 + 校验 + 状态」的端点 ADMIN/web/controller/cockpit/ 全部 20 个 @GetMapping;docs/cockpit/README.md:27-61 的端点清单;docs/cockpit/*.html 26 个 demo 页(重点 monitoring-point-detail-demo.html、map-snapshot-demo.html、equipment-archive-demo.html)
后端设备级相对位置 / 同井同杆 / 安装高度 / 朝向 pipe-network-service/ 全模块 grep 同井\|同杆\|杆塔\|relativePosition\|offsetX\|offsetY\|mountHeight\|installHeight\|poleCode\|wellCode → 0 命中;pipe-network-service/sql/ 全目录 grep offset\|height\|azimuth\|heading\|elevation\|altitude\|relative → 0 命中
后端把「设备坐标 == 点位坐标」写成契约的地方 docs/cockpit/README.md 全文;CockpitMonitoringDistributionSnapshot.java:9-19 与 CockpitMapSnapshot.java 的类注释;CockpitCoordinateValidator.java 全文(只做单点范围校验,不做两点相等校验)
equipment_point_rel 之外的设备-点位关系表 全后端 grep equipment_point_rel\|monitor_point → 只命中 1 个 SQL、1 个实体、1 个 mapper、1 个 WS 监听器与 1 个测试
Cesium 随包 d.ts 里的 @performance / 「一次 draw call」文字 CES 全文 51,152 行 grep @performance → 0 命中;grep one draw call / single draw / batched / draw call → 只命中 3D Tiles 与 Provider 的无关批处理
node_modules/cesium/Build/Cesium/Cesium.d.ts Test-Path = False;全仓 glob **/Cesium.d.ts 只命中 node_modules/cesium/Source/Cesium.d.ts
PointPrimitive.heightReference CES:42667-42755 全类无该属性;ENG/Scene/PointPrimitive.js 与 PointPrimitiveCollection.js 各 grep heightReference → 0 命中;官方 PointPrimitiveCollection / PointPrimitive ref-doc 页无该项
PointPrimitiveCollection 的 scene / coarseDepthTestDistance / threePointDepthTestDistance 构造器 CES:42805-42810 只有 4 个 option
Cesium 官方给出的「N 个点耗时多少 ms」数值 随包 d.ts(@performance 已删)、ENG/ 实现源码、官方 ref-doc 三处都只有复杂度与机制,无实测数值
仓库内设备级 glTF/GLB 全仓 glob 各扩展名 + 逐名判定 + 小 JSON/GLB 头内容核查(见 §5.1)
仓库内 city massing 资产文件 全仓 glob .glb/.gltf/.b3dm + 文本 grep cityMassing\|city-massing → 只有工具/文档/票据/配置引用,产物在 repo 外 D:/GIS/GIS/cityMassing
src/assets/ 目录 Test-Path = False
FE/ 下的图片资产 递归 *.png,*.svg,*.jpg,*.webp,*.gif,*.ico → 0 个图片文件
设备/状态专用图标(alarm / status / equip 形状) 图片名匹配 icon\|marker\|device\|point\|equip\|alarm\|status\|legend 与 点\|标\|图例,范围 public/、public/plugins/、src/ → 只有插件 demo 图
离屏 canvas 光栅化 marker FE/ grep OffscreenCanvas\|drawImage\|toDataURL\|createElement('canvas')\|devicePixelRatio → 0 命中
前端对 deviceCodes 的任何消费 全前端 src/ grep deviceCodes → 0 命中
前端对 BillboardCollection / PointPrimitiveCollection / LabelCollection / drillPick 的任何使用 全前端 src/ grep 上述四项 → 0 命中

已知的未验证项(诚实标注): 后端的真实数据适配器未落地(SYS/cockpit/point/EmptyCockpitMonitoringPointDetailAdapter.java:8-10 是空实现),因此「设备坐标与点位坐标实际差多少」「同一点位下多台设备坐标是否真的重叠」这类数据事实在本工作区无法观测,只能从 schema 与契约推断(§3.3)。按 AGENTS.md,本次未运行任何构建/调试命令,也未做浏览器走查。