@honua/sdk-esri-compat 0.1.12 → 0.1.13
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/README.md +2 -0
- package/columnar/apache-arrow.d.ts +17 -0
- package/columnar/apache-arrow.d.ts.map +1 -0
- package/columnar/apache-arrow.js +1036 -0
- package/columnar/batch-cache-conformance.d.ts +66 -0
- package/columnar/batch-cache-conformance.d.ts.map +1 -0
- package/columnar/batch-cache-conformance.js +317 -0
- package/columnar/batch-cache-indexeddb.d.ts +31 -0
- package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
- package/columnar/batch-cache-indexeddb.js +148 -0
- package/columnar/batch-cache.d.ts +298 -0
- package/columnar/batch-cache.d.ts.map +1 -0
- package/columnar/batch-cache.js +757 -0
- package/columnar/geoarrow-aggregate.d.ts +178 -0
- package/columnar/geoarrow-aggregate.d.ts.map +1 -0
- package/columnar/geoarrow-aggregate.js +976 -0
- package/columnar/geoarrow-patch.d.ts +382 -0
- package/columnar/geoarrow-patch.d.ts.map +1 -0
- package/columnar/geoarrow-patch.js +1656 -0
- package/columnar/geoarrow-reproject.d.ts +22 -0
- package/columnar/geoarrow-reproject.d.ts.map +1 -0
- package/columnar/geoarrow-reproject.js +119 -0
- package/columnar/geoarrow-serialization.d.ts +68 -0
- package/columnar/geoarrow-serialization.d.ts.map +1 -0
- package/columnar/geoarrow-serialization.js +331 -0
- package/columnar/geoarrow-types.d.ts +271 -0
- package/columnar/geoarrow-types.d.ts.map +1 -0
- package/columnar/geoarrow-types.js +15 -0
- package/columnar/geoarrow.d.ts +52 -0
- package/columnar/geoarrow.d.ts.map +1 -0
- package/columnar/geoarrow.js +1469 -0
- package/columnar/index.d.ts +43 -0
- package/columnar/index.d.ts.map +1 -0
- package/columnar/index.js +29 -0
- package/columnar/operators.d.ts +70 -0
- package/columnar/operators.d.ts.map +1 -0
- package/columnar/operators.js +307 -0
- package/columnar/result.d.ts +369 -0
- package/columnar/result.d.ts.map +1 -0
- package/columnar/result.js +815 -0
- package/columnar/telemetry.d.ts +152 -0
- package/columnar/telemetry.d.ts.map +1 -0
- package/columnar/telemetry.js +265 -0
- package/columnar/transfer.d.ts +28 -0
- package/columnar/transfer.d.ts.map +1 -0
- package/columnar/transfer.js +669 -0
- package/columnar/types.d.ts +175 -0
- package/columnar/types.d.ts.map +1 -0
- package/columnar/types.js +22 -0
- package/columnar/worker.d.ts +157 -0
- package/columnar/worker.d.ts.map +1 -0
- package/columnar/worker.js +1059 -0
- package/connect-url-safety.d.ts +53 -0
- package/connect-url-safety.js +189 -0
- package/contract/pmtiles.d.ts +6 -5
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +30 -5
- package/core/client.d.ts.map +1 -1
- package/core/client.js +87 -19
- package/core/deprecation-notice.d.ts +28 -0
- package/core/deprecation-notice.d.ts.map +1 -0
- package/core/deprecation-notice.js +22 -0
- package/core/esri-geojson.d.ts.map +1 -1
- package/core/esri-geojson.js +10 -6
- package/core/optional-grpc-peer.d.ts +15 -0
- package/core/optional-grpc-peer.d.ts.map +1 -0
- package/core/optional-grpc-peer.js +60 -0
- package/esri-compat/basemap-gallery.d.ts +2 -0
- package/esri-compat/basemap-gallery.d.ts.map +1 -1
- package/esri-compat/basemap-gallery.js +12 -0
- package/esri-compat/basemap.d.ts +4 -0
- package/esri-compat/basemap.d.ts.map +1 -1
- package/esri-compat/basemap.js +70 -0
- package/esri-compat/bookmarks.d.ts +2 -0
- package/esri-compat/bookmarks.d.ts.map +1 -1
- package/esri-compat/bookmarks.js +12 -0
- package/esri-compat/controls.d.ts +18 -0
- package/esri-compat/controls.d.ts.map +1 -1
- package/esri-compat/controls.js +120 -57
- package/esri-compat/directions.d.ts +4 -0
- package/esri-compat/directions.d.ts.map +1 -1
- package/esri-compat/directions.js +30 -20
- package/esri-compat/editor.d.ts +4 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +16 -0
- package/esri-compat/esri-config.d.ts +2 -1
- package/esri-compat/esri-config.d.ts.map +1 -1
- package/esri-compat/feature-effect.d.ts +23 -0
- package/esri-compat/feature-effect.d.ts.map +1 -0
- package/esri-compat/feature-effect.js +12 -0
- package/esri-compat/feature-form.d.ts +6 -0
- package/esri-compat/feature-form.d.ts.map +1 -1
- package/esri-compat/feature-form.js +63 -0
- package/esri-compat/feature-layer.d.ts +21 -2
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +172 -1
- package/esri-compat/feature-table.d.ts +5 -0
- package/esri-compat/feature-table.d.ts.map +1 -1
- package/esri-compat/feature-table.js +23 -0
- package/esri-compat/identity-manager.d.ts +1 -1
- package/esri-compat/identity-manager.js +1 -1
- package/esri-compat/legend.d.ts +2 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +98 -0
- package/esri-compat/locator.d.ts +1 -1
- package/esri-compat/locator.js +1 -1
- package/esri-compat/map-view-mount.d.ts +1 -0
- package/esri-compat/map-view-mount.d.ts.map +1 -1
- package/esri-compat/map-view-mount.js +46 -25
- package/esri-compat/map-view.d.ts +13 -0
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +94 -8
- package/esri-compat/map.d.ts +5 -1
- package/esri-compat/map.d.ts.map +1 -1
- package/esri-compat/map.js +10 -0
- package/esri-compat/measurement.d.ts +2 -0
- package/esri-compat/measurement.d.ts.map +1 -1
- package/esri-compat/measurement.js +14 -0
- package/esri-compat/point.d.ts +7 -0
- package/esri-compat/point.d.ts.map +1 -1
- package/esri-compat/point.js +33 -3
- package/esri-compat/popup.d.ts +2 -0
- package/esri-compat/popup.d.ts.map +1 -1
- package/esri-compat/popup.js +12 -0
- package/esri-compat/portal.d.ts +1 -1
- package/esri-compat/portal.js +1 -1
- package/esri-compat/print.d.ts +3 -0
- package/esri-compat/print.d.ts.map +1 -1
- package/esri-compat/print.js +15 -0
- package/esri-compat/search.d.ts +4 -0
- package/esri-compat/search.d.ts.map +1 -1
- package/esri-compat/search.js +25 -28
- package/esri-compat/sketch.d.ts +2 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +12 -0
- package/esri-compat/widget-host.d.ts +46 -17
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +84 -20
- package/esri-compat/widget-shell-hosts.d.ts +61 -0
- package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
- package/esri-compat/widget-shell-hosts.js +76 -0
- package/index.d.ts +3 -1
- package/index.js +1 -0
- package/offline/digest.d.ts +15 -0
- package/offline/digest.js +58 -0
- package/offline/quota.d.ts +161 -0
- package/offline/quota.js +370 -0
- package/offline/types.d.ts +349 -0
- package/offline/types.js +75 -0
- package/package.json +21 -2
- 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/canonical.d.ts +7 -0
- package/query-planner/canonical.d.ts.map +1 -0
- package/query-planner/canonical.js +145 -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/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 +386 -0
- package/query-planner/duckdb.d.ts +79 -0
- package/query-planner/duckdb.d.ts.map +1 -0
- package/query-planner/duckdb.js +772 -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 +52 -0
- package/query-planner/executor.d.ts +5 -0
- package/query-planner/executor.d.ts.map +1 -0
- package/query-planner/executor.js +229 -0
- package/query-planner/geoservices.d.ts +91 -0
- package/query-planner/geoservices.d.ts.map +1 -0
- package/query-planner/geoservices.js +688 -0
- package/query-planner/grpc.d.ts +100 -0
- package/query-planner/grpc.d.ts.map +1 -0
- package/query-planner/grpc.js +760 -0
- package/query-planner/index.d.ts +59 -0
- package/query-planner/index.d.ts.map +1 -0
- package/query-planner/index.js +43 -0
- package/query-planner/ir.d.ts +25 -0
- package/query-planner/ir.d.ts.map +1 -0
- package/query-planner/ir.js +479 -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-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 +45 -0
- package/query-planner/odata.d.ts.map +1 -0
- package/query-planner/odata.js +519 -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 +639 -0
- package/query-planner/planner.d.ts +17 -0
- package/query-planner/planner.d.ts.map +1 -0
- package/query-planner/planner.js +937 -0
- 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/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 +712 -0
- package/query-planner/types.d.ts.map +1 -0
- package/query-planner/types.js +49 -0
- 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-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-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 +4 -0
- package/query-planner/wfs.d.ts.map +1 -0
- package/query-planner/wfs.js +2 -0
- package/widget-capabilities.d.ts +67 -0
- package/widget-capabilities.js +152 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { compileQueryFilterToFes } from "../contract/query-filter-fes.js";
|
|
2
|
+
import { andQueryFilters, temporalFilterToExpression, } from "../contract/query-filter.js";
|
|
3
|
+
import { UNSUPPORTED_FES, compileSpatialFilter, compileWhere, formatWfsBboxKvp, serializeFes, } from "../core/wfs-filter.js";
|
|
4
|
+
import { queryIrAuthorityFingerprint } from "./ir.js";
|
|
5
|
+
import { HonuaQueryPlanningError } from "./types.js";
|
|
6
|
+
const WFS_GET_FILTER_BUDGET = 7000;
|
|
7
|
+
/**
|
|
8
|
+
* Metadata-free planning fallback only. The live WFS adapter resolves the real
|
|
9
|
+
* geometry property per feature type (DescribeFeatureType, or an explicit
|
|
10
|
+
* `locator.geometryName`) and carries it on the IR source identity, so this
|
|
11
|
+
* legacy PostGIS-via-GeoServer default is used only when a caller compiles a
|
|
12
|
+
* plan without any geometry evidence at all.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_WFS_GEOMETRY_PROPERTY = "the_geom";
|
|
15
|
+
/** Compile canonical query IR to the legacy deterministic WFS 2.0 GetFeature request. */
|
|
16
|
+
export function compileWfsQuery(source, query) {
|
|
17
|
+
const typeName = requireWfsTypeName(source);
|
|
18
|
+
const geometryProperty = wfsGeometryProperty(source);
|
|
19
|
+
assertNoWfsAggregation(query);
|
|
20
|
+
if (query.spatialFilter && query.outSr !== undefined) {
|
|
21
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS planning cannot use outSr to label input filter coordinates; stamp the input geometry through the typed WFS escape hatch");
|
|
22
|
+
}
|
|
23
|
+
const filter = compileWfsFes(typeName, query, geometryProperty);
|
|
24
|
+
const propertyName = compileWfsPropertyNames(query, geometryProperty);
|
|
25
|
+
return {
|
|
26
|
+
compiler: "wfs-2.0-get-feature-v1",
|
|
27
|
+
typeName,
|
|
28
|
+
...(filter !== undefined ? { filter } : {}),
|
|
29
|
+
...(propertyName ? { propertyName } : {}),
|
|
30
|
+
...(query.orderBy && query.orderBy.length > 0
|
|
31
|
+
? {
|
|
32
|
+
sortBy: query.orderBy.map((sort) => `${sort.field} ${sort.direction === "desc" ? "DESC" : "ASC"}`).join(","),
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
...(query.pagination?.offset !== undefined ? { startIndex: query.pagination.offset } : {}),
|
|
36
|
+
...(query.pagination?.limit !== undefined ? { count: query.pagination.limit } : {}),
|
|
37
|
+
...(query.outSr !== undefined ? { srsName: String(query.outSr) } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The exact deterministic compiler hook installed on the first-party WFS
|
|
42
|
+
* protocol module and used by planner dispatch.
|
|
43
|
+
*/
|
|
44
|
+
export const wfsProtocolQueryCompiler = Object.freeze({
|
|
45
|
+
kind: "wfs",
|
|
46
|
+
compile(input) {
|
|
47
|
+
const typeName = requireWfsTypeName(input.source);
|
|
48
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]*(?::[A-Za-z_][A-Za-z0-9_.-]*)?$/.test(typeName)) {
|
|
49
|
+
throw new HonuaQueryPlanningError("invalid-query", `Source "${input.source.id}" has an invalid WFS typeName`);
|
|
50
|
+
}
|
|
51
|
+
assertNoWfsAggregation(input.query);
|
|
52
|
+
const endpoint = requireCredentialFreeWfsCompileEndpoint(input.source.endpoint);
|
|
53
|
+
const authorityFingerprint = input.source.authorityFingerprint ??
|
|
54
|
+
queryIrAuthorityFingerprint(input.source.endpoint, input.source.authorizationScope);
|
|
55
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(authorityFingerprint)) {
|
|
56
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS source authority fingerprint is invalid");
|
|
57
|
+
}
|
|
58
|
+
if (typeof input.source.id !== "string" ||
|
|
59
|
+
input.source.id.length === 0 ||
|
|
60
|
+
containsControlCharacter(input.source.id)) {
|
|
61
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS source id is invalid");
|
|
62
|
+
}
|
|
63
|
+
const srsName = wfsSrsNameFromOutSr(input.query.outSr);
|
|
64
|
+
const filterSrsName = wfsSpatialFilterSrsName(input.source, input.query);
|
|
65
|
+
const geometryProperty = wfsGeometryProperty(input.source);
|
|
66
|
+
const bbox = wfsBboxKvp(input.query, filterSrsName);
|
|
67
|
+
const filter = bbox === undefined ? compileWfsFes(typeName, input.query, geometryProperty, filterSrsName) : undefined;
|
|
68
|
+
const method = wfsProtocolMethodForFilter(filter);
|
|
69
|
+
const propertyName = compileWfsPropertyNames(input.query, geometryProperty);
|
|
70
|
+
const sortBy = input.query.orderBy && input.query.orderBy.length > 0
|
|
71
|
+
? input.query.orderBy.map((sort) => `${sort.field}${sort.direction === "desc" ? " D" : " A"}`).join(",")
|
|
72
|
+
: undefined;
|
|
73
|
+
const startIndex = input.query.pagination?.offset !== undefined && input.query.pagination.offset > 0
|
|
74
|
+
? input.query.pagination.offset
|
|
75
|
+
: undefined;
|
|
76
|
+
return bindWfsProtocolQueryOperation({
|
|
77
|
+
sourceId: input.source.id,
|
|
78
|
+
endpoint,
|
|
79
|
+
authorityFingerprint,
|
|
80
|
+
typeName,
|
|
81
|
+
method,
|
|
82
|
+
...(filter !== undefined ? { filter } : {}),
|
|
83
|
+
...(bbox !== undefined ? { bbox } : {}),
|
|
84
|
+
...(propertyName !== undefined ? { propertyName: Object.freeze([...propertyName]) } : {}),
|
|
85
|
+
...(sortBy !== undefined ? { sortBy } : {}),
|
|
86
|
+
...(startIndex !== undefined ? { startIndex } : {}),
|
|
87
|
+
...(input.query.pagination?.limit !== undefined ? { count: input.query.pagination.limit } : {}),
|
|
88
|
+
...(filterSrsName !== undefined ? { filterSrsName } : {}),
|
|
89
|
+
...(srsName !== undefined ? { srsName } : {}),
|
|
90
|
+
}, input.operation);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
/** Bind a WFS wire request to the exact operation authorized to execute it. */
|
|
94
|
+
export function bindWfsProtocolQueryOperation(request, operation) {
|
|
95
|
+
if (operation !== "query" && operation !== "queryAll" && operation !== "queryAggregate") {
|
|
96
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS protocol operation is invalid");
|
|
97
|
+
}
|
|
98
|
+
let count = request.count;
|
|
99
|
+
if (operation === "queryAll" && count !== undefined) {
|
|
100
|
+
if (!Number.isSafeInteger(count) || count < 0 || count >= Number.MAX_SAFE_INTEGER) {
|
|
101
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS queryAll pagination.limit must leave room for one lookahead row");
|
|
102
|
+
}
|
|
103
|
+
count += 1;
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
compiler: "wfs-2.0-protocol-query-v1",
|
|
107
|
+
operation,
|
|
108
|
+
sourceId: request.sourceId,
|
|
109
|
+
endpoint: request.endpoint,
|
|
110
|
+
authorityFingerprint: request.authorityFingerprint,
|
|
111
|
+
typeName: request.typeName,
|
|
112
|
+
method: request.method,
|
|
113
|
+
...(request.filter !== undefined ? { filter: request.filter } : {}),
|
|
114
|
+
...(request.bbox !== undefined ? { bbox: request.bbox } : {}),
|
|
115
|
+
...(request.propertyName !== undefined ? { propertyName: Object.freeze([...request.propertyName]) } : {}),
|
|
116
|
+
...(request.sortBy !== undefined ? { sortBy: request.sortBy } : {}),
|
|
117
|
+
...(request.startIndex !== undefined ? { startIndex: request.startIndex } : {}),
|
|
118
|
+
...(count !== undefined ? { count } : {}),
|
|
119
|
+
...(request.filterSrsName !== undefined ? { filterSrsName: request.filterSrsName } : {}),
|
|
120
|
+
...(request.srsName !== undefined ? { srsName: request.srsName } : {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Credential-free endpoint identity used to bind an executable artifact to a runtime handle. */
|
|
124
|
+
export function wfsRuntimeEndpointIdentity(value) {
|
|
125
|
+
return normalizeWfsEndpoint(value, true);
|
|
126
|
+
}
|
|
127
|
+
/** Derive the only transport method authorized by the compiled filter budget. */
|
|
128
|
+
export function wfsProtocolMethodForFilter(filter) {
|
|
129
|
+
return filter !== undefined && encodeURIComponent(filter).length > WFS_GET_FILTER_BUDGET ? "POST" : "GET";
|
|
130
|
+
}
|
|
131
|
+
function requireCredentialFreeWfsCompileEndpoint(value) {
|
|
132
|
+
return normalizeWfsEndpoint(value, false);
|
|
133
|
+
}
|
|
134
|
+
function normalizeWfsEndpoint(value, allowRuntimeAuthority) {
|
|
135
|
+
if (typeof value !== "string" ||
|
|
136
|
+
value.length === 0 ||
|
|
137
|
+
value === "[invalid-endpoint]" ||
|
|
138
|
+
containsControlCharacter(value)) {
|
|
139
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS endpoint identity is invalid");
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const parsed = new URL(value);
|
|
143
|
+
if (!allowRuntimeAuthority && (parsed.username || parsed.password || parsed.search || parsed.hash)) {
|
|
144
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS compile endpoint must not contain credentials, query, or fragment");
|
|
145
|
+
}
|
|
146
|
+
parsed.username = "";
|
|
147
|
+
parsed.password = "";
|
|
148
|
+
parsed.search = "";
|
|
149
|
+
parsed.hash = "";
|
|
150
|
+
// Match queryIrSourceIdentity(): normalize the URL serializer's single
|
|
151
|
+
// terminal separator without collapsing a path that intentionally ends
|
|
152
|
+
// in multiple slashes.
|
|
153
|
+
return parsed.toString().replace(/\/$/, "");
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof HonuaQueryPlanningError)
|
|
157
|
+
throw error;
|
|
158
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(value) || value.startsWith("//") || value.includes("@")) {
|
|
159
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS endpoint identity is invalid");
|
|
160
|
+
}
|
|
161
|
+
const queryIndex = value.search(/[?#]/);
|
|
162
|
+
if (!allowRuntimeAuthority && queryIndex >= 0) {
|
|
163
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS compile endpoint must not contain credentials, query, or fragment");
|
|
164
|
+
}
|
|
165
|
+
const path = queryIndex >= 0 ? value.slice(0, queryIndex) : value;
|
|
166
|
+
const normalized = path.replace(/\/$/, "");
|
|
167
|
+
if (path === "/")
|
|
168
|
+
return "/";
|
|
169
|
+
if (normalized.length === 0) {
|
|
170
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS endpoint identity is invalid");
|
|
171
|
+
}
|
|
172
|
+
return normalized;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function wfsBboxKvp(query, filterSrsName) {
|
|
176
|
+
const envelope = wfsBboxShortcutEnvelope(query);
|
|
177
|
+
if (!envelope)
|
|
178
|
+
return undefined;
|
|
179
|
+
return formatWfsBboxKvp(envelope.xmin, envelope.ymin, envelope.xmax, envelope.ymax, filterSrsName);
|
|
180
|
+
}
|
|
181
|
+
function wfsBboxShortcutEnvelope(query) {
|
|
182
|
+
const filter = query.spatialFilter;
|
|
183
|
+
// The `bbox=` KVP is mutually exclusive with `FILTER=`, so any other
|
|
184
|
+
// constraint that has to be expressed as FES (source-native `where`, the
|
|
185
|
+
// typed filter, a temporal predicate) disables the shortcut.
|
|
186
|
+
if (query.where !== undefined || query.filter !== undefined || query.temporalFilter !== undefined)
|
|
187
|
+
return undefined;
|
|
188
|
+
if (!filter || filter.geometryType !== "esriGeometryEnvelope")
|
|
189
|
+
return undefined;
|
|
190
|
+
if (filter.spatialRel !== undefined &&
|
|
191
|
+
filter.spatialRel !== "esriSpatialRelIntersects" &&
|
|
192
|
+
filter.spatialRel !== "esriSpatialRelEnvelopeIntersects") {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const envelope = filter.geometry;
|
|
196
|
+
if (typeof envelope.xmin !== "number" ||
|
|
197
|
+
typeof envelope.ymin !== "number" ||
|
|
198
|
+
typeof envelope.xmax !== "number" ||
|
|
199
|
+
typeof envelope.ymax !== "number") {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return { xmin: envelope.xmin, ymin: envelope.ymin, xmax: envelope.xmax, ymax: envelope.ymax };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* @internal True when the compiled request must name the geometry property in
|
|
206
|
+
* an FES spatial predicate. The `bbox=` KVP shortcut addresses the feature
|
|
207
|
+
* type's default geometry positionally, so it needs no name; every other
|
|
208
|
+
* spatial predicate emits `<fes:ValueReference>` and does. The live adapter
|
|
209
|
+
* uses this to resolve the property (and fail closed) only when the wire
|
|
210
|
+
* actually depends on it.
|
|
211
|
+
*/
|
|
212
|
+
export function wfsSpatialFilterNeedsGeometryProperty(query) {
|
|
213
|
+
if (query.filter !== undefined && queryFilterContainsSpatial(query.filter))
|
|
214
|
+
return true;
|
|
215
|
+
return query.spatialFilter !== undefined && wfsBboxShortcutEnvelope(query) === undefined;
|
|
216
|
+
}
|
|
217
|
+
/** True when the typed filter references the feature type's geometry property. */
|
|
218
|
+
function queryFilterContainsSpatial(filter) {
|
|
219
|
+
if (filter.kind === "spatial")
|
|
220
|
+
return filter.property === undefined;
|
|
221
|
+
if (filter.kind === "boolean")
|
|
222
|
+
return filter.args.some(queryFilterContainsSpatial);
|
|
223
|
+
if (filter.kind === "not")
|
|
224
|
+
return queryFilterContainsSpatial(filter.arg);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
/** Geometry property the compiled request references by name. */
|
|
228
|
+
function wfsGeometryProperty(source) {
|
|
229
|
+
return source.geometryProperty ?? DEFAULT_WFS_GEOMETRY_PROPERTY;
|
|
230
|
+
}
|
|
231
|
+
/** @internal Shared with legacy non-query WFS drains to preserve CRS parity. */
|
|
232
|
+
export function wfsSrsNameFromOutSr(outSr) {
|
|
233
|
+
if (typeof outSr === "string") {
|
|
234
|
+
if (outSr.length === 0)
|
|
235
|
+
return undefined;
|
|
236
|
+
if (outSr.trim() !== outSr || /[\s,]/.test(outSr) || containsControlCharacter(outSr)) {
|
|
237
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS CRS identifier is invalid");
|
|
238
|
+
}
|
|
239
|
+
return outSr;
|
|
240
|
+
}
|
|
241
|
+
if (typeof outSr === "number" && Number.isSafeInteger(outSr) && outSr > 0) {
|
|
242
|
+
return `urn:ogc:def:crs:EPSG::${outSr}`;
|
|
243
|
+
}
|
|
244
|
+
if (outSr === undefined)
|
|
245
|
+
return undefined;
|
|
246
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS CRS identifier is invalid");
|
|
247
|
+
}
|
|
248
|
+
function wfsSpatialFilterSrsName(source, query) {
|
|
249
|
+
const spatialFilter = query.spatialFilter;
|
|
250
|
+
if (!spatialFilter)
|
|
251
|
+
return undefined;
|
|
252
|
+
return wfsFilterSrsNameFromGeometry(spatialFilter.geometry, source.srsName);
|
|
253
|
+
}
|
|
254
|
+
/** @internal Resolve only the input geometry CRS; response `outSr` is separate. */
|
|
255
|
+
export function wfsFilterSrsNameFromGeometry(geometry, fallback) {
|
|
256
|
+
const spatialReference = geometry.spatialReference;
|
|
257
|
+
if (spatialReference === undefined)
|
|
258
|
+
return wfsSrsNameFromOutSr(fallback);
|
|
259
|
+
if (spatialReference === null || typeof spatialReference !== "object" || Array.isArray(spatialReference)) {
|
|
260
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS spatial filter geometry has an invalid spatialReference");
|
|
261
|
+
}
|
|
262
|
+
const reference = spatialReference;
|
|
263
|
+
const keys = Object.keys(reference);
|
|
264
|
+
if (keys.some((key) => key !== "wkid" && key !== "latestWkid" && key !== "wkt")) {
|
|
265
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS spatial filter geometry has an unsupported spatialReference member");
|
|
266
|
+
}
|
|
267
|
+
if (reference.wkt !== undefined) {
|
|
268
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS spatial filters require a WKID-backed CRS; WKT cannot be serialized as a portable srsName");
|
|
269
|
+
}
|
|
270
|
+
for (const [name, value] of [
|
|
271
|
+
["wkid", reference.wkid],
|
|
272
|
+
["latestWkid", reference.latestWkid],
|
|
273
|
+
]) {
|
|
274
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)) {
|
|
275
|
+
throw new HonuaQueryPlanningError("invalid-query", `WFS spatial filter geometry has an invalid ${name}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const wkid = reference.latestWkid ?? reference.wkid;
|
|
279
|
+
if (typeof wkid !== "number") {
|
|
280
|
+
throw new HonuaQueryPlanningError("invalid-query", "WFS spatial filter geometry requires a positive WKID");
|
|
281
|
+
}
|
|
282
|
+
return wfsSrsNameFromOutSr(wkid);
|
|
283
|
+
}
|
|
284
|
+
function requireWfsTypeName(source) {
|
|
285
|
+
if (source.protocol !== "wfs") {
|
|
286
|
+
throw new HonuaQueryPlanningError("unsupported-compiler", `wfs-2.0-get-feature-v1 does not compile protocol "${source.protocol}"`);
|
|
287
|
+
}
|
|
288
|
+
if (!source.typeName) {
|
|
289
|
+
throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.typeName for WFS planning`);
|
|
290
|
+
}
|
|
291
|
+
return source.typeName;
|
|
292
|
+
}
|
|
293
|
+
function assertNoWfsAggregation(query) {
|
|
294
|
+
if (query.aggregation) {
|
|
295
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS 2.0 has no portable remote aggregation; use degraded policy with an explicit bounded-local fallback");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function compileWfsFes(typeName, query, geometryProperty, srsName) {
|
|
299
|
+
const filters = [];
|
|
300
|
+
const typed = compileWfsTypedFilter(query, geometryProperty);
|
|
301
|
+
if (typed)
|
|
302
|
+
filters.push(typed);
|
|
303
|
+
if (query.where) {
|
|
304
|
+
const where = compileWhere(query.where.expression);
|
|
305
|
+
if (where === UNSUPPORTED_FES) {
|
|
306
|
+
throw new HonuaQueryPlanningError("unsupported-query", "The where clause is not expressible as FES 2.0");
|
|
307
|
+
}
|
|
308
|
+
if (where.kind !== "and" || where.operands.length > 0)
|
|
309
|
+
filters.push(where);
|
|
310
|
+
}
|
|
311
|
+
if (query.spatialFilter) {
|
|
312
|
+
const spatial = compileSpatialFilter({
|
|
313
|
+
geometry: query.spatialFilter.geometry,
|
|
314
|
+
geometryType: query.spatialFilter.geometryType,
|
|
315
|
+
...(query.spatialFilter.spatialRel ? { spatialRel: query.spatialFilter.spatialRel } : {}),
|
|
316
|
+
}, {
|
|
317
|
+
geometryProperty,
|
|
318
|
+
...(srsName !== undefined ? { srsName } : {}),
|
|
319
|
+
});
|
|
320
|
+
if (spatial === UNSUPPORTED_FES) {
|
|
321
|
+
throw new HonuaQueryPlanningError("unsupported-query", "The spatial predicate is not expressible as FES 2.0");
|
|
322
|
+
}
|
|
323
|
+
filters.push(spatial);
|
|
324
|
+
}
|
|
325
|
+
return filters.length > 0 ? serializeFes(filters, { typeName }) : undefined;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Compile the canonical typed filter (and a field-bound temporal filter) into
|
|
329
|
+
* FES 2.0. WFS has no protocol-level time parameter, so a temporal filter
|
|
330
|
+
* without a field is refused instead of being applied to a guessed column.
|
|
331
|
+
*/
|
|
332
|
+
function compileWfsTypedFilter(query, geometryProperty) {
|
|
333
|
+
const ctx = { protocol: "wfs", geometryProperty };
|
|
334
|
+
const temporalExpression = query.temporalFilter ? temporalFilterToExpression(query.temporalFilter, ctx) : undefined;
|
|
335
|
+
if (query.temporalFilter && temporalExpression === undefined) {
|
|
336
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS has no protocol-level time parameter; Query.temporalFilter must name the temporal field");
|
|
337
|
+
}
|
|
338
|
+
const expression = andQueryFilters(query.filter, temporalExpression);
|
|
339
|
+
if (!expression)
|
|
340
|
+
return undefined;
|
|
341
|
+
return compileQueryFilterToFes(expression, ctx);
|
|
342
|
+
}
|
|
343
|
+
function compileWfsPropertyNames(query, geometryProperty) {
|
|
344
|
+
if (query.outFields && query.outFields.length > 0) {
|
|
345
|
+
const fields = [...query.outFields];
|
|
346
|
+
if (query.returnGeometry === false && fields.includes(geometryProperty)) {
|
|
347
|
+
throw new HonuaQueryPlanningError("unsupported-query", `WFS returnGeometry=false conflicts with outFields containing the geometry property "${geometryProperty}"`);
|
|
348
|
+
}
|
|
349
|
+
if (query.returnGeometry !== false && !fields.includes(geometryProperty)) {
|
|
350
|
+
fields.push(geometryProperty);
|
|
351
|
+
}
|
|
352
|
+
return fields;
|
|
353
|
+
}
|
|
354
|
+
if (query.returnGeometry === false) {
|
|
355
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS returnGeometry=false requires explicit outFields because propertyName cannot otherwise suppress geometry exactly");
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
function containsControlCharacter(value) {
|
|
360
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
361
|
+
const code = value.charCodeAt(index);
|
|
362
|
+
if (code <= 0x1f || code === 0x7f)
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { compileWfsQuery } from "./wfs-v1.js";
|
|
2
|
+
export { compileSemanticWfsQuery } from "./wfs-semantic.js";
|
|
3
|
+
export type { SemanticWfsCompileOptions, SemanticWfsCompiledQueryV1, SemanticWfsSourceIdentity, Wfs20FilterCapabilitiesEvidence, } from "./wfs-semantic.js";
|
|
4
|
+
//# sourceMappingURL=wfs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wfs.d.ts","sourceRoot":"","sources":["../../../src/query-planner/wfs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind sketch snapping, histogram bins, form expressions,
|
|
3
|
+
* multi-feature edits, and elevation samples.
|
|
4
|
+
*/
|
|
5
|
+
export interface SnapPoint {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function snapVertex(pointer: SnapPoint, vertices: readonly SnapPoint[], enabled: boolean, tolerance?: number): SnapPoint;
|
|
10
|
+
export interface HistogramStats {
|
|
11
|
+
count: number;
|
|
12
|
+
sum: number;
|
|
13
|
+
min: number;
|
|
14
|
+
max: number;
|
|
15
|
+
average: number;
|
|
16
|
+
}
|
|
17
|
+
export interface HistogramBin {
|
|
18
|
+
label: string;
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
value: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function histogramBins(values: readonly number[], stats: HistogramStats, classes: number, method: "equal-interval" | "quantile"): HistogramBin[];
|
|
24
|
+
export declare function histogramRangeFilter(min: number, max: number): string;
|
|
25
|
+
export interface FormExpressionField {
|
|
26
|
+
name: string;
|
|
27
|
+
visibleExpression?: string;
|
|
28
|
+
valueExpression?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface FormExpressionResult {
|
|
31
|
+
hidden: string[];
|
|
32
|
+
values: Record<string, unknown>;
|
|
33
|
+
errors: {
|
|
34
|
+
field: string;
|
|
35
|
+
message: string;
|
|
36
|
+
}[];
|
|
37
|
+
}
|
|
38
|
+
export declare function evaluateFormExpressions(fields: readonly FormExpressionField[], feature: {
|
|
39
|
+
attributes?: Record<string, unknown>;
|
|
40
|
+
}): FormExpressionResult;
|
|
41
|
+
export interface FeatureEdit {
|
|
42
|
+
attributes?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
export interface ApplyEditsResult {
|
|
45
|
+
error?: string;
|
|
46
|
+
reason?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function applyToFeatures(features: readonly FeatureEdit[], change: Record<string, unknown>, applyEdits: (edit: {
|
|
49
|
+
update: FeatureEdit;
|
|
50
|
+
}) => Promise<ApplyEditsResult>): Promise<{
|
|
51
|
+
applied: FeatureEdit[];
|
|
52
|
+
rejected: {
|
|
53
|
+
feature: FeatureEdit;
|
|
54
|
+
reason: string;
|
|
55
|
+
}[];
|
|
56
|
+
}>;
|
|
57
|
+
export interface ElevationSample {
|
|
58
|
+
position: [number, number];
|
|
59
|
+
elevation: number;
|
|
60
|
+
}
|
|
61
|
+
export declare function sampleElevations(line: readonly [number, number][], terrain: {
|
|
62
|
+
elevationAt: (longitude: number, latitude: number) => number;
|
|
63
|
+
} | undefined, spacing?: number): {
|
|
64
|
+
samples: ElevationSample[];
|
|
65
|
+
error?: string;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=widget-capabilities.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind sketch snapping, histogram bins, form expressions,
|
|
3
|
+
* multi-feature edits, and elevation samples.
|
|
4
|
+
*/
|
|
5
|
+
export function snapVertex(pointer, vertices, enabled, tolerance = 1) {
|
|
6
|
+
if (!enabled || vertices.length === 0)
|
|
7
|
+
return { ...pointer };
|
|
8
|
+
let nearest = vertices[0];
|
|
9
|
+
let nearestDistance = distance(pointer, nearest);
|
|
10
|
+
for (const vertex of vertices.slice(1)) {
|
|
11
|
+
const candidate = distance(pointer, vertex);
|
|
12
|
+
if (candidate < nearestDistance) {
|
|
13
|
+
nearest = vertex;
|
|
14
|
+
nearestDistance = candidate;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return nearestDistance <= tolerance ? { x: nearest.x, y: nearest.y } : { ...pointer };
|
|
18
|
+
}
|
|
19
|
+
function distance(a, b) {
|
|
20
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
21
|
+
}
|
|
22
|
+
export function histogramBins(values, stats, classes, method) {
|
|
23
|
+
if (classes < 1)
|
|
24
|
+
throw new Error("histogram classes must be at least 1");
|
|
25
|
+
assertStats(values, stats);
|
|
26
|
+
const breaks = method === "quantile" ? quantileEdges(values, classes) : equalIntervalEdges(stats.min, stats.max, classes);
|
|
27
|
+
const bins = [];
|
|
28
|
+
for (let index = 0; index < breaks.length - 1; index += 1) {
|
|
29
|
+
const min = breaks[index] ?? stats.min;
|
|
30
|
+
const max = breaks[index + 1] ?? stats.max;
|
|
31
|
+
const last = index === breaks.length - 2;
|
|
32
|
+
const value = values.filter((item) => (last ? item >= min && item <= max : item >= min && item < max)).length;
|
|
33
|
+
bins.push({ label: `${min}–${max}`, min, max, value });
|
|
34
|
+
}
|
|
35
|
+
return bins;
|
|
36
|
+
}
|
|
37
|
+
export function histogramRangeFilter(min, max) {
|
|
38
|
+
return `value >= ${min} AND value <= ${max}`;
|
|
39
|
+
}
|
|
40
|
+
function assertStats(values, stats) {
|
|
41
|
+
const count = values.length;
|
|
42
|
+
const sum = values.reduce((total, value) => total + value, 0);
|
|
43
|
+
const min = values.length === 0 ? Number.NaN : Math.min(...values);
|
|
44
|
+
const max = values.length === 0 ? Number.NaN : Math.max(...values);
|
|
45
|
+
const average = count === 0 ? Number.NaN : sum / count;
|
|
46
|
+
if (stats.count !== count ||
|
|
47
|
+
stats.sum !== sum ||
|
|
48
|
+
stats.min !== min ||
|
|
49
|
+
stats.max !== max ||
|
|
50
|
+
stats.average !== average) {
|
|
51
|
+
throw new Error("histogram statistics do not match the feature values");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function equalIntervalEdges(min, max, classes) {
|
|
55
|
+
const width = classes === 0 ? 0 : (max - min) / classes;
|
|
56
|
+
return Array.from({ length: classes + 1 }, (_, index) => min + width * index);
|
|
57
|
+
}
|
|
58
|
+
function quantileEdges(values, classes) {
|
|
59
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
60
|
+
if (sorted.length === 0)
|
|
61
|
+
return [0, 0];
|
|
62
|
+
const edges = [sorted[0] ?? 0];
|
|
63
|
+
for (let index = 1; index < classes; index += 1) {
|
|
64
|
+
const position = (index * (sorted.length - 1)) / classes;
|
|
65
|
+
const lower = Math.floor(position);
|
|
66
|
+
const upper = Math.ceil(position);
|
|
67
|
+
const weight = position - lower;
|
|
68
|
+
const left = sorted[lower] ?? sorted[0] ?? 0;
|
|
69
|
+
const right = sorted[upper] ?? left;
|
|
70
|
+
edges.push(left + (right - left) * weight);
|
|
71
|
+
}
|
|
72
|
+
edges.push(sorted[sorted.length - 1] ?? edges[0] ?? 0);
|
|
73
|
+
return edges;
|
|
74
|
+
}
|
|
75
|
+
export function evaluateFormExpressions(fields, feature) {
|
|
76
|
+
const hidden = [];
|
|
77
|
+
const values = {};
|
|
78
|
+
const errors = [];
|
|
79
|
+
for (const field of fields) {
|
|
80
|
+
if (field.visibleExpression) {
|
|
81
|
+
const visible = evaluateExpression(field.visibleExpression, feature.attributes ?? {}, field.name, errors);
|
|
82
|
+
if (visible === false)
|
|
83
|
+
hidden.push(field.name);
|
|
84
|
+
}
|
|
85
|
+
if (field.valueExpression) {
|
|
86
|
+
const value = evaluateExpression(field.valueExpression, feature.attributes ?? {}, field.name, errors);
|
|
87
|
+
if (value !== undefined &&
|
|
88
|
+
!errors.some((error) => error.field === field.name && error.message.includes(field.valueExpression ?? ""))) {
|
|
89
|
+
values[field.name] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { hidden, values, errors };
|
|
94
|
+
}
|
|
95
|
+
function evaluateExpression(expression, attributes, field, errors) {
|
|
96
|
+
const call = expression.match(/^([A-Za-z_][A-Za-z0-9_]*)\(/);
|
|
97
|
+
if (call && call[1] !== "Number") {
|
|
98
|
+
errors.push({ field, message: `unknown function ${call[1]} in ${expression}` });
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const equality = expression.match(/^\$feature\.([A-Za-z0-9_]+)\s*==\s*'([^']*)'$/);
|
|
102
|
+
if (equality)
|
|
103
|
+
return attributes[equality[1] ?? ""] === equality[2];
|
|
104
|
+
const sum = expression.match(/^\$feature\.([A-Za-z0-9_]+)\s*\+\s*\$feature\.([A-Za-z0-9_]+)$/);
|
|
105
|
+
if (sum)
|
|
106
|
+
return Number(attributes[sum[1] ?? ""]) + Number(attributes[sum[2] ?? ""]);
|
|
107
|
+
const reference = expression.match(/^\$feature\.([A-Za-z0-9_]+)$/);
|
|
108
|
+
if (reference)
|
|
109
|
+
return attributes[reference[1] ?? ""];
|
|
110
|
+
errors.push({ field, message: `unsupported expression ${expression}` });
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
export async function applyToFeatures(features, change, applyEdits) {
|
|
114
|
+
const applied = [];
|
|
115
|
+
const rejected = [];
|
|
116
|
+
for (const feature of features) {
|
|
117
|
+
const update = { attributes: { ...(feature.attributes ?? {}), ...change } };
|
|
118
|
+
const result = await applyEdits({ update });
|
|
119
|
+
if (result.error || result.reason)
|
|
120
|
+
rejected.push({ feature, reason: result.reason ?? result.error ?? "rejected" });
|
|
121
|
+
else
|
|
122
|
+
applied.push(update);
|
|
123
|
+
}
|
|
124
|
+
return { applied, rejected };
|
|
125
|
+
}
|
|
126
|
+
export function sampleElevations(line, terrain, spacing = 1) {
|
|
127
|
+
if (!terrain)
|
|
128
|
+
return { samples: [], error: "terrain-missing" };
|
|
129
|
+
const densified = densify(line, spacing);
|
|
130
|
+
return {
|
|
131
|
+
samples: densified.map((position) => ({
|
|
132
|
+
position,
|
|
133
|
+
elevation: terrain.elevationAt(position[0], position[1]),
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function densify(line, spacing) {
|
|
138
|
+
if (line.length === 0)
|
|
139
|
+
return [];
|
|
140
|
+
const points = [line[0]];
|
|
141
|
+
for (let index = 1; index < line.length; index += 1) {
|
|
142
|
+
const start = line[index - 1];
|
|
143
|
+
const end = line[index];
|
|
144
|
+
const length = Math.hypot(end[0] - start[0], end[1] - start[1]);
|
|
145
|
+
const steps = Math.max(1, Math.ceil(length / spacing));
|
|
146
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
147
|
+
const t = step / steps;
|
|
148
|
+
points.push([start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return points;
|
|
152
|
+
}
|