@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.
- package/collaboration/client.d.ts.map +1 -1
- package/connect-url-safety.d.ts +53 -0
- package/connect-url-safety.js +189 -0
- package/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +87 -18
- package/contract/types.d.ts.map +1 -1
- package/contract/widget-source.d.ts.map +1 -1
- package/controls/basemap-switcher.d.ts +19 -11
- package/controls/basemap-switcher.d.ts.map +1 -1
- package/controls/basemap-switcher.js +58 -25
- package/controls/catalog.d.ts +105 -0
- package/controls/catalog.d.ts.map +1 -0
- package/controls/catalog.js +341 -0
- package/controls/element-utils.d.ts +9 -0
- package/controls/element-utils.d.ts.map +1 -1
- package/controls/element-utils.js +17 -0
- package/controls/index.d.ts +29 -8
- package/controls/index.d.ts.map +1 -1
- package/controls/index.js +26 -8
- package/controls/layer-list.d.ts +13 -5
- package/controls/layer-list.d.ts.map +1 -1
- package/controls/layer-list.js +72 -15
- package/controls/legend.d.ts +32 -5
- package/controls/legend.d.ts.map +1 -1
- package/controls/legend.js +95 -25
- package/controls/qualification.d.ts +152 -0
- package/controls/qualification.d.ts.map +1 -0
- package/controls/qualification.js +1265 -0
- package/controls/registry.d.ts +104 -0
- package/controls/registry.d.ts.map +1 -0
- package/controls/registry.js +150 -0
- package/controls/swipe-control.d.ts +6 -1
- package/controls/swipe-control.d.ts.map +1 -1
- package/controls/swipe-control.js +82 -20
- package/core/client.d.ts +45 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +224 -46
- package/core/error-classifications.d.ts +6 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +6 -0
- package/core/error-code-registry.d.ts +6 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +6 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +68 -4
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +325 -8
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +31 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +136 -19
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +123 -9
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/operator/workspace/workspace.d.ts.map +1 -1
- package/operator/workspace/workspace.js +0 -2
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/honua-server.d.ts +8 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +123 -14
- package/realtime/index.d.ts +4 -0
- package/realtime/index.d.ts.map +1 -1
- package/realtime/index.js +2 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/resumable-transport.d.ts.map +1 -1
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/websocket.d.ts.map +1 -1
- package/realtime/wire.d.ts +2 -1
- package/realtime/wire.d.ts.map +1 -1
- package/realtime/wire.js +24 -8
- package/replica-sync/errors.d.ts +19 -1
- package/replica-sync/errors.d.ts.map +1 -1
- package/replica-sync/errors.js +16 -0
- package/replica-sync/geoservices.d.ts +260 -0
- package/replica-sync/geoservices.d.ts.map +1 -0
- package/replica-sync/geoservices.js +1093 -0
- package/replica-sync/index.d.ts +5 -1
- package/replica-sync/index.d.ts.map +1 -1
- package/replica-sync/index.js +3 -1
- package/replica-sync/transport-conformance.d.ts +58 -0
- package/replica-sync/transport-conformance.d.ts.map +1 -0
- package/replica-sync/transport-conformance.js +265 -0
- package/replica-sync/types.d.ts +22 -0
- package/replica-sync/types.d.ts.map +1 -1
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/scene-workspace/analysis-widgets.d.ts +4 -2
- package/scene-workspace/analysis-widgets.d.ts.map +1 -1
- package/scene-workspace/analysis-widgets.js +4 -2
- package/scene-workspace/camera-correspondence.d.ts +151 -0
- package/scene-workspace/camera-correspondence.d.ts.map +1 -0
- package/scene-workspace/camera-correspondence.js +220 -0
- package/scene-workspace/cesium-adapter.d.ts +230 -29
- package/scene-workspace/cesium-adapter.d.ts.map +1 -1
- package/scene-workspace/cesium-adapter.js +741 -56
- package/scene-workspace/cesium-mount.d.ts +205 -0
- package/scene-workspace/cesium-mount.d.ts.map +1 -0
- package/scene-workspace/cesium-mount.js +392 -0
- package/scene-workspace/cesium-scene-owner.d.ts +160 -0
- package/scene-workspace/cesium-scene-owner.d.ts.map +1 -0
- package/scene-workspace/cesium-scene-owner.js +205 -0
- package/scene-workspace/cesium-state-sync.d.ts +101 -0
- package/scene-workspace/cesium-state-sync.d.ts.map +1 -0
- package/scene-workspace/cesium-state-sync.js +432 -0
- package/scene-workspace/cesium-time.d.ts +346 -0
- package/scene-workspace/cesium-time.d.ts.map +1 -0
- package/scene-workspace/cesium-time.js +403 -0
- package/scene-workspace/index.d.ts +42 -10
- package/scene-workspace/index.d.ts.map +1 -1
- package/scene-workspace/index.js +32 -7
- package/scene-workspace/maplibre-state-sync.d.ts +112 -0
- package/scene-workspace/maplibre-state-sync.d.ts.map +1 -0
- package/scene-workspace/maplibre-state-sync.js +400 -0
- package/scene-workspace/primitives.d.ts +237 -3
- package/scene-workspace/primitives.d.ts.map +1 -1
- package/scene-workspace/primitives.js +1087 -17
- package/scene-workspace/scene-discovery.d.ts +5 -2
- package/scene-workspace/scene-discovery.d.ts.map +1 -1
- package/scene-workspace/scene-discovery.js +5 -2
- package/scene-workspace/scene-view.d.ts +5 -2
- package/scene-workspace/scene-view.d.ts.map +1 -1
- package/scene-workspace/scene-view.js +5 -2
- package/scene-workspace/source-to-cesium.d.ts +141 -8
- package/scene-workspace/source-to-cesium.d.ts.map +1 -1
- package/scene-workspace/source-to-cesium.js +497 -72
- package/scene-workspace/state-sync-port-kit.d.ts +156 -0
- package/scene-workspace/state-sync-port-kit.d.ts.map +1 -0
- package/scene-workspace/state-sync-port-kit.js +319 -0
- package/scene-workspace/state-sync.d.ts +34 -1
- package/scene-workspace/state-sync.d.ts.map +1 -1
- package/scene-workspace/state-sync.js +48 -0
- package/scene-workspace/types.d.ts +8 -0
- package/scene-workspace/types.d.ts.map +1 -1
- package/scene-workspace/types.js +2 -0
- package/scene-workspace/workspace.d.ts +12 -1
- package/scene-workspace/workspace.d.ts.map +1 -1
- package/scene-workspace/workspace.js +13 -0
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/web-components/controller.d.ts.map +1 -1
- package/web-components/csp-styles.d.ts +6 -0
- package/web-components/csp-styles.d.ts.map +1 -0
- package/web-components/csp-styles.js +53 -0
- package/web-components/elements.d.ts +158 -2
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +1302 -215
- package/web-components/export-redaction.d.ts +252 -0
- package/web-components/export-redaction.d.ts.map +1 -0
- package/web-components/export-redaction.js +588 -0
- package/web-components/export.d.ts +417 -0
- package/web-components/export.d.ts.map +1 -0
- package/web-components/export.js +719 -0
- package/web-components/feature-editor-model.d.ts +265 -0
- package/web-components/feature-editor-model.d.ts.map +1 -0
- package/web-components/feature-editor-model.js +438 -0
- package/web-components/feature-editor-sketch.d.ts +125 -0
- package/web-components/feature-editor-sketch.d.ts.map +1 -0
- package/web-components/feature-editor-sketch.js +194 -0
- package/web-components/feature-editor-workflow.d.ts +286 -0
- package/web-components/feature-editor-workflow.d.ts.map +1 -0
- package/web-components/feature-editor-workflow.js +889 -0
- package/web-components/feature-editor.d.ts +83 -0
- package/web-components/feature-editor.d.ts.map +1 -0
- package/web-components/feature-editor.js +829 -0
- package/web-components/feature-table-engine.d.ts +465 -0
- package/web-components/feature-table-engine.d.ts.map +1 -0
- package/web-components/feature-table-engine.js +1360 -0
- package/web-components/feature-table-view.d.ts +95 -0
- package/web-components/feature-table-view.d.ts.map +1 -0
- package/web-components/feature-table-view.js +343 -0
- package/web-components/index.d.ts +34 -3
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +35 -2
- package/web-components/maplibre-renderer.d.ts.map +1 -1
- package/web-components/maplibre-renderer.js +13 -4
- package/web-components/measurement.d.ts +3 -1
- package/web-components/measurement.d.ts.map +1 -1
- package/web-components/measurement.js +62 -18
- package/web-components/time-slider.d.ts +122 -0
- package/web-components/time-slider.d.ts.map +1 -0
- package/web-components/time-slider.js +740 -0
- package/web-components/types.d.ts +310 -0
- package/web-components/types.d.ts.map +1 -1
- package/webmap/convert-label.js +0 -1
- 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
|
-
*
|
|
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
|
|
156
|
-
assertEntityIdsAvailable(collection,
|
|
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,
|
|
218
|
+
for (const [id, record] of initialRecords) {
|
|
161
219
|
initialSignal.throwIfAborted();
|
|
162
220
|
attemptedInitialIds.push(id);
|
|
163
|
-
mounted.set(id,
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
222
|
-
for (const [id, spec] of previous) {
|
|
354
|
+
for (const write of [...writes].reverse()) {
|
|
223
355
|
try {
|
|
224
|
-
|
|
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
|
|
239
|
-
const disposalCleanupFailures = removeIds(collection, [...
|
|
240
|
-
mounted =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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
|
|
382
|
+
throw mutationError("Failed to reconcile the mounted Cesium entity snapshot.", cause, [
|
|
260
383
|
...cleanupFailures,
|
|
261
384
|
...restoreFailures,
|
|
262
385
|
]);
|
|
263
386
|
}
|
|
264
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
? {
|
|
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
|
-
: {
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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)
|
|
405
|
-
const
|
|
406
|
-
return
|
|
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
|
|
543
|
-
|
|
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
|
|
1160
|
+
function presentOwnedRecords(collection, records) {
|
|
736
1161
|
const present = new Map();
|
|
737
|
-
for (const [id,
|
|
1162
|
+
for (const [id, record] of records) {
|
|
738
1163
|
try {
|
|
739
1164
|
if (collection.getById(id))
|
|
740
|
-
present.set(id,
|
|
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,
|
|
1169
|
+
present.set(id, record);
|
|
745
1170
|
}
|
|
746
1171
|
}
|
|
747
1172
|
return present;
|