@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
package/core/wms-capabilities.js
CHANGED
|
@@ -1,559 +1,262 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WMS
|
|
3
|
-
* over a `WMS_Capabilities` document. Returns a typed shape (layers,
|
|
4
|
-
* styles, bbox-per-CRS, dimensions, advertised formats) that the SDK
|
|
5
|
-
* can route through the canonical `Source` surface without leaking XML
|
|
6
|
-
* nodes into its public API.
|
|
2
|
+
* Bounded WMS capabilities projection.
|
|
7
3
|
*
|
|
8
|
-
* The parser
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* thrown when a required root or top-level node is missing.
|
|
4
|
+
* The parser accepts the metadata subset needed by discovery and the WMS
|
|
5
|
+
* runtime, rejects malformed/active XML, and returns plain immutable-friendly
|
|
6
|
+
* records. Unknown vendor extensions are ignored; required structural drift is
|
|
7
|
+
* surfaced through {@link HonuaWmsCapabilitiesParseError}.
|
|
13
8
|
*
|
|
14
9
|
* @module
|
|
15
10
|
*/
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* `<Abstract>`, etc.) are tolerated and return defaults.
|
|
22
|
-
*/
|
|
23
|
-
export class HonuaWmsCapabilitiesParseError extends Error {
|
|
24
|
-
constructor(message) {
|
|
25
|
-
super(message);
|
|
11
|
+
import { parseCapabilitiesXml, xmlAttribute, xmlChild, xmlChildren, xmlText, } from "./capabilities-xml.js";
|
|
12
|
+
import { HonuaSdkError } from "./error-envelope.js";
|
|
13
|
+
export class HonuaWmsCapabilitiesParseError extends HonuaSdkError {
|
|
14
|
+
constructor(message, options = {}) {
|
|
15
|
+
super("core.wms-capabilities-parse", message, options);
|
|
26
16
|
this.name = "HonuaWmsCapabilitiesParseError";
|
|
27
17
|
}
|
|
28
18
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Parse a `WMS_Capabilities` text body into the typed `WmsCapabilities`
|
|
31
|
-
* shape. The input is the raw XML string returned by `requestText`; the
|
|
32
|
-
* parser walks it once with a small named-element matcher and tolerates
|
|
33
|
-
* vendor extensions (unknown elements are skipped).
|
|
34
|
-
*/
|
|
35
19
|
export function parseWmsCapabilities(xml) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!root) {
|
|
41
|
-
throw new HonuaWmsCapabilitiesParseError("missing <WMS_Capabilities> root element (received WMS Capabilities XML?)");
|
|
42
|
-
}
|
|
43
|
-
const version = readAttribute(root.openTag, "version") ?? "1.3.0";
|
|
44
|
-
const service = parseServiceMetadata(root.inner);
|
|
45
|
-
const capabilityNode = findElement(root.inner, 0, "Capability");
|
|
46
|
-
const requestNode = capabilityNode ? findElement(capabilityNode.inner, 0, "Request") : undefined;
|
|
47
|
-
const formats = parseFormats(requestNode?.inner ?? "");
|
|
48
|
-
const requestSupport = {
|
|
49
|
-
getFeatureInfo: requestNode ? findElement(requestNode.inner, 0, "GetFeatureInfo") !== undefined : false,
|
|
50
|
-
getLegendGraphic: requestNode ? findElement(requestNode.inner, 0, "GetLegendGraphic") !== undefined : false,
|
|
51
|
-
};
|
|
52
|
-
const layers = [];
|
|
53
|
-
if (capabilityNode) {
|
|
54
|
-
for (const layer of iterChildLayers(capabilityNode.inner, [])) {
|
|
55
|
-
layers.push(layer);
|
|
20
|
+
try {
|
|
21
|
+
const root = parseCapabilitiesXml(xml, "WMS");
|
|
22
|
+
if (root.localName !== "WMS_Capabilities" && root.localName !== "WMT_MS_Capabilities") {
|
|
23
|
+
throw new Error("missing <WMS_Capabilities> root element (received WMS Capabilities XML?)");
|
|
56
24
|
}
|
|
25
|
+
const version = xmlAttribute(root, "version") ?? "";
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const serviceNode = xmlChild(root, "Service");
|
|
28
|
+
const service = compactObject({
|
|
29
|
+
title: xmlText(xmlChild(serviceNode, "Title")),
|
|
30
|
+
abstract: xmlText(xmlChild(serviceNode, "Abstract")),
|
|
31
|
+
});
|
|
32
|
+
const capability = xmlChild(root, "Capability");
|
|
33
|
+
const requestNode = xmlChild(capability, "Request");
|
|
34
|
+
const operations = parseOperations(requestNode);
|
|
35
|
+
const operation = (name) => operations.find((candidate) => candidate.name === name);
|
|
36
|
+
const formats = Object.freeze({
|
|
37
|
+
map: operation("GetMap")?.formats ?? Object.freeze([]),
|
|
38
|
+
featureInfo: operation("GetFeatureInfo")?.formats ?? Object.freeze([]),
|
|
39
|
+
legend: operation("GetLegendGraphic")?.formats ?? Object.freeze([]),
|
|
40
|
+
});
|
|
41
|
+
const request = Object.freeze({
|
|
42
|
+
getMap: operation("GetMap") !== undefined,
|
|
43
|
+
getFeatureInfo: operation("GetFeatureInfo") !== undefined,
|
|
44
|
+
getLegendGraphic: operation("GetLegendGraphic") !== undefined,
|
|
45
|
+
operations,
|
|
46
|
+
});
|
|
47
|
+
const layers = Object.freeze(xmlChildren(capability, "Layer").map((layer) => parseLayer(layer, emptyInheritance(), 0, warnings)));
|
|
48
|
+
return Object.freeze({ version, service, layers, formats, request, warnings: Object.freeze(unique(warnings)) });
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
request: requestSupport,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function parseServiceMetadata(capabilityXml) {
|
|
67
|
-
const node = findElement(capabilityXml, 0, "Service");
|
|
68
|
-
if (!node)
|
|
69
|
-
return {};
|
|
70
|
-
const out = {};
|
|
71
|
-
const title = readTextElement(node.inner, "Title");
|
|
72
|
-
if (title !== undefined)
|
|
73
|
-
out.title = title;
|
|
74
|
-
const abstract = readTextElement(node.inner, "Abstract");
|
|
75
|
-
if (abstract !== undefined)
|
|
76
|
-
out.abstract = abstract;
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
function parseFormats(requestInnerXml) {
|
|
80
|
-
const map = collectFormats(requestInnerXml, "GetMap");
|
|
81
|
-
const featureInfo = collectFormats(requestInnerXml, "GetFeatureInfo");
|
|
82
|
-
const legend = collectFormats(requestInnerXml, "GetLegendGraphic");
|
|
83
|
-
return { map, featureInfo, legend };
|
|
84
|
-
}
|
|
85
|
-
function collectFormats(requestInnerXml, requestName) {
|
|
86
|
-
const node = findElement(requestInnerXml, 0, requestName);
|
|
87
|
-
if (!node)
|
|
88
|
-
return [];
|
|
89
|
-
const formats = [];
|
|
90
|
-
let cursor = 0;
|
|
91
|
-
while (cursor < node.inner.length) {
|
|
92
|
-
const fmt = findElement(node.inner, cursor, "Format");
|
|
93
|
-
if (!fmt)
|
|
94
|
-
break;
|
|
95
|
-
const text = decodeXmlText(fmt.inner.trim());
|
|
96
|
-
if (text.length > 0)
|
|
97
|
-
formats.push(text);
|
|
98
|
-
cursor = fmt.endIndex;
|
|
50
|
+
catch (cause) {
|
|
51
|
+
if (cause instanceof HonuaWmsCapabilitiesParseError)
|
|
52
|
+
throw cause;
|
|
53
|
+
const message = cause instanceof Error ? cause.message : "WMS Capabilities XML is malformed";
|
|
54
|
+
throw new HonuaWmsCapabilitiesParseError(message, { cause });
|
|
99
55
|
}
|
|
100
|
-
return formats;
|
|
101
56
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const localStyles = collectStyles(ownInner);
|
|
129
|
-
const localDimensions = collectDimensions(ownInner);
|
|
130
|
-
const mergedCrs = uniqueOrder([...inherited.crs, ...localCrs]);
|
|
131
|
-
const mergedBbox = mergeBoundingBoxes(inherited.bbox, localBbox);
|
|
132
|
-
const mergedStyles = mergeStyles(inherited.styles, localStyles);
|
|
133
|
-
const mergedDimensions = mergeDimensions(inherited.dimensions, localDimensions);
|
|
134
|
-
const queryable = queryableAttr === "1" || /^true$/i.test(queryableAttr ?? "")
|
|
135
|
-
? true
|
|
136
|
-
: queryableAttr === "0"
|
|
137
|
-
? false
|
|
138
|
-
: inherited.queryable;
|
|
139
|
-
const node = {
|
|
140
|
-
name,
|
|
141
|
-
...(title !== undefined ? { title } : {}),
|
|
142
|
-
...(abstract !== undefined ? { abstract } : {}),
|
|
143
|
-
crs: mergedCrs,
|
|
144
|
-
bbox: mergedBbox,
|
|
145
|
-
styles: mergedStyles,
|
|
146
|
-
dimensions: mergedDimensions,
|
|
147
|
-
queryable,
|
|
148
|
-
children: [],
|
|
149
|
-
};
|
|
150
|
-
const ancestorsForChildren = [...ancestors, node];
|
|
151
|
-
const children = [];
|
|
152
|
-
for (const child of iterChildLayers(layer.inner, ancestorsForChildren)) {
|
|
153
|
-
children.push(child);
|
|
57
|
+
function parseOperations(request) {
|
|
58
|
+
const names = ["GetCapabilities", "GetMap", "GetFeatureInfo", "GetLegendGraphic"];
|
|
59
|
+
return Object.freeze(names.flatMap((name) => {
|
|
60
|
+
const nodes = xmlChildren(request, name);
|
|
61
|
+
if (nodes.length > 1)
|
|
62
|
+
throw new Error(`WMS Request repeats ${name} operation metadata.`);
|
|
63
|
+
const node = nodes[0];
|
|
64
|
+
if (!node)
|
|
65
|
+
return [];
|
|
66
|
+
const formats = unique(xmlChildren(node, "Format").flatMap((entry) => xmlText(entry) ?? []));
|
|
67
|
+
const methods = [];
|
|
68
|
+
const getUrls = [];
|
|
69
|
+
const postUrls = [];
|
|
70
|
+
for (const dcp of xmlChildren(node, "DCPType")) {
|
|
71
|
+
const http = xmlChild(dcp, "HTTP");
|
|
72
|
+
for (const method of http?.children ?? []) {
|
|
73
|
+
if (method.localName !== "Get" && method.localName !== "Post")
|
|
74
|
+
continue;
|
|
75
|
+
const online = xmlChild(method, "OnlineResource") ?? method;
|
|
76
|
+
const href = xmlAttribute(online, "href");
|
|
77
|
+
const normalizedMethod = method.localName === "Get" ? "GET" : "POST";
|
|
78
|
+
if (!methods.includes(normalizedMethod))
|
|
79
|
+
methods.push(normalizedMethod);
|
|
80
|
+
if (href)
|
|
81
|
+
(normalizedMethod === "GET" ? getUrls : postUrls).push(href);
|
|
82
|
+
}
|
|
154
83
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
84
|
+
return [
|
|
85
|
+
Object.freeze({
|
|
86
|
+
name,
|
|
87
|
+
formats: Object.freeze(formats),
|
|
88
|
+
methods: Object.freeze(methods),
|
|
89
|
+
getUrls: Object.freeze(unique(getUrls)),
|
|
90
|
+
postUrls: Object.freeze(unique(postUrls)),
|
|
91
|
+
}),
|
|
92
|
+
];
|
|
93
|
+
}));
|
|
158
94
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let queryable = false;
|
|
168
|
-
for (const ancestor of ancestors) {
|
|
169
|
-
crs = uniqueOrder([...crs, ...ancestor.crs]);
|
|
170
|
-
bbox = mergeBoundingBoxes(bbox, ancestor.bbox);
|
|
171
|
-
styles = mergeStyles(styles, ancestor.styles);
|
|
172
|
-
dimensions = mergeDimensions(dimensions, ancestor.dimensions);
|
|
173
|
-
queryable = queryable || ancestor.queryable;
|
|
174
|
-
}
|
|
175
|
-
return { crs, bbox, styles, dimensions, queryable };
|
|
95
|
+
function emptyInheritance() {
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
crs: Object.freeze([]),
|
|
98
|
+
bbox: Object.freeze([]),
|
|
99
|
+
styles: Object.freeze([]),
|
|
100
|
+
dimensions: Object.freeze([]),
|
|
101
|
+
queryable: false,
|
|
102
|
+
});
|
|
176
103
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
104
|
+
function parseLayer(node, inherited, depth, warnings) {
|
|
105
|
+
// The shared XML reader already enforces 32 document levels. This separate
|
|
106
|
+
// semantic limit keeps a capabilities document from constructing a deeper
|
|
107
|
+
// recursive public layer object through wrapper elements.
|
|
108
|
+
if (depth >= 24)
|
|
109
|
+
throw new Error("WMS Capabilities exceeds the 24-level layer hierarchy limit");
|
|
110
|
+
const localCrs = xmlChildren(node, "CRS")
|
|
111
|
+
.concat(xmlChildren(node, "SRS"))
|
|
112
|
+
.flatMap((candidate) => xmlText(candidate)?.split(/\s+/).filter(Boolean) ?? []);
|
|
113
|
+
const crs = Object.freeze(unique([...inherited.crs, ...localCrs]));
|
|
114
|
+
const bbox = Object.freeze(mergeByKey(inherited.bbox, parseBoundingBoxes(node, warnings), (entry) => entry.crs));
|
|
115
|
+
const styles = Object.freeze(mergeByKey(inherited.styles, parseStyles(node, warnings), (entry) => entry.name));
|
|
116
|
+
const dimensions = Object.freeze(mergeByKey(inherited.dimensions, parseDimensions(node, warnings), (entry) => entry.name));
|
|
117
|
+
const queryableValue = xmlAttribute(node, "queryable")?.toLowerCase();
|
|
118
|
+
if (queryableValue !== undefined && !["0", "1", "false", "true"].includes(queryableValue)) {
|
|
119
|
+
warnings.push("WMS layer queryable metadata was malformed and inherited conservatively.");
|
|
185
120
|
}
|
|
186
|
-
|
|
121
|
+
const queryable = queryableValue === "1" || queryableValue === "true"
|
|
122
|
+
? true
|
|
123
|
+
: queryableValue === "0" || queryableValue === "false"
|
|
124
|
+
? false
|
|
125
|
+
: inherited.queryable;
|
|
126
|
+
const next = { crs, bbox, styles, dimensions, queryable };
|
|
127
|
+
const children = Object.freeze(xmlChildren(node, "Layer").map((child) => parseLayer(child, next, depth + 1, warnings)));
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
name: xmlText(xmlChild(node, "Name")) ?? "",
|
|
130
|
+
...optional("title", xmlText(xmlChild(node, "Title"))),
|
|
131
|
+
...optional("abstract", xmlText(xmlChild(node, "Abstract"))),
|
|
132
|
+
crs,
|
|
133
|
+
bbox,
|
|
134
|
+
styles,
|
|
135
|
+
dimensions,
|
|
136
|
+
queryable,
|
|
137
|
+
children,
|
|
138
|
+
});
|
|
187
139
|
}
|
|
188
|
-
function
|
|
189
|
-
if (local.length === 0)
|
|
190
|
-
return parent;
|
|
140
|
+
function parseBoundingBoxes(node, warnings) {
|
|
191
141
|
const out = [];
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const seen = new Set(local.map((s) => s.name));
|
|
207
|
-
const out = [...local];
|
|
208
|
-
for (const s of parent) {
|
|
209
|
-
if (!seen.has(s.name))
|
|
210
|
-
out.push(s);
|
|
211
|
-
}
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
function mergeDimensions(parent, local) {
|
|
215
|
-
if (parent.length === 0)
|
|
216
|
-
return local;
|
|
217
|
-
const seen = new Set(local.map((d) => d.name));
|
|
218
|
-
const out = [...local];
|
|
219
|
-
for (const d of parent) {
|
|
220
|
-
if (!seen.has(d.name))
|
|
221
|
-
out.push(d);
|
|
222
|
-
}
|
|
223
|
-
return out;
|
|
224
|
-
}
|
|
225
|
-
function collectBoundingBoxes(inner) {
|
|
226
|
-
const out = [];
|
|
227
|
-
let cursor = 0;
|
|
228
|
-
while (cursor < inner.length) {
|
|
229
|
-
const node = findElement(inner, cursor, "BoundingBox");
|
|
230
|
-
if (!node)
|
|
231
|
-
break;
|
|
232
|
-
const crs = readAttribute(node.openTag, "CRS") ?? readAttribute(node.openTag, "SRS");
|
|
233
|
-
const minx = parseFloatAttr(node.openTag, "minx");
|
|
234
|
-
const miny = parseFloatAttr(node.openTag, "miny");
|
|
235
|
-
const maxx = parseFloatAttr(node.openTag, "maxx");
|
|
236
|
-
const maxy = parseFloatAttr(node.openTag, "maxy");
|
|
237
|
-
if (crs && Number.isFinite(minx) && Number.isFinite(miny) && Number.isFinite(maxx) && Number.isFinite(maxy)) {
|
|
238
|
-
out.push({ crs, minx, miny, maxx, maxy });
|
|
142
|
+
for (const bbox of xmlChildren(node, "BoundingBox")) {
|
|
143
|
+
const crs = xmlAttribute(bbox, "CRS") ?? xmlAttribute(bbox, "SRS");
|
|
144
|
+
const minx = finiteNumber(xmlAttribute(bbox, "minx"));
|
|
145
|
+
const miny = finiteNumber(xmlAttribute(bbox, "miny"));
|
|
146
|
+
const maxx = finiteNumber(xmlAttribute(bbox, "maxx"));
|
|
147
|
+
const maxy = finiteNumber(xmlAttribute(bbox, "maxy"));
|
|
148
|
+
if (crs &&
|
|
149
|
+
minx !== undefined &&
|
|
150
|
+
miny !== undefined &&
|
|
151
|
+
maxx !== undefined &&
|
|
152
|
+
maxy !== undefined &&
|
|
153
|
+
minx <= maxx &&
|
|
154
|
+
miny <= maxy) {
|
|
155
|
+
out.push(Object.freeze({ crs, minx, miny, maxx, maxy }));
|
|
239
156
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// EX_GeographicBoundingBox is the WMS 1.3 alternative geographic bbox node
|
|
243
|
-
// (always WGS84). honua-server emits both; we pull it as a CRS:84 entry
|
|
244
|
-
// when the layer has not advertised one already.
|
|
245
|
-
const geo = findElement(inner, 0, "EX_GeographicBoundingBox");
|
|
246
|
-
if (geo) {
|
|
247
|
-
const w = parseFloatText(readTextElement(geo.inner, "westBoundLongitude"));
|
|
248
|
-
const e = parseFloatText(readTextElement(geo.inner, "eastBoundLongitude"));
|
|
249
|
-
const s = parseFloatText(readTextElement(geo.inner, "southBoundLatitude"));
|
|
250
|
-
const n = parseFloatText(readTextElement(geo.inner, "northBoundLatitude"));
|
|
251
|
-
if (Number.isFinite(w) &&
|
|
252
|
-
Number.isFinite(e) &&
|
|
253
|
-
Number.isFinite(s) &&
|
|
254
|
-
Number.isFinite(n) &&
|
|
255
|
-
!out.some((bb) => bb.crs === "CRS:84")) {
|
|
256
|
-
out.push({ crs: "CRS:84", minx: w, miny: s, maxx: e, maxy: n });
|
|
157
|
+
else {
|
|
158
|
+
warnings.push("WMS BoundingBox metadata was malformed and ignored.");
|
|
257
159
|
}
|
|
258
160
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
entry.title = title;
|
|
277
|
-
if (legendUrl !== undefined)
|
|
278
|
-
entry.legendUrl = legendUrl;
|
|
279
|
-
out.push(entry);
|
|
161
|
+
const geographic = xmlChild(node, "EX_GeographicBoundingBox");
|
|
162
|
+
if (geographic && !out.some((entry) => entry.crs === "CRS:84")) {
|
|
163
|
+
const minx = finiteNumber(xmlText(xmlChild(geographic, "westBoundLongitude")));
|
|
164
|
+
const miny = finiteNumber(xmlText(xmlChild(geographic, "southBoundLatitude")));
|
|
165
|
+
const maxx = finiteNumber(xmlText(xmlChild(geographic, "eastBoundLongitude")));
|
|
166
|
+
const maxy = finiteNumber(xmlText(xmlChild(geographic, "northBoundLatitude")));
|
|
167
|
+
if (minx !== undefined &&
|
|
168
|
+
miny !== undefined &&
|
|
169
|
+
maxx !== undefined &&
|
|
170
|
+
maxy !== undefined &&
|
|
171
|
+
minx <= maxx &&
|
|
172
|
+
miny <= maxy &&
|
|
173
|
+
minx >= -180 &&
|
|
174
|
+
maxx <= 180 &&
|
|
175
|
+
miny >= -90 &&
|
|
176
|
+
maxy <= 90) {
|
|
177
|
+
out.push(Object.freeze({ crs: "CRS:84", minx, miny, maxx, maxy }));
|
|
280
178
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return out;
|
|
284
|
-
}
|
|
285
|
-
function collectDimensions(inner) {
|
|
286
|
-
const out = [];
|
|
287
|
-
let cursor = 0;
|
|
288
|
-
while (cursor < inner.length) {
|
|
289
|
-
const node = findElement(inner, cursor, "Dimension");
|
|
290
|
-
if (!node)
|
|
291
|
-
break;
|
|
292
|
-
const name = readAttribute(node.openTag, "name");
|
|
293
|
-
if (typeof name === "string" && name.length > 0) {
|
|
294
|
-
const units = readAttribute(node.openTag, "units");
|
|
295
|
-
const def = readAttribute(node.openTag, "default");
|
|
296
|
-
const text = decodeXmlText(node.inner.trim());
|
|
297
|
-
const values = text.length > 0
|
|
298
|
-
? text
|
|
299
|
-
.split(",")
|
|
300
|
-
.map((v) => v.trim())
|
|
301
|
-
.filter((v) => v.length > 0)
|
|
302
|
-
: [];
|
|
303
|
-
const dim = { name, values };
|
|
304
|
-
if (units !== undefined)
|
|
305
|
-
dim.units = units;
|
|
306
|
-
if (def !== undefined)
|
|
307
|
-
dim.default = def;
|
|
308
|
-
out.push(dim);
|
|
179
|
+
else {
|
|
180
|
+
warnings.push("WMS EX_GeographicBoundingBox metadata was malformed and ignored.");
|
|
309
181
|
}
|
|
310
|
-
cursor = node.endIndex;
|
|
311
182
|
}
|
|
312
183
|
return out;
|
|
313
184
|
}
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
break;
|
|
321
|
-
const text = decodeXmlText(node.inner.trim());
|
|
322
|
-
if (text.length > 0)
|
|
323
|
-
out.push(text);
|
|
324
|
-
cursor = node.endIndex;
|
|
325
|
-
}
|
|
326
|
-
return out;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Return `inner` with every nested `<Layer>...</Layer>` (and self-closed
|
|
330
|
-
* `<Layer .../>`) subtree elided. Used by `iterChildLayers` to read the
|
|
331
|
-
* layer's *own* metadata without walking into descendant layers.
|
|
332
|
-
*/
|
|
333
|
-
function stripNestedLayers(inner) {
|
|
334
|
-
if (inner.indexOf("<Layer") < 0)
|
|
335
|
-
return inner;
|
|
336
|
-
let cursor = 0;
|
|
337
|
-
let out = "";
|
|
338
|
-
while (cursor < inner.length) {
|
|
339
|
-
const node = findElement(inner, cursor, "Layer");
|
|
340
|
-
if (!node) {
|
|
341
|
-
out += inner.slice(cursor);
|
|
342
|
-
break;
|
|
185
|
+
function parseStyles(node, warnings) {
|
|
186
|
+
return xmlChildren(node, "Style").flatMap((style) => {
|
|
187
|
+
const name = xmlText(xmlChild(style, "Name"));
|
|
188
|
+
if (!name) {
|
|
189
|
+
warnings.push("WMS Style metadata without a name was ignored.");
|
|
190
|
+
return [];
|
|
343
191
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
192
|
+
const legend = xmlChild(style, "LegendURL");
|
|
193
|
+
const resource = xmlChild(legend, "OnlineResource");
|
|
194
|
+
return [
|
|
195
|
+
Object.freeze({
|
|
196
|
+
name,
|
|
197
|
+
...optional("title", xmlText(xmlChild(style, "Title"))),
|
|
198
|
+
...optional("legendUrl", resource ? xmlAttribute(resource, "href") : undefined),
|
|
199
|
+
...optional("legendFormat", xmlText(xmlChild(legend, "Format"))),
|
|
200
|
+
}),
|
|
201
|
+
];
|
|
202
|
+
});
|
|
348
203
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
function findElement(xml, from, tag) {
|
|
357
|
-
let i = from;
|
|
358
|
-
while (i < xml.length) {
|
|
359
|
-
if (xml[i] !== "<") {
|
|
360
|
-
i += 1;
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
const next = xml[i + 1];
|
|
364
|
-
if (next === "!" && xml.startsWith("<!--", i)) {
|
|
365
|
-
const end = xml.indexOf("-->", i + 4);
|
|
366
|
-
if (end < 0)
|
|
367
|
-
return undefined;
|
|
368
|
-
i = end + 3;
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (next === "!" && xml.startsWith("<![CDATA[", i)) {
|
|
372
|
-
const end = xml.indexOf("]]>", i + 9);
|
|
373
|
-
if (end < 0)
|
|
374
|
-
return undefined;
|
|
375
|
-
i = end + 3;
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
if (next === "?" || next === "!") {
|
|
379
|
-
const end = xml.indexOf(">", i);
|
|
380
|
-
if (end < 0)
|
|
381
|
-
return undefined;
|
|
382
|
-
i = end + 1;
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
const isClosing = next === "/";
|
|
386
|
-
const nameStart = isClosing ? i + 2 : i + 1;
|
|
387
|
-
const matchesTag = xml.startsWith(tag, nameStart) && isTagBoundary(xml.charCodeAt(nameStart + tag.length));
|
|
388
|
-
if (!matchesTag) {
|
|
389
|
-
const close = xml.indexOf(">", i);
|
|
390
|
-
if (close < 0)
|
|
391
|
-
return undefined;
|
|
392
|
-
i = close + 1;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
const close = xml.indexOf(">", i);
|
|
396
|
-
if (close < 0)
|
|
397
|
-
return undefined;
|
|
398
|
-
const openTag = xml.slice(i, close + 1);
|
|
399
|
-
const selfClosing = !isClosing && xml.charCodeAt(close - 1) === 47; // '/'
|
|
400
|
-
if (selfClosing) {
|
|
401
|
-
return { openTag, inner: "", startIndex: i, endIndex: close + 1 };
|
|
402
|
-
}
|
|
403
|
-
if (isClosing) {
|
|
404
|
-
// unmatched closing tag at this level — caller advanced past the
|
|
405
|
-
// opener. Skip past and keep searching.
|
|
406
|
-
i = close + 1;
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
// Walk forward looking for the matching close tag, accounting for
|
|
410
|
-
// nested same-name openings.
|
|
411
|
-
let depth = 1;
|
|
412
|
-
let cursor = close + 1;
|
|
413
|
-
while (cursor < xml.length && depth > 0) {
|
|
414
|
-
const angle = xml.indexOf("<", cursor);
|
|
415
|
-
if (angle < 0)
|
|
416
|
-
break;
|
|
417
|
-
const next2 = xml[angle + 1];
|
|
418
|
-
if (next2 === "!" && xml.startsWith("<!--", angle)) {
|
|
419
|
-
const end = xml.indexOf("-->", angle + 4);
|
|
420
|
-
if (end < 0)
|
|
421
|
-
return undefined;
|
|
422
|
-
cursor = end + 3;
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
if (next2 === "!" && xml.startsWith("<![CDATA[", angle)) {
|
|
426
|
-
const end = xml.indexOf("]]>", angle + 9);
|
|
427
|
-
if (end < 0)
|
|
428
|
-
return undefined;
|
|
429
|
-
cursor = end + 3;
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
const isCloser = next2 === "/";
|
|
433
|
-
const inspectStart = isCloser ? angle + 2 : angle + 1;
|
|
434
|
-
const matchesSame = xml.startsWith(tag, inspectStart) && isTagBoundary(xml.charCodeAt(inspectStart + tag.length));
|
|
435
|
-
const angleClose = xml.indexOf(">", angle);
|
|
436
|
-
if (angleClose < 0)
|
|
437
|
-
return undefined;
|
|
438
|
-
if (matchesSame) {
|
|
439
|
-
if (isCloser) {
|
|
440
|
-
depth -= 1;
|
|
441
|
-
if (depth === 0) {
|
|
442
|
-
return {
|
|
443
|
-
openTag,
|
|
444
|
-
inner: xml.slice(close + 1, angle),
|
|
445
|
-
startIndex: i,
|
|
446
|
-
endIndex: angleClose + 1,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
const innerSelfClose = xml.charCodeAt(angleClose - 1) === 47;
|
|
452
|
-
if (!innerSelfClose)
|
|
453
|
-
depth += 1;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
cursor = angleClose + 1;
|
|
204
|
+
function parseDimensions(node, warnings) {
|
|
205
|
+
const candidates = [...xmlChildren(node, "Dimension"), ...xmlChildren(node, "Extent")];
|
|
206
|
+
return candidates.flatMap((dimension) => {
|
|
207
|
+
const name = xmlAttribute(dimension, "name");
|
|
208
|
+
if (!name) {
|
|
209
|
+
warnings.push("WMS Dimension metadata without a name was ignored.");
|
|
210
|
+
return [];
|
|
457
211
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
212
|
+
const raw = xmlText(dimension);
|
|
213
|
+
const values = raw
|
|
214
|
+
? raw
|
|
215
|
+
.split(",")
|
|
216
|
+
.map((value) => value.trim())
|
|
217
|
+
.filter(Boolean)
|
|
218
|
+
: [];
|
|
219
|
+
return [
|
|
220
|
+
Object.freeze({
|
|
221
|
+
name,
|
|
222
|
+
...optional("units", xmlAttribute(dimension, "units")),
|
|
223
|
+
...optional("default", xmlAttribute(dimension, "default")),
|
|
224
|
+
values: Object.freeze(unique(values)),
|
|
225
|
+
}),
|
|
226
|
+
];
|
|
227
|
+
});
|
|
461
228
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
return code === 32 || code === 9 || code === 10 || code === 13 || code === 47 || code === 62;
|
|
465
|
-
}
|
|
466
|
-
function readAttribute(openTag, name) {
|
|
467
|
-
// Match `name="value"` or `name='value'`. Tolerates leading whitespace
|
|
468
|
-
// and case-sensitive name match (WMS attribute names are case-sensitive).
|
|
469
|
-
const idx = findAttributeIndex(openTag, name);
|
|
470
|
-
if (idx < 0)
|
|
471
|
-
return undefined;
|
|
472
|
-
const after = openTag.slice(idx + name.length);
|
|
473
|
-
const eq = after.indexOf("=");
|
|
474
|
-
if (eq < 0)
|
|
229
|
+
function finiteNumber(value) {
|
|
230
|
+
if (value === undefined || value.trim() === "")
|
|
475
231
|
return undefined;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
cursor += 1;
|
|
479
|
-
const quote = after[cursor];
|
|
480
|
-
if (quote !== '"' && quote !== "'")
|
|
481
|
-
return undefined;
|
|
482
|
-
const end = after.indexOf(quote, cursor + 1);
|
|
483
|
-
if (end < 0)
|
|
484
|
-
return undefined;
|
|
485
|
-
return decodeXmlText(after.slice(cursor + 1, end));
|
|
486
|
-
}
|
|
487
|
-
function findAttributeIndex(openTag, name) {
|
|
488
|
-
let i = 0;
|
|
489
|
-
while (i < openTag.length) {
|
|
490
|
-
const found = openTag.indexOf(name, i);
|
|
491
|
-
if (found < 0)
|
|
492
|
-
return -1;
|
|
493
|
-
const before = openTag.charCodeAt(found - 1);
|
|
494
|
-
const isBoundaryStart = found === 0 || before === 32 || before === 9 || before === 10 || before === 13;
|
|
495
|
-
const after = openTag.charCodeAt(found + name.length);
|
|
496
|
-
const isBoundaryEnd = after === 61 || after === 32 || after === 9 || after === 10 || after === 13;
|
|
497
|
-
if (isBoundaryStart && isBoundaryEnd) {
|
|
498
|
-
return found;
|
|
499
|
-
}
|
|
500
|
-
i = found + 1;
|
|
501
|
-
}
|
|
502
|
-
return -1;
|
|
232
|
+
const parsed = Number(value);
|
|
233
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
503
234
|
}
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
if (!node)
|
|
507
|
-
return undefined;
|
|
508
|
-
const decoded = decodeXmlText(node.inner.trim());
|
|
509
|
-
return decoded;
|
|
235
|
+
function unique(values) {
|
|
236
|
+
return [...new Set(values)];
|
|
510
237
|
}
|
|
511
|
-
function
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
return Number.NaN;
|
|
515
|
-
const parsed = Number(value);
|
|
516
|
-
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
238
|
+
function mergeByKey(parent, local, key) {
|
|
239
|
+
const localKeys = new Set(local.map(key));
|
|
240
|
+
return [...local, ...parent.filter((entry) => !localKeys.has(key(entry)))];
|
|
517
241
|
}
|
|
518
|
-
function
|
|
519
|
-
|
|
520
|
-
return Number.NaN;
|
|
521
|
-
const parsed = Number(value);
|
|
522
|
-
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
242
|
+
function optional(key, value) {
|
|
243
|
+
return value === undefined ? {} : { [key]: value };
|
|
523
244
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
return text;
|
|
527
|
-
return decodeXmlEntities(text);
|
|
245
|
+
function compactObject(value) {
|
|
246
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)));
|
|
528
247
|
}
|
|
529
|
-
/**
|
|
530
|
-
* Walk a parsed `WmsCapabilities` tree and yield every named layer
|
|
531
|
-
* (skipping container layers without a `name`). Useful for descriptor
|
|
532
|
-
* resolution: callers want to bind a `LAYER` name without re-walking
|
|
533
|
-
* the parent hierarchy.
|
|
534
|
-
*/
|
|
535
248
|
export function* iterateWmsLayers(capabilities) {
|
|
536
249
|
const stack = [...capabilities.layers];
|
|
537
250
|
while (stack.length > 0) {
|
|
538
251
|
const next = stack.shift();
|
|
539
|
-
if (!next)
|
|
540
|
-
break;
|
|
541
252
|
if (next.name.length > 0)
|
|
542
253
|
yield next;
|
|
543
|
-
|
|
544
|
-
stack.push(child);
|
|
545
|
-
}
|
|
254
|
+
stack.unshift(...next.children);
|
|
546
255
|
}
|
|
547
256
|
}
|
|
548
|
-
/**
|
|
549
|
-
* Find the first named layer with a matching name, or `undefined` if the
|
|
550
|
-
* capabilities document does not advertise it. Walks both root layers and
|
|
551
|
-
* children. Intended for the `HonuaWms.layer(name)` resolver path.
|
|
552
|
-
*/
|
|
553
257
|
export function findWmsLayer(capabilities, name) {
|
|
554
|
-
for (const layer of iterateWmsLayers(capabilities))
|
|
258
|
+
for (const layer of iterateWmsLayers(capabilities))
|
|
555
259
|
if (layer.name === name)
|
|
556
260
|
return layer;
|
|
557
|
-
}
|
|
558
261
|
return undefined;
|
|
559
262
|
}
|