@honua/react 0.0.20-alpha.0 → 0.1.0-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/index.d.ts +2 -0
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +1 -0
- package/contract/pmtiles.d.ts +162 -0
- package/contract/pmtiles.d.ts.map +1 -0
- package/contract/pmtiles.js +155 -0
- package/contract/source.d.ts +24 -0
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +115 -8
- package/contract/types.d.ts +31 -2
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +8 -0
- 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/client.d.ts +31 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +52 -0
- package/core/errors.d.ts +33 -1
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +20 -0
- package/core/geoparquet-sql.d.ts +162 -0
- package/core/geoparquet-sql.d.ts.map +1 -0
- package/core/geoparquet-sql.js +333 -0
- 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 +32 -17
- package/core/stac-static.d.ts +51 -0
- package/core/stac-static.d.ts.map +1 -0
- package/core/stac-static.js +195 -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 +58 -11
- package/core/types.d.ts +49 -0
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +10 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +14 -2
- package/core/wfs.d.ts +13 -0
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +25 -5
- 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/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/package.json +6 -1
- package/runtime/index.d.ts +2 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +1 -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/pmtiles-protocol.d.ts +77 -0
- package/runtime/pmtiles-protocol.d.ts.map +1 -0
- package/runtime/pmtiles-protocol.js +120 -0
- package/runtime/source-bridge.d.ts.map +1 -1
- package/runtime/source-bridge.js +27 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OGC API endpoint-layout resolution.
|
|
3
|
+
*
|
|
4
|
+
* The SDK's headline claim is one typed contract against ANY standards
|
|
5
|
+
* server. For OGC API Features / Records that means the client must not
|
|
6
|
+
* assume a fixed path prefix: a Honua Server mounts the facade at
|
|
7
|
+
* `/ogc/features/...`, but pygeoapi, ldproxy, and GeoServer's OGC API each
|
|
8
|
+
* serve the landing page at their own root and advertise their collections
|
|
9
|
+
* and conformance URLs through the landing page's `links` array
|
|
10
|
+
* (OGC API - Common Requirement 5).
|
|
11
|
+
*
|
|
12
|
+
* This module produces an {@link OgcEndpointLayout} — the concrete set of
|
|
13
|
+
* resource paths a Features client addresses:
|
|
14
|
+
*
|
|
15
|
+
* - {@link honuaFacadeFeaturesLayout} is the fixed facade layout and the
|
|
16
|
+
* default. It performs no network round-trips, so existing callers see
|
|
17
|
+
* zero behaviour change.
|
|
18
|
+
* - {@link resolveOgcEndpointLayout} discovers the layout from the landing
|
|
19
|
+
* page (`ogc-api` mode) or auto-detects the facade first and falls back
|
|
20
|
+
* to discovery (`auto` mode).
|
|
21
|
+
*
|
|
22
|
+
* Per OGC API - Features Requirement 17 the items resource is always at
|
|
23
|
+
* `{collections}/{collectionId}/items`, so a single landing-page fetch is
|
|
24
|
+
* enough to derive every per-collection path from the discovered
|
|
25
|
+
* collections URL.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { HonuaCapabilityNotSupportedError } from "./errors.js";
|
|
30
|
+
const DEFAULT_FACADE_BASE = "/ogc/features";
|
|
31
|
+
function enc(id) {
|
|
32
|
+
return encodeURIComponent(String(id));
|
|
33
|
+
}
|
|
34
|
+
function trimTrailingSlash(url) {
|
|
35
|
+
let end = url.length;
|
|
36
|
+
while (end > 0 && url.charCodeAt(end - 1) === 0x2f)
|
|
37
|
+
end--;
|
|
38
|
+
return url.slice(0, end);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The Honua Server facade layout (`/ogc/features/...`). No network access;
|
|
42
|
+
* this is the default fast path and preserves the pre-existing behaviour of
|
|
43
|
+
* every OGC Features caller that points at a Honua Server.
|
|
44
|
+
*/
|
|
45
|
+
export function honuaFacadeFeaturesLayout(base = DEFAULT_FACADE_BASE) {
|
|
46
|
+
const root = trimTrailingSlash(base) || DEFAULT_FACADE_BASE;
|
|
47
|
+
return {
|
|
48
|
+
mode: "honua-facade",
|
|
49
|
+
landing: () => root,
|
|
50
|
+
conformance: () => `${root}/conformance`,
|
|
51
|
+
collections: () => `${root}/collections`,
|
|
52
|
+
collection: (id) => `${root}/collections/${enc(id)}`,
|
|
53
|
+
queryables: (id) => `${root}/collections/${enc(id)}/queryables`,
|
|
54
|
+
items: (id) => `${root}/collections/${enc(id)}/items`,
|
|
55
|
+
item: (id, featureId) => `${root}/collections/${enc(id)}/items/${enc(featureId)}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A raw OGC API layout built from a discovered (or configured) landing +
|
|
60
|
+
* collections + conformance URL triple. Per-collection paths are templated
|
|
61
|
+
* off the collections URL per OGC API - Features Requirement 17.
|
|
62
|
+
*/
|
|
63
|
+
export function ogcApiFeaturesLayout(options) {
|
|
64
|
+
const landing = trimTrailingSlash(options.landingUrl);
|
|
65
|
+
const collections = trimTrailingSlash(options.collectionsUrl);
|
|
66
|
+
const conformance = trimTrailingSlash(options.conformanceUrl);
|
|
67
|
+
return {
|
|
68
|
+
mode: "ogc-api",
|
|
69
|
+
// A bare landing path can be empty (the client baseUrl is the landing
|
|
70
|
+
// root); the wire layer still appends the `f=` param, producing
|
|
71
|
+
// `${baseUrl}?f=json`.
|
|
72
|
+
landing: () => landing,
|
|
73
|
+
conformance: () => conformance,
|
|
74
|
+
collections: () => collections,
|
|
75
|
+
collection: (id) => `${collections}/${enc(id)}`,
|
|
76
|
+
queryables: (id) => `${collections}/${enc(id)}/queryables`,
|
|
77
|
+
items: (id) => `${collections}/${enc(id)}/items`,
|
|
78
|
+
item: (id, featureId) => `${collections}/${enc(id)}/items/${enc(featureId)}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a link href to an absolute URL against a base. Absolute hrefs are
|
|
83
|
+
* returned unchanged; relative hrefs resolve against `${baseUrl}/`.
|
|
84
|
+
*/
|
|
85
|
+
function absolutize(href, baseUrl) {
|
|
86
|
+
if (/^https?:\/\//i.test(href))
|
|
87
|
+
return href;
|
|
88
|
+
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
89
|
+
try {
|
|
90
|
+
return new URL(href, root).toString();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return href;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Find the first link whose `rel` matches one of `rels` (exact or as the
|
|
98
|
+
* final path segment of a full OGC relation URI, e.g.
|
|
99
|
+
* `http://www.opengis.net/def/rel/ogc/1.0/data`).
|
|
100
|
+
*/
|
|
101
|
+
export function findOgcLink(links, ...rels) {
|
|
102
|
+
if (!links)
|
|
103
|
+
return undefined;
|
|
104
|
+
const wanted = new Set(rels.map((r) => r.toLowerCase()));
|
|
105
|
+
for (const link of links) {
|
|
106
|
+
if (typeof link.href !== "string" || link.href.length === 0)
|
|
107
|
+
continue;
|
|
108
|
+
const rel = (link.rel ?? "").toLowerCase();
|
|
109
|
+
if (wanted.has(rel))
|
|
110
|
+
return link.href;
|
|
111
|
+
const tail = rel.slice(rel.lastIndexOf("/") + 1);
|
|
112
|
+
if (tail && wanted.has(tail))
|
|
113
|
+
return link.href;
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Discover an OGC API layout from the landing page. The client `baseUrl`
|
|
119
|
+
* IS the landing page root; the collections and conformance URLs are read
|
|
120
|
+
* from the landing `links` (falling back to the mandated relative
|
|
121
|
+
* `collections` / `conformance` sub-paths when a server omits them).
|
|
122
|
+
*/
|
|
123
|
+
async function discoverOgcApiLayout(transport) {
|
|
124
|
+
const landing = await transport.requestCachedMetadataJson("ogc-features:layout:landing", "?f=json", {});
|
|
125
|
+
const baseUrl = trimTrailingSlash(transport.baseUrl);
|
|
126
|
+
const links = landing?.links;
|
|
127
|
+
const dataHref = findOgcLink(links, "data");
|
|
128
|
+
const conformanceHref = findOgcLink(links, "conformance");
|
|
129
|
+
const collectionsUrl = dataHref ? absolutize(dataHref, baseUrl) : `${baseUrl}/collections`;
|
|
130
|
+
const conformanceUrl = conformanceHref ? absolutize(conformanceHref, baseUrl) : `${baseUrl}/conformance`;
|
|
131
|
+
return ogcApiFeaturesLayout({ landingUrl: baseUrl, collectionsUrl, conformanceUrl });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Probe whether the Honua facade landing (`/ogc/features`) exists. Used by
|
|
135
|
+
* `auto` mode: a valid OGC landing document there confirms the facade
|
|
136
|
+
* fast path; anything else falls through to landing-page discovery.
|
|
137
|
+
*/
|
|
138
|
+
async function facadeLandingLooksValid(transport) {
|
|
139
|
+
try {
|
|
140
|
+
const landing = await transport.requestCachedMetadataJson("ogc-features:layout:facade-probe", `${DEFAULT_FACADE_BASE}?f=json`, {});
|
|
141
|
+
return Array.isArray(landing?.links);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the endpoint layout for the requested `mode`:
|
|
149
|
+
*
|
|
150
|
+
* - `honua-facade` (default) — the fixed facade layout, no round-trips.
|
|
151
|
+
* - `ogc-api` — discover from the landing page at the client baseUrl.
|
|
152
|
+
* - `auto` — probe the facade landing; use it when valid, otherwise
|
|
153
|
+
* discover from the root.
|
|
154
|
+
*
|
|
155
|
+
* Discovery failures surface as {@link HonuaCapabilityNotSupportedError}
|
|
156
|
+
* (per the SDK convention of throwing rather than returning empty data).
|
|
157
|
+
*/
|
|
158
|
+
export async function resolveOgcEndpointLayout(transport, mode = "honua-facade") {
|
|
159
|
+
if (mode === "honua-facade")
|
|
160
|
+
return honuaFacadeFeaturesLayout();
|
|
161
|
+
if (mode === "auto") {
|
|
162
|
+
if (await facadeLandingLooksValid(transport))
|
|
163
|
+
return honuaFacadeFeaturesLayout();
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
return await discoverOgcApiLayout(transport);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (err instanceof HonuaCapabilityNotSupportedError)
|
|
170
|
+
throw err;
|
|
171
|
+
throw new HonuaCapabilityNotSupportedError("query", "ogc-features", `ogc-api layout discovery failed for ${transport.baseUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ogc-features.d.ts","sourceRoot":"","sources":["../../../src/core/ogc-features.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"ogc-features.d.ts","sourceRoot":"","sources":["../../../src/core/ogc-features.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,2BAA2B,EAC3B,iCAAiC,EACjC,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EAEpB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAoBpB,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CAQlC;AAED,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,2BAA2B,CAAC,CAQtC;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,2BAA2B,CAAC,CAQtC;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,0BAA0B,CAAC,CAQrC;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,0BAA0B,CAAC,CAQrC;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,iCAAiC,CAAC,CAoC5C;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,uBAAuB,CAAC,CAYlC;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,uBAAuB,CAAC,CAYlC;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,uBAAuB,CAAC,CAelC;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAelC;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,sBAAsB,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnH"}
|
package/core/ogc-features.js
CHANGED
|
@@ -7,28 +7,47 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
+
import { honuaFacadeFeaturesLayout } from "./ogc-endpoint-layout.js";
|
|
10
11
|
import { createOgcMetadataParams, mergeHeaders, normalizeCsv } from "./wire-shared.js";
|
|
12
|
+
/**
|
|
13
|
+
* The endpoint layout to build paths against. Defaults to the Honua facade
|
|
14
|
+
* fast path (`/ogc/features/...`); backend-agnostic callers thread a
|
|
15
|
+
* spec-discovered layout onto `request.layout`.
|
|
16
|
+
*/
|
|
17
|
+
function layoutOf(request) {
|
|
18
|
+
return request.layout ?? honuaFacadeFeaturesLayout();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A layout key fragment for cache keys so the facade and a discovered raw
|
|
22
|
+
* layout never collide in the metadata cache.
|
|
23
|
+
*/
|
|
24
|
+
function layoutKey(layout) {
|
|
25
|
+
return layout.mode === "honua-facade" ? "facade" : layout.collections();
|
|
26
|
+
}
|
|
11
27
|
export async function getOgcFeaturesLanding(transport, request = {}) {
|
|
12
28
|
const params = createOgcMetadataParams(request);
|
|
13
|
-
|
|
29
|
+
const layout = layoutOf(request);
|
|
30
|
+
return transport.requestCachedMetadataJson(`ogc-features:landing:${layoutKey(layout)}:${params.toString()}`, `${layout.landing()}?${params.toString()}`, request);
|
|
14
31
|
}
|
|
15
32
|
export async function getOgcFeaturesConformance(transport, request = {}) {
|
|
16
33
|
const params = createOgcMetadataParams(request);
|
|
17
|
-
|
|
34
|
+
const layout = layoutOf(request);
|
|
35
|
+
return transport.requestCachedMetadataJson(`ogc-features:conformance:${layoutKey(layout)}:${params.toString()}`, `${layout.conformance()}?${params.toString()}`, request);
|
|
18
36
|
}
|
|
19
37
|
export async function listOgcCollections(transport, request = {}) {
|
|
20
38
|
const params = createOgcMetadataParams(request);
|
|
21
|
-
|
|
39
|
+
const layout = layoutOf(request);
|
|
40
|
+
return transport.requestCachedMetadataJson(`ogc-features:collections:${layoutKey(layout)}:${params.toString()}`, `${layout.collections()}?${params.toString()}`, request);
|
|
22
41
|
}
|
|
23
42
|
export async function getOgcCollection(transport, request) {
|
|
24
43
|
const params = createOgcMetadataParams(request);
|
|
25
|
-
const
|
|
26
|
-
return transport.requestCachedMetadataJson(`ogc-features:collection:${request.collectionId}:${params.toString()}`, `${
|
|
44
|
+
const layout = layoutOf(request);
|
|
45
|
+
return transport.requestCachedMetadataJson(`ogc-features:collection:${layoutKey(layout)}:${request.collectionId}:${params.toString()}`, `${layout.collection(request.collectionId)}?${params.toString()}`, request);
|
|
27
46
|
}
|
|
28
47
|
export async function getOgcQueryables(transport, request) {
|
|
29
48
|
const params = createOgcMetadataParams(request);
|
|
30
|
-
const
|
|
31
|
-
return transport.requestCachedMetadataJson(`ogc-features:queryables:${request.collectionId}:${params.toString()}`, `${
|
|
49
|
+
const layout = layoutOf(request);
|
|
50
|
+
return transport.requestCachedMetadataJson(`ogc-features:queryables:${layoutKey(layout)}:${request.collectionId}:${params.toString()}`, `${layout.queryables(request.collectionId)}?${params.toString()}`, request);
|
|
32
51
|
}
|
|
33
52
|
export async function listOgcItems(transport, request) {
|
|
34
53
|
const params = createOgcMetadataParams(request);
|
|
@@ -59,7 +78,7 @@ export async function listOgcItems(transport, request) {
|
|
|
59
78
|
if (request.crs !== undefined) {
|
|
60
79
|
params.set("crs", request.crs);
|
|
61
80
|
}
|
|
62
|
-
const path =
|
|
81
|
+
const path = layoutOf(request).items(request.collectionId);
|
|
63
82
|
return transport.requestJson("GET", `${path}?${params.toString()}`, undefined, request.signal);
|
|
64
83
|
}
|
|
65
84
|
export async function getOgcItem(transport, request) {
|
|
@@ -67,13 +86,12 @@ export async function getOgcItem(transport, request) {
|
|
|
67
86
|
if (request.crs !== undefined) {
|
|
68
87
|
params.set("crs", request.crs);
|
|
69
88
|
}
|
|
70
|
-
const path =
|
|
71
|
-
`/items/${encodeURIComponent(String(request.featureId))}`;
|
|
89
|
+
const path = layoutOf(request).item(request.collectionId, request.featureId);
|
|
72
90
|
return transport.requestJson("GET", `${path}?${params.toString()}`, undefined, request.signal);
|
|
73
91
|
}
|
|
74
92
|
export async function createOgcItem(transport, request) {
|
|
75
93
|
const params = createOgcMetadataParams(request);
|
|
76
|
-
const path =
|
|
94
|
+
const path = layoutOf(request).items(request.collectionId);
|
|
77
95
|
return transport.requestJson("POST", `${path}?${params.toString()}`, {
|
|
78
96
|
headers: mergeHeaders({ "Content-Type": "application/geo+json" }, request.headers),
|
|
79
97
|
body: JSON.stringify(request.feature),
|
|
@@ -84,8 +102,7 @@ export async function replaceOgcItem(transport, request) {
|
|
|
84
102
|
if (request.crs !== undefined) {
|
|
85
103
|
params.set("crs", request.crs);
|
|
86
104
|
}
|
|
87
|
-
const path =
|
|
88
|
-
`/items/${encodeURIComponent(String(request.featureId))}`;
|
|
105
|
+
const path = layoutOf(request).item(request.collectionId, request.featureId);
|
|
89
106
|
return transport.requestJson("PUT", `${path}?${params.toString()}`, {
|
|
90
107
|
headers: mergeHeaders({ "Content-Type": "application/geo+json" }, request.headers),
|
|
91
108
|
body: JSON.stringify(request.feature),
|
|
@@ -96,8 +113,7 @@ export async function patchOgcItem(transport, request) {
|
|
|
96
113
|
if (request.crs !== undefined) {
|
|
97
114
|
params.set("crs", request.crs);
|
|
98
115
|
}
|
|
99
|
-
const path =
|
|
100
|
-
`/items/${encodeURIComponent(String(request.featureId))}`;
|
|
116
|
+
const path = layoutOf(request).item(request.collectionId, request.featureId);
|
|
101
117
|
return transport.requestJson("PATCH", `${path}?${params.toString()}`, {
|
|
102
118
|
headers: mergeHeaders({ "Content-Type": "application/merge-patch+json" }, request.headers),
|
|
103
119
|
body: JSON.stringify(request.patch),
|
|
@@ -108,7 +124,6 @@ export async function deleteOgcItem(transport, request) {
|
|
|
108
124
|
if (request.crs !== undefined) {
|
|
109
125
|
params.set("crs", request.crs);
|
|
110
126
|
}
|
|
111
|
-
const path =
|
|
112
|
-
`/items/${encodeURIComponent(String(request.featureId))}`;
|
|
127
|
+
const path = layoutOf(request).item(request.collectionId, request.featureId);
|
|
113
128
|
await transport.requestJson("DELETE", `${path}?${params.toString()}`, undefined, request.signal);
|
|
114
129
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 type { HonuaClient } from "./client.js";
|
|
19
|
+
import type { HonuaStacItemCollectionResponse } from "./types.js";
|
|
20
|
+
/** Bounded search parameters for a static catalog traversal. */
|
|
21
|
+
export interface StacStaticSearchParams {
|
|
22
|
+
collections?: readonly string[];
|
|
23
|
+
bbox?: readonly [number, number, number, number];
|
|
24
|
+
datetime?: string;
|
|
25
|
+
limit?: number;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reader bound to the URL of a static STAC catalog / collection root. The
|
|
30
|
+
* URL is the client baseUrl (the transport resolves same-origin absolute
|
|
31
|
+
* child/item hrefs directly), so `rootUrl` is normally `""` (the baseUrl
|
|
32
|
+
* itself) but may be an absolute `catalog.json` URL.
|
|
33
|
+
*/
|
|
34
|
+
export declare class HonuaStacStaticCatalog {
|
|
35
|
+
readonly client: HonuaClient;
|
|
36
|
+
private readonly rootUrl;
|
|
37
|
+
constructor(client: HonuaClient, rootUrl?: string);
|
|
38
|
+
/**
|
|
39
|
+
* Traverse the catalog tree, collect matching items, and return them in
|
|
40
|
+
* the STAC search envelope shape.
|
|
41
|
+
*/
|
|
42
|
+
search(params?: StacStaticSearchParams): Promise<HonuaStacItemCollectionResponse>;
|
|
43
|
+
private fetchDoc;
|
|
44
|
+
/**
|
|
45
|
+
* Breadth-first walk collecting item documents. Descends `rel="child"`
|
|
46
|
+
* links; a `collections` filter prunes Collection subtrees whose `id`
|
|
47
|
+
* is not requested. Bounded by {@link MAX_DOCUMENTS} and the item cap.
|
|
48
|
+
*/
|
|
49
|
+
private collectItems;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=stac-static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stac-static.d.ts","sourceRoot":"","sources":["../../../src/core/stac-static.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAE,+BAA+B,EAAyB,MAAM,YAAY,CAAC;AAEzF,gEAAgE;AAChE,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAeD;;;;;GAKG;AACH,qBAAa,sBAAsB;IACjC,SAAgB,MAAM,EAAE,WAAW,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEd,MAAM,EAAE,WAAW,EAAE,OAAO,SAAK;IAKpD;;;OAGG;IACU,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,+BAA+B,CAAC;YAcpF,QAAQ;IAMtB;;;;OAIG;YACW,YAAY;CA6D3B"}
|
|
@@ -0,0 +1,195 @@
|
|
|
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 { HonuaCapabilityNotSupportedError } from "./errors.js";
|
|
19
|
+
/** Guardrails so a malformed / cyclic catalog cannot fan out unbounded. */
|
|
20
|
+
const MAX_DOCUMENTS = 500;
|
|
21
|
+
const DEFAULT_ITEM_CAP = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Reader bound to the URL of a static STAC catalog / collection root. The
|
|
24
|
+
* URL is the client baseUrl (the transport resolves same-origin absolute
|
|
25
|
+
* child/item hrefs directly), so `rootUrl` is normally `""` (the baseUrl
|
|
26
|
+
* itself) but may be an absolute `catalog.json` URL.
|
|
27
|
+
*/
|
|
28
|
+
export class HonuaStacStaticCatalog {
|
|
29
|
+
client;
|
|
30
|
+
rootUrl;
|
|
31
|
+
constructor(client, rootUrl = "") {
|
|
32
|
+
this.client = client;
|
|
33
|
+
this.rootUrl = rootUrl;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Traverse the catalog tree, collect matching items, and return them in
|
|
37
|
+
* the STAC search envelope shape.
|
|
38
|
+
*/
|
|
39
|
+
async search(params = {}) {
|
|
40
|
+
const items = await this.collectItems(params);
|
|
41
|
+
const filtered = items.filter((item) => matchesFilters(item, params));
|
|
42
|
+
const cap = typeof params.limit === "number" && params.limit >= 0 ? params.limit : filtered.length;
|
|
43
|
+
const sliced = filtered.slice(0, cap);
|
|
44
|
+
return {
|
|
45
|
+
type: "FeatureCollection",
|
|
46
|
+
features: sliced,
|
|
47
|
+
numberReturned: sliced.length,
|
|
48
|
+
numberMatched: filtered.length,
|
|
49
|
+
links: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async fetchDoc(url, signal) {
|
|
53
|
+
// An empty seed root means "the client baseUrl is the catalog document".
|
|
54
|
+
const path = url === "" ? this.client.serverBaseUrl : url;
|
|
55
|
+
return this.client.pipelineRequestJson("GET", path, undefined, signal);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Breadth-first walk collecting item documents. Descends `rel="child"`
|
|
59
|
+
* links; a `collections` filter prunes Collection subtrees whose `id`
|
|
60
|
+
* is not requested. Bounded by {@link MAX_DOCUMENTS} and the item cap.
|
|
61
|
+
*/
|
|
62
|
+
async collectItems(params) {
|
|
63
|
+
const itemCap = typeof params.limit === "number" && params.limit >= 0 ? Math.max(params.limit, 1) : DEFAULT_ITEM_CAP;
|
|
64
|
+
const wantedCollections = params.collections ? new Set(params.collections) : undefined;
|
|
65
|
+
const visited = new Set();
|
|
66
|
+
const queue = [this.rootUrl];
|
|
67
|
+
const itemUrls = [];
|
|
68
|
+
let docCount = 0;
|
|
69
|
+
while (queue.length > 0 && docCount < MAX_DOCUMENTS) {
|
|
70
|
+
const url = queue.shift();
|
|
71
|
+
if (url === undefined || visited.has(url))
|
|
72
|
+
continue;
|
|
73
|
+
visited.add(url);
|
|
74
|
+
docCount += 1;
|
|
75
|
+
let doc;
|
|
76
|
+
try {
|
|
77
|
+
doc = await this.fetchDoc(url, params.signal);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (url === this.rootUrl) {
|
|
81
|
+
throw new HonuaCapabilityNotSupportedError("query", "stac", `static catalog root ${url || "(baseUrl)"} could not be read: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// A Collection subtree not in the requested set is pruned entirely.
|
|
86
|
+
if (wantedCollections &&
|
|
87
|
+
doc.type === "Collection" &&
|
|
88
|
+
typeof doc.id === "string" &&
|
|
89
|
+
!wantedCollections.has(doc.id)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const base = absoluteBaseFor(url, this.client.serverBaseUrl);
|
|
93
|
+
for (const link of doc.links ?? []) {
|
|
94
|
+
if (typeof link.href !== "string")
|
|
95
|
+
continue;
|
|
96
|
+
const rel = (link.rel ?? "").toLowerCase();
|
|
97
|
+
const resolved = resolveHref(link.href, base);
|
|
98
|
+
if (rel === "child") {
|
|
99
|
+
if (!visited.has(resolved))
|
|
100
|
+
queue.push(resolved);
|
|
101
|
+
}
|
|
102
|
+
else if (rel === "item" || rel === "items") {
|
|
103
|
+
itemUrls.push(resolved);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const items = [];
|
|
108
|
+
for (const itemUrl of itemUrls) {
|
|
109
|
+
if (items.length >= itemCap)
|
|
110
|
+
break;
|
|
111
|
+
try {
|
|
112
|
+
const item = (await this.fetchDoc(itemUrl, params.signal));
|
|
113
|
+
if (item && item.type === "Feature")
|
|
114
|
+
items.push(item);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Skip an unreadable item; the traversal is best-effort per item.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return items;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The base URL a document's relative links resolve against. When the reader
|
|
125
|
+
* was seeded with a bare `""` root (the client baseUrl IS the catalog), use
|
|
126
|
+
* the client's server base URL so `./child.json` resolves correctly.
|
|
127
|
+
*/
|
|
128
|
+
function absoluteBaseFor(docUrl, serverBaseUrl) {
|
|
129
|
+
if (docUrl === "")
|
|
130
|
+
return serverBaseUrl;
|
|
131
|
+
if (/^https?:\/\//i.test(docUrl))
|
|
132
|
+
return docUrl;
|
|
133
|
+
const root = serverBaseUrl.endsWith("/") ? serverBaseUrl : `${serverBaseUrl}/`;
|
|
134
|
+
try {
|
|
135
|
+
return new URL(docUrl, root).toString();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return serverBaseUrl;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function resolveHref(href, base) {
|
|
142
|
+
if (/^https?:\/\//i.test(href))
|
|
143
|
+
return href;
|
|
144
|
+
try {
|
|
145
|
+
return new URL(href, base).toString();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return href;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function matchesFilters(item, params) {
|
|
152
|
+
const doc = item;
|
|
153
|
+
if (params.collections && params.collections.length > 0) {
|
|
154
|
+
if (typeof doc.collection !== "string" || !params.collections.includes(doc.collection))
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (params.bbox && Array.isArray(doc.bbox) && doc.bbox.length >= 4) {
|
|
158
|
+
if (!bboxIntersects(params.bbox, doc.bbox))
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (params.datetime) {
|
|
162
|
+
if (!datetimeMatches(params.datetime, doc))
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
function bboxIntersects(a, b) {
|
|
168
|
+
// 2D envelope intersection; ignores any z components.
|
|
169
|
+
const [axmin, aymin, axmax, aymax] = a;
|
|
170
|
+
const bxmin = b[0];
|
|
171
|
+
const bymin = b[1];
|
|
172
|
+
// A 4- or 6-tuple bbox: max x/y are the second half.
|
|
173
|
+
const bxmax = b.length >= 6 ? b[3] : b[2];
|
|
174
|
+
const bymax = b.length >= 6 ? b[4] : b[3];
|
|
175
|
+
return axmin <= bxmax && axmax >= bxmin && aymin <= bymax && aymax >= bymin;
|
|
176
|
+
}
|
|
177
|
+
function datetimeMatches(query, doc) {
|
|
178
|
+
const dt = doc.properties?.datetime;
|
|
179
|
+
// Closed / open interval query per STAC (`start/end`, `../end`, `start/..`).
|
|
180
|
+
if (query.includes("/")) {
|
|
181
|
+
const [rawStart, rawEnd] = query.split("/");
|
|
182
|
+
const start = rawStart && rawStart !== ".." ? Date.parse(rawStart) : Number.NEGATIVE_INFINITY;
|
|
183
|
+
const end = rawEnd && rawEnd !== ".." ? Date.parse(rawEnd) : Number.POSITIVE_INFINITY;
|
|
184
|
+
const startProp = doc.properties?.start_datetime ?? dt;
|
|
185
|
+
const endProp = doc.properties?.end_datetime ?? dt;
|
|
186
|
+
if (!startProp && !endProp)
|
|
187
|
+
return false;
|
|
188
|
+
const itemStart = startProp ? Date.parse(startProp) : Number.NEGATIVE_INFINITY;
|
|
189
|
+
const itemEnd = endProp ? Date.parse(endProp) : Number.POSITIVE_INFINITY;
|
|
190
|
+
return itemStart <= end && itemEnd >= start;
|
|
191
|
+
}
|
|
192
|
+
if (!dt)
|
|
193
|
+
return false;
|
|
194
|
+
return Date.parse(dt) === Date.parse(query);
|
|
195
|
+
}
|
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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"}
|