@honua/react 0.1.0-beta.0 → 0.1.2-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/contract/discovery.d.ts +231 -0
- package/contract/discovery.d.ts.map +1 -0
- package/contract/discovery.js +554 -0
- package/contract/edit-sketch.d.ts +8 -0
- package/contract/edit-sketch.d.ts.map +1 -1
- package/contract/edit-sketch.js +15 -0
- package/contract/edit-snapping.d.ts +160 -0
- package/contract/edit-snapping.d.ts.map +1 -0
- package/contract/edit-snapping.js +675 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
- package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
- package/contract/index.d.ts +11 -2
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -1
- package/contract/pmtiles.d.ts +31 -0
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +60 -20
- package/contract/protocol-module.d.ts +70 -0
- package/contract/protocol-module.d.ts.map +1 -0
- package/contract/protocol-module.js +1 -0
- package/contract/schema-envelope.d.ts +26 -0
- package/contract/schema-envelope.d.ts.map +1 -0
- package/contract/schema-envelope.js +25 -0
- package/contract/schema.d.ts +582 -0
- package/contract/schema.d.ts.map +1 -0
- package/contract/schema.js +2483 -0
- package/contract/source-capability-support.d.ts +6 -0
- package/contract/source-capability-support.d.ts.map +1 -0
- package/contract/source-capability-support.js +154 -0
- package/contract/source.d.ts +35 -17
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +267 -71
- package/contract/types.d.ts +140 -4
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +6 -14
- package/core/bounded-json.d.ts +4 -0
- package/core/bounded-json.d.ts.map +1 -0
- package/core/bounded-json.js +38 -0
- package/core/cache-state.d.ts +9 -0
- package/core/cache-state.d.ts.map +1 -1
- package/core/cache-state.js +10 -0
- package/core/capabilities-xml.d.ts +33 -0
- package/core/capabilities-xml.d.ts.map +1 -0
- package/core/capabilities-xml.js +331 -0
- package/core/client.d.ts +4 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +115 -29
- package/core/error-classifications.d.ts +153 -0
- package/core/error-classifications.d.ts.map +1 -0
- package/core/error-classifications.js +140 -0
- package/core/error-code-registry.d.ts +158 -0
- package/core/error-code-registry.d.ts.map +1 -0
- package/core/error-code-registry.js +143 -0
- package/core/error-envelope.d.ts +68 -0
- package/core/error-envelope.d.ts.map +1 -0
- package/core/error-envelope.js +357 -0
- package/core/errors.d.ts +45 -29
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +97 -50
- package/core/geoparquet-sql.d.ts +30 -2
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +38 -3
- package/core/odata-key-path.d.ts +12 -0
- package/core/odata-key-path.d.ts.map +1 -0
- package/core/odata-key-path.js +28 -0
- package/core/odata-write-codec.d.ts +66 -0
- package/core/odata-write-codec.d.ts.map +1 -0
- package/core/odata-write-codec.js +1083 -0
- package/core/odata.d.ts +59 -7
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +431 -99
- package/core/ogc-features.d.ts.map +1 -1
- package/core/ogc-features.js +3 -0
- package/core/ogc-maps.d.ts +7 -0
- package/core/ogc-maps.d.ts.map +1 -1
- package/core/ogc-maps.js +46 -7
- package/core/ogc-processes.d.ts +2 -1
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +33 -5
- package/core/ogc-records.d.ts +9 -0
- package/core/ogc-records.d.ts.map +1 -1
- package/core/ogc-records.js +78 -29
- package/core/ogc-tiles.d.ts +10 -1
- package/core/ogc-tiles.d.ts.map +1 -1
- package/core/ogc-tiles.js +56 -17
- package/core/path-utils.d.ts +9 -0
- package/core/path-utils.d.ts.map +1 -1
- package/core/path-utils.js +19 -0
- package/core/protocol-transport.d.ts +1 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +1 -1
- package/core/spatial-filter.d.ts +8 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +206 -16
- package/core/stac-static.d.ts +6 -2
- package/core/stac-static.d.ts.map +1 -1
- package/core/stac-static.js +260 -69
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +69 -5
- package/core/types.d.ts +125 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +2 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +8 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +10 -0
- package/core/wms-capabilities.d.ts +56 -84
- package/core/wms-capabilities.d.ts.map +1 -1
- package/core/wms-capabilities.js +212 -509
- package/core/wmts-capabilities.d.ts +62 -62
- package/core/wmts-capabilities.d.ts.map +1 -1
- package/core/wmts-capabilities.js +224 -371
- package/esri-compat/editor.d.ts +8 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +13 -0
- package/esri-compat/feature-layer.d.ts +2 -0
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +1 -0
- package/esri-compat/layer-list.d.ts +11 -0
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +49 -0
- package/esri-compat/legend.d.ts +10 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +27 -0
- package/esri-compat/portal.d.ts +197 -0
- package/esri-compat/portal.d.ts.map +1 -0
- package/esri-compat/portal.js +251 -0
- package/esri-compat/renderer-objects.d.ts +40 -0
- package/esri-compat/renderer-objects.d.ts.map +1 -0
- package/esri-compat/renderer-objects.js +62 -0
- package/esri-compat/sketch.d.ts +43 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +46 -0
- package/esri-compat/snapping.d.ts +43 -0
- package/esri-compat/snapping.d.ts.map +1 -0
- package/esri-compat/snapping.js +45 -0
- package/esri-compat/url.d.ts.map +1 -1
- package/esri-compat/url.js +1 -1
- package/esri-compat/widget-host.d.ts +84 -0
- package/esri-compat/widget-host.d.ts.map +1 -0
- package/esri-compat/widget-host.js +159 -0
- package/filter-registry/index.d.ts +145 -0
- package/filter-registry/index.d.ts.map +1 -0
- package/filter-registry/index.js +531 -0
- package/generated-app/errors.d.ts +18 -2
- package/generated-app/errors.d.ts.map +1 -1
- package/generated-app/errors.js +44 -11
- package/map/automatic-mount-integration.d.ts +134 -0
- package/map/automatic-mount-integration.d.ts.map +1 -0
- package/map/automatic-mount-integration.js +305 -0
- package/map/automatic-source-strategy.d.ts +125 -0
- package/map/automatic-source-strategy.d.ts.map +1 -0
- package/map/automatic-source-strategy.js +494 -0
- package/map/data-to-map-bridge.d.ts +293 -0
- package/map/data-to-map-bridge.d.ts.map +1 -0
- package/map/data-to-map-bridge.js +817 -0
- package/map/geojson-projection.d.ts +33 -0
- package/map/geojson-projection.d.ts.map +1 -0
- package/map/geojson-projection.js +118 -0
- package/map/index.d.ts +14 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +8 -0
- package/map/raster-source-spec.d.ts +30 -0
- package/map/raster-source-spec.d.ts.map +1 -0
- package/map/raster-source-spec.js +190 -0
- package/map/raster-source-strategy.d.ts +65 -0
- package/map/raster-source-strategy.d.ts.map +1 -0
- package/map/raster-source-strategy.js +232 -0
- package/map/source-to-maplibre.d.ts +91 -0
- package/map/source-to-maplibre.d.ts.map +1 -0
- package/map/source-to-maplibre.js +454 -0
- package/map/temporal-playback.d.ts +139 -0
- package/map/temporal-playback.d.ts.map +1 -0
- package/map/temporal-playback.js +247 -0
- package/package.json +14 -1
- package/query-planner/canonical.d.ts +7 -0
- package/query-planner/canonical.d.ts.map +1 -0
- package/query-planner/canonical.js +145 -0
- package/query-planner/cql2-json.d.ts +17 -0
- package/query-planner/cql2-json.d.ts.map +1 -0
- package/query-planner/cql2-json.js +646 -0
- package/query-planner/diagnostics.d.ts +35 -0
- package/query-planner/diagnostics.d.ts.map +1 -0
- package/query-planner/diagnostics.js +373 -0
- package/query-planner/duckdb.d.ts +79 -0
- package/query-planner/duckdb.d.ts.map +1 -0
- package/query-planner/duckdb.js +758 -0
- package/query-planner/execution-context.d.ts +5 -0
- package/query-planner/execution-context.d.ts.map +1 -0
- package/query-planner/execution-context.js +50 -0
- package/query-planner/executor.d.ts +5 -0
- package/query-planner/executor.d.ts.map +1 -0
- package/query-planner/executor.js +223 -0
- package/query-planner/geoservices.d.ts +91 -0
- package/query-planner/geoservices.d.ts.map +1 -0
- package/query-planner/geoservices.js +667 -0
- package/query-planner/grpc.d.ts +100 -0
- package/query-planner/grpc.d.ts.map +1 -0
- package/query-planner/grpc.js +736 -0
- package/query-planner/index.d.ts +44 -0
- package/query-planner/index.d.ts.map +1 -0
- package/query-planner/index.js +30 -0
- package/query-planner/ir.d.ts +20 -0
- package/query-planner/ir.d.ts.map +1 -0
- package/query-planner/ir.js +417 -0
- package/query-planner/local-aggregate.d.ts +5 -0
- package/query-planner/local-aggregate.d.ts.map +1 -0
- package/query-planner/local-aggregate.js +126 -0
- package/query-planner/odata.d.ts +48 -0
- package/query-planner/odata.d.ts.map +1 -0
- package/query-planner/odata.js +662 -0
- package/query-planner/ogc-compiler.d.ts +55 -0
- package/query-planner/ogc-compiler.d.ts.map +1 -0
- package/query-planner/ogc-compiler.js +480 -0
- package/query-planner/ogc-features.d.ts +53 -0
- package/query-planner/ogc-features.d.ts.map +1 -0
- package/query-planner/ogc-features.js +617 -0
- package/query-planner/planner.d.ts +17 -0
- package/query-planner/planner.d.ts.map +1 -0
- package/query-planner/planner.js +818 -0
- package/query-planner/resource.d.ts +94 -0
- package/query-planner/resource.d.ts.map +1 -0
- package/query-planner/resource.js +526 -0
- package/query-planner/semantic-canonical.d.ts +15 -0
- package/query-planner/semantic-canonical.d.ts.map +1 -0
- package/query-planner/semantic-canonical.js +62 -0
- package/query-planner/semantic-compat.d.ts +10 -0
- package/query-planner/semantic-compat.d.ts.map +1 -0
- package/query-planner/semantic-compat.js +35 -0
- package/query-planner/semantic-compiler.d.ts +64 -0
- package/query-planner/semantic-compiler.d.ts.map +1 -0
- package/query-planner/semantic-compiler.js +81 -0
- package/query-planner/semantic-literals.d.ts +30 -0
- package/query-planner/semantic-literals.d.ts.map +1 -0
- package/query-planner/semantic-literals.js +133 -0
- package/query-planner/semantic-types.d.ts +265 -0
- package/query-planner/semantic-types.d.ts.map +1 -0
- package/query-planner/semantic-types.js +1 -0
- package/query-planner/semantic.d.ts +54 -0
- package/query-planner/semantic.d.ts.map +1 -0
- package/query-planner/semantic.js +1421 -0
- package/query-planner/types.d.ts +560 -0
- package/query-planner/types.d.ts.map +1 -0
- package/query-planner/types.js +49 -0
- package/query-planner/wfs-semantic.d.ts +58 -0
- package/query-planner/wfs-semantic.d.ts.map +1 -0
- package/query-planner/wfs-semantic.js +547 -0
- package/query-planner/wfs.d.ts +6 -0
- package/query-planner/wfs.d.ts.map +1 -0
- package/query-planner/wfs.js +71 -0
- package/react/external-map.d.ts +67 -0
- package/react/external-map.d.ts.map +1 -0
- package/react/external-map.js +63 -0
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +3 -2
- package/react/honua-source-layer.d.ts +110 -0
- package/react/honua-source-layer.d.ts.map +1 -0
- package/react/honua-source-layer.js +101 -0
- package/react/hooks.d.ts +28 -0
- package/react/hooks.d.ts.map +1 -1
- package/react/hooks.js +26 -4
- package/react/index.d.ts +8 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +5 -0
- package/react/query-cache.d.ts +26 -4
- package/react/query-cache.d.ts.map +1 -1
- package/react/query-cache.js +51 -8
- package/react/selection.d.ts +177 -0
- package/react/selection.d.ts.map +1 -0
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +69 -0
- package/react/use-mounted-source.d.ts.map +1 -0
- package/react/use-mounted-source.js +346 -0
- package/runtime/edit-snapping.d.ts +107 -0
- package/runtime/edit-snapping.d.ts.map +1 -0
- package/runtime/edit-snapping.js +190 -0
- package/runtime/errors.d.ts +3 -2
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +19 -2
- package/runtime/index.d.ts +6 -0
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -0
- package/runtime/maplibre-renderer.d.ts +41 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -0
- package/runtime/maplibre-renderer.js +322 -0
- package/runtime/query-tiles.d.ts +2 -1
- package/runtime/query-tiles.d.ts.map +1 -1
- package/runtime/query-tiles.js +6 -2
- package/runtime/source-bridge.d.ts +4 -4
- package/runtime/source-bridge.d.ts.map +1 -1
- package/runtime/source-bridge.js +9 -56
- package/runtime/style-interactions.d.ts +3 -2
- package/runtime/style-interactions.d.ts.map +1 -1
- package/runtime/style-interactions.js +11 -3
- package/runtime/terra-draw-sketch.d.ts +275 -0
- package/runtime/terra-draw-sketch.d.ts.map +1 -0
- package/runtime/terra-draw-sketch.js +404 -0
- package/source-capability-registry.d.ts +1 -0
- package/source-capability-registry.js +1 -0
- package/source-capability-types.d.ts +186 -0
- package/source-capability-types.js +7 -0
- package/style/index.d.ts +2 -0
- package/style/index.d.ts.map +1 -1
- package/style/index.js +2 -0
- package/style/renderers.d.ts +326 -0
- package/style/renderers.d.ts.map +1 -0
- package/style/renderers.js +517 -0
- package/webmap/convert-renderer.d.ts +24 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +93 -112
- package/webmap/index.d.ts +1 -1
- package/webmap/index.d.ts.map +1 -1
- package/webmap/index.js +1 -1
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-bound canonical Source → MapLibre GeoJSON workflow.
|
|
3
|
+
*
|
|
4
|
+
* This module is peer-injected and safe to import in Node/SSR/worker code: it
|
|
5
|
+
* never imports `maplibre-gl`. The first production slice deliberately covers
|
|
6
|
+
* exact feature-query plans; tile and realtime strategies remain separate.
|
|
7
|
+
*/
|
|
8
|
+
import { HonuaSdkError, mergeHonuaErrorContext } from "../core/error-envelope.js";
|
|
9
|
+
import { HonuaCapabilityNotSupportedError } from "../core/errors.js";
|
|
10
|
+
import { canonicalStringify, toJsonValue } from "../query-planner/canonical.js";
|
|
11
|
+
import { assertQueryPlanExecutionContextV1 } from "../query-planner/execution-context.js";
|
|
12
|
+
import { queryFromCanonical, queryIrSourceIdentity } from "../query-planner/ir.js";
|
|
13
|
+
import { hashQueryPlanV1 } from "../query-planner/planner.js";
|
|
14
|
+
import { HonuaQueryPlanExecutionError, } from "../query-planner/types.js";
|
|
15
|
+
import { canonicalFeaturesToGeoJson, defaultPaint, geometryKind, geometryKinds, layerType, mapLibreGeometryType, removeUndefined, safeId, } from "./geojson-projection.js";
|
|
16
|
+
/** A stable, machine-readable adapter failure. */
|
|
17
|
+
export class HonuaMapLibreSourceAdapterError extends HonuaSdkError {
|
|
18
|
+
code;
|
|
19
|
+
detail;
|
|
20
|
+
constructor(code, message, detail, options = {}) {
|
|
21
|
+
super(MAPLIBRE_SOURCE_ADAPTER_CODES[code], message, {
|
|
22
|
+
...options,
|
|
23
|
+
context: mergeHonuaErrorContext(detail, options.context),
|
|
24
|
+
});
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.detail = detail;
|
|
27
|
+
this.name = "HonuaMapLibreSourceAdapterError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const MAPLIBRE_SOURCE_ADAPTER_CODES = {
|
|
31
|
+
disposed: "map.source-adapter.disposed",
|
|
32
|
+
"source-conflict": "map.source-adapter.source-conflict",
|
|
33
|
+
"layer-conflict": "map.source-adapter.layer-conflict",
|
|
34
|
+
"unsupported-plan": "map.source-adapter.unsupported-plan",
|
|
35
|
+
"invalid-option": "map.source-adapter.invalid-option",
|
|
36
|
+
"map-mutation-failed": "map.source-adapter.map-mutation-failed",
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Project an already-executed plan result into native MapLibre source/layers.
|
|
40
|
+
* This function is deterministic and does not mutate a map.
|
|
41
|
+
*/
|
|
42
|
+
export function projectSourceToMapLibre(source, plan, result, options = {}) {
|
|
43
|
+
assertProjectionPlanContext(source, plan);
|
|
44
|
+
assertFeaturePlan(plan, result);
|
|
45
|
+
assertQueryable(source);
|
|
46
|
+
validateStaticOptions(options);
|
|
47
|
+
const sourceId = options.sourceId ?? `honua-${safeId(source.descriptor.id)}`;
|
|
48
|
+
const layerId = options.layerId ?? `${sourceId}-features`;
|
|
49
|
+
const converted = canonicalFeaturesToGeoJson(result.features, source.descriptor.schema?.primaryKey);
|
|
50
|
+
const presentKinds = geometryKinds(converted.data.features);
|
|
51
|
+
const requestedKind = options.geometry === "auto" || options.geometry === undefined ? undefined : options.geometry;
|
|
52
|
+
// Auto mode installs a stable three-layer geometry matrix. Refresh can then
|
|
53
|
+
// move between geometry kinds through setData without structural layer churn.
|
|
54
|
+
const renderKinds = requestedKind
|
|
55
|
+
? [requestedKind]
|
|
56
|
+
: options.cluster
|
|
57
|
+
? ["point"]
|
|
58
|
+
: ["point", "line", "polygon"];
|
|
59
|
+
if (options.cluster &&
|
|
60
|
+
(requestedKind !== undefined ? requestedKind !== "point" : presentKinds.some((kind) => kind !== "point"))) {
|
|
61
|
+
throw new HonuaMapLibreSourceAdapterError("invalid-option", "MapLibre clustering is supported only for point GeoJSON sources.", { sourceId, geometryKinds: renderKinds });
|
|
62
|
+
}
|
|
63
|
+
const diagnostics = [
|
|
64
|
+
diagnostic(source, plan, "strategy-selected", "info", "project", "exact", "Selected exact bounded GeoJSON query strategy."),
|
|
65
|
+
];
|
|
66
|
+
if (result.features.length === 0) {
|
|
67
|
+
diagnostics.push(diagnostic(source, plan, "empty-result", "info", "execute", "exact", "The accepted plan returned no features.", {
|
|
68
|
+
rowCount: 0,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
if (result.exceededTransferLimit) {
|
|
72
|
+
diagnostics.push(diagnostic(source, plan, "transfer-limit", "warning", "execute", "equivalent", "The accepted bounded query returned a page while additional source rows exist.", { renderedRowCount: result.features.length }));
|
|
73
|
+
}
|
|
74
|
+
if (result.degraded && result.degraded.length > 0) {
|
|
75
|
+
diagnostics.push(diagnostic(source, plan, "source-degraded", "warning", "execute", "equivalent", "The source reported degraded execution semantics.", { reasons: result.degraded.map((entry) => entry.reason) }));
|
|
76
|
+
}
|
|
77
|
+
if (converted.unsupported > 0) {
|
|
78
|
+
diagnostics.push(diagnostic(source, plan, "geometry-unsupported", "warning", "project", "unsupported", `${converted.unsupported} feature geometry value(s) could not be projected and remain attribute-only.`, { unsupportedGeometryCount: converted.unsupported }));
|
|
79
|
+
}
|
|
80
|
+
const mismatchedGeometryCount = requestedKind
|
|
81
|
+
? converted.data.features.filter((feature) => {
|
|
82
|
+
const kind = geometryKind(feature.geometry);
|
|
83
|
+
return kind !== undefined && kind !== requestedKind;
|
|
84
|
+
}).length
|
|
85
|
+
: 0;
|
|
86
|
+
if (requestedKind && mismatchedGeometryCount > 0) {
|
|
87
|
+
diagnostics.push(diagnostic(source, plan, "geometry-mismatch", "warning", "project", "unsupported", `${mismatchedGeometryCount} feature(s) do not match the explicit ${requestedKind} geometry layer and will not render.`, {
|
|
88
|
+
requestedGeometry: requestedKind,
|
|
89
|
+
mismatchedGeometryCount,
|
|
90
|
+
renderedGeometryCount: result.features.length - mismatchedGeometryCount - converted.unsupported,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
if (presentKinds.length > 1 && !requestedKind) {
|
|
94
|
+
diagnostics.push(diagnostic(source, plan, "mixed-geometry", "warning", "project", "equivalent", "Mixed geometry output uses one filtered layer per geometry kind.", {
|
|
95
|
+
geometryKinds: presentKinds,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
const mapSource = {
|
|
99
|
+
type: "geojson",
|
|
100
|
+
data: converted.data,
|
|
101
|
+
promoteId: source.descriptor.schema?.primaryKey,
|
|
102
|
+
attribution: options.attribution ?? source.descriptor.attribution,
|
|
103
|
+
...(options.cluster
|
|
104
|
+
? { cluster: true, clusterRadius: positiveInteger(options.clusterRadius, 50, "clusterRadius") }
|
|
105
|
+
: {}),
|
|
106
|
+
};
|
|
107
|
+
removeUndefined(mapSource);
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
strategy: "geojson-query",
|
|
110
|
+
sourceId,
|
|
111
|
+
layerId,
|
|
112
|
+
planId: plan.id,
|
|
113
|
+
planFingerprint: plan.fingerprint,
|
|
114
|
+
sourceVersion: plan.ir.source.sourceVersion,
|
|
115
|
+
schemaVersion: plan.ir.source.schemaVersion,
|
|
116
|
+
authorizationScope: plan.ir.source.authorizationScope,
|
|
117
|
+
source: Object.freeze(mapSource),
|
|
118
|
+
layers: Object.freeze(renderKinds.map((kind, index) => Object.freeze({
|
|
119
|
+
id: renderKinds.length === 1 ? layerId : `${layerId}-${kind}`,
|
|
120
|
+
type: layerType(kind),
|
|
121
|
+
source: sourceId,
|
|
122
|
+
filter: ["==", ["geometry-type"], mapLibreGeometryType(kind)],
|
|
123
|
+
paint: { ...defaultPaint(kind), ...(options.paint ?? {}) },
|
|
124
|
+
...(options.layout ? { layout: { ...options.layout } } : {}),
|
|
125
|
+
metadata: {
|
|
126
|
+
"honua:strategy": "geojson-query",
|
|
127
|
+
"honua:planId": plan.id,
|
|
128
|
+
"honua:planFingerprint": plan.fingerprint,
|
|
129
|
+
"honua:sourceVersion": plan.ir.source.sourceVersion,
|
|
130
|
+
"honua:geometryOrder": index,
|
|
131
|
+
},
|
|
132
|
+
}))),
|
|
133
|
+
diagnostics: Object.freeze(diagnostics),
|
|
134
|
+
state: diagnostics.some((entry) => entry.severity === "warning")
|
|
135
|
+
? "degraded"
|
|
136
|
+
: result.features.length === 0
|
|
137
|
+
? "empty"
|
|
138
|
+
: "ready",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute an accepted plan, mount its projection, and return one lifecycle.
|
|
143
|
+
* Omitted execution bindings inherit the accepted plan; explicit overrides
|
|
144
|
+
* remain fail-closed when they describe another context.
|
|
145
|
+
*/
|
|
146
|
+
export async function mountSourceToMapLibre(map, source, plan, options = {}) {
|
|
147
|
+
assertProjectionPlanContext(source, plan);
|
|
148
|
+
assertFeaturePlan(plan);
|
|
149
|
+
assertQueryable(source);
|
|
150
|
+
validateStaticOptions(options);
|
|
151
|
+
const intendedSourceId = options.sourceId ?? `honua-${safeId(source.descriptor.id)}`;
|
|
152
|
+
if (map.getSource(intendedSourceId)) {
|
|
153
|
+
throw new HonuaMapLibreSourceAdapterError("source-conflict", `MapLibre source "${intendedSourceId}" already exists.`, {
|
|
154
|
+
sourceId: intendedSourceId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
for (const layerId of projectedLayerIds(intendedSourceId, options)) {
|
|
158
|
+
if (map.getLayer(layerId)) {
|
|
159
|
+
throw new HonuaMapLibreSourceAdapterError("layer-conflict", `MapLibre layer "${layerId}" already exists.`, {
|
|
160
|
+
layerId,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const lifecycleController = new AbortController();
|
|
165
|
+
const executeOptions = {
|
|
166
|
+
...executionOptions(plan, options),
|
|
167
|
+
signal: combineSignals([lifecycleController.signal, options.signal]),
|
|
168
|
+
};
|
|
169
|
+
const result = await executeAcceptedFeaturePlan(plan, source, executeOptions);
|
|
170
|
+
throwIfAborted(executeOptions.signal);
|
|
171
|
+
let projection = projectSourceToMapLibre(source, plan, result, options);
|
|
172
|
+
throwIfAborted(executeOptions.signal);
|
|
173
|
+
assertMapIdsAvailable(map, projection);
|
|
174
|
+
const attemptedLayerIds = [];
|
|
175
|
+
try {
|
|
176
|
+
map.addSource(projection.sourceId, projection.source);
|
|
177
|
+
for (const layer of projection.layers) {
|
|
178
|
+
attemptedLayerIds.push(String(layer.id));
|
|
179
|
+
map.addLayer(layer, options.beforeId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (cause) {
|
|
183
|
+
const rollbackFailures = rollbackMapMutation(map, projection.sourceId, attemptedLayerIds);
|
|
184
|
+
throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Failed to mount source "${projection.sourceId}" transactionally.`, {
|
|
185
|
+
sourceId: projection.sourceId,
|
|
186
|
+
attemptedLayerIds,
|
|
187
|
+
rollbackFailureCount: rollbackFailures.length,
|
|
188
|
+
rollbackFailures: rollbackFailures.map(errorMessage),
|
|
189
|
+
}, { cause });
|
|
190
|
+
}
|
|
191
|
+
let state = projection.state;
|
|
192
|
+
let diagnostics = [...projection.diagnostics];
|
|
193
|
+
const layerIds = projection.layers.map((layer) => String(layer.id));
|
|
194
|
+
const isDisposed = () => state === "disposed";
|
|
195
|
+
let refreshTail = Promise.resolve();
|
|
196
|
+
const runRefresh = async (refreshOptions) => {
|
|
197
|
+
if (isDisposed())
|
|
198
|
+
throw new HonuaMapLibreSourceAdapterError("disposed", "Cannot refresh a disposed MapLibre source mount.");
|
|
199
|
+
const effectiveSignal = combineSignals([lifecycleController.signal, refreshOptions.signal]);
|
|
200
|
+
throwIfAborted(effectiveSignal);
|
|
201
|
+
const nextExecutionOptions = {
|
|
202
|
+
...executeOptions,
|
|
203
|
+
...refreshOptions,
|
|
204
|
+
signal: effectiveSignal,
|
|
205
|
+
};
|
|
206
|
+
const nextResult = await executeAcceptedFeaturePlan(plan, source, nextExecutionOptions);
|
|
207
|
+
throwIfAborted(effectiveSignal);
|
|
208
|
+
if (isDisposed())
|
|
209
|
+
throw new HonuaMapLibreSourceAdapterError("disposed", "MapLibre source mount was disposed during refresh.");
|
|
210
|
+
const next = projectSourceToMapLibre(source, plan, nextResult, options);
|
|
211
|
+
throwIfAborted(effectiveSignal);
|
|
212
|
+
const handle = map.getSource(projection.sourceId);
|
|
213
|
+
if (!handle || typeof handle.setData !== "function") {
|
|
214
|
+
throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Mounted source "${projection.sourceId}" no longer exposes setData().`);
|
|
215
|
+
}
|
|
216
|
+
throwIfAborted(effectiveSignal);
|
|
217
|
+
const previousData = projection.source.data;
|
|
218
|
+
try {
|
|
219
|
+
handle.setData(next.source.data);
|
|
220
|
+
}
|
|
221
|
+
catch (cause) {
|
|
222
|
+
let rollbackFailure;
|
|
223
|
+
try {
|
|
224
|
+
handle.setData(previousData);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
rollbackFailure = error;
|
|
228
|
+
}
|
|
229
|
+
state = "degraded";
|
|
230
|
+
diagnostics = [
|
|
231
|
+
...diagnostics,
|
|
232
|
+
diagnostic(source, plan, "incremental-update-failed", "warning", "update", "unsupported", rollbackFailure
|
|
233
|
+
? "MapLibre setData failed and the previous projection could not be restored."
|
|
234
|
+
: "MapLibre setData failed; the previous projection was restored.", { rollbackSucceeded: rollbackFailure === undefined }),
|
|
235
|
+
];
|
|
236
|
+
throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Failed to refresh mounted source "${projection.sourceId}" through setData().`, {
|
|
237
|
+
sourceId: projection.sourceId,
|
|
238
|
+
planId: plan.id,
|
|
239
|
+
rollbackSucceeded: rollbackFailure === undefined,
|
|
240
|
+
...(rollbackFailure ? { rollbackFailure: errorMessage(rollbackFailure) } : {}),
|
|
241
|
+
}, { cause });
|
|
242
|
+
}
|
|
243
|
+
diagnostics = [
|
|
244
|
+
...next.diagnostics,
|
|
245
|
+
diagnostic(source, plan, "incremental-update", "info", "update", "exact", "Updated GeoJSON data in place with setData()."),
|
|
246
|
+
];
|
|
247
|
+
projection = { ...next, diagnostics };
|
|
248
|
+
state = next.state;
|
|
249
|
+
return projection;
|
|
250
|
+
};
|
|
251
|
+
const enqueueRefresh = (refreshOptions) => {
|
|
252
|
+
const result = refreshTail.then(() => runRefresh(refreshOptions));
|
|
253
|
+
refreshTail = result.then(() => undefined, () => undefined);
|
|
254
|
+
return result;
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
strategy: "geojson-query",
|
|
258
|
+
sourceId: projection.sourceId,
|
|
259
|
+
layerIds,
|
|
260
|
+
planId: plan.id,
|
|
261
|
+
planFingerprint: plan.fingerprint,
|
|
262
|
+
get state() {
|
|
263
|
+
return state;
|
|
264
|
+
},
|
|
265
|
+
get diagnostics() {
|
|
266
|
+
return diagnostics;
|
|
267
|
+
},
|
|
268
|
+
refresh(refreshOptions = {}) {
|
|
269
|
+
return enqueueRefresh(refreshOptions);
|
|
270
|
+
},
|
|
271
|
+
dispose() {
|
|
272
|
+
if (state === "disposed")
|
|
273
|
+
return;
|
|
274
|
+
lifecycleController.abort(new DOMException("MapLibre source mount disposed", "AbortError"));
|
|
275
|
+
const failures = [];
|
|
276
|
+
for (const layerId of [...layerIds].reverse()) {
|
|
277
|
+
try {
|
|
278
|
+
if (map.getLayer(layerId))
|
|
279
|
+
map.removeLayer(layerId);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
failures.push(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
if (map.getSource(projection.sourceId))
|
|
287
|
+
map.removeSource(projection.sourceId);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
failures.push(error);
|
|
291
|
+
}
|
|
292
|
+
state = "disposed";
|
|
293
|
+
if (failures.length > 0) {
|
|
294
|
+
throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `MapLibre source "${projection.sourceId}" disposal completed with ${failures.length} renderer failure(s).`, { sourceId: projection.sourceId, failureCount: failures.length }, { cause: failures[0] });
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function assertQueryable(source) {
|
|
300
|
+
if (!source.capabilities.has("query")) {
|
|
301
|
+
throw new HonuaCapabilityNotSupportedError("query", source.descriptor.protocol, source.descriptor.id);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function assertProjectionPlanContext(source, plan) {
|
|
305
|
+
if (hashQueryPlanV1(plan) !== plan.fingerprint) {
|
|
306
|
+
throw new HonuaQueryPlanExecutionError("invalid-plan", "Plan content does not match its fingerprint; project only the accepted immutable plan.");
|
|
307
|
+
}
|
|
308
|
+
const identity = queryIrSourceIdentity(source.descriptor, {
|
|
309
|
+
schemaVersion: plan.ir.source.schemaVersion,
|
|
310
|
+
sourceVersion: plan.ir.source.sourceVersion,
|
|
311
|
+
authorizationScope: plan.ir.source.authorizationScope,
|
|
312
|
+
});
|
|
313
|
+
const runtimeCapabilities = [...source.capabilities].sort();
|
|
314
|
+
const descriptorCapabilities = [...source.descriptor.capabilities].sort();
|
|
315
|
+
const planCapabilities = [...plan.ir.source.capabilities].sort();
|
|
316
|
+
if (canonicalStringify(toJsonValue(identity)) !== canonicalStringify(toJsonValue(plan.ir.source)) ||
|
|
317
|
+
canonicalStringify(toJsonValue(runtimeCapabilities)) !== canonicalStringify(toJsonValue(descriptorCapabilities)) ||
|
|
318
|
+
canonicalStringify(toJsonValue(runtimeCapabilities)) !== canonicalStringify(toJsonValue(planCapabilities))) {
|
|
319
|
+
throw new HonuaQueryPlanExecutionError("plan-context-mismatch", "Source identity, descriptor capabilities, or runtime capabilities do not match the accepted plan projection context.");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function assertFeaturePlan(plan, result) {
|
|
323
|
+
const first = plan.steps[0];
|
|
324
|
+
const featureShape = plan.ir.query.aggregation === undefined &&
|
|
325
|
+
plan.ir.query.returnGeometry !== false &&
|
|
326
|
+
plan.steps.length === 1 &&
|
|
327
|
+
first?.engine === "remote" &&
|
|
328
|
+
(first.operation === "query" || first.operation === "queryAll");
|
|
329
|
+
if (!featureShape || result?.aggregateRows !== undefined) {
|
|
330
|
+
throw new HonuaMapLibreSourceAdapterError("unsupported-plan", "MapLibre GeoJSON mounting requires a feature-query plan; aggregate or client-materialization plans are not renderable by this strategy.", { planId: plan.id, operations: plan.steps.map((step) => step.operation) });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function executeAcceptedFeaturePlan(plan, source, options) {
|
|
334
|
+
try {
|
|
335
|
+
assertQueryPlanExecutionContextV1(plan, source, options);
|
|
336
|
+
const step = plan.steps[0];
|
|
337
|
+
if (!step || step.engine !== "remote" || (step.operation !== "query" && step.operation !== "queryAll")) {
|
|
338
|
+
throw new HonuaMapLibreSourceAdapterError("unsupported-plan", "MapLibre GeoJSON mounting requires one remote feature-query step.", { planId: plan.id });
|
|
339
|
+
}
|
|
340
|
+
const query = queryFromCanonical(step.query, options.signal);
|
|
341
|
+
return step.operation === "queryAll" ? await source.queryAll(query) : await source.query(query);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
// Preserve this bridge's established AbortSignal reason while the shared
|
|
345
|
+
// canonical execution-context boundary supplies plan validation.
|
|
346
|
+
if (options.signal?.aborted)
|
|
347
|
+
options.signal.throwIfAborted();
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function assertMapIdsAvailable(map, projection) {
|
|
352
|
+
if (map.getSource(projection.sourceId)) {
|
|
353
|
+
throw new HonuaMapLibreSourceAdapterError("source-conflict", `MapLibre source "${projection.sourceId}" already exists.`, {
|
|
354
|
+
sourceId: projection.sourceId,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
for (const layer of projection.layers) {
|
|
358
|
+
if (map.getLayer(String(layer.id))) {
|
|
359
|
+
throw new HonuaMapLibreSourceAdapterError("layer-conflict", `MapLibre layer "${String(layer.id)}" already exists.`, {
|
|
360
|
+
layerId: layer.id,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function rollbackMapMutation(map, sourceId, layerIds) {
|
|
366
|
+
const failures = [];
|
|
367
|
+
for (const layerId of [...layerIds].reverse()) {
|
|
368
|
+
try {
|
|
369
|
+
if (map.getLayer(layerId))
|
|
370
|
+
map.removeLayer(layerId);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
failures.push(error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
if (map.getSource(sourceId))
|
|
378
|
+
map.removeSource(sourceId);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
failures.push(error);
|
|
382
|
+
}
|
|
383
|
+
return failures;
|
|
384
|
+
}
|
|
385
|
+
function diagnostic(source, plan, code, severity, stage, fidelity, message, detail) {
|
|
386
|
+
return {
|
|
387
|
+
code,
|
|
388
|
+
severity,
|
|
389
|
+
stage,
|
|
390
|
+
fidelity,
|
|
391
|
+
sourceId: source.descriptor.id,
|
|
392
|
+
planId: plan.id,
|
|
393
|
+
message,
|
|
394
|
+
...(detail ? { detail } : {}),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function executionOptions(plan, options) {
|
|
398
|
+
const schemaVersion = options.schemaVersion ?? plan.ir.source.schemaVersion;
|
|
399
|
+
const sourceVersion = options.sourceVersion ?? plan.ir.source.sourceVersion;
|
|
400
|
+
const authorizationScope = options.authorizationScope ?? plan.ir.source.authorizationScope;
|
|
401
|
+
const discovery = options.discovery ?? plan.provenance.discovery;
|
|
402
|
+
const executionMode = options.executionMode ?? plan.validity.executionMode;
|
|
403
|
+
return {
|
|
404
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
405
|
+
...(schemaVersion !== undefined ? { schemaVersion } : {}),
|
|
406
|
+
...(sourceVersion !== undefined ? { sourceVersion } : {}),
|
|
407
|
+
...(authorizationScope !== undefined ? { authorizationScope } : {}),
|
|
408
|
+
...(discovery !== undefined ? { discovery } : {}),
|
|
409
|
+
...(executionMode !== undefined ? { executionMode } : {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function combineSignals(signals) {
|
|
413
|
+
const available = signals.filter((signal) => signal !== undefined);
|
|
414
|
+
return available.length === 1 ? available[0] : AbortSignal.any(available);
|
|
415
|
+
}
|
|
416
|
+
function throwIfAborted(signal) {
|
|
417
|
+
signal.throwIfAborted();
|
|
418
|
+
}
|
|
419
|
+
function errorMessage(error) {
|
|
420
|
+
return error instanceof Error ? error.message : String(error);
|
|
421
|
+
}
|
|
422
|
+
function positiveInteger(value, fallback, name) {
|
|
423
|
+
if (value === undefined)
|
|
424
|
+
return fallback;
|
|
425
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
426
|
+
throw new HonuaMapLibreSourceAdapterError("invalid-option", `${name} must be a positive integer.`, {
|
|
427
|
+
[name]: value,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return value;
|
|
431
|
+
}
|
|
432
|
+
function validateStaticOptions(options) {
|
|
433
|
+
if (options.clusterRadius !== undefined && options.cluster !== true) {
|
|
434
|
+
throw new HonuaMapLibreSourceAdapterError("invalid-option", "clusterRadius requires cluster: true.", {
|
|
435
|
+
clusterRadius: options.clusterRadius,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
if (options.cluster) {
|
|
439
|
+
positiveInteger(options.clusterRadius, 50, "clusterRadius");
|
|
440
|
+
if (options.geometry !== undefined && options.geometry !== "auto" && options.geometry !== "point") {
|
|
441
|
+
throw new HonuaMapLibreSourceAdapterError("invalid-option", "MapLibre clustering is supported only for point GeoJSON sources.", { geometry: options.geometry });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function projectedLayerIds(sourceId, options) {
|
|
446
|
+
const base = options.layerId ?? `${sourceId}-features`;
|
|
447
|
+
const kind = options.geometry === "auto" ? undefined : options.geometry;
|
|
448
|
+
const kinds = kind
|
|
449
|
+
? [kind]
|
|
450
|
+
: options.cluster
|
|
451
|
+
? ["point"]
|
|
452
|
+
: ["point", "line", "polygon"];
|
|
453
|
+
return kinds.length === 1 ? [base] : kinds.map((entry) => `${base}-${entry}`);
|
|
454
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal playback (issue #497 REQ-004): a renderer-neutral play/pause/
|
|
3
|
+
* scrub controller over a time extent, plus thin bindings that drive time
|
|
4
|
+
* filters through the data-to-map bridge (`MountedSource.setFilter`) or
|
|
5
|
+
* directly through MapLibre layer filters.
|
|
6
|
+
*
|
|
7
|
+
* Memory is bounded by construction: the controller keeps only the current
|
|
8
|
+
* window (two numbers). Filter application is coalesced — at most one
|
|
9
|
+
* request in flight and at most one pending window; intermediate windows
|
|
10
|
+
* are dropped, never queued. No window data is pre-fetched or cached by the
|
|
11
|
+
* controller itself (the issue's snapshot-plus-delta realtime vocabulary
|
|
12
|
+
* from #393 applies only when playback is layered over a realtime feed;
|
|
13
|
+
* playback over static data has no realtime dependency).
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { HonuaSdkError } from "../core/error-envelope.js";
|
|
18
|
+
/** A half-open time window `[start, end)` in epoch milliseconds. @experimental */
|
|
19
|
+
export interface TemporalWindow {
|
|
20
|
+
readonly start: number;
|
|
21
|
+
readonly end: number;
|
|
22
|
+
}
|
|
23
|
+
/** Accepted time inputs: epoch ms, `Date`, or an ISO-8601 string. @experimental */
|
|
24
|
+
export type TemporalInstant = number | Date | string;
|
|
25
|
+
/** Payload for `"tick"` listeners. @experimental */
|
|
26
|
+
export interface TemporalPlaybackTick {
|
|
27
|
+
/** The window that was just applied. */
|
|
28
|
+
readonly window: TemporalWindow;
|
|
29
|
+
/** Window-start progress through the extent, 0..1. */
|
|
30
|
+
readonly progress: number;
|
|
31
|
+
readonly playing: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Event names emitted by the controller. @experimental */
|
|
34
|
+
export type TemporalPlaybackEventType = "tick" | "play" | "pause" | "end" | "error";
|
|
35
|
+
/** Listener registration handle. @experimental */
|
|
36
|
+
export interface TemporalPlaybackSubscription {
|
|
37
|
+
remove(): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bridge-shaped filter sink: the subset of `MountedSource` the playback
|
|
41
|
+
* binding needs. Any object with a Query-shaped `setFilter` works.
|
|
42
|
+
*
|
|
43
|
+
* @experimental
|
|
44
|
+
*/
|
|
45
|
+
export interface TemporalFilterHandle {
|
|
46
|
+
setFilter(query: {
|
|
47
|
+
where?: string;
|
|
48
|
+
} | undefined): Promise<unknown>;
|
|
49
|
+
}
|
|
50
|
+
/** MapLibre-shaped filter sink for client-side (already materialized) data. @experimental */
|
|
51
|
+
export interface TemporalFilterMap {
|
|
52
|
+
setFilter(layerId: string, filter: unknown): void;
|
|
53
|
+
getFilter?(layerId: string): unknown;
|
|
54
|
+
}
|
|
55
|
+
/** Options for {@link createTemporalPlayback}. @experimental */
|
|
56
|
+
export interface TemporalPlaybackOptions {
|
|
57
|
+
/** Full playable time extent as `[start, end]`. */
|
|
58
|
+
readonly extent: readonly [TemporalInstant, TemporalInstant];
|
|
59
|
+
/** Window length in milliseconds. */
|
|
60
|
+
readonly windowMs: number;
|
|
61
|
+
/** Wall-clock delay between frames. @default 500 */
|
|
62
|
+
readonly frameIntervalMs?: number;
|
|
63
|
+
/** How far the window advances per frame. @default `windowMs` (non-overlapping windows) */
|
|
64
|
+
readonly stepMs?: number;
|
|
65
|
+
/** Restart from the extent start after the last window. @default false */
|
|
66
|
+
readonly loop?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Bridge binding: a `MountedSource` (or anything with a Query-shaped
|
|
69
|
+
* `setFilter`). Each window becomes a `where` clause on `timeField`.
|
|
70
|
+
*/
|
|
71
|
+
readonly handle?: TemporalFilterHandle;
|
|
72
|
+
/**
|
|
73
|
+
* MapLibre binding: client-side `setFilter` over already-materialized
|
|
74
|
+
* layers. Each window becomes a numeric range filter on `timeField`
|
|
75
|
+
* composed (`all`) with the layer's filter at bind time, so bridge
|
|
76
|
+
* geometry filters survive. Requires numeric (epoch ms) properties.
|
|
77
|
+
*/
|
|
78
|
+
readonly layer?: {
|
|
79
|
+
readonly map: TemporalFilterMap;
|
|
80
|
+
readonly layerIds: readonly string[];
|
|
81
|
+
};
|
|
82
|
+
/** Custom sink; use instead of (or in addition to) `handle`/`layer`. */
|
|
83
|
+
readonly apply?: (window: TemporalWindow) => void | Promise<void>;
|
|
84
|
+
/** Feature property holding the feature's time. Required with `handle`/`layer`. */
|
|
85
|
+
readonly timeField?: string;
|
|
86
|
+
/** Extra query properties merged under the generated `where` (handle binding). */
|
|
87
|
+
readonly baseQuery?: Readonly<Record<string, unknown>> & {
|
|
88
|
+
readonly where?: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Override the generated `where` clause (handle binding). The default is
|
|
92
|
+
* `<timeField> >= <startMs> AND <timeField> < <endMs>`.
|
|
93
|
+
*/
|
|
94
|
+
readonly formatWhere?: (window: TemporalWindow, timeField: string) => string;
|
|
95
|
+
}
|
|
96
|
+
/** Playback controller returned by {@link createTemporalPlayback}. @experimental */
|
|
97
|
+
export interface TemporalPlayback {
|
|
98
|
+
readonly playing: boolean;
|
|
99
|
+
/** The current window (epoch ms, half-open). */
|
|
100
|
+
readonly window: TemporalWindow;
|
|
101
|
+
/** The normalized extent (epoch ms). */
|
|
102
|
+
readonly extent: TemporalWindow;
|
|
103
|
+
/** Start advancing one window per frame. Applies the current window immediately. */
|
|
104
|
+
play(): void;
|
|
105
|
+
pause(): void;
|
|
106
|
+
/** Jump the window start to a time (clamped to the extent) and apply it. */
|
|
107
|
+
scrub(time: TemporalInstant): void;
|
|
108
|
+
/** Change the window length, keeping the current start, and apply it. */
|
|
109
|
+
setWindow(windowMs: number): void;
|
|
110
|
+
on(type: "tick", listener: (event: TemporalPlaybackTick) => void): TemporalPlaybackSubscription;
|
|
111
|
+
on(type: "play" | "pause" | "end", listener: () => void): TemporalPlaybackSubscription;
|
|
112
|
+
on(type: "error", listener: (error: unknown) => void): TemporalPlaybackSubscription;
|
|
113
|
+
/** Stop the timer and drop all listeners. Idempotent. */
|
|
114
|
+
dispose(): void;
|
|
115
|
+
}
|
|
116
|
+
/** Raised for invalid playback options. @experimental */
|
|
117
|
+
export declare class HonuaTemporalPlaybackError extends HonuaSdkError {
|
|
118
|
+
constructor(message: string);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a temporal playback controller over a time extent.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const playback = createTemporalPlayback({
|
|
126
|
+
* handle: mounted, // from mountSource()
|
|
127
|
+
* timeField: "event_time",
|
|
128
|
+
* extent: ["2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z"],
|
|
129
|
+
* windowMs: 24 * 60 * 60 * 1000,
|
|
130
|
+
* frameIntervalMs: 400,
|
|
131
|
+
* });
|
|
132
|
+
* playback.on("tick", ({ window, progress }) => updateSlider(progress));
|
|
133
|
+
* playback.play();
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @experimental
|
|
137
|
+
*/
|
|
138
|
+
export declare function createTemporalPlayback(options: TemporalPlaybackOptions): TemporalPlayback;
|
|
139
|
+
//# sourceMappingURL=temporal-playback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal-playback.d.ts","sourceRoot":"","sources":["../../../src/map/temporal-playback.ts"],"names":[],"mappings":"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"}
|