@honua/react 0.1.2-beta.0 → 0.1.7-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 +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +88 -19
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +4 -0
- package/contract/widget-source.d.ts.map +1 -1
- package/core/client.d.ts +51 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +234 -54
- package/core/error-classifications.d.ts +23 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +23 -0
- package/core/error-code-registry.d.ts +24 -1
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +23 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +2 -2
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +69 -5
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +327 -10
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +32 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +158 -23
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +138 -11
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/interactions/declarative-contract.d.ts +149 -0
- package/interactions/declarative-contract.d.ts.map +1 -0
- package/interactions/declarative-contract.js +293 -0
- package/interactions/declarative.d.ts +198 -0
- package/interactions/declarative.d.ts.map +1 -0
- package/interactions/declarative.js +470 -0
- package/interactions/feature-state.d.ts +7 -0
- package/interactions/feature-state.d.ts.map +1 -1
- package/interactions/feature-state.js +10 -2
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/maplibre-target.js +1 -1
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/react/honua-map.d.ts +3 -1
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +11 -1
- package/react/hooks.d.ts +1 -1
- package/react/hooks.js +1 -1
- package/react/selection.d.ts.map +1 -1
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +1 -1
- package/react/use-mounted-source.js +1 -1
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/exploration.d.ts +16 -0
- package/realtime/exploration.d.ts.map +1 -0
- package/realtime/exploration.js +32 -0
- package/realtime/honua-server.d.ts +131 -0
- package/realtime/honua-server.d.ts.map +1 -0
- package/realtime/honua-server.js +302 -0
- package/realtime/index.d.ts +39 -0
- package/realtime/index.d.ts.map +1 -0
- package/realtime/index.js +26 -0
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/projections.d.ts +30 -0
- package/realtime/projections.d.ts.map +1 -0
- package/realtime/projections.js +51 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/reducer.d.ts +14 -0
- package/realtime/reducer.d.ts.map +1 -0
- package/realtime/reducer.js +351 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts +23 -0
- package/realtime/sse.d.ts.map +1 -0
- package/realtime/sse.js +153 -0
- package/realtime/store.d.ts +18 -0
- package/realtime/store.d.ts.map +1 -0
- package/realtime/store.js +63 -0
- package/realtime/types.d.ts +190 -0
- package/realtime/types.d.ts.map +1 -0
- package/realtime/types.js +10 -0
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +28 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +73 -0
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/runtime/style-compose.d.ts.map +1 -1
- package/runtime/style-compose.js +78 -9
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
package/core/wfs-capabilities.js
CHANGED
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
* @module
|
|
20
20
|
*/
|
|
21
21
|
import { HonuaWfsExceptionError } from "./errors.js";
|
|
22
|
+
/** Fixed allocation ceilings for every WFS metadata XML document. */
|
|
23
|
+
export const WFS_XML_LIMITS = Object.freeze({
|
|
24
|
+
maxBytes: 2 * 1024 * 1024,
|
|
25
|
+
maxElements: 12_000,
|
|
26
|
+
maxDepth: 32,
|
|
27
|
+
maxAttributesPerElement: 64,
|
|
28
|
+
maxAttributeBytesPerElement: 64 * 1024,
|
|
29
|
+
maxTextBytes: 512 * 1024,
|
|
30
|
+
});
|
|
22
31
|
/**
|
|
23
32
|
* Parse a WFS `GetCapabilities` document. Throws when the document declares
|
|
24
33
|
* a DOCTYPE / ENTITY (XXE defense) or when the root element is not
|
|
@@ -73,7 +82,7 @@ export function parseWfsCapabilities(xml) {
|
|
|
73
82
|
for (const value of findChildren(allowed, "Value")) {
|
|
74
83
|
const text = value.text.trim();
|
|
75
84
|
if (text)
|
|
76
|
-
outputFormats.push(text
|
|
85
|
+
outputFormats.push(text);
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
// Older WFS servers advertise output formats as direct <Value> children
|
|
@@ -81,17 +90,19 @@ export function parseWfsCapabilities(xml) {
|
|
|
81
90
|
for (const value of findChildren(param, "Value")) {
|
|
82
91
|
const text = value.text.trim();
|
|
83
92
|
if (text)
|
|
84
|
-
outputFormats.push(text
|
|
93
|
+
outputFormats.push(text);
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
|
-
|
|
96
|
+
const immutableMethods = Object.freeze([...new Set(methods)]);
|
|
97
|
+
const immutableOutputFormats = Object.freeze([...outputFormats]);
|
|
98
|
+
operations.set(name, Object.freeze({
|
|
88
99
|
name,
|
|
89
|
-
methods:
|
|
90
|
-
outputFormats,
|
|
100
|
+
methods: immutableMethods,
|
|
101
|
+
outputFormats: immutableOutputFormats,
|
|
91
102
|
...(getUrl !== undefined ? { getUrl } : {}),
|
|
92
103
|
...(postUrl !== undefined ? { postUrl } : {}),
|
|
93
|
-
});
|
|
94
|
-
outputFormatsByOp.set(name,
|
|
104
|
+
}));
|
|
105
|
+
outputFormatsByOp.set(name, immutableOutputFormats);
|
|
95
106
|
}
|
|
96
107
|
const featureTypeList = findChild(root, "FeatureTypeList");
|
|
97
108
|
const featureTypes = [];
|
|
@@ -100,11 +111,18 @@ export function parseWfsCapabilities(xml) {
|
|
|
100
111
|
const nameNode = findChild(ft, "Name");
|
|
101
112
|
const titleNode = findChild(ft, "Title");
|
|
102
113
|
const defaultCrsNode = findChild(ft, "DefaultCRS") ?? findChild(ft, "DefaultSRS");
|
|
114
|
+
const otherCrs = [
|
|
115
|
+
...findChildren(ft, "OtherCRS").map((node) => node.text.trim()),
|
|
116
|
+
...findChildren(ft, "OtherSRS").map((node) => node.text.trim()),
|
|
117
|
+
].filter((value) => value.length > 0);
|
|
103
118
|
const bboxNode = findChild(ft, "WGS84BoundingBox");
|
|
104
119
|
const lowerCorner = bboxNode ? findChild(bboxNode, "LowerCorner") : undefined;
|
|
105
120
|
const upperCorner = bboxNode ? findChild(bboxNode, "UpperCorner") : undefined;
|
|
121
|
+
const namespace = nameNode ? namespaceBindingForQName(nameNode, nameNode.text.trim()) : undefined;
|
|
106
122
|
const entry = {
|
|
107
123
|
name: nameNode?.text.trim() ?? "",
|
|
124
|
+
otherCrs: Object.freeze([...otherCrs]),
|
|
125
|
+
...(namespace ? { namespace } : {}),
|
|
108
126
|
};
|
|
109
127
|
if (titleNode?.text)
|
|
110
128
|
entry.title = titleNode.text.trim();
|
|
@@ -114,11 +132,11 @@ export function parseWfsCapabilities(xml) {
|
|
|
114
132
|
const [xmin, ymin] = lowerCorner.text.trim().split(/\s+/).map(Number);
|
|
115
133
|
const [xmax, ymax] = upperCorner.text.trim().split(/\s+/).map(Number);
|
|
116
134
|
if (Number.isFinite(xmin) && Number.isFinite(ymin) && Number.isFinite(xmax) && Number.isFinite(ymax)) {
|
|
117
|
-
entry.wgs84BoundingBox = { xmin, ymin, xmax, ymax };
|
|
135
|
+
entry.wgs84BoundingBox = Object.freeze({ xmin, ymin, xmax, ymax });
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
138
|
if (entry.name)
|
|
121
|
-
featureTypes.push(entry);
|
|
139
|
+
featureTypes.push(Object.freeze(entry));
|
|
122
140
|
}
|
|
123
141
|
}
|
|
124
142
|
const filterCapabilities = parseFilterCapabilities(findChild(root, "Filter_Capabilities"));
|
|
@@ -128,17 +146,61 @@ export function parseWfsCapabilities(xml) {
|
|
|
128
146
|
if (name.startsWith("xmlns:") && value)
|
|
129
147
|
namespaces.set(name.slice("xmlns:".length), value);
|
|
130
148
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
return Object.freeze({
|
|
150
|
+
...(version ? { version } : {}),
|
|
151
|
+
operations: immutableMap(operations),
|
|
152
|
+
outputFormatsByOp: immutableMap(outputFormatsByOp),
|
|
153
|
+
featureTypes: Object.freeze([...featureTypes]),
|
|
154
|
+
namespaces: immutableMap(namespaces),
|
|
155
|
+
filterCapabilities: immutableFilterCapabilities(filterCapabilities),
|
|
156
|
+
storedQueryNames: Object.freeze([...storedQueryNames]),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function namespaceBindingForQName(node, qname) {
|
|
160
|
+
const separator = qname.indexOf(":");
|
|
161
|
+
if (separator <= 0 || separator === qname.length - 1 || qname.indexOf(":", separator + 1) !== -1) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const prefix = qname.slice(0, separator);
|
|
165
|
+
return Object.freeze({ prefix, uri: node.namespaces[prefix] ?? "" });
|
|
166
|
+
}
|
|
167
|
+
function immutableMap(source) {
|
|
168
|
+
const owned = new Map(source);
|
|
169
|
+
const view = {
|
|
170
|
+
get size() {
|
|
171
|
+
return owned.size;
|
|
172
|
+
},
|
|
173
|
+
get(key) {
|
|
174
|
+
return owned.get(key);
|
|
175
|
+
},
|
|
176
|
+
has(key) {
|
|
177
|
+
return owned.has(key);
|
|
178
|
+
},
|
|
179
|
+
forEach(callbackfn, thisArg) {
|
|
180
|
+
for (const [key, value] of owned)
|
|
181
|
+
callbackfn.call(thisArg, value, key, view);
|
|
182
|
+
},
|
|
183
|
+
entries() {
|
|
184
|
+
return owned.entries();
|
|
185
|
+
},
|
|
186
|
+
keys() {
|
|
187
|
+
return owned.keys();
|
|
188
|
+
},
|
|
189
|
+
values() {
|
|
190
|
+
return owned.values();
|
|
191
|
+
},
|
|
192
|
+
[Symbol.iterator]() {
|
|
193
|
+
return owned[Symbol.iterator]();
|
|
194
|
+
},
|
|
138
195
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
196
|
+
return Object.freeze(view);
|
|
197
|
+
}
|
|
198
|
+
function immutableFilterCapabilities(value) {
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
spatial: Object.freeze([...value.spatial]),
|
|
201
|
+
scalar: Object.freeze([...value.scalar]),
|
|
202
|
+
temporal: Object.freeze([...value.temporal]),
|
|
203
|
+
});
|
|
142
204
|
}
|
|
143
205
|
/**
|
|
144
206
|
* Parse an `ows:ExceptionReport` document into structured fields. Used when
|
|
@@ -235,6 +297,106 @@ export function parseListStoredQueriesResponse(xml) {
|
|
|
235
297
|
}
|
|
236
298
|
return ids;
|
|
237
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* GML property types that carry a geometry value. A `DescribeFeatureType`
|
|
302
|
+
* XSD names the feature type's geometry property through one of these, so the
|
|
303
|
+
* set is what lets the adapter resolve the real property name per server
|
|
304
|
+
* (`the_geom` on PostGIS-via-GeoServer, `msGeometry` on MapServer, arbitrary
|
|
305
|
+
* per-schema names elsewhere) instead of assuming one vendor default.
|
|
306
|
+
*/
|
|
307
|
+
const GML_GEOMETRY_PROPERTY_TYPES = new Set([
|
|
308
|
+
"GeometryPropertyType",
|
|
309
|
+
"GeometryAssociationType",
|
|
310
|
+
"GeometryArrayPropertyType",
|
|
311
|
+
"MultiGeometryPropertyType",
|
|
312
|
+
"AbstractGeometryType",
|
|
313
|
+
"PointPropertyType",
|
|
314
|
+
"MultiPointPropertyType",
|
|
315
|
+
"LineStringPropertyType",
|
|
316
|
+
"MultiLineStringPropertyType",
|
|
317
|
+
"CurvePropertyType",
|
|
318
|
+
"MultiCurvePropertyType",
|
|
319
|
+
"PolygonPropertyType",
|
|
320
|
+
"MultiPolygonPropertyType",
|
|
321
|
+
"SurfacePropertyType",
|
|
322
|
+
"MultiSurfacePropertyType",
|
|
323
|
+
"SolidPropertyType",
|
|
324
|
+
"MultiSolidPropertyType",
|
|
325
|
+
]);
|
|
326
|
+
/** GML namespace prefixes bind to a URI under this root in every GML version. */
|
|
327
|
+
const GML_NAMESPACE_ROOT = "http://www.opengis.net/gml";
|
|
328
|
+
/**
|
|
329
|
+
* Parse a `DescribeFeatureType` XSD and return the geometry property names
|
|
330
|
+
* declared for `typeName`, in schema declaration order. Empty when the schema
|
|
331
|
+
* declares no GML geometry property (schema-less / non-spatial types) — the
|
|
332
|
+
* caller fails closed rather than guessing a vendor default.
|
|
333
|
+
*
|
|
334
|
+
* Only the requested feature type's own complex type is walked, so a schema
|
|
335
|
+
* carrying several types cannot leak another type's geometry property.
|
|
336
|
+
*/
|
|
337
|
+
export function parseWfsDescribeFeatureTypeGeometry(xml, typeName) {
|
|
338
|
+
const root = parseXml(xml);
|
|
339
|
+
if (root.local !== "schema") {
|
|
340
|
+
if (root.local === "ExceptionReport") {
|
|
341
|
+
const report = parseExceptionFromRoot(root);
|
|
342
|
+
throw new HonuaWfsExceptionError(report.exceptionCode, report.message, report.locator);
|
|
343
|
+
}
|
|
344
|
+
throw new Error(`WFS DescribeFeatureType: expected <schema>, got <${root.qname}>`);
|
|
345
|
+
}
|
|
346
|
+
const declaration = findFeatureTypeDeclaration(root, stripPrefix(typeName));
|
|
347
|
+
if (!declaration)
|
|
348
|
+
return Object.freeze([]);
|
|
349
|
+
const names = [];
|
|
350
|
+
collectGeometryPropertyNames(declaration, names);
|
|
351
|
+
return Object.freeze(names);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Locate the complex type that declares one feature type's properties. Servers
|
|
355
|
+
* spell this three ways: a global `<element name="lot" type="ns:lotType"/>`
|
|
356
|
+
* pointing at a named `<complexType>`, an inline anonymous `<complexType>` on
|
|
357
|
+
* that element, or (single-type responses) just the one complex type in the
|
|
358
|
+
* document.
|
|
359
|
+
*/
|
|
360
|
+
function findFeatureTypeDeclaration(root, localTypeName) {
|
|
361
|
+
const complexTypes = findChildren(root, "complexType");
|
|
362
|
+
const element = findChildren(root, "element").find((node) => node.attributes.name === localTypeName);
|
|
363
|
+
if (element) {
|
|
364
|
+
const typeRef = element.attributes.type;
|
|
365
|
+
const named = typeRef
|
|
366
|
+
? complexTypes.find((node) => node.attributes.name === stripPrefix(typeRef))
|
|
367
|
+
: findChild(element, "complexType");
|
|
368
|
+
if (named)
|
|
369
|
+
return named;
|
|
370
|
+
}
|
|
371
|
+
const conventional = complexTypes.find((node) => node.attributes.name === `${localTypeName}Type`);
|
|
372
|
+
if (conventional)
|
|
373
|
+
return conventional;
|
|
374
|
+
return complexTypes.length === 1 ? complexTypes[0] : undefined;
|
|
375
|
+
}
|
|
376
|
+
function collectGeometryPropertyNames(node, out) {
|
|
377
|
+
for (const child of node.children) {
|
|
378
|
+
if (child.local === "element") {
|
|
379
|
+
const name = child.attributes.name;
|
|
380
|
+
const type = child.attributes.type;
|
|
381
|
+
if (name && type && isGmlGeometryPropertyType(child, type)) {
|
|
382
|
+
out.push(name);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
collectGeometryPropertyNames(child, out);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function isGmlGeometryPropertyType(node, type) {
|
|
390
|
+
const colon = type.indexOf(":");
|
|
391
|
+
const prefix = colon > 0 ? type.slice(0, colon) : undefined;
|
|
392
|
+
if (!GML_GEOMETRY_PROPERTY_TYPES.has(stripPrefix(type)))
|
|
393
|
+
return false;
|
|
394
|
+
// A prefix that resolves to a non-GML namespace is a same-named schema type
|
|
395
|
+
// from another vocabulary, not a geometry property. An unresolvable prefix
|
|
396
|
+
// is accepted: servers routinely omit the binding from the fragment we read.
|
|
397
|
+
const uri = prefix === undefined ? undefined : node.namespaces[prefix];
|
|
398
|
+
return uri === undefined || uri.startsWith(GML_NAMESPACE_ROOT);
|
|
399
|
+
}
|
|
238
400
|
export function parseWfsTransactionResponse(xml) {
|
|
239
401
|
const root = parseXml(xml);
|
|
240
402
|
if (root.local !== "TransactionResponse") {
|
|
@@ -291,6 +453,9 @@ export function parseXml(xml) {
|
|
|
291
453
|
if (typeof xml !== "string" || xml.length === 0) {
|
|
292
454
|
throw new Error("WFS XML parser: empty document");
|
|
293
455
|
}
|
|
456
|
+
if (utf8Length(xml) > WFS_XML_LIMITS.maxBytes) {
|
|
457
|
+
throw new Error(`WFS XML parser: document exceeds the ${WFS_XML_LIMITS.maxBytes}-byte limit`);
|
|
458
|
+
}
|
|
294
459
|
// XXE / external-entity defense: refuse documents that declare a DOCTYPE
|
|
295
460
|
// (which is the only way to introduce ENTITY declarations in XML 1.0).
|
|
296
461
|
// This is intentionally strict — Honua's WFS surfaces never emit a
|
|
@@ -305,6 +470,7 @@ export function parseXml(xml) {
|
|
|
305
470
|
let i = 0;
|
|
306
471
|
const stack = [];
|
|
307
472
|
let root;
|
|
473
|
+
const budget = { elements: 0, textBytes: 0 };
|
|
308
474
|
while (i < len) {
|
|
309
475
|
if (stripped[i] === "<") {
|
|
310
476
|
if (stripped.startsWith("<?", i)) {
|
|
@@ -328,7 +494,7 @@ export function parseXml(xml) {
|
|
|
328
494
|
const text = stripped.slice(i + 9, end);
|
|
329
495
|
const top = stack[stack.length - 1];
|
|
330
496
|
if (top)
|
|
331
|
-
top
|
|
497
|
+
appendXmlText(top, text, budget);
|
|
332
498
|
i = end + 3;
|
|
333
499
|
continue;
|
|
334
500
|
}
|
|
@@ -356,17 +522,32 @@ export function parseXml(xml) {
|
|
|
356
522
|
const selfClosing = inner.endsWith("/");
|
|
357
523
|
const tagBody = selfClosing ? inner.slice(0, -1).trim() : inner.trim();
|
|
358
524
|
const { name, attributes } = parseTagBody(tagBody);
|
|
525
|
+
budget.elements += 1;
|
|
526
|
+
if (budget.elements > WFS_XML_LIMITS.maxElements) {
|
|
527
|
+
throw new Error(`WFS XML parser: document exceeds the ${WFS_XML_LIMITS.maxElements}-element limit`);
|
|
528
|
+
}
|
|
529
|
+
if (stack.length + 1 > WFS_XML_LIMITS.maxDepth) {
|
|
530
|
+
throw new Error(`WFS XML parser: document exceeds the ${WFS_XML_LIMITS.maxDepth}-level depth limit`);
|
|
531
|
+
}
|
|
359
532
|
const local = stripPrefix(name);
|
|
533
|
+
const parent = stack[stack.length - 1];
|
|
534
|
+
const namespaces = Object.assign(Object.create(null), parent?.namespaces);
|
|
535
|
+
for (const [attributeName, value] of Object.entries(attributes)) {
|
|
536
|
+
if (attributeName === "xmlns")
|
|
537
|
+
namespaces[""] = value;
|
|
538
|
+
else if (attributeName.startsWith("xmlns:"))
|
|
539
|
+
namespaces[attributeName.slice("xmlns:".length)] = value;
|
|
540
|
+
}
|
|
360
541
|
const node = {
|
|
361
542
|
local,
|
|
362
543
|
qname: name,
|
|
363
544
|
attributes,
|
|
545
|
+
namespaces,
|
|
364
546
|
children: [],
|
|
365
547
|
text: "",
|
|
366
548
|
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
top.children.push(node);
|
|
549
|
+
if (parent)
|
|
550
|
+
parent.children.push(node);
|
|
370
551
|
else if (root) {
|
|
371
552
|
throw new Error("WFS XML parser: multiple root elements");
|
|
372
553
|
}
|
|
@@ -383,7 +564,7 @@ export function parseXml(xml) {
|
|
|
383
564
|
const text = stripped.slice(i, next === -1 ? len : next);
|
|
384
565
|
const top = stack[stack.length - 1];
|
|
385
566
|
if (top)
|
|
386
|
-
top
|
|
567
|
+
appendXmlText(top, decodeXmlEntities(text), budget);
|
|
387
568
|
i = next === -1 ? len : next;
|
|
388
569
|
}
|
|
389
570
|
if (stack.length > 0) {
|
|
@@ -433,7 +614,9 @@ function parseTagBody(body) {
|
|
|
433
614
|
const name = trimmed.slice(0, i);
|
|
434
615
|
if (!name)
|
|
435
616
|
throw new Error("WFS XML parser: missing tag name");
|
|
436
|
-
const attributes =
|
|
617
|
+
const attributes = Object.create(null);
|
|
618
|
+
let attributeCount = 0;
|
|
619
|
+
let attributeBytes = 0;
|
|
437
620
|
let j = i;
|
|
438
621
|
while (j < trimmed.length) {
|
|
439
622
|
while (j < trimmed.length && /\s/.test(trimmed[j]))
|
|
@@ -442,8 +625,15 @@ function parseTagBody(body) {
|
|
|
442
625
|
break;
|
|
443
626
|
const eq = trimmed.indexOf("=", j);
|
|
444
627
|
if (eq === -1)
|
|
445
|
-
|
|
628
|
+
throw new Error("WFS XML parser: attribute lacks '='");
|
|
446
629
|
const attrName = trimmed.slice(j, eq).trim();
|
|
630
|
+
if (attrName.length === 0 || /\s/.test(attrName)) {
|
|
631
|
+
throw new Error("WFS XML parser: invalid attribute name");
|
|
632
|
+
}
|
|
633
|
+
attributeCount += 1;
|
|
634
|
+
if (attributeCount > WFS_XML_LIMITS.maxAttributesPerElement) {
|
|
635
|
+
throw new Error("WFS XML parser: element exceeds the bounded attribute count");
|
|
636
|
+
}
|
|
447
637
|
let k = eq + 1;
|
|
448
638
|
while (k < trimmed.length && /\s/.test(trimmed[k]))
|
|
449
639
|
k += 1;
|
|
@@ -456,11 +646,31 @@ function parseTagBody(body) {
|
|
|
456
646
|
throw new Error(`WFS XML parser: unterminated attribute value for "${attrName}"`);
|
|
457
647
|
const rawValue = trimmed.slice(k + 1, close);
|
|
458
648
|
const normalizedName = attrName.startsWith("xmlns:") ? attrName : stripPrefix(attrName);
|
|
459
|
-
|
|
649
|
+
const value = decodeXmlEntities(rawValue);
|
|
650
|
+
attributeBytes += utf8Length(attrName) + utf8Length(value);
|
|
651
|
+
if (attributeBytes > WFS_XML_LIMITS.maxAttributeBytesPerElement) {
|
|
652
|
+
throw new Error("WFS XML parser: element exceeds the bounded attribute-byte limit");
|
|
653
|
+
}
|
|
654
|
+
if (Object.hasOwn(attributes, normalizedName)) {
|
|
655
|
+
throw new Error(`WFS XML parser: repeated attribute "${normalizedName}"`);
|
|
656
|
+
}
|
|
657
|
+
attributes[normalizedName] = value;
|
|
460
658
|
j = close + 1;
|
|
461
659
|
}
|
|
462
660
|
return { name, attributes };
|
|
463
661
|
}
|
|
662
|
+
function appendXmlText(node, value, budget) {
|
|
663
|
+
if (value.length === 0)
|
|
664
|
+
return;
|
|
665
|
+
budget.textBytes += utf8Length(value);
|
|
666
|
+
if (budget.textBytes > WFS_XML_LIMITS.maxTextBytes) {
|
|
667
|
+
throw new Error(`WFS XML parser: document exceeds the ${WFS_XML_LIMITS.maxTextBytes}-byte text limit`);
|
|
668
|
+
}
|
|
669
|
+
node.text += value;
|
|
670
|
+
}
|
|
671
|
+
function utf8Length(value) {
|
|
672
|
+
return new TextEncoder().encode(value).byteLength;
|
|
673
|
+
}
|
|
464
674
|
function stripPrefix(name) {
|
|
465
675
|
const idx = name.indexOf(":");
|
|
466
676
|
return idx === -1 ? name : name.slice(idx + 1);
|
package/core/wfs-filter.d.ts
CHANGED
|
@@ -63,6 +63,11 @@ export type FesNode = {
|
|
|
63
63
|
op: FesSpatialOp;
|
|
64
64
|
property: string;
|
|
65
65
|
gml: string;
|
|
66
|
+
} | {
|
|
67
|
+
kind: "temporal";
|
|
68
|
+
op: FesTemporalOp;
|
|
69
|
+
property: string;
|
|
70
|
+
time: FesTimeOperand;
|
|
66
71
|
} | {
|
|
67
72
|
kind: "bbox";
|
|
68
73
|
property: string;
|
|
@@ -74,6 +79,19 @@ export type FesNode = {
|
|
|
74
79
|
srsName?: string;
|
|
75
80
|
};
|
|
76
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* FES 2.0 temporal operators the canonical filter compiles to. `AnyInteracts`
|
|
84
|
+
* is the FES spelling of "the value and the requested period overlap"; the
|
|
85
|
+
* remaining three map one-to-one onto the canonical predicates.
|
|
86
|
+
*/
|
|
87
|
+
export type FesTemporalOp = "Before" | "After" | "During" | "AnyInteracts";
|
|
88
|
+
/** GML time primitive operand: an instant, or a (closed) period. */
|
|
89
|
+
export type FesTimeOperand = {
|
|
90
|
+
readonly instant: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly start: string;
|
|
93
|
+
readonly end: string;
|
|
94
|
+
};
|
|
77
95
|
export type FesCompareOp = "=" | "<>" | "<" | "<=" | ">" | ">=";
|
|
78
96
|
export type FesSpatialOp = "Intersects" | "Within" | "Contains" | "Crosses" | "Disjoint" | "Touches" | "Overlaps" | "Equals";
|
|
79
97
|
export type FesLiteral = {
|
|
@@ -91,14 +109,19 @@ export type FesLiteral = {
|
|
|
91
109
|
export declare function compileWhere(input: string | undefined): FesCompileResult;
|
|
92
110
|
/**
|
|
93
111
|
* Compile the canonical `SpatialFilter` (Esri-flavored) into the FES spatial
|
|
94
|
-
* leaf the adapter splices into the request.
|
|
95
|
-
*
|
|
96
|
-
* `shape`,
|
|
112
|
+
* leaf the adapter splices into the request. `geometryProperty` is required
|
|
113
|
+
* and caller-resolved: it is per feature type (`the_geom`, `msGeometry`,
|
|
114
|
+
* `shape`, …), and a wrong name silently matches nothing.
|
|
97
115
|
*/
|
|
98
116
|
export declare function compileSpatialFilter(filter: SpatialFilter, options: {
|
|
99
117
|
geometryProperty: string;
|
|
100
118
|
srsName?: string;
|
|
101
119
|
}): FesCompileResult;
|
|
120
|
+
/**
|
|
121
|
+
* Serialize a WFS 2.0 BBOX KVP in the authority axis order of its input CRS.
|
|
122
|
+
* Canonical geometries remain x/y; only the wire tuple is swapped.
|
|
123
|
+
*/
|
|
124
|
+
export declare function formatWfsBboxKvp(xmin: number, ymin: number, xmax: number, ymax: number, srsName?: string): string;
|
|
102
125
|
/**
|
|
103
126
|
* Serialize a list of FES nodes into a single `<fes:Filter>` element. Used
|
|
104
127
|
* for both the POST GetFeature body and (URL-encoded) the KVP `filter=`
|
package/core/wfs-filter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wfs-filter.d.ts","sourceRoot":"","sources":["../../../src/core/wfs-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"wfs-filter.d.ts","sourceRoot":"","sources":["../../../src/core/wfs-filter.ts"],"names":[],"mappings":"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"}
|
package/core/wfs-filter.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
|
+
import { requireWfsAxisOrder } from "./wfs-axis-order.js";
|
|
23
24
|
/**
|
|
24
25
|
* Sentinel used by the `where` compiler when the input contains a construct
|
|
25
26
|
* the FES emitter does not know how to translate. Callers should treat this
|
|
@@ -53,9 +54,9 @@ export function compileWhere(input) {
|
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
56
|
* Compile the canonical `SpatialFilter` (Esri-flavored) into the FES spatial
|
|
56
|
-
* leaf the adapter splices into the request.
|
|
57
|
-
*
|
|
58
|
-
* `shape`,
|
|
57
|
+
* leaf the adapter splices into the request. `geometryProperty` is required
|
|
58
|
+
* and caller-resolved: it is per feature type (`the_geom`, `msGeometry`,
|
|
59
|
+
* `shape`, …), and a wrong name silently matches nothing.
|
|
59
60
|
*/
|
|
60
61
|
export function compileSpatialFilter(filter, options) {
|
|
61
62
|
const property = options.geometryProperty;
|
|
@@ -98,49 +99,22 @@ export function compileSpatialFilter(filter, options) {
|
|
|
98
99
|
}
|
|
99
100
|
function envelopeToPolygonGml(xmin, ymin, xmax, ymax, srsName) {
|
|
100
101
|
const srsAttr = srsName ? ` srsName=${attr(srsName)}` : "";
|
|
101
|
-
const
|
|
102
|
-
const c = (x, y) => formatCoord(x, y,
|
|
102
|
+
const axisOrder = requireWfsAxisOrder(srsName);
|
|
103
|
+
const c = (x, y) => formatCoord(x, y, axisOrder);
|
|
103
104
|
const ring = `${c(xmin, ymin)} ${c(xmax, ymin)} ${c(xmax, ymax)} ${c(xmin, ymax)} ${c(xmin, ymin)}`;
|
|
104
105
|
return `<gml:Polygon${srsAttr}><gml:exterior><gml:LinearRing><gml:posList>${ring}</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon>`;
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* (`http://www.opengis.net/def/crs/EPSG/0/4326`) form, WFS 2.0 / GML 3.2 require
|
|
110
|
-
* authority axis order — latitude,longitude — for these CRSes, whereas the
|
|
111
|
-
* legacy short `EPSG:4326` form and `CRS84` stay longitude,latitude. Projected
|
|
112
|
-
* CRSes (easting,northing) already match the canonical x,y order, so they are
|
|
113
|
-
* intentionally excluded.
|
|
108
|
+
* Serialize a WFS 2.0 BBOX KVP in the authority axis order of its input CRS.
|
|
109
|
+
* Canonical geometries remain x/y; only the wire tuple is swapped.
|
|
114
110
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Decide whether a `srsName` implies latitude,longitude axis order. Defaults to
|
|
120
|
-
* longitude,latitude (the canonical x,y order) for the legacy short EPSG form,
|
|
121
|
-
* CRS84, unknown codes, and an absent srsName — preserving existing behavior and
|
|
122
|
-
* avoiding regressions for callers that already supply lon,lat-oriented CRSes.
|
|
123
|
-
*/
|
|
124
|
-
function srsAxisIsLatLon(srsName) {
|
|
125
|
-
if (!srsName)
|
|
126
|
-
return false;
|
|
127
|
-
const value = srsName.trim();
|
|
128
|
-
// CRS84 / CRS:84 are explicitly longitude,latitude regardless of form.
|
|
129
|
-
if (/(?:^|[:/])CRS:?84$/i.test(value) || /CRS84/i.test(value))
|
|
130
|
-
return false;
|
|
131
|
-
// URN form: urn:ogc:def:crs:EPSG::4326 (the version segment may be empty).
|
|
132
|
-
const urn = value.match(/^urn:[\w-]+:def:crs:EPSG:[^:]*:(\d+)$/i);
|
|
133
|
-
// OGC HTTP form: http://www.opengis.net/def/crs/EPSG/0/4326
|
|
134
|
-
const http = value.match(/def\/crs\/EPSG\/[^/]+\/(\d+)$/i);
|
|
135
|
-
const code = urn?.[1] ?? http?.[1];
|
|
136
|
-
// The legacy short "EPSG:4326" form keeps GIS-conventional lon,lat order.
|
|
137
|
-
if (!code)
|
|
138
|
-
return false;
|
|
139
|
-
return LATLON_GEOGRAPHIC_EPSG.has(Number(code));
|
|
111
|
+
export function formatWfsBboxKvp(xmin, ymin, xmax, ymax, srsName) {
|
|
112
|
+
const coordinates = requireWfsAxisOrder(srsName) === "yx" ? [ymin, xmin, ymax, xmax] : [xmin, ymin, xmax, ymax];
|
|
113
|
+
return `${coordinates.join(",")}${srsName ? `,${srsName}` : ""}`;
|
|
140
114
|
}
|
|
141
115
|
/** Serialize a coordinate pair honoring the resolved CRS axis order. */
|
|
142
|
-
function formatCoord(x, y,
|
|
143
|
-
return
|
|
116
|
+
function formatCoord(x, y, axisOrder) {
|
|
117
|
+
return axisOrder === "yx" ? `${y} ${x}` : `${x} ${y}`;
|
|
144
118
|
}
|
|
145
119
|
function mapSpatialRel(rel) {
|
|
146
120
|
switch (rel) {
|
|
@@ -211,6 +185,8 @@ function serializeNode(node) {
|
|
|
211
185
|
return `<fes:PropertyIsNull><fes:ValueReference>${escapeXml(node.property)}</fes:ValueReference></fes:PropertyIsNull>`;
|
|
212
186
|
case "spatial":
|
|
213
187
|
return `<fes:${node.op}><fes:ValueReference>${escapeXml(node.property)}</fes:ValueReference>${node.gml}</fes:${node.op}>`;
|
|
188
|
+
case "temporal":
|
|
189
|
+
return `<fes:${node.op}><fes:ValueReference>${escapeXml(node.property)}</fes:ValueReference>${timeGml(node.time)}</fes:${node.op}>`;
|
|
214
190
|
case "bbox":
|
|
215
191
|
return `<fes:BBOX><fes:ValueReference>${escapeXml(node.property)}</fes:ValueReference>${envelopeGml(node.envelope)}</fes:BBOX>`;
|
|
216
192
|
}
|
|
@@ -245,10 +221,31 @@ function escapeXml(input) {
|
|
|
245
221
|
function attr(value) {
|
|
246
222
|
return `"${value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<")}"`;
|
|
247
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* GML 3.2 time primitive for a FES temporal predicate. FES temporal operators
|
|
226
|
+
* take a `gml:TimeInstant` or `gml:TimePeriod` as their second operand; the
|
|
227
|
+
* `gml:id` is required by the GML schema and is derived from the position so
|
|
228
|
+
* the same filter always serializes to the same bytes.
|
|
229
|
+
*/
|
|
230
|
+
function timeGml(time) {
|
|
231
|
+
if ("instant" in time) {
|
|
232
|
+
return `<gml:TimeInstant gml:id=${attr(timeGmlId(time.instant))}><gml:timePosition>${escapeXml(time.instant)}</gml:timePosition></gml:TimeInstant>`;
|
|
233
|
+
}
|
|
234
|
+
return `<gml:TimePeriod gml:id=${attr(timeGmlId(`${time.start}_${time.end}`))}><gml:beginPosition>${escapeXml(time.start)}</gml:beginPosition><gml:endPosition>${escapeXml(time.end)}</gml:endPosition></gml:TimePeriod>`;
|
|
235
|
+
}
|
|
236
|
+
/** Deterministic NCName for a GML time primitive. */
|
|
237
|
+
function timeGmlId(seed) {
|
|
238
|
+
let hash = 0x811c9dc5;
|
|
239
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
240
|
+
hash ^= seed.charCodeAt(index);
|
|
241
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
242
|
+
}
|
|
243
|
+
return `honua-t${hash.toString(16)}`;
|
|
244
|
+
}
|
|
248
245
|
function envelopeGml(env) {
|
|
249
246
|
const srsAttr = env.srsName ? ` srsName=${attr(env.srsName)}` : "";
|
|
250
|
-
const
|
|
251
|
-
return `<gml:Envelope${srsAttr}><gml:lowerCorner>${formatCoord(env.xmin, env.ymin,
|
|
247
|
+
const axisOrder = requireWfsAxisOrder(env.srsName);
|
|
248
|
+
return `<gml:Envelope${srsAttr}><gml:lowerCorner>${formatCoord(env.xmin, env.ymin, axisOrder)}</gml:lowerCorner><gml:upperCorner>${formatCoord(env.xmax, env.ymax, axisOrder)}</gml:upperCorner></gml:Envelope>`;
|
|
252
249
|
}
|
|
253
250
|
/**
|
|
254
251
|
* Translate a GeoJSON-style geometry into a tiny GML 3.2 fragment. Only the
|
|
@@ -258,14 +255,14 @@ function envelopeGml(env) {
|
|
|
258
255
|
*/
|
|
259
256
|
function geometryToGml(geometry, geometryType, srsName) {
|
|
260
257
|
const srsAttr = srsName ? ` srsName=${attr(srsName)}` : "";
|
|
261
|
-
const
|
|
258
|
+
const axisOrder = requireWfsAxisOrder(srsName);
|
|
262
259
|
switch (geometryType) {
|
|
263
260
|
case "esriGeometryPoint": {
|
|
264
261
|
const x = geometry.x;
|
|
265
262
|
const y = geometry.y;
|
|
266
263
|
if (typeof x !== "number" || typeof y !== "number")
|
|
267
264
|
return undefined;
|
|
268
|
-
return `<gml:Point${srsAttr}><gml:pos>${formatCoord(x, y,
|
|
265
|
+
return `<gml:Point${srsAttr}><gml:pos>${formatCoord(x, y, axisOrder)}</gml:pos></gml:Point>`;
|
|
269
266
|
}
|
|
270
267
|
case "esriGeometryPolyline": {
|
|
271
268
|
const paths = geometry.paths;
|
|
@@ -276,7 +273,7 @@ function geometryToGml(geometry, geometryType, srsName) {
|
|
|
276
273
|
if (!Array.isArray(path))
|
|
277
274
|
return undefined;
|
|
278
275
|
const coords = path
|
|
279
|
-
.map((p) => (Array.isArray(p) ? formatCoord(p[0], p[1],
|
|
276
|
+
.map((p) => (Array.isArray(p) ? formatCoord(p[0], p[1], axisOrder) : ""))
|
|
280
277
|
.filter(Boolean)
|
|
281
278
|
.join(" ");
|
|
282
279
|
if (!coords)
|
|
@@ -287,12 +284,12 @@ function geometryToGml(geometry, geometryType, srsName) {
|
|
|
287
284
|
const rings = geometry.rings;
|
|
288
285
|
if (!Array.isArray(rings) || rings.length === 0)
|
|
289
286
|
return undefined;
|
|
290
|
-
const exterior = ringToPosList(rings[0],
|
|
287
|
+
const exterior = ringToPosList(rings[0], axisOrder);
|
|
291
288
|
if (!exterior)
|
|
292
289
|
return undefined;
|
|
293
290
|
const interiors = [];
|
|
294
291
|
for (let i = 1; i < rings.length; i += 1) {
|
|
295
|
-
const inner = ringToPosList(rings[i],
|
|
292
|
+
const inner = ringToPosList(rings[i], axisOrder);
|
|
296
293
|
if (!inner)
|
|
297
294
|
return undefined;
|
|
298
295
|
interiors.push(`<gml:interior><gml:LinearRing><gml:posList>${inner}</gml:posList></gml:LinearRing></gml:interior>`);
|
|
@@ -303,11 +300,11 @@ function geometryToGml(geometry, geometryType, srsName) {
|
|
|
303
300
|
return undefined;
|
|
304
301
|
}
|
|
305
302
|
}
|
|
306
|
-
function ringToPosList(ring,
|
|
303
|
+
function ringToPosList(ring, axisOrder) {
|
|
307
304
|
if (!Array.isArray(ring) || ring.length < 3)
|
|
308
305
|
return undefined;
|
|
309
306
|
return ring
|
|
310
|
-
.map((p) => (Array.isArray(p) ? formatCoord(p[0], p[1],
|
|
307
|
+
.map((p) => (Array.isArray(p) ? formatCoord(p[0], p[1], axisOrder) : ""))
|
|
311
308
|
.filter(Boolean)
|
|
312
309
|
.join(" ");
|
|
313
310
|
}
|
|
@@ -320,11 +317,9 @@ export function geoJsonGeometryToGml(geometry, srsName) {
|
|
|
320
317
|
return undefined;
|
|
321
318
|
const geom = geometry;
|
|
322
319
|
const srsAttr = srsName ? ` srsName=${attr(srsName)}` : "";
|
|
323
|
-
// Honor
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const latLon = srsAxisIsLatLon(srsName);
|
|
327
|
-
const c = (p) => formatCoord(p[0], p[1], latLon);
|
|
320
|
+
// Honor the authority definition for every supported CRS spelling.
|
|
321
|
+
const axisOrder = requireWfsAxisOrder(srsName);
|
|
322
|
+
const c = (p) => formatCoord(p[0], p[1], axisOrder);
|
|
328
323
|
switch (geom.type) {
|
|
329
324
|
case "Point": {
|
|
330
325
|
const coord = geom.coordinates;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HonuaSdkError } from "./error-envelope.js";
|
|
2
|
+
export type WfsProtocolErrorReason = "invalid-capabilities" | "invalid-feature-response" | "paging-stalled" | "unknown-axis-order"
|
|
3
|
+
/** `DescribeFeatureType` did not yield the feature type's geometry property. */
|
|
4
|
+
| "unresolved-geometry-property";
|
|
5
|
+
/** Typed, credential-safe fail-closed WFS protocol error. */
|
|
6
|
+
export declare class HonuaWfsProtocolError extends HonuaSdkError {
|
|
7
|
+
readonly reason: WfsProtocolErrorReason;
|
|
8
|
+
constructor(reason: WfsProtocolErrorReason, message: string, options?: {
|
|
9
|
+
readonly cause?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=wfs-protocol-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wfs-protocol-error.d.ts","sourceRoot":"","sources":["../../../src/core/wfs-protocol-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,MAAM,sBAAsB,GAC9B,sBAAsB,GACtB,0BAA0B,GAC1B,gBAAgB,GAChB,oBAAoB;AACtB,gFAAgF;GAC9E,8BAA8B,CAAC;AAEnC,6DAA6D;AAC7D,qBAAa,qBAAsB,SAAQ,aAAa;aAEpC,MAAM,EAAE,sBAAsB;gBAA9B,MAAM,EAAE,sBAAsB,EAC9C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAQ7C"}
|