@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,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static STAC catalog reader.
|
|
3
|
+
*
|
|
4
|
+
* A large share of public STAC catalogs are not API servers — they are
|
|
5
|
+
* static `catalog.json` / `collection.json` / item trees served from object
|
|
6
|
+
* storage, with no `/search` endpoint. This reader walks such a tree the
|
|
7
|
+
* spec-driven way: it follows `rel="child"` links to descend catalogs /
|
|
8
|
+
* collections and `rel="item"` links to reach STAC Items, resolving every
|
|
9
|
+
* href against the document it was found in (STAC best-practice relative
|
|
10
|
+
* links). Filtering (`collections` / `bbox` / `datetime` / `limit`) is
|
|
11
|
+
* applied client-side and the result is projected onto the same
|
|
12
|
+
* {@link HonuaStacItemCollectionResponse} envelope the STAC API search path
|
|
13
|
+
* returns, so the canonical `Source` surface is identical across both
|
|
14
|
+
* layouts.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { boundedJsonStructureViolation } from "./bounded-json.js";
|
|
19
|
+
import { HonuaAbortError, HonuaCapabilityNotSupportedError } from "./errors.js";
|
|
20
|
+
/** Guardrails match `connect()` static-STAC admission defaults and hard caps. */
|
|
21
|
+
const DEFAULT_MAX_DOCUMENTS = 32;
|
|
22
|
+
const DEFAULT_MAX_DEPTH = 4;
|
|
23
|
+
const DEFAULT_MAX_LINKS_PER_DOCUMENT = 64;
|
|
24
|
+
const DEFAULT_MAX_DOCUMENT_BYTES = 1024 * 1024;
|
|
25
|
+
const HARD_MAX_DOCUMENTS = 64;
|
|
26
|
+
const HARD_MAX_DEPTH = 8;
|
|
27
|
+
const HARD_MAX_LINKS_PER_DOCUMENT = 128;
|
|
28
|
+
const HARD_MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
|
|
29
|
+
const MAX_STATIC_JSON_DEPTH = 64;
|
|
30
|
+
const MAX_STATIC_JSON_NODES = 100_000;
|
|
31
|
+
const JSON_MEDIA_TYPES = new Set(["application/json", "application/geo+json"]);
|
|
32
|
+
/**
|
|
33
|
+
* Reader bound to the URL of a static STAC catalog / collection root. The
|
|
34
|
+
* URL is the client baseUrl (the transport resolves same-origin absolute
|
|
35
|
+
* child/item hrefs directly), so `rootUrl` is normally `""` (the baseUrl
|
|
36
|
+
* itself) but may be an absolute `catalog.json` URL.
|
|
37
|
+
*/
|
|
38
|
+
export class HonuaStacStaticCatalog {
|
|
39
|
+
client;
|
|
40
|
+
rootUrl;
|
|
41
|
+
policy;
|
|
42
|
+
constructor(client, rootUrl = "", policy) {
|
|
43
|
+
this.client = client;
|
|
44
|
+
this.rootUrl = canonicalRuntimeUrl(rootUrl || client.serverBaseUrl, client.serverBaseUrl);
|
|
45
|
+
this.policy = normalizeRuntimePolicy(policy);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Traverse the catalog tree, collect matching items, and return them in
|
|
49
|
+
* the STAC search envelope shape.
|
|
50
|
+
*/
|
|
51
|
+
async search(params = {}) {
|
|
52
|
+
const items = await this.collectItems(params);
|
|
53
|
+
const filtered = items.filter((item) => matchesFilters(item, params));
|
|
54
|
+
const cap = typeof params.limit === "number" && params.limit >= 0 ? params.limit : filtered.length;
|
|
55
|
+
const sliced = filtered.slice(0, cap);
|
|
56
|
+
return {
|
|
57
|
+
type: "FeatureCollection",
|
|
58
|
+
features: sliced,
|
|
59
|
+
numberReturned: sliced.length,
|
|
60
|
+
numberMatched: filtered.length,
|
|
61
|
+
links: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async fetchDoc(url, signal) {
|
|
65
|
+
const response = await this.client.pipelineFetch("GET", url, { headers: { Accept: "application/json, application/geo+json;q=0.9" } }, signal, { redirect: "error" });
|
|
66
|
+
const contentType = mediaEssence(response.headers.get("content-type") ?? "");
|
|
67
|
+
if (contentType && !JSON_MEDIA_TYPES.has(contentType)) {
|
|
68
|
+
await response.body?.cancel().catch(() => undefined);
|
|
69
|
+
throw new Error("static STAC document did not return JSON");
|
|
70
|
+
}
|
|
71
|
+
const finalUrl = canonicalRuntimeUrl(response.url || url, url);
|
|
72
|
+
if (new URL(finalUrl).origin !== new URL(this.rootUrl).origin) {
|
|
73
|
+
await response.body?.cancel().catch(() => undefined);
|
|
74
|
+
throw new Error("static STAC redirect left the admitted origin");
|
|
75
|
+
}
|
|
76
|
+
const value = await readBoundedJson(response, this.policy.maxDocumentBytes);
|
|
77
|
+
if (!isPlainObject(value))
|
|
78
|
+
throw new Error("static STAC document must be a JSON object");
|
|
79
|
+
if (value.links !== undefined && !Array.isArray(value.links)) {
|
|
80
|
+
throw new Error("static STAC links must be an array");
|
|
81
|
+
}
|
|
82
|
+
return { value: value, url: finalUrl };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Breadth-first walk collecting item documents. Descends `rel="child"`
|
|
86
|
+
* links; a `collections` filter prunes Collection subtrees whose `id`
|
|
87
|
+
* is not requested. Every attempted document, traversal depth, per-document
|
|
88
|
+
* link list, response body, redirect, and origin is bounded by the locator's
|
|
89
|
+
* admitted policy.
|
|
90
|
+
*/
|
|
91
|
+
async collectItems(params) {
|
|
92
|
+
const wantedCollections = params.collections ? new Set(params.collections) : undefined;
|
|
93
|
+
const visitedRequests = new Set();
|
|
94
|
+
const completedUrls = new Set();
|
|
95
|
+
const queue = [{ url: this.rootUrl, depth: 0 }];
|
|
96
|
+
const items = [];
|
|
97
|
+
let attempts = 0;
|
|
98
|
+
while (queue.length > 0 && attempts < this.policy.maxDocuments) {
|
|
99
|
+
throwIfRuntimeAborted(params.signal);
|
|
100
|
+
const next = queue.shift();
|
|
101
|
+
if (!next || visitedRequests.has(next.url))
|
|
102
|
+
continue;
|
|
103
|
+
visitedRequests.add(next.url);
|
|
104
|
+
attempts += 1;
|
|
105
|
+
let fetched;
|
|
106
|
+
try {
|
|
107
|
+
fetched = await this.fetchDoc(next.url, params.signal);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (params.signal?.aborted || err instanceof HonuaAbortError)
|
|
111
|
+
throw new HonuaAbortError();
|
|
112
|
+
if (next.depth === 0) {
|
|
113
|
+
throw new HonuaCapabilityNotSupportedError("query", "stac", `static catalog root ${this.rootUrl} could not be read: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (completedUrls.has(fetched.url))
|
|
118
|
+
continue;
|
|
119
|
+
completedUrls.add(fetched.url);
|
|
120
|
+
visitedRequests.add(fetched.url);
|
|
121
|
+
const doc = fetched.value;
|
|
122
|
+
if (doc.type === "Feature") {
|
|
123
|
+
if (isMinimallyValidStacItem(doc)) {
|
|
124
|
+
items.push(doc);
|
|
125
|
+
}
|
|
126
|
+
else if (next.depth === 0) {
|
|
127
|
+
throw new HonuaCapabilityNotSupportedError("query", "stac", "static catalog root Feature is not a minimally valid STAC Item");
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// A Collection subtree not in the requested set is pruned entirely.
|
|
132
|
+
if (wantedCollections &&
|
|
133
|
+
doc.type === "Collection" &&
|
|
134
|
+
typeof doc.id === "string" &&
|
|
135
|
+
!wantedCollections.has(doc.id)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (next.depth >= this.policy.maxDepth)
|
|
139
|
+
continue;
|
|
140
|
+
for (const link of (doc.links ?? []).slice(0, this.policy.maxLinksPerDocument)) {
|
|
141
|
+
if (!isPlainObject(link))
|
|
142
|
+
continue;
|
|
143
|
+
if (typeof link.href !== "string")
|
|
144
|
+
continue;
|
|
145
|
+
const rel = typeof link.rel === "string" ? link.rel.toLowerCase() : "";
|
|
146
|
+
if (rel !== "child" && rel !== "item")
|
|
147
|
+
continue;
|
|
148
|
+
if (typeof link.type === "string" && !JSON_MEDIA_TYPES.has(mediaEssence(link.type)))
|
|
149
|
+
continue;
|
|
150
|
+
const resolved = safeTraversalUrl(link.href, fetched.url, this.rootUrl);
|
|
151
|
+
if (resolved && !visitedRequests.has(resolved))
|
|
152
|
+
queue.push({ url: resolved, depth: next.depth + 1 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return items;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function safeTraversalUrl(href, base, root) {
|
|
159
|
+
let url;
|
|
160
|
+
try {
|
|
161
|
+
url = new URL(href, base);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
167
|
+
url.username ||
|
|
168
|
+
url.password ||
|
|
169
|
+
url.search ||
|
|
170
|
+
url.hash ||
|
|
171
|
+
url.origin !== new URL(root).origin) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return url.toString();
|
|
175
|
+
}
|
|
176
|
+
function canonicalRuntimeUrl(value, base) {
|
|
177
|
+
const url = new URL(value, base);
|
|
178
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
179
|
+
url.username ||
|
|
180
|
+
url.password ||
|
|
181
|
+
url.search ||
|
|
182
|
+
url.hash) {
|
|
183
|
+
throw new HonuaCapabilityNotSupportedError("query", "stac", "static STAC URLs must be credential-free HTTP(S) URLs without query parameters or fragments");
|
|
184
|
+
}
|
|
185
|
+
return url.toString();
|
|
186
|
+
}
|
|
187
|
+
function normalizeRuntimePolicy(value) {
|
|
188
|
+
if (value === undefined) {
|
|
189
|
+
return Object.freeze({
|
|
190
|
+
maxDocuments: DEFAULT_MAX_DOCUMENTS,
|
|
191
|
+
maxDepth: DEFAULT_MAX_DEPTH,
|
|
192
|
+
maxLinksPerDocument: DEFAULT_MAX_LINKS_PER_DOCUMENT,
|
|
193
|
+
maxDocumentBytes: DEFAULT_MAX_DOCUMENT_BYTES,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const maxDocuments = runtimeBound(value.maxDocuments, 1, HARD_MAX_DOCUMENTS, "maxDocuments");
|
|
197
|
+
const maxDepth = runtimeBound(value.maxDepth, 1, HARD_MAX_DEPTH, "maxDepth");
|
|
198
|
+
const maxLinksPerDocument = runtimeBound(value.maxLinksPerDocument, 1, HARD_MAX_LINKS_PER_DOCUMENT, "maxLinksPerDocument");
|
|
199
|
+
const maxDocumentBytes = runtimeBound(value.maxDocumentBytes, 1, HARD_MAX_DOCUMENT_BYTES, "maxDocumentBytes");
|
|
200
|
+
// Validate the complete admitted locator even though asset inspection and
|
|
201
|
+
// HEAD-probe budgets are used only by connect-time discovery.
|
|
202
|
+
runtimeBound(value.maxAssets, 1, 1024, "maxAssets");
|
|
203
|
+
runtimeBound(value.maxAssetProbes, 0, 16, "maxAssetProbes");
|
|
204
|
+
return Object.freeze({ maxDocuments, maxDepth, maxLinksPerDocument, maxDocumentBytes });
|
|
205
|
+
}
|
|
206
|
+
function runtimeBound(value, minimum, maximum, name) {
|
|
207
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
208
|
+
throw new HonuaCapabilityNotSupportedError("query", "stac", `static STAC ${name} must be an integer between ${minimum} and ${maximum}`);
|
|
209
|
+
}
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
async function readBoundedJson(response, maximum) {
|
|
213
|
+
const advertised = response.headers.get("content-length");
|
|
214
|
+
if (advertised !== null && Number(advertised) > maximum) {
|
|
215
|
+
await response.body?.cancel().catch(() => undefined);
|
|
216
|
+
throw new Error(`static STAC response exceeds the ${maximum}-byte limit`);
|
|
217
|
+
}
|
|
218
|
+
if (!response.body)
|
|
219
|
+
throw new Error("static STAC response body is empty");
|
|
220
|
+
const reader = response.body.getReader();
|
|
221
|
+
const chunks = [];
|
|
222
|
+
let length = 0;
|
|
223
|
+
try {
|
|
224
|
+
for (;;) {
|
|
225
|
+
const { done, value } = await reader.read();
|
|
226
|
+
if (done)
|
|
227
|
+
break;
|
|
228
|
+
length += value.byteLength;
|
|
229
|
+
if (length > maximum) {
|
|
230
|
+
await reader.cancel().catch(() => undefined);
|
|
231
|
+
throw new Error(`static STAC response exceeds the ${maximum}-byte limit`);
|
|
232
|
+
}
|
|
233
|
+
chunks.push(value);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
reader.releaseLock();
|
|
238
|
+
}
|
|
239
|
+
const bytes = new Uint8Array(length);
|
|
240
|
+
let offset = 0;
|
|
241
|
+
for (const chunk of chunks) {
|
|
242
|
+
bytes.set(chunk, offset);
|
|
243
|
+
offset += chunk.byteLength;
|
|
244
|
+
}
|
|
245
|
+
let value;
|
|
246
|
+
try {
|
|
247
|
+
value = JSON.parse(new TextDecoder().decode(bytes));
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
throw new Error("static STAC response contained invalid JSON");
|
|
251
|
+
}
|
|
252
|
+
const violation = boundedJsonStructureViolation(value, MAX_STATIC_JSON_DEPTH, MAX_STATIC_JSON_NODES);
|
|
253
|
+
if (violation === "max-depth") {
|
|
254
|
+
throw new Error(`static STAC response exceeds the ${MAX_STATIC_JSON_DEPTH}-level nesting limit`);
|
|
255
|
+
}
|
|
256
|
+
if (violation === "max-nodes") {
|
|
257
|
+
throw new Error(`static STAC response exceeds the ${MAX_STATIC_JSON_NODES}-node structural limit`);
|
|
258
|
+
}
|
|
259
|
+
if (violation === "non-json")
|
|
260
|
+
throw new Error("static STAC response contains non-finite JSON data");
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function mediaEssence(value) {
|
|
264
|
+
return value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
265
|
+
}
|
|
266
|
+
function isPlainObject(value) {
|
|
267
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
268
|
+
return false;
|
|
269
|
+
const prototype = Object.getPrototypeOf(value);
|
|
270
|
+
return prototype === Object.prototype || prototype === null;
|
|
271
|
+
}
|
|
272
|
+
function throwIfRuntimeAborted(signal) {
|
|
273
|
+
if (signal?.aborted)
|
|
274
|
+
throw new HonuaAbortError();
|
|
275
|
+
}
|
|
276
|
+
function isMinimallyValidStacItem(value) {
|
|
277
|
+
if (value.type !== "Feature" ||
|
|
278
|
+
typeof value.stac_version !== "string" ||
|
|
279
|
+
!value.stac_version.trim() ||
|
|
280
|
+
value.stac_version.length > 64 ||
|
|
281
|
+
typeof value.id !== "string" ||
|
|
282
|
+
!value.id.trim() ||
|
|
283
|
+
value.id.length > 512 ||
|
|
284
|
+
!isPlainObject(value.properties) ||
|
|
285
|
+
!Array.isArray(value.links) ||
|
|
286
|
+
!isPlainObject(value.assets)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
if (value.stac_extensions !== undefined &&
|
|
290
|
+
(!Array.isArray(value.stac_extensions) || value.stac_extensions.some((entry) => typeof entry !== "string"))) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (value.collection !== undefined && (typeof value.collection !== "string" || !value.collection.trim())) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
if (!Object.hasOwn(value, "geometry") || !isRuntimeGeometry(value.geometry))
|
|
297
|
+
return false;
|
|
298
|
+
if (value.bbox !== undefined && !isRuntimeBbox(value.bbox))
|
|
299
|
+
return false;
|
|
300
|
+
if (value.links.some((link) => !isPlainObject(link) ||
|
|
301
|
+
typeof link.rel !== "string" ||
|
|
302
|
+
!link.rel.trim() ||
|
|
303
|
+
typeof link.href !== "string" ||
|
|
304
|
+
!link.href.trim())) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
if (Object.values(value.assets).some((asset) => !isPlainObject(asset) || typeof asset.href !== "string" || !asset.href.trim())) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const datetime = value.properties.datetime;
|
|
311
|
+
if (datetime === null) {
|
|
312
|
+
return validRuntimeDatetime(value.properties.start_datetime) && validRuntimeDatetime(value.properties.end_datetime);
|
|
313
|
+
}
|
|
314
|
+
return validRuntimeDatetime(datetime);
|
|
315
|
+
}
|
|
316
|
+
function isRuntimeGeometry(value) {
|
|
317
|
+
if (value === null)
|
|
318
|
+
return true;
|
|
319
|
+
if (!isPlainObject(value) || typeof value.type !== "string")
|
|
320
|
+
return false;
|
|
321
|
+
if (value.type === "GeometryCollection")
|
|
322
|
+
return Array.isArray(value.geometries);
|
|
323
|
+
return ((value.type === "Point" ||
|
|
324
|
+
value.type === "MultiPoint" ||
|
|
325
|
+
value.type === "LineString" ||
|
|
326
|
+
value.type === "MultiLineString" ||
|
|
327
|
+
value.type === "Polygon" ||
|
|
328
|
+
value.type === "MultiPolygon") &&
|
|
329
|
+
Array.isArray(value.coordinates));
|
|
330
|
+
}
|
|
331
|
+
function isRuntimeBbox(value) {
|
|
332
|
+
return (Array.isArray(value) &&
|
|
333
|
+
(value.length === 4 || value.length === 6) &&
|
|
334
|
+
value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) &&
|
|
335
|
+
value[0] <= value[value.length / 2] &&
|
|
336
|
+
value[1] <= value[value.length / 2 + 1] &&
|
|
337
|
+
(value.length === 4 || value[2] <= value[5]));
|
|
338
|
+
}
|
|
339
|
+
function validRuntimeDatetime(value) {
|
|
340
|
+
return typeof value === "string" && value.length <= 128 && !Number.isNaN(Date.parse(value));
|
|
341
|
+
}
|
|
342
|
+
function matchesFilters(item, params) {
|
|
343
|
+
const doc = item;
|
|
344
|
+
if (params.collections && params.collections.length > 0) {
|
|
345
|
+
if (typeof doc.collection !== "string" || !params.collections.includes(doc.collection))
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
if (params.bbox && Array.isArray(doc.bbox) && doc.bbox.length >= 4) {
|
|
349
|
+
if (!bboxIntersects(params.bbox, doc.bbox))
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (params.datetime) {
|
|
353
|
+
if (!datetimeMatches(params.datetime, doc))
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
function bboxIntersects(a, b) {
|
|
359
|
+
// 2D envelope intersection; ignores any z components.
|
|
360
|
+
const [axmin, aymin, axmax, aymax] = a;
|
|
361
|
+
const bxmin = b[0];
|
|
362
|
+
const bymin = b[1];
|
|
363
|
+
// A 4- or 6-tuple bbox: max x/y are the second half.
|
|
364
|
+
const bxmax = b.length >= 6 ? b[3] : b[2];
|
|
365
|
+
const bymax = b.length >= 6 ? b[4] : b[3];
|
|
366
|
+
return axmin <= bxmax && axmax >= bxmin && aymin <= bymax && aymax >= bymin;
|
|
367
|
+
}
|
|
368
|
+
function datetimeMatches(query, doc) {
|
|
369
|
+
const dt = typeof doc.properties?.datetime === "string" ? doc.properties.datetime : undefined;
|
|
370
|
+
// Closed / open interval query per STAC (`start/end`, `../end`, `start/..`).
|
|
371
|
+
if (query.includes("/")) {
|
|
372
|
+
const [rawStart, rawEnd] = query.split("/");
|
|
373
|
+
const start = rawStart && rawStart !== ".." ? Date.parse(rawStart) : Number.NEGATIVE_INFINITY;
|
|
374
|
+
const end = rawEnd && rawEnd !== ".." ? Date.parse(rawEnd) : Number.POSITIVE_INFINITY;
|
|
375
|
+
const startProp = typeof doc.properties?.start_datetime === "string" ? doc.properties.start_datetime : dt;
|
|
376
|
+
const endProp = typeof doc.properties?.end_datetime === "string" ? doc.properties.end_datetime : dt;
|
|
377
|
+
if (!startProp && !endProp)
|
|
378
|
+
return false;
|
|
379
|
+
const itemStart = startProp ? Date.parse(startProp) : Number.NEGATIVE_INFINITY;
|
|
380
|
+
const itemEnd = endProp ? Date.parse(endProp) : Number.POSITIVE_INFINITY;
|
|
381
|
+
return itemStart <= end && itemEnd >= start;
|
|
382
|
+
}
|
|
383
|
+
if (!dt)
|
|
384
|
+
return false;
|
|
385
|
+
return Date.parse(dt) === Date.parse(query);
|
|
386
|
+
}
|
package/core/stac.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ import type { HonuaProtocolTransport } from "./protocol-transport.js";
|
|
|
11
11
|
import type { HonuaOgcCollectionMetadata, HonuaOgcCollectionsResponse, HonuaStacItemCollectionResponse, HonuaStacItemResponse, HonuaStacLandingResponse, OgcCollectionRequest, OgcMetadataRequest, StacSearchRequest } from "./types.js";
|
|
12
12
|
export interface HonuaStacSearchOptions {
|
|
13
13
|
client: HonuaClient;
|
|
14
|
+
/**
|
|
15
|
+
* Path prefix the STAC endpoints are mounted under. Defaults to `/stac`
|
|
16
|
+
* (Honua facade). Pass `""` for a raw STAC API root (e.g. Earth Search
|
|
17
|
+
* served at `.../v1`), so the search / collection paths resolve directly
|
|
18
|
+
* under the client baseUrl.
|
|
19
|
+
*/
|
|
20
|
+
basePath?: string;
|
|
14
21
|
}
|
|
15
22
|
export interface HonuaStacSearchAllRequest extends StacSearchRequest {
|
|
16
23
|
/** Maximum number of items to materialize across pages. */
|
|
@@ -25,7 +32,10 @@ export interface HonuaStacSearchAllRequest extends StacSearchRequest {
|
|
|
25
32
|
*/
|
|
26
33
|
export declare class HonuaStacSearch {
|
|
27
34
|
readonly client: HonuaClient;
|
|
35
|
+
private readonly basePath;
|
|
28
36
|
constructor(options: HonuaStacSearchOptions);
|
|
37
|
+
/** Inject the configured STAC base path unless the caller already set one. */
|
|
38
|
+
private withBase;
|
|
29
39
|
landing(request?: OgcMetadataRequest): Promise<HonuaStacLandingResponse>;
|
|
30
40
|
collections(request?: OgcMetadataRequest): Promise<HonuaOgcCollectionsResponse>;
|
|
31
41
|
collection(request: OgcCollectionRequest): Promise<HonuaOgcCollectionMetadata>;
|
|
@@ -35,6 +45,7 @@ export declare class HonuaStacSearch {
|
|
|
35
45
|
signal?: AbortSignal;
|
|
36
46
|
responseFormat?: string;
|
|
37
47
|
extraParams?: Record<string, string | number | boolean>;
|
|
48
|
+
stacBasePath?: string;
|
|
38
49
|
}): Promise<HonuaStacItemResponse>;
|
|
39
50
|
search(request?: StacSearchRequest): Promise<HonuaStacItemCollectionResponse>;
|
|
40
51
|
searchAll(request?: HonuaStacSearchAllRequest): Promise<HonuaStacItemResponse[]>;
|
|
@@ -50,6 +61,7 @@ export declare function getStacItem(transport: HonuaProtocolTransport, request:
|
|
|
50
61
|
signal?: AbortSignal;
|
|
51
62
|
responseFormat?: string;
|
|
52
63
|
extraParams?: Record<string, string | number | boolean>;
|
|
64
|
+
stacBasePath?: string;
|
|
53
65
|
}): Promise<HonuaStacItemResponse>;
|
|
54
66
|
export declare function searchStac(transport: HonuaProtocolTransport, request?: StacSearchRequest): Promise<HonuaStacItemCollectionResponse>;
|
|
55
67
|
//# sourceMappingURL=stac.d.ts.map
|
package/core/stac.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stac.d.ts","sourceRoot":"","sources":["../../../src/core/stac.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,+BAA+B,EAC/B,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"stac.d.ts","sourceRoot":"","sources":["../../../src/core/stac.ts"],"names":[],"mappings":"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"}
|
package/core/stac.js
CHANGED
|
@@ -16,23 +16,31 @@ const DEFAULT_STAC_MAX_PAGES = 100;
|
|
|
16
16
|
*/
|
|
17
17
|
export class HonuaStacSearch {
|
|
18
18
|
client;
|
|
19
|
+
basePath;
|
|
19
20
|
constructor(options) {
|
|
20
21
|
this.client = options.client;
|
|
22
|
+
this.basePath = options.basePath;
|
|
23
|
+
}
|
|
24
|
+
/** Inject the configured STAC base path unless the caller already set one. */
|
|
25
|
+
withBase(request) {
|
|
26
|
+
if (this.basePath === undefined || request.stacBasePath !== undefined)
|
|
27
|
+
return request;
|
|
28
|
+
return { ...request, stacBasePath: this.basePath };
|
|
21
29
|
}
|
|
22
30
|
async landing(request = {}) {
|
|
23
|
-
return this.client.getStacLanding(request);
|
|
31
|
+
return this.client.getStacLanding(this.withBase(request));
|
|
24
32
|
}
|
|
25
33
|
async collections(request = {}) {
|
|
26
|
-
return this.client.listStacCollections(request);
|
|
34
|
+
return this.client.listStacCollections(this.withBase(request));
|
|
27
35
|
}
|
|
28
36
|
async collection(request) {
|
|
29
|
-
return this.client.getStacCollection(request);
|
|
37
|
+
return this.client.getStacCollection(this.withBase(request));
|
|
30
38
|
}
|
|
31
39
|
async item(request) {
|
|
32
|
-
return this.client.getStacItem(request);
|
|
40
|
+
return this.client.getStacItem(this.withBase(request));
|
|
33
41
|
}
|
|
34
42
|
async search(request = {}) {
|
|
35
|
-
return this.client.searchStac(request);
|
|
43
|
+
return this.client.searchStac(this.withBase(request));
|
|
36
44
|
}
|
|
37
45
|
async searchAll(request = {}) {
|
|
38
46
|
const pageSize = request.pageSize ?? request.limit ?? DEFAULT_STAC_PAGE_SIZE;
|
|
@@ -40,12 +48,12 @@ export class HonuaStacSearch {
|
|
|
40
48
|
const items = [];
|
|
41
49
|
let cursor = { offset: request.offset, next: request.next };
|
|
42
50
|
for (let page = 0; page < maxPages; page += 1) {
|
|
43
|
-
const response = await this.client.searchStac({
|
|
51
|
+
const response = await this.client.searchStac(this.withBase({
|
|
44
52
|
...request,
|
|
45
53
|
limit: pageSize,
|
|
46
54
|
offset: cursor.offset,
|
|
47
55
|
next: cursor.next,
|
|
48
|
-
});
|
|
56
|
+
}));
|
|
49
57
|
const pageItems = response.features ?? [];
|
|
50
58
|
if (pageItems.length === 0)
|
|
51
59
|
break;
|
|
@@ -65,12 +73,12 @@ export class HonuaStacSearch {
|
|
|
65
73
|
const maxPages = request.maxPages ?? DEFAULT_STAC_MAX_PAGES;
|
|
66
74
|
let cursor = { offset: request.offset, next: request.next };
|
|
67
75
|
for (let page = 0; page < maxPages; page += 1) {
|
|
68
|
-
const response = await this.client.searchStac({
|
|
76
|
+
const response = await this.client.searchStac(this.withBase({
|
|
69
77
|
...request,
|
|
70
78
|
limit: pageSize,
|
|
71
79
|
offset: cursor.offset,
|
|
72
80
|
next: cursor.next,
|
|
73
|
-
});
|
|
81
|
+
}));
|
|
74
82
|
const pageItems = response.features ?? [];
|
|
75
83
|
if (pageItems.length === 0)
|
|
76
84
|
break;
|
|
@@ -118,33 +126,55 @@ export function createHonuaStacSearch(client) {
|
|
|
118
126
|
return new HonuaStacSearch({ client });
|
|
119
127
|
}
|
|
120
128
|
// ── STAC API wire methods ───────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Path prefix the STAC endpoints are mounted under. Defaults to `/stac`
|
|
131
|
+
* (the Honua Server facade). Backend-agnostic callers pointing at a raw
|
|
132
|
+
* STAC API root pass `""` so the paths resolve directly under the client
|
|
133
|
+
* baseUrl (e.g. Earth Search served at `.../v1`).
|
|
134
|
+
*/
|
|
135
|
+
function stacBase(request) {
|
|
136
|
+
const base = request.stacBasePath;
|
|
137
|
+
if (base === undefined)
|
|
138
|
+
return "/stac";
|
|
139
|
+
// Trim trailing slashes; an explicit "" means "the client baseUrl is the
|
|
140
|
+
// STAC API root".
|
|
141
|
+
let end = base.length;
|
|
142
|
+
while (end > 0 && base.charCodeAt(end - 1) === 0x2f)
|
|
143
|
+
end--;
|
|
144
|
+
return base.slice(0, end);
|
|
145
|
+
}
|
|
121
146
|
export async function getStacLanding(transport, request = {}) {
|
|
122
147
|
const params = createOgcMetadataParams(request);
|
|
123
|
-
|
|
148
|
+
const base = stacBase(request);
|
|
149
|
+
return transport.requestCachedMetadataJson(`stac:landing:${base}:${params.toString()}`, `${base}?${params.toString()}`, request);
|
|
124
150
|
}
|
|
125
151
|
export async function listStacCollections(transport, request = {}) {
|
|
126
152
|
const params = createOgcMetadataParams(request);
|
|
127
|
-
|
|
153
|
+
const base = stacBase(request);
|
|
154
|
+
return transport.requestCachedMetadataJson(`stac:collections:${base}:${params.toString()}`, `${base}/collections?${params.toString()}`, request);
|
|
128
155
|
}
|
|
129
156
|
export async function getStacCollection(transport, request) {
|
|
130
157
|
const params = createOgcMetadataParams(request);
|
|
131
|
-
|
|
158
|
+
const base = stacBase(request);
|
|
159
|
+
return transport.requestCachedMetadataJson(`stac:collection:${base}:${request.collectionId}:${params.toString()}`, `${base}/collections/${encodeURIComponent(String(request.collectionId))}?${params.toString()}`, request);
|
|
132
160
|
}
|
|
133
161
|
export async function getStacItem(transport, request) {
|
|
134
162
|
const params = createOgcMetadataParams(request);
|
|
135
|
-
const
|
|
163
|
+
const base = stacBase(request);
|
|
164
|
+
const path = `${base}/collections/${encodeURIComponent(String(request.collectionId))}` +
|
|
136
165
|
`/items/${encodeURIComponent(String(request.itemId))}`;
|
|
137
166
|
return transport.requestJson("GET", `${path}?${params.toString()}`, undefined, request.signal);
|
|
138
167
|
}
|
|
139
168
|
export async function searchStac(transport, request = {}) {
|
|
169
|
+
const base = stacBase(request);
|
|
140
170
|
if (request.usePost) {
|
|
141
|
-
return transport.requestJson("POST",
|
|
171
|
+
return transport.requestJson("POST", `${base}/search`, {
|
|
142
172
|
headers: mergeHeaders({ "Content-Type": "application/json", Accept: "application/json" }),
|
|
143
173
|
body: JSON.stringify(stacSearchBody(request)),
|
|
144
174
|
}, request.signal);
|
|
145
175
|
}
|
|
146
176
|
const params = serializeStacSearchParams(request);
|
|
147
|
-
return transport.requestJson("GET",
|
|
177
|
+
return transport.requestJson("GET", `${base}/search?${params.toString()}`, undefined, request.signal);
|
|
148
178
|
}
|
|
149
179
|
function serializeStacSearchParams(request) {
|
|
150
180
|
const params = new URLSearchParams();
|
package/core/surfaces.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { SpatialAggregationRequest, SpatialAggregationResult } from "../con
|
|
|
3
3
|
import type { SourceId } from "../contract/types.js";
|
|
4
4
|
import type { HonuaMetadataRequestOptions } from "./cache-state.js";
|
|
5
5
|
import type { HonuaClient } from "./client.js";
|
|
6
|
+
import type { OgcApiLayoutMode } from "./ogc-endpoint-layout.js";
|
|
6
7
|
import type { ApplyEditsRequest, ExportMapRequest, HonuaAddAttachmentResponse, HonuaApplyEditsResponse, HonuaAttachmentListResponse, HonuaDeleteAttachmentsResponse, HonuaExportMapResponse, HonuaExtent, HonuaFeature, HonuaFindResponse, HonuaIdentifyResponse, HonuaLayerMetadata, HonuaLegendResponse, HonuaOgcCollectionMetadata, HonuaOgcCollectionsResponse, HonuaOgcConformanceResponse, HonuaOgcFeatureCollectionResponse, HonuaOgcFeatureResponse, HonuaOgcLandingResponse, HonuaOgcQueryablesResponse, HonuaQueryAttachmentsResponse, HonuaQueryResponse, HonuaRawRequest, HonuaRelatedRecordsResponse, HonuaServiceMetadata, HonuaTypedFeature, HonuaTypedQueryResponse, HonuaUpdateAttachmentResponse, MapFindRequest, MapIdentifyRequest, MapLayerQueryRequest, MapLegendRequest, MapRelatedRecordsRequest, OgcCollectionRequest, OgcCreateItemRequest, OgcDeleteItemRequest, OgcItemRequest, OgcItemsRequest, OgcMetadataRequest, OgcPatchItemRequest, OgcReplaceItemRequest, QueryFeaturesRequest, QueryMethod, QueryRelatedRecordsRequest } from "./types.js";
|
|
7
8
|
export interface HonuaServiceOptions {
|
|
8
9
|
client: HonuaClient;
|
|
@@ -231,14 +232,24 @@ export declare class HonuaMapLayer {
|
|
|
231
232
|
}
|
|
232
233
|
export interface HonuaOgcFeaturesOptions {
|
|
233
234
|
client: HonuaClient;
|
|
235
|
+
/**
|
|
236
|
+
* Endpoint-layout discovery mode. `honua-facade` (default) uses the fixed
|
|
237
|
+
* `/ogc/features/...` fast path; `ogc-api` / `auto` discover the layout
|
|
238
|
+
* from the server landing page so the surface works against raw pygeoapi
|
|
239
|
+
* / ldproxy / GeoServer OGC API endpoints.
|
|
240
|
+
*/
|
|
241
|
+
layout?: OgcApiLayoutMode;
|
|
234
242
|
}
|
|
235
243
|
export interface HonuaOgcFeatureCollectionOptions {
|
|
236
244
|
client: HonuaClient;
|
|
237
245
|
collectionId: string | number;
|
|
246
|
+
layout?: OgcApiLayoutMode;
|
|
238
247
|
}
|
|
239
248
|
export declare class HonuaOgcFeatures {
|
|
240
249
|
readonly client: HonuaClient;
|
|
250
|
+
private readonly layoutMode;
|
|
241
251
|
constructor(options: HonuaOgcFeaturesOptions);
|
|
252
|
+
private layout;
|
|
242
253
|
collection(collectionId: string | number): HonuaOgcFeatureCollection;
|
|
243
254
|
landing(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcLandingResponse>;
|
|
244
255
|
conformance(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcConformanceResponse>;
|
|
@@ -256,7 +267,9 @@ export declare class HonuaOgcFeatures {
|
|
|
256
267
|
export declare class HonuaOgcFeatureCollection {
|
|
257
268
|
readonly client: HonuaClient;
|
|
258
269
|
readonly collectionId: string | number;
|
|
270
|
+
private readonly layoutMode;
|
|
259
271
|
constructor(options: HonuaOgcFeatureCollectionOptions);
|
|
272
|
+
private layout;
|
|
260
273
|
metadata(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcCollectionMetadata>;
|
|
261
274
|
queryables(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcQueryablesResponse>;
|
|
262
275
|
items(request?: HonuaOgcCollectionItemsRequest): Promise<HonuaOgcFeatureCollectionResponse>;
|