@honua/app-platform 0.1.1-beta.0 → 0.1.4-beta.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.
Files changed (383) hide show
  1. package/collaboration/client.d.ts.map +1 -1
  2. package/connect-url-safety.d.ts +53 -0
  3. package/connect-url-safety.js +189 -0
  4. package/contract/discovery.d.ts +34 -0
  5. package/contract/discovery.d.ts.map +1 -1
  6. package/contract/index.d.ts +7 -5
  7. package/contract/index.d.ts.map +1 -1
  8. package/contract/index.js +3 -2
  9. package/contract/pmtiles.d.ts +10 -1
  10. package/contract/pmtiles.d.ts.map +1 -1
  11. package/contract/pmtiles.js +10 -2
  12. package/contract/protocol-module.d.ts +84 -9
  13. package/contract/protocol-module.d.ts.map +1 -1
  14. package/contract/query-filter-fes.d.ts +18 -0
  15. package/contract/query-filter-fes.d.ts.map +1 -0
  16. package/contract/query-filter-fes.js +122 -0
  17. package/contract/query-filter.d.ts +300 -0
  18. package/contract/query-filter.d.ts.map +1 -0
  19. package/contract/query-filter.js +883 -0
  20. package/contract/schema.d.ts +12 -0
  21. package/contract/schema.d.ts.map +1 -1
  22. package/contract/schema.js +34 -0
  23. package/contract/source-capability-support.d.ts.map +1 -1
  24. package/contract/source-capability-support.js +15 -3
  25. package/contract/source.d.ts +45 -25
  26. package/contract/source.d.ts.map +1 -1
  27. package/contract/source.js +719 -479
  28. package/contract/types.d.ts +87 -18
  29. package/contract/types.d.ts.map +1 -1
  30. package/contract/widget-source.d.ts.map +1 -1
  31. package/controls/basemap-switcher.d.ts +19 -11
  32. package/controls/basemap-switcher.d.ts.map +1 -1
  33. package/controls/basemap-switcher.js +58 -25
  34. package/controls/catalog.d.ts +105 -0
  35. package/controls/catalog.d.ts.map +1 -0
  36. package/controls/catalog.js +341 -0
  37. package/controls/element-utils.d.ts +9 -0
  38. package/controls/element-utils.d.ts.map +1 -1
  39. package/controls/element-utils.js +17 -0
  40. package/controls/index.d.ts +29 -8
  41. package/controls/index.d.ts.map +1 -1
  42. package/controls/index.js +26 -8
  43. package/controls/layer-list.d.ts +13 -5
  44. package/controls/layer-list.d.ts.map +1 -1
  45. package/controls/layer-list.js +72 -15
  46. package/controls/legend.d.ts +32 -5
  47. package/controls/legend.d.ts.map +1 -1
  48. package/controls/legend.js +95 -25
  49. package/controls/qualification.d.ts +152 -0
  50. package/controls/qualification.d.ts.map +1 -0
  51. package/controls/qualification.js +1265 -0
  52. package/controls/registry.d.ts +104 -0
  53. package/controls/registry.d.ts.map +1 -0
  54. package/controls/registry.js +150 -0
  55. package/controls/swipe-control.d.ts +6 -1
  56. package/controls/swipe-control.d.ts.map +1 -1
  57. package/controls/swipe-control.js +82 -20
  58. package/core/client.d.ts +45 -21
  59. package/core/client.d.ts.map +1 -1
  60. package/core/client.js +224 -46
  61. package/core/error-classifications.d.ts +6 -0
  62. package/core/error-classifications.d.ts.map +1 -1
  63. package/core/error-classifications.js +6 -0
  64. package/core/error-code-registry.d.ts +6 -0
  65. package/core/error-code-registry.d.ts.map +1 -1
  66. package/core/error-code-registry.js +6 -0
  67. package/core/error-envelope.js +2 -0
  68. package/core/errors.d.ts +1 -1
  69. package/core/errors.js +1 -1
  70. package/core/geoparquet-sql.d.ts +68 -4
  71. package/core/geoparquet-sql.d.ts.map +1 -1
  72. package/core/geoparquet-sql.js +325 -8
  73. package/core/odata-write-codec.d.ts.map +1 -1
  74. package/core/odata-write-codec.js +0 -1
  75. package/core/odata.d.ts +6 -0
  76. package/core/odata.d.ts.map +1 -1
  77. package/core/odata.js +6 -1
  78. package/core/ogc-processes.d.ts +182 -25
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +602 -30
  81. package/core/process-runner.d.ts +8 -1
  82. package/core/process-runner.d.ts.map +1 -1
  83. package/core/process-runner.js +1 -0
  84. package/core/protocol-transport.d.ts +2 -0
  85. package/core/protocol-transport.d.ts.map +1 -1
  86. package/core/stac.d.ts +31 -0
  87. package/core/stac.d.ts.map +1 -1
  88. package/core/stac.js +136 -19
  89. package/core/surfaces.d.ts.map +1 -1
  90. package/core/surfaces.js +273 -68
  91. package/core/types.d.ts +123 -9
  92. package/core/types.d.ts.map +1 -1
  93. package/core/wfs-axis-order.d.ts +18 -0
  94. package/core/wfs-axis-order.d.ts.map +1 -0
  95. package/core/wfs-axis-order.js +108 -0
  96. package/core/wfs-capabilities.d.ts +65 -24
  97. package/core/wfs-capabilities.d.ts.map +1 -1
  98. package/core/wfs-capabilities.js +237 -27
  99. package/core/wfs-filter.d.ts +26 -3
  100. package/core/wfs-filter.d.ts.map +1 -1
  101. package/core/wfs-filter.js +48 -53
  102. package/core/wfs-protocol-error.d.ts +12 -0
  103. package/core/wfs-protocol-error.d.ts.map +1 -0
  104. package/core/wfs-protocol-error.js +13 -0
  105. package/core/wfs.d.ts +53 -1
  106. package/core/wfs.d.ts.map +1 -1
  107. package/core/wfs.js +251 -29
  108. package/core/wms-feature-info.d.ts +92 -0
  109. package/core/wms-feature-info.d.ts.map +1 -0
  110. package/core/wms-feature-info.js +285 -0
  111. package/core/wms.d.ts +9 -0
  112. package/core/wms.d.ts.map +1 -1
  113. package/core/wms.js +17 -5
  114. package/esri-compat/event-bus.d.ts +20 -0
  115. package/esri-compat/event-bus.d.ts.map +1 -1
  116. package/esri-compat/feature-filter.d.ts +46 -1
  117. package/esri-compat/feature-filter.d.ts.map +1 -1
  118. package/esri-compat/feature-filter.js +49 -0
  119. package/esri-compat/geojson-layer.d.ts +4 -2
  120. package/esri-compat/geojson-layer.d.ts.map +1 -1
  121. package/esri-compat/geojson-layer.js +4 -2
  122. package/esri-compat/identity-manager.js +2 -2
  123. package/esri-compat/imagery-layer.d.ts +2 -1
  124. package/esri-compat/imagery-layer.d.ts.map +1 -1
  125. package/esri-compat/imagery-layer.js +2 -1
  126. package/esri-compat/layer-list.d.ts +8 -1
  127. package/esri-compat/layer-list.d.ts.map +1 -1
  128. package/esri-compat/layer-list.js +11 -4
  129. package/esri-compat/legend.d.ts.map +1 -1
  130. package/esri-compat/legend.js +1 -1
  131. package/esri-compat/locator.d.ts +297 -0
  132. package/esri-compat/locator.d.ts.map +1 -0
  133. package/esri-compat/locator.js +579 -0
  134. package/esri-compat/map-image-layer.d.ts.map +1 -1
  135. package/esri-compat/map-view.d.ts.map +1 -1
  136. package/esri-compat/map-view.js +1 -1
  137. package/esri-compat/scene-view.d.ts.map +1 -1
  138. package/esri-compat/time-slider.d.ts +29 -0
  139. package/esri-compat/time-slider.d.ts.map +1 -1
  140. package/esri-compat/time-slider.js +117 -0
  141. package/esri-compat/wfs-layer.d.ts +2 -1
  142. package/esri-compat/wfs-layer.d.ts.map +1 -1
  143. package/esri-compat/wfs-layer.js +2 -1
  144. package/esri-compat/widget-host.d.ts +42 -6
  145. package/esri-compat/widget-host.d.ts.map +1 -1
  146. package/esri-compat/widget-host.js +99 -10
  147. package/generated-app/projection.d.ts.map +1 -1
  148. package/generated-app/runtime.d.ts.map +1 -1
  149. package/map/data-to-map-bridge.d.ts +1 -1
  150. package/map/data-to-map-bridge.d.ts.map +1 -1
  151. package/map/data-to-map-bridge.js +1 -2
  152. package/map/honua-map.d.ts.map +1 -1
  153. package/map/honua-map.js +1 -1
  154. package/map/index.d.ts +2 -0
  155. package/map/index.d.ts.map +1 -1
  156. package/map/index.js +2 -0
  157. package/map/realtime-reconciliation-adapter.d.ts +98 -0
  158. package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
  159. package/map/realtime-reconciliation-adapter.js +84 -0
  160. package/map/temporal-playback.d.ts +25 -0
  161. package/map/temporal-playback.d.ts.map +1 -1
  162. package/map/temporal-playback.js +29 -1
  163. package/operator/workspace/workspace.d.ts.map +1 -1
  164. package/operator/workspace/workspace.js +0 -2
  165. package/package.json +4 -3
  166. package/query-planner/canonical-filter.d.ts +30 -0
  167. package/query-planner/canonical-filter.d.ts.map +1 -0
  168. package/query-planner/canonical-filter.js +30 -0
  169. package/query-planner/columnar-plan.d.ts +63 -0
  170. package/query-planner/columnar-plan.d.ts.map +1 -0
  171. package/query-planner/columnar-plan.js +94 -0
  172. package/query-planner/diagnostics.d.ts +3 -3
  173. package/query-planner/diagnostics.d.ts.map +1 -1
  174. package/query-planner/diagnostics.js +17 -4
  175. package/query-planner/duckdb.d.ts.map +1 -1
  176. package/query-planner/duckdb.js +16 -2
  177. package/query-planner/execution-context.d.ts.map +1 -1
  178. package/query-planner/execution-context.js +3 -1
  179. package/query-planner/executor.d.ts.map +1 -1
  180. package/query-planner/executor.js +7 -1
  181. package/query-planner/geoservices.d.ts.map +1 -1
  182. package/query-planner/geoservices.js +24 -5
  183. package/query-planner/grpc.d.ts.map +1 -1
  184. package/query-planner/grpc.js +29 -5
  185. package/query-planner/index.d.ts +20 -5
  186. package/query-planner/index.d.ts.map +1 -1
  187. package/query-planner/index.js +16 -3
  188. package/query-planner/ir.d.ts +6 -1
  189. package/query-planner/ir.d.ts.map +1 -1
  190. package/query-planner/ir.js +63 -1
  191. package/query-planner/odata-protocol-module.d.ts +54 -0
  192. package/query-planner/odata-protocol-module.d.ts.map +1 -0
  193. package/query-planner/odata-protocol-module.js +199 -0
  194. package/query-planner/odata-v1.d.ts +23 -0
  195. package/query-planner/odata-v1.d.ts.map +1 -0
  196. package/query-planner/odata-v1.js +202 -0
  197. package/query-planner/odata.d.ts +0 -3
  198. package/query-planner/odata.d.ts.map +1 -1
  199. package/query-planner/odata.js +0 -143
  200. package/query-planner/ogc-features.d.ts.map +1 -1
  201. package/query-planner/ogc-features.js +23 -1
  202. package/query-planner/planner.d.ts +2 -2
  203. package/query-planner/planner.d.ts.map +1 -1
  204. package/query-planner/planner.js +146 -27
  205. package/query-planner/representation.d.ts +59 -0
  206. package/query-planner/representation.d.ts.map +1 -0
  207. package/query-planner/representation.js +140 -0
  208. package/query-planner/types.d.ts +153 -3
  209. package/query-planner/types.d.ts.map +1 -1
  210. package/query-planner/wfs-protocol-module.d.ts +57 -0
  211. package/query-planner/wfs-protocol-module.d.ts.map +1 -0
  212. package/query-planner/wfs-protocol-module.js +731 -0
  213. package/query-planner/wfs-v1.d.ts +41 -0
  214. package/query-planner/wfs-v1.d.ts.map +1 -0
  215. package/query-planner/wfs-v1.js +366 -0
  216. package/query-planner/wfs.d.ts +1 -3
  217. package/query-planner/wfs.d.ts.map +1 -1
  218. package/query-planner/wfs.js +1 -70
  219. package/realtime/checkpoint-store.d.ts +206 -0
  220. package/realtime/checkpoint-store.d.ts.map +1 -0
  221. package/realtime/checkpoint-store.js +504 -0
  222. package/realtime/honua-server.d.ts +8 -0
  223. package/realtime/honua-server.d.ts.map +1 -1
  224. package/realtime/honua-server.js +123 -14
  225. package/realtime/index.d.ts +4 -0
  226. package/realtime/index.d.ts.map +1 -1
  227. package/realtime/index.js +2 -0
  228. package/realtime/reconciliation.d.ts +245 -0
  229. package/realtime/reconciliation.d.ts.map +1 -0
  230. package/realtime/reconciliation.js +377 -0
  231. package/realtime/resumable-transport.d.ts.map +1 -1
  232. package/realtime/sse.d.ts.map +1 -1
  233. package/realtime/websocket.d.ts.map +1 -1
  234. package/realtime/wire.d.ts +2 -1
  235. package/realtime/wire.d.ts.map +1 -1
  236. package/realtime/wire.js +24 -8
  237. package/replica-sync/errors.d.ts +19 -1
  238. package/replica-sync/errors.d.ts.map +1 -1
  239. package/replica-sync/errors.js +16 -0
  240. package/replica-sync/geoservices.d.ts +260 -0
  241. package/replica-sync/geoservices.d.ts.map +1 -0
  242. package/replica-sync/geoservices.js +1093 -0
  243. package/replica-sync/index.d.ts +5 -1
  244. package/replica-sync/index.d.ts.map +1 -1
  245. package/replica-sync/index.js +3 -1
  246. package/replica-sync/transport-conformance.d.ts +58 -0
  247. package/replica-sync/transport-conformance.d.ts.map +1 -0
  248. package/replica-sync/transport-conformance.js +265 -0
  249. package/replica-sync/types.d.ts +22 -0
  250. package/replica-sync/types.d.ts.map +1 -1
  251. package/runtime/index.d.ts +6 -2
  252. package/runtime/index.d.ts.map +1 -1
  253. package/runtime/index.js +3 -1
  254. package/runtime/load-package.d.ts +24 -0
  255. package/runtime/load-package.d.ts.map +1 -1
  256. package/runtime/load-package.js +45 -1
  257. package/runtime/maplibre-renderer.d.ts +11 -0
  258. package/runtime/maplibre-renderer.d.ts.map +1 -1
  259. package/runtime/maplibre-renderer.js +32 -7
  260. package/runtime/offline-region-protocol.d.ts +73 -0
  261. package/runtime/offline-region-protocol.d.ts.map +1 -0
  262. package/runtime/offline-region-protocol.js +63 -0
  263. package/runtime/offline-region-style.d.ts +78 -0
  264. package/runtime/offline-region-style.d.ts.map +1 -0
  265. package/runtime/offline-region-style.js +147 -0
  266. package/runtime/pmtiles-protocol.d.ts +7 -8
  267. package/runtime/pmtiles-protocol.d.ts.map +1 -1
  268. package/runtime/pmtiles-protocol.js +19 -66
  269. package/runtime/protocol-registry.d.ts +55 -0
  270. package/runtime/protocol-registry.d.ts.map +1 -0
  271. package/runtime/protocol-registry.js +103 -0
  272. package/runtime/runtime.d.ts +2 -0
  273. package/runtime/runtime.d.ts.map +1 -1
  274. package/runtime/runtime.js +6 -1
  275. package/scene-workspace/analysis-widgets.d.ts +4 -2
  276. package/scene-workspace/analysis-widgets.d.ts.map +1 -1
  277. package/scene-workspace/analysis-widgets.js +4 -2
  278. package/scene-workspace/camera-correspondence.d.ts +151 -0
  279. package/scene-workspace/camera-correspondence.d.ts.map +1 -0
  280. package/scene-workspace/camera-correspondence.js +220 -0
  281. package/scene-workspace/cesium-adapter.d.ts +230 -29
  282. package/scene-workspace/cesium-adapter.d.ts.map +1 -1
  283. package/scene-workspace/cesium-adapter.js +741 -56
  284. package/scene-workspace/cesium-mount.d.ts +205 -0
  285. package/scene-workspace/cesium-mount.d.ts.map +1 -0
  286. package/scene-workspace/cesium-mount.js +392 -0
  287. package/scene-workspace/cesium-scene-owner.d.ts +160 -0
  288. package/scene-workspace/cesium-scene-owner.d.ts.map +1 -0
  289. package/scene-workspace/cesium-scene-owner.js +205 -0
  290. package/scene-workspace/cesium-state-sync.d.ts +101 -0
  291. package/scene-workspace/cesium-state-sync.d.ts.map +1 -0
  292. package/scene-workspace/cesium-state-sync.js +432 -0
  293. package/scene-workspace/cesium-time.d.ts +346 -0
  294. package/scene-workspace/cesium-time.d.ts.map +1 -0
  295. package/scene-workspace/cesium-time.js +403 -0
  296. package/scene-workspace/index.d.ts +42 -10
  297. package/scene-workspace/index.d.ts.map +1 -1
  298. package/scene-workspace/index.js +32 -7
  299. package/scene-workspace/maplibre-state-sync.d.ts +112 -0
  300. package/scene-workspace/maplibre-state-sync.d.ts.map +1 -0
  301. package/scene-workspace/maplibre-state-sync.js +400 -0
  302. package/scene-workspace/primitives.d.ts +237 -3
  303. package/scene-workspace/primitives.d.ts.map +1 -1
  304. package/scene-workspace/primitives.js +1087 -17
  305. package/scene-workspace/scene-discovery.d.ts +5 -2
  306. package/scene-workspace/scene-discovery.d.ts.map +1 -1
  307. package/scene-workspace/scene-discovery.js +5 -2
  308. package/scene-workspace/scene-view.d.ts +5 -2
  309. package/scene-workspace/scene-view.d.ts.map +1 -1
  310. package/scene-workspace/scene-view.js +5 -2
  311. package/scene-workspace/source-to-cesium.d.ts +141 -8
  312. package/scene-workspace/source-to-cesium.d.ts.map +1 -1
  313. package/scene-workspace/source-to-cesium.js +497 -72
  314. package/scene-workspace/state-sync-port-kit.d.ts +156 -0
  315. package/scene-workspace/state-sync-port-kit.d.ts.map +1 -0
  316. package/scene-workspace/state-sync-port-kit.js +319 -0
  317. package/scene-workspace/state-sync.d.ts +34 -1
  318. package/scene-workspace/state-sync.d.ts.map +1 -1
  319. package/scene-workspace/state-sync.js +48 -0
  320. package/scene-workspace/types.d.ts +8 -0
  321. package/scene-workspace/types.d.ts.map +1 -1
  322. package/scene-workspace/types.js +2 -0
  323. package/scene-workspace/workspace.d.ts +12 -1
  324. package/scene-workspace/workspace.d.ts.map +1 -1
  325. package/scene-workspace/workspace.js +13 -0
  326. package/studio/index.d.ts +24 -7
  327. package/studio/index.d.ts.map +1 -1
  328. package/studio/index.js +21 -7
  329. package/studio/lifecycle-client.d.ts +164 -0
  330. package/studio/lifecycle-client.d.ts.map +1 -0
  331. package/studio/lifecycle-client.js +299 -0
  332. package/studio/lifecycle-errors.d.ts +91 -0
  333. package/studio/lifecycle-errors.d.ts.map +1 -0
  334. package/studio/lifecycle-errors.js +77 -0
  335. package/studio/lifecycle-types.d.ts +321 -0
  336. package/studio/lifecycle-types.d.ts.map +1 -0
  337. package/studio/lifecycle-types.js +22 -0
  338. package/web-components/controller.d.ts.map +1 -1
  339. package/web-components/csp-styles.d.ts +6 -0
  340. package/web-components/csp-styles.d.ts.map +1 -0
  341. package/web-components/csp-styles.js +53 -0
  342. package/web-components/elements.d.ts +158 -2
  343. package/web-components/elements.d.ts.map +1 -1
  344. package/web-components/elements.js +1302 -215
  345. package/web-components/export-redaction.d.ts +252 -0
  346. package/web-components/export-redaction.d.ts.map +1 -0
  347. package/web-components/export-redaction.js +588 -0
  348. package/web-components/export.d.ts +417 -0
  349. package/web-components/export.d.ts.map +1 -0
  350. package/web-components/export.js +719 -0
  351. package/web-components/feature-editor-model.d.ts +265 -0
  352. package/web-components/feature-editor-model.d.ts.map +1 -0
  353. package/web-components/feature-editor-model.js +438 -0
  354. package/web-components/feature-editor-sketch.d.ts +125 -0
  355. package/web-components/feature-editor-sketch.d.ts.map +1 -0
  356. package/web-components/feature-editor-sketch.js +194 -0
  357. package/web-components/feature-editor-workflow.d.ts +286 -0
  358. package/web-components/feature-editor-workflow.d.ts.map +1 -0
  359. package/web-components/feature-editor-workflow.js +889 -0
  360. package/web-components/feature-editor.d.ts +83 -0
  361. package/web-components/feature-editor.d.ts.map +1 -0
  362. package/web-components/feature-editor.js +829 -0
  363. package/web-components/feature-table-engine.d.ts +465 -0
  364. package/web-components/feature-table-engine.d.ts.map +1 -0
  365. package/web-components/feature-table-engine.js +1360 -0
  366. package/web-components/feature-table-view.d.ts +95 -0
  367. package/web-components/feature-table-view.d.ts.map +1 -0
  368. package/web-components/feature-table-view.js +343 -0
  369. package/web-components/index.d.ts +34 -3
  370. package/web-components/index.d.ts.map +1 -1
  371. package/web-components/index.js +35 -2
  372. package/web-components/maplibre-renderer.d.ts.map +1 -1
  373. package/web-components/maplibre-renderer.js +13 -4
  374. package/web-components/measurement.d.ts +3 -1
  375. package/web-components/measurement.d.ts.map +1 -1
  376. package/web-components/measurement.js +62 -18
  377. package/web-components/time-slider.d.ts +122 -0
  378. package/web-components/time-slider.d.ts.map +1 -0
  379. package/web-components/time-slider.js +740 -0
  380. package/web-components/types.d.ts +310 -0
  381. package/web-components/types.d.ts.map +1 -1
  382. package/webmap/convert-label.js +0 -1
  383. package/webmap/convert-symbol.d.ts.map +1 -1
@@ -5,7 +5,26 @@
5
5
  * only while mounting, either through an injected module/loader or a lazy
6
6
  * optional-peer import.
7
7
  *
8
- * @experimental Not covered by the pre-1.0 semver contract.
8
+ * `refresh()` is a diff, not a rebuild (issue #1050). It runs the same
9
+ * discipline the beta primitive mount runs on (#930): identity is the projected
10
+ * entity id qualified by geometry kind, configuration is an order-independent
11
+ * fingerprint of the projected feature, a feature that cannot be fingerprinted
12
+ * is treated as changed rather than reusable, and every crossing is reported.
13
+ * The consequence a host can rely on is object identity: a feature whose row did
14
+ * not change keeps the *same* live `Entity`, so `viewer.selectedEntity`, a
15
+ * tracked entity, and a graphic the host mutated all survive a refresh. A
16
+ * feature that did change keeps its `Entity` too — only the facets that moved
17
+ * are written onto it.
18
+ *
19
+ * {@link mountCesiumScene} (`./cesium-scene-owner.ts`) composes this mount with
20
+ * the primitive mount under one owner, so a host does not sequence two
21
+ * teardowns itself.
22
+ *
23
+ * @experimental Held back from the beta `@honua/app-platform/scene-workspace`
24
+ * tier: the slice has no symbology surface, and adding one means new required
25
+ * shapes rather than purely additive ones (see `docs/cesium-entity-adapter.md`).
26
+ * Not covered by the pre-1.0 semver contract. The scene primitive adapter
27
+ * alongside it is beta.
9
28
  */
10
29
  import { HonuaCapabilityNotSupportedError } from "../core/errors.js";
11
30
  import { canonicalStringify, toJsonValue } from "../query-planner/canonical.js";
@@ -23,6 +42,33 @@ export class HonuaCesiumEntityAdapterError extends Error {
23
42
  this.name = "HonuaCesiumEntityAdapterError";
24
43
  }
25
44
  }
45
+ /** The entity boundary vocabulary, in escalation order. */
46
+ export const CESIUM_ENTITY_REBUILD_BOUNDARIES = Object.freeze([
47
+ "none",
48
+ "entity-configuration",
49
+ "entity-identity",
50
+ "snapshot-membership",
51
+ "entity-geometry-kind",
52
+ "unfingerprintable",
53
+ ]);
54
+ const ENTITY_BOUNDARY_REASONS = Object.freeze({
55
+ none: "Identity and configuration are unchanged; the live entity was reused untouched.",
56
+ "entity-configuration": "The configuration fingerprint changed; the live entity was updated in place.",
57
+ "entity-identity": "The feature was not previously mounted; an entity was constructed for it.",
58
+ "snapshot-membership": "The feature left the snapshot; its entity was released.",
59
+ "entity-geometry-kind": "The feature's geometry kind changed; the entity was released and rebuilt.",
60
+ unfingerprintable: "The feature could not be fingerprinted deterministically and was rebuilt conservatively.",
61
+ });
62
+ /** Build one boundary report with the vocabulary's own stable reason text. */
63
+ function entityRebuildBoundaryReport(entityId, boundary) {
64
+ return Object.freeze({
65
+ entityId,
66
+ boundary,
67
+ incremental: boundary === "none",
68
+ preserved: boundary === "none" || boundary === "entity-configuration",
69
+ reason: ENTITY_BOUNDARY_REASONS[boundary],
70
+ });
71
+ }
26
72
  export function projectSourceToCesium(source, plan, result, options = {}) {
27
73
  assertProjectionPlanContext(source, plan);
28
74
  const maxEntities = validatePlanAndOptions(source, plan, options);
@@ -43,6 +89,7 @@ export function projectSourceToCesium(source, plan, result, options = {}) {
43
89
  let unsupportedGeometry = 0;
44
90
  let missingIdentity = 0;
45
91
  let invalidIntervals = 0;
92
+ let invalidPositionSamples = 0;
46
93
  let unsupportedVerticalDatum = 0;
47
94
  let unsupportedAttributes = 0;
48
95
  const entityIds = new Set();
@@ -71,6 +118,13 @@ export function projectSourceToCesium(source, plan, result, options = {}) {
71
118
  invalidIntervals += 1;
72
119
  continue;
73
120
  }
121
+ const positionSamples = options.time?.positionField
122
+ ? projectPositionSamples(attributes, options.time.positionField, geometry, options.verticalDatum)
123
+ : undefined;
124
+ if (positionSamples === null) {
125
+ invalidPositionSamples += 1;
126
+ continue;
127
+ }
74
128
  const entityId = `${sourceId}:${typeof featureId === "number" ? "n" : "s"}:${encodeURIComponent(String(featureId))}`;
75
129
  if (entityIds.has(entityId)) {
76
130
  throw new HonuaCesiumEntityAdapterError("entity-conflict", `Accepted query returned duplicate stable feature identity "${String(featureId)}".`, { entityId, featureId });
@@ -82,6 +136,7 @@ export function projectSourceToCesium(source, plan, result, options = {}) {
82
136
  properties: attributes,
83
137
  geometry: immutableJsonSnapshot(geometry),
84
138
  ...(interval ? { interval: immutableJsonSnapshot(interval) } : {}),
139
+ ...(positionSamples ? { positionSamples: immutableJsonSnapshot(positionSamples) } : {}),
85
140
  }));
86
141
  if (entities.length > maxEntities) {
87
142
  throw new HonuaCesiumEntityAdapterError("entity-limit-exceeded", `Cesium projection exceeded the ${maxEntities} entity safety ceiling.`, { maxEntities, sourceId: source.descriptor.id });
@@ -122,6 +177,9 @@ export function projectSourceToCesium(source, plan, result, options = {}) {
122
177
  omittedFeatureCount: invalidIntervals,
123
178
  }));
124
179
  }
180
+ if (invalidPositionSamples > 0) {
181
+ diagnostics.push(diagnostic(source, plan, "time-position-invalid", "warning", "project", "unsupported", `${invalidPositionSamples} temporal point track(s) were invalid and omitted.`, { omittedFeatureCount: invalidPositionSamples }));
182
+ }
125
183
  return Object.freeze({
126
184
  strategy: "entity-query",
127
185
  sourceId,
@@ -152,16 +210,16 @@ export async function mountSourceToCesium(collection, source, plan, options = {}
152
210
  const result = await executeAcceptedPlan(source, plan, initialSignal);
153
211
  initialSignal.throwIfAborted();
154
212
  let projection = projectSourceToCesium(source, plan, result, mountOptions);
155
- const initialSpecs = materializeProjection(cesium, projection);
156
- assertEntityIdsAvailable(collection, initialSpecs.keys());
213
+ const initialRecords = materializeProjection(cesium, projection);
214
+ assertEntityIdsAvailable(collection, initialRecords.keys());
157
215
  let mounted = new Map();
158
216
  const attemptedInitialIds = [];
159
217
  try {
160
- for (const [id, spec] of initialSpecs) {
218
+ for (const [id, record] of initialRecords) {
161
219
  initialSignal.throwIfAborted();
162
220
  attemptedInitialIds.push(id);
163
- mounted.set(id, spec);
164
- collection.add(spec);
221
+ mounted.set(id, record);
222
+ collection.add(record.spec);
165
223
  initialSignal.throwIfAborted();
166
224
  }
167
225
  }
@@ -175,6 +233,8 @@ export async function mountSourceToCesium(collection, source, plan, options = {}
175
233
  }
176
234
  let state = projection.state;
177
235
  let diagnostics = projection.diagnostics;
236
+ let rebuildBoundaries = Object.freeze([]);
237
+ let revision = 1;
178
238
  let refreshTail = Promise.resolve();
179
239
  let lifecycleEpoch = 0;
180
240
  let disposeActive = false;
@@ -192,58 +252,121 @@ export async function mountSourceToCesium(collection, source, plan, options = {}
192
252
  assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
193
253
  const next = projectSourceToCesium(source, plan, nextResult, mountOptions);
194
254
  signal.throwIfAborted();
195
- const nextSpecs = materializeProjection(cesium, next);
196
- const previous = new Map(mounted);
197
- assertEntityIdsAvailable(collection, [...nextSpecs.keys()].filter((id) => !previous.has(id)));
198
- const attemptedNextIds = [];
255
+ // ── Identity + configuration diff ──────────────────────────────
256
+ //
257
+ // Identity is the projected entity id qualified by geometry kind, exactly
258
+ // the discipline the beta primitive mount runs on (#930): a kind change owns
259
+ // a different renderer object, so it is a replacement rather than an update.
260
+ // A feature that cannot be fingerprinted is treated as changed, never as
261
+ // unchanged, so a stale entity can never claim to satisfy a snapshot it no
262
+ // longer matches. Nothing is materialized for a feature that is reused, so a
263
+ // steady-state refresh allocates no Cesium values at all.
264
+ const previous = mounted;
265
+ const nextRecords = new Map();
266
+ const boundaries = [];
267
+ const reusedIds = [];
268
+ const updatedIds = [];
269
+ const createdIds = [];
270
+ const updates = [];
271
+ const replacements = [];
272
+ const creations = [];
273
+ for (const item of next.entities) {
274
+ const existing = previous.get(item.id);
275
+ const facets = entityFacetFingerprints(item);
276
+ const boundary = entityRebuildBoundary(existing, item.geometry.kind, entityFingerprint(facets));
277
+ if (boundary === "none" && existing) {
278
+ nextRecords.set(item.id, existing);
279
+ reusedIds.push(item.id);
280
+ continue;
281
+ }
282
+ const record = materializeEntityRecord(cesium, item, facets);
283
+ nextRecords.set(item.id, record);
284
+ boundaries.push(entityRebuildBoundaryReport(item.id, boundary));
285
+ if (boundary === "entity-configuration" && existing) {
286
+ updates.push({ previous: existing, next: record });
287
+ updatedIds.push(item.id);
288
+ }
289
+ else if (existing) {
290
+ replacements.push(record);
291
+ createdIds.push(item.id);
292
+ }
293
+ else {
294
+ creations.push(record);
295
+ createdIds.push(item.id);
296
+ }
297
+ }
298
+ const departed = [];
299
+ for (const record of previous.values()) {
300
+ if (nextRecords.has(record.id))
301
+ continue;
302
+ departed.push(record);
303
+ boundaries.push(entityRebuildBoundaryReport(record.id, "snapshot-membership"));
304
+ }
305
+ const disposedIds = [...replacements.map((record) => record.id), ...departed.map((record) => record.id)];
306
+ assertEntityIdsAvailable(collection, creations.map((record) => record.id));
307
+ // Mutation order is chosen so that every step before the last is undoable:
308
+ // in-place writes are journaled, additions can be removed, and the entities
309
+ // that left the snapshot are released only once everything else has landed.
310
+ const writes = [];
311
+ const addedIds = [];
312
+ const replacedRecords = [];
199
313
  try {
200
- removeIdsOrThrow(collection, [...previous.keys()]);
201
- assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
202
- mounted = new Map();
203
- for (const [id, spec] of nextSpecs) {
314
+ for (const update of updates) {
204
315
  assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
205
- attemptedNextIds.push(id);
206
- mounted.set(id, spec);
207
- collection.add(spec);
316
+ updateEntityInPlace(collection, update.previous, update.next, writes);
208
317
  assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
209
318
  }
319
+ for (const record of replacements) {
320
+ assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
321
+ const displaced = previous.get(record.id);
322
+ removeIdsOrThrow(collection, [record.id]);
323
+ if (displaced)
324
+ replacedRecords.push(displaced);
325
+ collection.add(record.spec);
326
+ addedIds.push(record.id);
327
+ assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
328
+ }
329
+ for (const record of creations) {
330
+ assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
331
+ collection.add(record.spec);
332
+ addedIds.push(record.id);
333
+ assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
334
+ }
335
+ removeIdsOrThrow(collection, departed.map((record) => record.id));
336
+ assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
210
337
  }
211
338
  catch (cause) {
212
- const ownedSpecs = new Map([...previous, ...nextSpecs]);
213
- const cleanupFailures = removeIds(collection, [...mounted.keys(), ...attemptedNextIds]);
214
- mounted = presentOwnedSpecs(collection, ownedSpecs);
215
- if (lifecycleInvalidated(currentEpoch, lifecycleEpoch, state)) {
216
- if (cleanupFailures.length > 0) {
217
- throw mutationError("Cesium entity disposal interrupted refresh and cleanup was incomplete.", cause, cleanupFailures);
339
+ // Roll back only what this refresh touched. Entities it reused were never
340
+ // mutated, and the departed set is still attached because its removal is
341
+ // the last step, so recovery is a matter of undoing this refresh's own
342
+ // additions and writes rather than rebuilding the previous snapshot.
343
+ const cleanupFailures = removeIds(collection, addedIds);
344
+ const restoreFailures = [];
345
+ for (const record of replacedRecords) {
346
+ try {
347
+ if (!collection.getById(record.id))
348
+ collection.add(record.spec);
349
+ }
350
+ catch (error) {
351
+ restoreFailures.push(error);
218
352
  }
219
- throw lifecycle.signal.reason;
220
353
  }
221
- const restoreFailures = [];
222
- for (const [id, spec] of previous) {
354
+ for (const write of [...writes].reverse()) {
223
355
  try {
224
- assertLifecycleActive(currentEpoch, lifecycleEpoch, state, lifecycle.signal);
225
- mounted.set(id, spec);
226
- if (!collection.getById(id))
227
- collection.add(spec);
228
- assertLifecycleActive(currentEpoch, lifecycleEpoch, state, lifecycle.signal);
356
+ write.target[write.key] = write.previous;
229
357
  }
230
358
  catch (error) {
231
- mounted.delete(id);
232
359
  restoreFailures.push(error);
233
- if (lifecycleInvalidated(currentEpoch, lifecycleEpoch, state))
234
- break;
235
360
  }
236
361
  }
362
+ mounted = presentOwnedRecords(collection, previous);
237
363
  if (lifecycleInvalidated(currentEpoch, lifecycleEpoch, state)) {
238
- const rollbackOwnedSpecs = new Map(mounted);
239
- const disposalCleanupFailures = removeIds(collection, [...rollbackOwnedSpecs.keys()]);
240
- mounted = presentOwnedSpecs(collection, rollbackOwnedSpecs);
241
- if (disposalCleanupFailures.length > 0) {
242
- throw mutationError("Cesium entity disposal interrupted rollback and cleanup was incomplete.", cause, [
243
- ...cleanupFailures,
244
- ...restoreFailures,
245
- ...disposalCleanupFailures,
246
- ]);
364
+ const owned = new Map(mounted);
365
+ const disposalCleanupFailures = removeIds(collection, [...owned.keys()]);
366
+ mounted = presentOwnedRecords(collection, owned);
367
+ const failures = [...cleanupFailures, ...restoreFailures, ...disposalCleanupFailures];
368
+ if (failures.length > 0) {
369
+ throw mutationError("Cesium entity disposal interrupted refresh and cleanup was incomplete.", cause, failures);
247
370
  }
248
371
  throw lifecycle.signal.reason;
249
372
  }
@@ -252,27 +375,55 @@ export async function mountSourceToCesium(collection, source, plan, options = {}
252
375
  state = "degraded";
253
376
  diagnostics = Object.freeze([
254
377
  ...diagnostics,
255
- diagnostic(source, plan, "incremental-update-failed", "warning", "update", "unsupported", "Cesium entity rebuild failed; restoration was attempted.", {
378
+ diagnostic(source, plan, "incremental-update-failed", "warning", "update", "unsupported", "Cesium entity reconciliation failed; restoration was attempted.", {
256
379
  rollbackSucceeded: cleanupFailures.length === 0 && restoreFailures.length === 0,
257
380
  }),
258
381
  ]);
259
- throw mutationError("Failed to rebuild the mounted Cesium entity snapshot.", cause, [
382
+ throw mutationError("Failed to reconcile the mounted Cesium entity snapshot.", cause, [
260
383
  ...cleanupFailures,
261
384
  ...restoreFailures,
262
385
  ]);
263
386
  }
264
- assertLifecycleActive(currentEpoch, lifecycleEpoch, state, signal);
387
+ mounted = nextRecords;
388
+ revision += 1;
389
+ rebuildBoundaries = Object.freeze([...boundaries]);
390
+ const escalated = escalatedEntityBoundary(rebuildBoundaries);
265
391
  diagnostics = Object.freeze([
266
392
  ...next.diagnostics,
267
- diagnostic(source, plan, "incremental-update", "info", "update", "equivalent", "Rebuilt the bounded entity snapshot while preserving stable entity ids.", {
393
+ diagnostic(source, plan, "incremental-update", "info", "update", "equivalent", `Reconciled the bounded entity snapshot: reused ${reusedIds.length}, updated ${updatedIds.length} in place, created ${createdIds.length}, disposed ${disposedIds.length}.`, {
394
+ revision,
268
395
  previousEntityCount: previous.size,
269
396
  nextEntityCount: mounted.size,
270
- rebuildBoundary: "entity-snapshot",
397
+ reusedEntityCount: reusedIds.length,
398
+ updatedEntityCount: updatedIds.length,
399
+ createdEntityCount: createdIds.length,
400
+ disposedEntityCount: disposedIds.length,
401
+ rebuildBoundary: escalated,
402
+ rebuildBoundaryCounts: boundaryCounts(rebuildBoundaries),
271
403
  }),
404
+ ...(rebuildBoundaries.some((report) => !report.preserved)
405
+ ? [
406
+ diagnostic(source, plan, "rebuild-boundary", "info", "update", "equivalent", `Revision ${revision} could not carry every entity across in place; the highest boundary crossed was ${escalated}.`, {
407
+ revision,
408
+ rebuildBoundary: escalated,
409
+ rebuildBoundaryCounts: boundaryCounts(rebuildBoundaries),
410
+ replacedEntityCount: replacements.length,
411
+ departedEntityCount: departed.length,
412
+ }),
413
+ ]
414
+ : []),
272
415
  ]);
273
416
  projection = Object.freeze({ ...next, diagnostics });
274
417
  state = next.state;
275
- return projection;
418
+ return Object.freeze({
419
+ ...projection,
420
+ revision,
421
+ reused: Object.freeze(reusedIds),
422
+ updated: Object.freeze(updatedIds),
423
+ created: Object.freeze(createdIds),
424
+ disposed: Object.freeze(disposedIds),
425
+ rebuildBoundaries,
426
+ });
276
427
  };
277
428
  return {
278
429
  strategy: "entity-query",
@@ -288,6 +439,12 @@ export async function mountSourceToCesium(collection, source, plan, options = {}
288
439
  get diagnostics() {
289
440
  return diagnostics;
290
441
  },
442
+ get revision() {
443
+ return revision;
444
+ },
445
+ get rebuildBoundaries() {
446
+ return rebuildBoundaries;
447
+ },
291
448
  refresh(refreshOptions = {}) {
292
449
  const refreshContext = snapshotExecutionOptions(refreshOptions);
293
450
  const value = refreshTail.then(() => runRefresh(refreshContext));
@@ -342,17 +499,155 @@ async function resolveCesium(injected) {
342
499
  }
343
500
  }
344
501
  function materializeProjection(cesium, projection) {
345
- return new Map(projection.entities.map((item) => [item.id, materializeEntity(cesium, item)]));
502
+ return new Map(projection.entities.map((item) => [item.id, materializeEntityRecord(cesium, item, entityFacetFingerprints(item))]));
503
+ }
504
+ function materializeEntityRecord(cesium, item, facets) {
505
+ return {
506
+ id: item.id,
507
+ kind: item.geometry.kind,
508
+ fingerprint: entityFingerprint(facets),
509
+ facets,
510
+ spec: materializeEntity(cesium, item),
511
+ };
512
+ }
513
+ /**
514
+ * Per-facet, order-independent fingerprints of one projected feature.
515
+ *
516
+ * Splitting the fingerprint by facet is what lets a changed feature be updated
517
+ * in place surgically: a feature that only moved rewrites its position and
518
+ * leaves its property bag and availability — and anything the host attached to
519
+ * them — alone.
520
+ *
521
+ * A facet is `undefined` when it cannot be fingerprinted deterministically.
522
+ * `projectSourceToCesium` only ever emits deeply frozen JSON-like snapshots, so
523
+ * that is unreachable through the public path today; it is the fail-closed
524
+ * backstop for the day the projection admits a value canonicalization refuses,
525
+ * and it resolves to "assume changed" rather than "assume reusable".
526
+ */
527
+ function entityFacetFingerprints(item) {
528
+ return {
529
+ geometry: fingerprintFacet({ geometry: item.geometry, positionSamples: item.positionSamples ?? null }),
530
+ properties: fingerprintFacet(item.properties),
531
+ availability: fingerprintFacet(item.interval ?? null),
532
+ };
533
+ }
534
+ function fingerprintFacet(value) {
535
+ try {
536
+ return canonicalStringify(toJsonValue(value));
537
+ }
538
+ catch {
539
+ return undefined;
540
+ }
541
+ }
542
+ /**
543
+ * A stable, order-independent fingerprint of a projected feature's whole
544
+ * configuration, or `undefined` when any facet could not be fingerprinted.
545
+ *
546
+ * @internal Exported for unit coverage of the diff's fail-closed behavior; not
547
+ * part of the `scene-workspace` barrel.
548
+ */
549
+ export function cesiumEntityFingerprint(item) {
550
+ return entityFingerprint(entityFacetFingerprints(item));
551
+ }
552
+ function entityFingerprint(facets) {
553
+ if (facets.geometry === undefined || facets.properties === undefined || facets.availability === undefined) {
554
+ return undefined;
555
+ }
556
+ return `${facets.geometry}${facets.properties}${facets.availability}`;
557
+ }
558
+ /**
559
+ * Classify one projected feature against what this mount already owns.
560
+ *
561
+ * The order matters, and mirrors the primitive mount's: a feature the mount has
562
+ * never seen is an identity crossing regardless of whether it can be
563
+ * fingerprinted, a geometry-kind change is a replacement rather than a
564
+ * configuration change, and an unfingerprintable feature is called out as such
565
+ * instead of being reported as a change nobody made.
566
+ */
567
+ function entityRebuildBoundary(previous, kind, fingerprint) {
568
+ if (previous === undefined)
569
+ return "entity-identity";
570
+ if (previous.kind !== kind)
571
+ return "entity-geometry-kind";
572
+ if (fingerprint === undefined || previous.fingerprint === undefined)
573
+ return "unfingerprintable";
574
+ return previous.fingerprint === fingerprint ? "none" : "entity-configuration";
575
+ }
576
+ function escalatedEntityBoundary(reports) {
577
+ let escalated = "none";
578
+ for (const report of reports) {
579
+ if (CESIUM_ENTITY_REBUILD_BOUNDARIES.indexOf(report.boundary) > CESIUM_ENTITY_REBUILD_BOUNDARIES.indexOf(escalated)) {
580
+ escalated = report.boundary;
581
+ }
582
+ }
583
+ return escalated;
584
+ }
585
+ function boundaryCounts(reports) {
586
+ const counts = {};
587
+ for (const report of reports)
588
+ counts[report.boundary] = (counts[report.boundary] ?? 0) + 1;
589
+ return counts;
590
+ }
591
+ /**
592
+ * Write a changed feature onto the `Entity` that already represents it.
593
+ *
594
+ * Only the facets whose fingerprint moved are assigned, and geometry is written
595
+ * at the deepest field Cesium accepts — `polyline.positions` rather than a fresh
596
+ * `polyline` — so a host that adjusted the graphic's width or material keeps it.
597
+ * Every assignment is journaled with the value it displaced so a later failure
598
+ * in the same refresh can undo it exactly.
599
+ */
600
+ function updateEntityInPlace(collection, previous, next, writes) {
601
+ const live = collection.getById(next.id);
602
+ if (!isMutableRecord(live)) {
603
+ throw new HonuaCesiumEntityAdapterError("renderer-mutation-failed", `Cesium entity "${next.id}" is no longer available for an in-place update.`, { entityId: next.id });
604
+ }
605
+ if (previous.facets.geometry !== next.facets.geometry) {
606
+ for (const write of geometryWrites(live, next))
607
+ applyEntityWrite(write.target, write.key, write.value, writes);
608
+ }
609
+ if (previous.facets.properties !== next.facets.properties) {
610
+ applyEntityWrite(live, "properties", next.spec.properties, writes);
611
+ }
612
+ if (previous.facets.availability !== next.facets.availability) {
613
+ applyEntityWrite(live, "availability", next.spec.availability, writes);
614
+ }
615
+ }
616
+ function geometryWrites(live, next) {
617
+ if (next.kind === "point")
618
+ return [{ target: live, key: "position", value: next.spec.position }];
619
+ const graphicsKey = next.kind === "polyline" ? "polyline" : "polygon";
620
+ const positionsKey = next.kind === "polyline" ? "positions" : "hierarchy";
621
+ const graphics = live[graphicsKey];
622
+ const nextGraphics = next.spec[graphicsKey];
623
+ if (isMutableRecord(graphics) && isMutableRecord(nextGraphics)) {
624
+ return [{ target: graphics, key: positionsKey, value: nextGraphics[positionsKey] }];
625
+ }
626
+ return [{ target: live, key: graphicsKey, value: nextGraphics }];
627
+ }
628
+ function applyEntityWrite(target, key, value, writes) {
629
+ writes.push({ target, key, previous: target[key] });
630
+ target[key] = value;
631
+ }
632
+ function isMutableRecord(value) {
633
+ return typeof value === "object" && value !== null && !Object.isFrozen(value);
346
634
  }
347
635
  function materializeEntity(cesium, item) {
348
636
  const positions = item.geometry.kind === "point"
349
637
  ? [toCartesian(cesium, item.geometry.coordinates)]
350
638
  : item.geometry.coordinates.map((coordinate) => toCartesian(cesium, coordinate));
351
639
  const geometry = item.geometry.kind === "point"
352
- ? { position: positions[0], point: { pixelSize: 8 } }
640
+ ? {
641
+ position: item.positionSamples ? sampledPosition(cesium, item.positionSamples) : positions[0],
642
+ point: { pixelSize: 8 },
643
+ }
353
644
  : item.geometry.kind === "polyline"
354
645
  ? { polyline: { positions, width: 2 } }
355
- : { polygon: { hierarchy: new cesium.PolygonHierarchy(positions) } };
646
+ : {
647
+ polygon: {
648
+ hierarchy: new cesium.PolygonHierarchy(positions, item.geometry.holes?.map((hole) => new cesium.PolygonHierarchy(hole.map((coordinate) => toCartesian(cesium, coordinate))))),
649
+ },
650
+ };
356
651
  const availability = item.interval
357
652
  ? new cesium.TimeIntervalCollection([
358
653
  new cesium.TimeInterval({
@@ -368,6 +663,16 @@ function materializeEntity(cesium, item) {
368
663
  ...(availability ? { availability } : {}),
369
664
  });
370
665
  }
666
+ function sampledPosition(cesium, samples) {
667
+ if (!cesium.SampledPositionProperty) {
668
+ throw new HonuaCesiumEntityAdapterError("peer-unavailable", "The Cesium runtime does not provide SampledPositionProperty for temporal entities.");
669
+ }
670
+ const property = new cesium.SampledPositionProperty();
671
+ for (const sample of samples) {
672
+ property.addSample(cesium.JulianDate.fromIso8601(sample.time), toCartesian(cesium, sample.coordinates));
673
+ }
674
+ return property;
675
+ }
371
676
  function toCartesian(cesium, coordinate) {
372
677
  return cesium.Cartesian3.fromDegrees(coordinate[0], coordinate[1], coordinate[2]);
373
678
  }
@@ -391,22 +696,89 @@ function projectGeometry(feature) {
391
696
  return coordinates && coordinates.length >= 2 ? { kind: "polyline", coordinates } : undefined;
392
697
  }
393
698
  if (geometry.type === "Polygon") {
394
- const rings = geometry.coordinates;
395
- if (!Array.isArray(rings) || rings.length !== 1 || !Object.hasOwn(rings, 0))
396
- return undefined;
397
- const coordinates = coordinates3(rings[0]);
398
- return coordinates && validRing(coordinates) ? { kind: "polygon", coordinates } : undefined;
699
+ const rings = polygonRings(geometry.coordinates);
700
+ return rings
701
+ ? { kind: "polygon", coordinates: rings.outer, ...(rings.holes.length ? { holes: rings.holes } : {}) }
702
+ : undefined;
399
703
  }
400
704
  if (Array.isArray(geometry.paths) && geometry.paths.length === 1 && Object.hasOwn(geometry.paths, 0)) {
401
705
  const coordinates = coordinates3(geometry.paths[0]);
402
706
  return coordinates && coordinates.length >= 2 ? { kind: "polyline", coordinates } : undefined;
403
707
  }
404
- if (Array.isArray(geometry.rings) && geometry.rings.length === 1 && Object.hasOwn(geometry.rings, 0)) {
405
- const coordinates = coordinates3(geometry.rings[0]);
406
- return coordinates && validRing(coordinates) ? { kind: "polygon", coordinates } : undefined;
708
+ if (Array.isArray(geometry.rings)) {
709
+ const rings = polygonRings(geometry.rings, true);
710
+ return rings
711
+ ? { kind: "polygon", coordinates: rings.outer, ...(rings.holes.length ? { holes: rings.holes } : {}) }
712
+ : undefined;
407
713
  }
408
714
  return undefined;
409
715
  }
716
+ function polygonRings(value, esriConvention = false) {
717
+ return polygonRingsWithConvention(value, esriConvention);
718
+ }
719
+ function polygonRingsWithConvention(value, esriConvention) {
720
+ if (!Array.isArray(value) || value.length === 0)
721
+ return undefined;
722
+ for (let index = 0; index < value.length; index += 1) {
723
+ if (!Object.hasOwn(value, index))
724
+ return undefined;
725
+ }
726
+ const projected = value.map((ring) => {
727
+ const coordinates = coordinates3(ring);
728
+ return coordinates && validRing(coordinates) ? coordinates : undefined;
729
+ });
730
+ if (projected.some((ring) => ring === undefined))
731
+ return undefined;
732
+ const rings = projected;
733
+ if (esriConvention)
734
+ return classifyEsriPolygonRings(rings);
735
+ const [outer, ...holes] = rings;
736
+ return { outer, holes };
737
+ }
738
+ function classifyEsriPolygonRings(rings) {
739
+ const exteriors = rings.filter((ring) => signedRingArea(ring) < 0);
740
+ if (exteriors.length !== 1)
741
+ return undefined;
742
+ const outer = exteriors[0];
743
+ if (!outer)
744
+ return undefined;
745
+ const holes = [];
746
+ for (const ring of rings) {
747
+ if (ring === outer)
748
+ continue;
749
+ if (signedRingArea(ring) <= 0 || !ringPointInside(ring[0], outer))
750
+ return undefined;
751
+ holes.push(ring);
752
+ }
753
+ return { outer, holes };
754
+ }
755
+ function signedRingArea(ring) {
756
+ let area = 0;
757
+ for (let index = 0; index < ring.length - 1; index += 1) {
758
+ const current = ring[index];
759
+ const next = ring[index + 1];
760
+ if (!current || !next)
761
+ return 0;
762
+ area += current[0] * next[1] - next[0] * current[1];
763
+ }
764
+ return area / 2;
765
+ }
766
+ function ringPointInside(point, ring) {
767
+ if (!point)
768
+ return false;
769
+ let inside = false;
770
+ for (let index = 0, previous = ring.length - 1; index < ring.length; previous = index, index += 1) {
771
+ const current = ring[index];
772
+ const prior = ring[previous];
773
+ if (!current || !prior)
774
+ return false;
775
+ const intersects = current[1] > point[1] !== prior[1] > point[1] &&
776
+ point[0] < ((prior[0] - current[0]) * (point[1] - current[1])) / (prior[1] - current[1]) + current[0];
777
+ if (intersects)
778
+ inside = !inside;
779
+ }
780
+ return inside;
781
+ }
410
782
  function geometryHasZ(value) {
411
783
  if (!value || typeof value !== "object")
412
784
  return false;
@@ -471,12 +843,45 @@ function validRing(coordinates) {
471
843
  return first !== undefined && last !== undefined && first.every((value, index) => value === last[index]);
472
844
  }
473
845
  function projectInterval(attributes, time) {
846
+ if (time.instantField !== undefined) {
847
+ const instant = isoInstant(attributes[time.instantField]);
848
+ return instant ? { start: instant, end: instant } : undefined;
849
+ }
474
850
  const start = isoInstant(attributes[time.startField]);
475
851
  const end = isoInstant(attributes[time.endField]);
476
852
  if (!start || !end || Date.parse(end) < Date.parse(start))
477
853
  return undefined;
478
854
  return { start, end };
479
855
  }
856
+ function projectPositionSamples(attributes, field, geometry, verticalDatum) {
857
+ if (geometry.kind !== "point")
858
+ return null;
859
+ const value = attributes[field];
860
+ if (value === undefined)
861
+ return null;
862
+ if (!Array.isArray(value) || value.length < 2)
863
+ return null;
864
+ const samples = [];
865
+ let previous = Number.NEGATIVE_INFINITY;
866
+ for (const entry of value) {
867
+ if (!entry || typeof entry !== "object")
868
+ return null;
869
+ const record = entry;
870
+ const time = isoInstant(record.time);
871
+ if (Array.isArray(record.coordinates) && record.coordinates.length >= 3 && verticalDatum !== "ellipsoidal-wgs84") {
872
+ return null;
873
+ }
874
+ const coordinates = coordinate3(record.coordinates);
875
+ if (!time || !coordinates)
876
+ return null;
877
+ const epoch = Date.parse(time);
878
+ if (epoch <= previous)
879
+ return null;
880
+ previous = epoch;
881
+ samples.push({ time, coordinates });
882
+ }
883
+ return samples;
884
+ }
480
885
  function isoInstant(value) {
481
886
  if (typeof value === "number" && !Number.isSafeInteger(value))
482
887
  return undefined;
@@ -539,11 +944,27 @@ function validatePlanAndOptions(source, plan, options) {
539
944
  if (!idField) {
540
945
  throw new HonuaCesiumEntityAdapterError("invalid-option", "Cesium entity projection requires featureIdField or descriptor.schema.primaryKey.");
541
946
  }
542
- if (options.time !== undefined && (!options.time.startField || !options.time.endField)) {
543
- throw new HonuaCesiumEntityAdapterError("invalid-option", "time.startField and time.endField must be non-empty.");
544
- }
947
+ if (options.time !== undefined)
948
+ assertTimeOptions(options.time);
545
949
  return maxEntities;
546
950
  }
951
+ /**
952
+ * Accept exactly one temporal mapping. A mixed instant/interval request is a
953
+ * caller mistake that TypeScript unions and untyped callers cannot both catch,
954
+ * so it is rejected instead of silently preferring either variant.
955
+ */
956
+ function assertTimeOptions(time) {
957
+ const declared = time;
958
+ const instantMapping = declared.instantField !== undefined;
959
+ const intervalMapping = declared.startField !== undefined || declared.endField !== undefined;
960
+ if (instantMapping === intervalMapping) {
961
+ throw new HonuaCesiumEntityAdapterError("invalid-option", "time must declare exactly one of instantField or startField/endField.");
962
+ }
963
+ const fields = instantMapping ? [declared.instantField] : [declared.startField, declared.endField];
964
+ if (fields.some((field) => typeof field !== "string" || field.length === 0)) {
965
+ throw new HonuaCesiumEntityAdapterError("invalid-option", "time.instantField or time.startField/time.endField must be non-empty.");
966
+ }
967
+ }
547
968
  function unsupportedPlan(plan) {
548
969
  return new HonuaCesiumEntityAdapterError("unsupported-plan", "Cesium entity mounting requires one bounded remote feature query with geometry.", { planId: plan.id, operations: plan.steps.map((step) => step.operation) });
549
970
  }
@@ -611,13 +1032,17 @@ function snapshotMountOptions(options) {
611
1032
  ...(options.featureIdField !== undefined ? { featureIdField: options.featureIdField } : {}),
612
1033
  ...(options.maxEntities !== undefined ? { maxEntities: options.maxEntities } : {}),
613
1034
  ...(options.verticalDatum !== undefined ? { verticalDatum: options.verticalDatum } : {}),
614
- ...(options.time
615
- ? {
616
- time: Object.freeze({ startField: options.time.startField, endField: options.time.endField }),
617
- }
618
- : {}),
1035
+ ...(options.time ? { time: snapshotTimeOptions(options.time) } : {}),
619
1036
  });
620
1037
  }
1038
+ /** Validate before narrowing so a mixed mapping cannot be normalized away. */
1039
+ function snapshotTimeOptions(time) {
1040
+ assertTimeOptions(time);
1041
+ const position = time.positionField !== undefined ? { positionField: time.positionField } : {};
1042
+ if (time.instantField !== undefined)
1043
+ return Object.freeze({ instantField: time.instantField, ...position });
1044
+ return Object.freeze({ startField: time.startField, endField: time.endField, ...position });
1045
+ }
621
1046
  function assertLifecycleActive(expectedEpoch, currentEpoch, state, signal) {
622
1047
  if (lifecycleInvalidated(expectedEpoch, currentEpoch, state)) {
623
1048
  throw new DOMException("Cesium entity mount disposed", "AbortError");
@@ -732,16 +1157,16 @@ function removeIdsOrThrow(collection, ids) {
732
1157
  if (failures.length > 0)
733
1158
  throw failures[0];
734
1159
  }
735
- function presentOwnedSpecs(collection, specs) {
1160
+ function presentOwnedRecords(collection, records) {
736
1161
  const present = new Map();
737
- for (const [id, spec] of specs) {
1162
+ for (const [id, record] of records) {
738
1163
  try {
739
1164
  if (collection.getById(id))
740
- present.set(id, spec);
1165
+ present.set(id, record);
741
1166
  }
742
1167
  catch {
743
1168
  // Conservatively retain ownership so a later disposal can retry the id.
744
- present.set(id, spec);
1169
+ present.set(id, record);
745
1170
  }
746
1171
  }
747
1172
  return present;