@honua/react 0.0.20-alpha.0 → 0.1.1-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 +12 -1
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -0
- package/contract/pmtiles.d.ts +193 -0
- package/contract/pmtiles.d.ts.map +1 -0
- package/contract/pmtiles.js +195 -0
- 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 +57 -15
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +368 -65
- package/contract/types.d.ts +171 -6
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +14 -14
- package/core/auth/client-credentials-provider.d.ts +51 -0
- package/core/auth/client-credentials-provider.d.ts.map +1 -0
- package/core/auth/client-credentials-provider.js +115 -0
- package/core/auth/credential-store.d.ts +52 -0
- package/core/auth/credential-store.d.ts.map +1 -0
- package/core/auth/credential-store.js +82 -0
- package/core/auth/index.d.ts +23 -0
- package/core/auth/index.d.ts.map +1 -0
- package/core/auth/index.js +20 -0
- package/core/auth/oauth2-provider.d.ts +114 -0
- package/core/auth/oauth2-provider.d.ts.map +1 -0
- package/core/auth/oauth2-provider.js +408 -0
- package/core/auth/pkce.d.ts +25 -0
- package/core/auth/pkce.d.ts.map +1 -0
- package/core/auth/pkce.js +46 -0
- package/core/auth/static-providers.d.ts +20 -0
- package/core/auth/static-providers.d.ts.map +1 -0
- package/core/auth/static-providers.js +26 -0
- package/core/auth/token-endpoint.d.ts +40 -0
- package/core/auth/token-endpoint.d.ts.map +1 -0
- package/core/auth/token-endpoint.js +133 -0
- package/core/auth/types.d.ts +144 -0
- package/core/auth/types.d.ts.map +1 -0
- package/core/auth/types.js +8 -0
- 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 +35 -2
- package/core/client.d.ts.map +1 -1
- package/core/client.js +167 -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 +73 -25
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +113 -46
- package/core/geoparquet-sql.d.ts +190 -0
- package/core/geoparquet-sql.d.ts.map +1 -0
- package/core/geoparquet-sql.js +368 -0
- 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-endpoint-layout.d.ts +70 -0
- package/core/ogc-endpoint-layout.d.ts.map +1 -0
- package/core/ogc-endpoint-layout.js +173 -0
- package/core/ogc-features.d.ts.map +1 -1
- package/core/ogc-features.js +35 -17
- 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 +55 -0
- package/core/stac-static.d.ts.map +1 -0
- package/core/stac-static.js +386 -0
- package/core/stac.d.ts +12 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +45 -15
- package/core/surfaces.d.ts +13 -0
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +127 -16
- package/core/types.d.ts +174 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +12 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +22 -3
- package/core/wfs.d.ts +13 -0
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +35 -5
- 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/identity-manager.d.ts +43 -0
- package/esri-compat/identity-manager.d.ts.map +1 -1
- package/esri-compat/identity-manager.js +131 -1
- 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/oauth-info.d.ts +15 -0
- package/esri-compat/oauth-info.d.ts.map +1 -1
- package/esri-compat/oauth-info.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 +19 -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 +8 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +4 -4
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +8 -0
- package/runtime/map-package-validation.d.ts +0 -13
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +0 -12
- package/runtime/map-package.d.ts +1 -1
- package/runtime/map-package.d.ts.map +1 -1
- 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/pmtiles-protocol.d.ts +77 -0
- package/runtime/pmtiles-protocol.d.ts.map +1 -0
- package/runtime/pmtiles-protocol.js +120 -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 +36 -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,247 @@
|
|
|
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
|
+
/** Raised for invalid playback options. @experimental */
|
|
19
|
+
export class HonuaTemporalPlaybackError extends HonuaSdkError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super("map.temporal-playback.invalid-option", message);
|
|
22
|
+
this.name = "HonuaTemporalPlaybackError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// ── Implementation ───────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Create a temporal playback controller over a time extent.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const playback = createTemporalPlayback({
|
|
32
|
+
* handle: mounted, // from mountSource()
|
|
33
|
+
* timeField: "event_time",
|
|
34
|
+
* extent: ["2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z"],
|
|
35
|
+
* windowMs: 24 * 60 * 60 * 1000,
|
|
36
|
+
* frameIntervalMs: 400,
|
|
37
|
+
* });
|
|
38
|
+
* playback.on("tick", ({ window, progress }) => updateSlider(progress));
|
|
39
|
+
* playback.play();
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
export function createTemporalPlayback(options) {
|
|
45
|
+
const extentStart = toEpochMs(options.extent?.[0], "extent[0]");
|
|
46
|
+
const extentEnd = toEpochMs(options.extent?.[1], "extent[1]");
|
|
47
|
+
if (extentEnd <= extentStart) {
|
|
48
|
+
throw new HonuaTemporalPlaybackError("extent end must be after extent start.");
|
|
49
|
+
}
|
|
50
|
+
let windowMs = requirePositive(options.windowMs, "windowMs");
|
|
51
|
+
const frameIntervalMs = requirePositive(options.frameIntervalMs ?? 500, "frameIntervalMs");
|
|
52
|
+
const stepMs = requirePositive(options.stepMs ?? options.windowMs, "stepMs");
|
|
53
|
+
if ((options.handle || options.layer) && !options.timeField) {
|
|
54
|
+
throw new HonuaTemporalPlaybackError("timeField is required with the handle/layer bindings.");
|
|
55
|
+
}
|
|
56
|
+
if (!options.handle && !options.layer && !options.apply) {
|
|
57
|
+
throw new HonuaTemporalPlaybackError("Provide at least one sink: handle, layer, or apply.");
|
|
58
|
+
}
|
|
59
|
+
const sinks = buildSinks(options);
|
|
60
|
+
const extent = Object.freeze({ start: extentStart, end: extentEnd });
|
|
61
|
+
const listeners = new Map();
|
|
62
|
+
let windowStart = extentStart;
|
|
63
|
+
let timer;
|
|
64
|
+
let disposed = false;
|
|
65
|
+
// Coalesced application: at most one in-flight window and one pending
|
|
66
|
+
// window; intermediate windows are dropped (bounded memory, REQ-004).
|
|
67
|
+
let inFlight = false;
|
|
68
|
+
let pending;
|
|
69
|
+
const emit = (type, payload) => {
|
|
70
|
+
for (const listener of listeners.get(type) ?? []) {
|
|
71
|
+
try {
|
|
72
|
+
listener(payload);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* listener errors must not break playback */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const currentWindow = () => Object.freeze({ start: windowStart, end: windowStart + windowMs });
|
|
80
|
+
const progress = () => {
|
|
81
|
+
const span = extent.end - extent.start;
|
|
82
|
+
return span <= 0 ? 1 : Math.min(1, Math.max(0, (windowStart - extent.start) / span));
|
|
83
|
+
};
|
|
84
|
+
const drainPending = () => {
|
|
85
|
+
if (pending === undefined)
|
|
86
|
+
return;
|
|
87
|
+
const next = pending;
|
|
88
|
+
pending = undefined;
|
|
89
|
+
applyWindow(next);
|
|
90
|
+
};
|
|
91
|
+
const applyWindow = (window) => {
|
|
92
|
+
if (disposed)
|
|
93
|
+
return;
|
|
94
|
+
if (inFlight) {
|
|
95
|
+
pending = window; // keep only the latest window
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
inFlight = true;
|
|
99
|
+
void Promise.all(sinks.map((sink) => Promise.resolve().then(() => sink(window))))
|
|
100
|
+
.then(() => {
|
|
101
|
+
if (!disposed)
|
|
102
|
+
emit("tick", { window, progress: progress(), playing: timer !== undefined });
|
|
103
|
+
})
|
|
104
|
+
.catch((error) => {
|
|
105
|
+
if (!disposed)
|
|
106
|
+
emit("error", error);
|
|
107
|
+
})
|
|
108
|
+
.finally(() => {
|
|
109
|
+
inFlight = false;
|
|
110
|
+
if (!disposed)
|
|
111
|
+
drainPending();
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const lastStart = () => Math.max(extent.start, extent.end - windowMs);
|
|
115
|
+
const advance = () => {
|
|
116
|
+
if (windowStart >= lastStart()) {
|
|
117
|
+
if (options.loop) {
|
|
118
|
+
windowStart = extent.start;
|
|
119
|
+
applyWindow(currentWindow());
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
pause();
|
|
123
|
+
emit("end");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
windowStart = Math.min(windowStart + stepMs, lastStart());
|
|
127
|
+
applyWindow(currentWindow());
|
|
128
|
+
};
|
|
129
|
+
const play = () => {
|
|
130
|
+
if (disposed || timer !== undefined)
|
|
131
|
+
return;
|
|
132
|
+
if (windowStart >= lastStart() && !options.loop)
|
|
133
|
+
windowStart = extent.start;
|
|
134
|
+
timer = setInterval(advance, frameIntervalMs);
|
|
135
|
+
emit("play");
|
|
136
|
+
applyWindow(currentWindow());
|
|
137
|
+
};
|
|
138
|
+
const pause = () => {
|
|
139
|
+
if (timer === undefined)
|
|
140
|
+
return;
|
|
141
|
+
clearInterval(timer);
|
|
142
|
+
timer = undefined;
|
|
143
|
+
emit("pause");
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
get playing() {
|
|
147
|
+
return timer !== undefined;
|
|
148
|
+
},
|
|
149
|
+
get window() {
|
|
150
|
+
return currentWindow();
|
|
151
|
+
},
|
|
152
|
+
extent,
|
|
153
|
+
play,
|
|
154
|
+
pause,
|
|
155
|
+
scrub(time) {
|
|
156
|
+
if (disposed)
|
|
157
|
+
return;
|
|
158
|
+
windowStart = Math.min(Math.max(toEpochMs(time, "scrub time"), extent.start), lastStart());
|
|
159
|
+
applyWindow(currentWindow());
|
|
160
|
+
},
|
|
161
|
+
setWindow(nextWindowMs) {
|
|
162
|
+
if (disposed)
|
|
163
|
+
return;
|
|
164
|
+
windowMs = requirePositive(nextWindowMs, "windowMs");
|
|
165
|
+
windowStart = Math.min(windowStart, lastStart());
|
|
166
|
+
applyWindow(currentWindow());
|
|
167
|
+
},
|
|
168
|
+
on(type, listener) {
|
|
169
|
+
const set = listeners.get(type) ?? new Set();
|
|
170
|
+
set.add(listener);
|
|
171
|
+
listeners.set(type, set);
|
|
172
|
+
return {
|
|
173
|
+
remove: () => {
|
|
174
|
+
set.delete(listener);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
dispose() {
|
|
179
|
+
if (disposed)
|
|
180
|
+
return;
|
|
181
|
+
disposed = true;
|
|
182
|
+
if (timer !== undefined)
|
|
183
|
+
clearInterval(timer);
|
|
184
|
+
timer = undefined;
|
|
185
|
+
pending = undefined;
|
|
186
|
+
listeners.clear();
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function buildSinks(options) {
|
|
191
|
+
const sinks = [];
|
|
192
|
+
if (options.handle && options.timeField) {
|
|
193
|
+
sinks.push(createHandleSink(options.handle, options.timeField, options.baseQuery, options.formatWhere));
|
|
194
|
+
}
|
|
195
|
+
if (options.layer && options.timeField) {
|
|
196
|
+
sinks.push(createLayerSink(options.layer.map, options.layer.layerIds, options.timeField));
|
|
197
|
+
}
|
|
198
|
+
if (options.apply)
|
|
199
|
+
sinks.push(options.apply);
|
|
200
|
+
return sinks;
|
|
201
|
+
}
|
|
202
|
+
function createHandleSink(handle, timeField, baseQuery, formatWhere) {
|
|
203
|
+
return async (window) => {
|
|
204
|
+
const windowWhere = formatWhere
|
|
205
|
+
? formatWhere(window, timeField)
|
|
206
|
+
: `${timeField} >= ${window.start} AND ${timeField} < ${window.end}`;
|
|
207
|
+
const where = baseQuery?.where ? `(${baseQuery.where}) AND (${windowWhere})` : windowWhere;
|
|
208
|
+
await handle.setFilter({ ...(baseQuery ?? {}), where });
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function createLayerSink(map, layerIds, timeField) {
|
|
212
|
+
// Capture each layer's filter at bind time so the window range composes
|
|
213
|
+
// with (instead of replacing) bridge geometry filters.
|
|
214
|
+
const baseFilters = new Map();
|
|
215
|
+
for (const layerId of layerIds) {
|
|
216
|
+
baseFilters.set(layerId, typeof map.getFilter === "function" ? map.getFilter(layerId) : undefined);
|
|
217
|
+
}
|
|
218
|
+
return (window) => {
|
|
219
|
+
const range = [
|
|
220
|
+
[">=", ["get", timeField], window.start],
|
|
221
|
+
["<", ["get", timeField], window.end],
|
|
222
|
+
];
|
|
223
|
+
for (const layerId of layerIds) {
|
|
224
|
+
const base = baseFilters.get(layerId);
|
|
225
|
+
map.setFilter(layerId, base !== undefined && base !== null ? ["all", base, ...range] : ["all", ...range]);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// ── Time parsing ─────────────────────────────────────────────────
|
|
230
|
+
function toEpochMs(value, label) {
|
|
231
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
232
|
+
return value;
|
|
233
|
+
if (value instanceof Date && !Number.isNaN(value.getTime()))
|
|
234
|
+
return value.getTime();
|
|
235
|
+
if (typeof value === "string") {
|
|
236
|
+
const parsed = Date.parse(value);
|
|
237
|
+
if (!Number.isNaN(parsed))
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
throw new HonuaTemporalPlaybackError(`${label} must be an epoch-ms number, Date, or ISO-8601 string.`);
|
|
241
|
+
}
|
|
242
|
+
function requirePositive(value, label) {
|
|
243
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
244
|
+
throw new HonuaTemporalPlaybackError(`${label} must be a positive, finite number.`);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honua/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1-beta.0",
|
|
4
4
|
"description": "React bindings for the Honua SDK: provider, hooks, and map components",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"gis",
|
|
7
|
+
"geospatial",
|
|
8
|
+
"maplibre",
|
|
9
|
+
"typescript",
|
|
10
|
+
"react",
|
|
11
|
+
"hooks",
|
|
12
|
+
"map",
|
|
13
|
+
"arcgis",
|
|
14
|
+
"ogc-api",
|
|
15
|
+
"geocoding"
|
|
16
|
+
],
|
|
5
17
|
"license": "Apache-2.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/honua-io/honua-sdk-js.git",
|
|
21
|
+
"directory": "."
|
|
22
|
+
},
|
|
6
23
|
"type": "module",
|
|
7
24
|
"main": "./react/index.js",
|
|
8
25
|
"types": "./react/index.d.ts",
|
|
@@ -16,6 +33,7 @@
|
|
|
16
33
|
"@maplibre/maplibre-gl-style-spec": "^24.7.0"
|
|
17
34
|
},
|
|
18
35
|
"peerDependencies": {
|
|
36
|
+
"@honua/sdk": "0.1.1-beta.0",
|
|
19
37
|
"react": "^18.2.0 || ^19.0.0",
|
|
20
38
|
"react-dom": "^18.2.0 || ^19.0.0",
|
|
21
39
|
"maplibre-gl": "^5.0.0"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonValue } from "./types.js";
|
|
2
|
+
/** Deterministic JSON serialization with sorted object keys and strict values. */
|
|
3
|
+
export declare function canonicalStringify(value: JsonValue): string;
|
|
4
|
+
export declare function toJsonValue(value: unknown, path?: string): JsonValue;
|
|
5
|
+
/** Portable synchronous SHA-256 over UTF-8 input (browser, worker, and Node). */
|
|
6
|
+
export declare function sha256(value: string): `sha256:${string}`;
|
|
7
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../../src/query-planner/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAE3D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAM,GAAG,SAAS,CAEjE;AA6DD,iFAAiF;AACjF,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,MAAM,EAAE,CA6DxD"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/** Deterministic JSON serialization with sorted object keys and strict values. */
|
|
2
|
+
export function canonicalStringify(value) {
|
|
3
|
+
return JSON.stringify(sortJson(value));
|
|
4
|
+
}
|
|
5
|
+
export function toJsonValue(value, path = "$") {
|
|
6
|
+
return convertJson(value, path, new WeakSet());
|
|
7
|
+
}
|
|
8
|
+
function convertJson(value, path, ancestors) {
|
|
9
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
10
|
+
return value;
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
if (!Number.isFinite(value))
|
|
13
|
+
throw new TypeError(`${path} must contain only finite numbers`);
|
|
14
|
+
return Object.is(value, -0) ? 0 : value;
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
if (ancestors.has(value))
|
|
18
|
+
throw new TypeError(`${path} must not contain cycles`);
|
|
19
|
+
ancestors.add(value);
|
|
20
|
+
const converted = value.map((entry, index) => {
|
|
21
|
+
if (entry === undefined)
|
|
22
|
+
throw new TypeError(`${path}[${index}] must not be undefined`);
|
|
23
|
+
return convertJson(entry, `${path}[${index}]`, ancestors);
|
|
24
|
+
});
|
|
25
|
+
ancestors.delete(value);
|
|
26
|
+
return converted;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "object") {
|
|
29
|
+
if (ancestors.has(value))
|
|
30
|
+
throw new TypeError(`${path} must not contain cycles`);
|
|
31
|
+
const prototype = Object.getPrototypeOf(value);
|
|
32
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
33
|
+
throw new TypeError(`${path} must contain only plain JSON objects`);
|
|
34
|
+
}
|
|
35
|
+
ancestors.add(value);
|
|
36
|
+
const converted = {};
|
|
37
|
+
for (const key of Object.keys(value).sort()) {
|
|
38
|
+
const entry = value[key];
|
|
39
|
+
if (entry !== undefined) {
|
|
40
|
+
Object.defineProperty(converted, key, {
|
|
41
|
+
value: convertJson(entry, `${path}.${key}`, ancestors),
|
|
42
|
+
enumerable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
ancestors.delete(value);
|
|
49
|
+
return converted;
|
|
50
|
+
}
|
|
51
|
+
throw new TypeError(`${path} contains unsupported ${typeof value}`);
|
|
52
|
+
}
|
|
53
|
+
function sortJson(value) {
|
|
54
|
+
if (Array.isArray(value))
|
|
55
|
+
return value.map(sortJson);
|
|
56
|
+
if (value !== null && typeof value === "object") {
|
|
57
|
+
const object = value;
|
|
58
|
+
const sorted = {};
|
|
59
|
+
for (const key of Object.keys(object).sort()) {
|
|
60
|
+
Object.defineProperty(sorted, key, {
|
|
61
|
+
value: sortJson(object[key]),
|
|
62
|
+
enumerable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return sorted;
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/** Portable synchronous SHA-256 over UTF-8 input (browser, worker, and Node). */
|
|
72
|
+
export function sha256(value) {
|
|
73
|
+
const bytes = new TextEncoder().encode(value);
|
|
74
|
+
const bitLength = bytes.length * 8;
|
|
75
|
+
const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
|
|
76
|
+
const padded = new Uint8Array(paddedLength);
|
|
77
|
+
padded.set(bytes);
|
|
78
|
+
padded[bytes.length] = 0x80;
|
|
79
|
+
const view = new DataView(padded.buffer);
|
|
80
|
+
const high = Math.floor(bitLength / 0x1_0000_0000);
|
|
81
|
+
const low = bitLength >>> 0;
|
|
82
|
+
view.setUint32(paddedLength - 8, high, false);
|
|
83
|
+
view.setUint32(paddedLength - 4, low, false);
|
|
84
|
+
const state = new Uint32Array([
|
|
85
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
86
|
+
]);
|
|
87
|
+
const words = new Uint32Array(64);
|
|
88
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
89
|
+
for (let index = 0; index < 16; index++)
|
|
90
|
+
words[index] = view.getUint32(offset + index * 4, false);
|
|
91
|
+
for (let index = 16; index < 64; index++) {
|
|
92
|
+
const before15 = words[index - 15] ?? 0;
|
|
93
|
+
const before2 = words[index - 2] ?? 0;
|
|
94
|
+
const s0 = rotateRight(before15, 7) ^ rotateRight(before15, 18) ^ (before15 >>> 3);
|
|
95
|
+
const s1 = rotateRight(before2, 17) ^ rotateRight(before2, 19) ^ (before2 >>> 10);
|
|
96
|
+
words[index] = ((words[index - 16] ?? 0) + s0 + (words[index - 7] ?? 0) + s1) >>> 0;
|
|
97
|
+
}
|
|
98
|
+
let a = state[0] ?? 0;
|
|
99
|
+
let b = state[1] ?? 0;
|
|
100
|
+
let c = state[2] ?? 0;
|
|
101
|
+
let d = state[3] ?? 0;
|
|
102
|
+
let e = state[4] ?? 0;
|
|
103
|
+
let f = state[5] ?? 0;
|
|
104
|
+
let g = state[6] ?? 0;
|
|
105
|
+
let h = state[7] ?? 0;
|
|
106
|
+
for (let index = 0; index < 64; index++) {
|
|
107
|
+
const upper1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
108
|
+
const choose = (e & f) ^ (~e & g);
|
|
109
|
+
const temp1 = (h + upper1 + choose + (SHA256_CONSTANTS[index] ?? 0) + (words[index] ?? 0)) >>> 0;
|
|
110
|
+
const upper0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
111
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
112
|
+
const temp2 = (upper0 + majority) >>> 0;
|
|
113
|
+
h = g;
|
|
114
|
+
g = f;
|
|
115
|
+
f = e;
|
|
116
|
+
e = (d + temp1) >>> 0;
|
|
117
|
+
d = c;
|
|
118
|
+
c = b;
|
|
119
|
+
b = a;
|
|
120
|
+
a = (temp1 + temp2) >>> 0;
|
|
121
|
+
}
|
|
122
|
+
state[0] = ((state[0] ?? 0) + a) >>> 0;
|
|
123
|
+
state[1] = ((state[1] ?? 0) + b) >>> 0;
|
|
124
|
+
state[2] = ((state[2] ?? 0) + c) >>> 0;
|
|
125
|
+
state[3] = ((state[3] ?? 0) + d) >>> 0;
|
|
126
|
+
state[4] = ((state[4] ?? 0) + e) >>> 0;
|
|
127
|
+
state[5] = ((state[5] ?? 0) + f) >>> 0;
|
|
128
|
+
state[6] = ((state[6] ?? 0) + g) >>> 0;
|
|
129
|
+
state[7] = ((state[7] ?? 0) + h) >>> 0;
|
|
130
|
+
}
|
|
131
|
+
return `sha256:${Array.from(state, (word) => word.toString(16).padStart(8, "0")).join("")}`;
|
|
132
|
+
}
|
|
133
|
+
function rotateRight(value, amount) {
|
|
134
|
+
return (value >>> amount) | (value << (32 - amount));
|
|
135
|
+
}
|
|
136
|
+
const SHA256_CONSTANTS = new Uint32Array([
|
|
137
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
|
|
138
|
+
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
139
|
+
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
|
|
140
|
+
0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
141
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
|
|
142
|
+
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
143
|
+
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
|
|
144
|
+
0xc67178f2,
|
|
145
|
+
]);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SourceProtocol } from "../contract/schema.js";
|
|
2
|
+
import type { Cql2JsonExpression, Cql2JsonInterchangeOptions, QueryFilter, SourceSpatiality } from "./semantic-types.js";
|
|
3
|
+
type RuntimeFilter = QueryFilter<Record<string, unknown>, SourceProtocol, SourceSpatiality>;
|
|
4
|
+
/**
|
|
5
|
+
* Export the CQL2-representable semantic-filter subset. CQL2 carries spatial
|
|
6
|
+
* CRS outside the expression, so every spatial export requires `filterCrs`
|
|
7
|
+
* and verifies that each operand uses that exact executable binding.
|
|
8
|
+
*/
|
|
9
|
+
export declare function semanticFilterToCql2Json<TRecord, TProtocol extends SourceProtocol, TSpatiality extends SourceSpatiality>(filter: QueryFilter<TRecord, TProtocol, TSpatiality>, options?: Cql2JsonInterchangeOptions): Cql2JsonExpression;
|
|
10
|
+
/**
|
|
11
|
+
* Import the strict CQL2 JSON subset into the same runtime-validated semantic
|
|
12
|
+
* AST used by builders. Unknown functions, property-property comparisons,
|
|
13
|
+
* distance extensions, and other non-representable constructs fail closed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function semanticFilterFromCql2Json(value: string | unknown, options?: Cql2JsonInterchangeOptions): RuntimeFilter;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=cql2-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cql2-json.d.ts","sourceRoot":"","sources":["../../../src/query-planner/cql2-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAIpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,WAAW,EAEX,gBAAgB,EAEjB,MAAM,qBAAqB,CAAC;AAI7B,KAAK,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;AAuE5F;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EACP,SAAS,SAAS,cAAc,EAChC,WAAW,SAAS,gBAAgB,EACpC,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,GAAE,0BAA+B,GAAG,kBAAkB,CAYpH;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,OAAO,EACvB,OAAO,GAAE,0BAA+B,GACvC,aAAa,CAaf"}
|