@honua/react 0.0.20-alpha.0 → 0.1.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contract/discovery.d.ts +231 -0
- package/contract/discovery.d.ts.map +1 -0
- package/contract/discovery.js +554 -0
- package/contract/edit-sketch.d.ts +8 -0
- package/contract/edit-sketch.d.ts.map +1 -1
- package/contract/edit-sketch.js +15 -0
- package/contract/edit-snapping.d.ts +160 -0
- package/contract/edit-snapping.d.ts.map +1 -0
- package/contract/edit-snapping.js +675 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
- package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
- package/contract/index.d.ts +12 -1
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -0
- package/contract/pmtiles.d.ts +193 -0
- package/contract/pmtiles.d.ts.map +1 -0
- package/contract/pmtiles.js +195 -0
- package/contract/protocol-module.d.ts +70 -0
- package/contract/protocol-module.d.ts.map +1 -0
- package/contract/protocol-module.js +1 -0
- package/contract/schema-envelope.d.ts +26 -0
- package/contract/schema-envelope.d.ts.map +1 -0
- package/contract/schema-envelope.js +25 -0
- package/contract/schema.d.ts +582 -0
- package/contract/schema.d.ts.map +1 -0
- package/contract/schema.js +2483 -0
- package/contract/source-capability-support.d.ts +6 -0
- package/contract/source-capability-support.d.ts.map +1 -0
- package/contract/source-capability-support.js +154 -0
- package/contract/source.d.ts +57 -15
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +368 -65
- package/contract/types.d.ts +171 -6
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +14 -14
- package/core/auth/client-credentials-provider.d.ts +51 -0
- package/core/auth/client-credentials-provider.d.ts.map +1 -0
- package/core/auth/client-credentials-provider.js +115 -0
- package/core/auth/credential-store.d.ts +52 -0
- package/core/auth/credential-store.d.ts.map +1 -0
- package/core/auth/credential-store.js +82 -0
- package/core/auth/index.d.ts +23 -0
- package/core/auth/index.d.ts.map +1 -0
- package/core/auth/index.js +20 -0
- package/core/auth/oauth2-provider.d.ts +114 -0
- package/core/auth/oauth2-provider.d.ts.map +1 -0
- package/core/auth/oauth2-provider.js +408 -0
- package/core/auth/pkce.d.ts +25 -0
- package/core/auth/pkce.d.ts.map +1 -0
- package/core/auth/pkce.js +46 -0
- package/core/auth/static-providers.d.ts +20 -0
- package/core/auth/static-providers.d.ts.map +1 -0
- package/core/auth/static-providers.js +26 -0
- package/core/auth/token-endpoint.d.ts +40 -0
- package/core/auth/token-endpoint.d.ts.map +1 -0
- package/core/auth/token-endpoint.js +133 -0
- package/core/auth/types.d.ts +144 -0
- package/core/auth/types.d.ts.map +1 -0
- package/core/auth/types.js +8 -0
- package/core/bounded-json.d.ts +4 -0
- package/core/bounded-json.d.ts.map +1 -0
- package/core/bounded-json.js +38 -0
- package/core/cache-state.d.ts +9 -0
- package/core/cache-state.d.ts.map +1 -1
- package/core/cache-state.js +10 -0
- package/core/capabilities-xml.d.ts +33 -0
- package/core/capabilities-xml.d.ts.map +1 -0
- package/core/capabilities-xml.js +331 -0
- package/core/client.d.ts +35 -2
- package/core/client.d.ts.map +1 -1
- package/core/client.js +167 -29
- package/core/error-classifications.d.ts +153 -0
- package/core/error-classifications.d.ts.map +1 -0
- package/core/error-classifications.js +140 -0
- package/core/error-code-registry.d.ts +158 -0
- package/core/error-code-registry.d.ts.map +1 -0
- package/core/error-code-registry.js +143 -0
- package/core/error-envelope.d.ts +68 -0
- package/core/error-envelope.d.ts.map +1 -0
- package/core/error-envelope.js +357 -0
- package/core/errors.d.ts +73 -25
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +113 -46
- package/core/geoparquet-sql.d.ts +190 -0
- package/core/geoparquet-sql.d.ts.map +1 -0
- package/core/geoparquet-sql.js +368 -0
- package/core/odata-key-path.d.ts +12 -0
- package/core/odata-key-path.d.ts.map +1 -0
- package/core/odata-key-path.js +28 -0
- package/core/odata-write-codec.d.ts +66 -0
- package/core/odata-write-codec.d.ts.map +1 -0
- package/core/odata-write-codec.js +1083 -0
- package/core/odata.d.ts +59 -7
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +431 -99
- package/core/ogc-endpoint-layout.d.ts +70 -0
- package/core/ogc-endpoint-layout.d.ts.map +1 -0
- package/core/ogc-endpoint-layout.js +173 -0
- package/core/ogc-features.d.ts.map +1 -1
- package/core/ogc-features.js +35 -17
- package/core/ogc-maps.d.ts +7 -0
- package/core/ogc-maps.d.ts.map +1 -1
- package/core/ogc-maps.js +46 -7
- package/core/ogc-processes.d.ts +2 -1
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +33 -5
- package/core/ogc-records.d.ts +9 -0
- package/core/ogc-records.d.ts.map +1 -1
- package/core/ogc-records.js +78 -29
- package/core/ogc-tiles.d.ts +10 -1
- package/core/ogc-tiles.d.ts.map +1 -1
- package/core/ogc-tiles.js +56 -17
- package/core/path-utils.d.ts +9 -0
- package/core/path-utils.d.ts.map +1 -1
- package/core/path-utils.js +19 -0
- package/core/protocol-transport.d.ts +1 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +1 -1
- package/core/spatial-filter.d.ts +8 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +206 -16
- package/core/stac-static.d.ts +55 -0
- package/core/stac-static.d.ts.map +1 -0
- package/core/stac-static.js +386 -0
- package/core/stac.d.ts +12 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +45 -15
- package/core/surfaces.d.ts +13 -0
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +127 -16
- package/core/types.d.ts +174 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +12 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +22 -3
- package/core/wfs.d.ts +13 -0
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +35 -5
- package/core/wms-capabilities.d.ts +56 -84
- package/core/wms-capabilities.d.ts.map +1 -1
- package/core/wms-capabilities.js +212 -509
- package/core/wmts-capabilities.d.ts +62 -62
- package/core/wmts-capabilities.d.ts.map +1 -1
- package/core/wmts-capabilities.js +224 -371
- package/esri-compat/editor.d.ts +8 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +13 -0
- package/esri-compat/feature-layer.d.ts +2 -0
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +1 -0
- package/esri-compat/identity-manager.d.ts +43 -0
- package/esri-compat/identity-manager.d.ts.map +1 -1
- package/esri-compat/identity-manager.js +131 -1
- package/esri-compat/layer-list.d.ts +11 -0
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +49 -0
- package/esri-compat/legend.d.ts +10 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +27 -0
- package/esri-compat/oauth-info.d.ts +15 -0
- package/esri-compat/oauth-info.d.ts.map +1 -1
- package/esri-compat/oauth-info.js +27 -0
- package/esri-compat/portal.d.ts +197 -0
- package/esri-compat/portal.d.ts.map +1 -0
- package/esri-compat/portal.js +251 -0
- package/esri-compat/renderer-objects.d.ts +40 -0
- package/esri-compat/renderer-objects.d.ts.map +1 -0
- package/esri-compat/renderer-objects.js +62 -0
- package/esri-compat/sketch.d.ts +43 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +46 -0
- package/esri-compat/snapping.d.ts +43 -0
- package/esri-compat/snapping.d.ts.map +1 -0
- package/esri-compat/snapping.js +45 -0
- package/esri-compat/url.d.ts.map +1 -1
- package/esri-compat/url.js +1 -1
- package/esri-compat/widget-host.d.ts +84 -0
- package/esri-compat/widget-host.d.ts.map +1 -0
- package/esri-compat/widget-host.js +159 -0
- package/filter-registry/index.d.ts +145 -0
- package/filter-registry/index.d.ts.map +1 -0
- package/filter-registry/index.js +531 -0
- package/generated-app/errors.d.ts +18 -2
- package/generated-app/errors.d.ts.map +1 -1
- package/generated-app/errors.js +44 -11
- package/map/automatic-mount-integration.d.ts +134 -0
- package/map/automatic-mount-integration.d.ts.map +1 -0
- package/map/automatic-mount-integration.js +305 -0
- package/map/automatic-source-strategy.d.ts +125 -0
- package/map/automatic-source-strategy.d.ts.map +1 -0
- package/map/automatic-source-strategy.js +494 -0
- package/map/data-to-map-bridge.d.ts +293 -0
- package/map/data-to-map-bridge.d.ts.map +1 -0
- package/map/data-to-map-bridge.js +817 -0
- package/map/geojson-projection.d.ts +33 -0
- package/map/geojson-projection.d.ts.map +1 -0
- package/map/geojson-projection.js +118 -0
- package/map/index.d.ts +14 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +8 -0
- package/map/raster-source-spec.d.ts +30 -0
- package/map/raster-source-spec.d.ts.map +1 -0
- package/map/raster-source-spec.js +190 -0
- package/map/raster-source-strategy.d.ts +65 -0
- package/map/raster-source-strategy.d.ts.map +1 -0
- package/map/raster-source-strategy.js +232 -0
- package/map/source-to-maplibre.d.ts +91 -0
- package/map/source-to-maplibre.d.ts.map +1 -0
- package/map/source-to-maplibre.js +454 -0
- package/map/temporal-playback.d.ts +139 -0
- package/map/temporal-playback.d.ts.map +1 -0
- package/map/temporal-playback.js +247 -0
- package/package.json +19 -1
- package/query-planner/canonical.d.ts +7 -0
- package/query-planner/canonical.d.ts.map +1 -0
- package/query-planner/canonical.js +145 -0
- package/query-planner/cql2-json.d.ts +17 -0
- package/query-planner/cql2-json.d.ts.map +1 -0
- package/query-planner/cql2-json.js +646 -0
- package/query-planner/diagnostics.d.ts +35 -0
- package/query-planner/diagnostics.d.ts.map +1 -0
- package/query-planner/diagnostics.js +373 -0
- package/query-planner/duckdb.d.ts +79 -0
- package/query-planner/duckdb.d.ts.map +1 -0
- package/query-planner/duckdb.js +758 -0
- package/query-planner/execution-context.d.ts +5 -0
- package/query-planner/execution-context.d.ts.map +1 -0
- package/query-planner/execution-context.js +50 -0
- package/query-planner/executor.d.ts +5 -0
- package/query-planner/executor.d.ts.map +1 -0
- package/query-planner/executor.js +223 -0
- package/query-planner/geoservices.d.ts +91 -0
- package/query-planner/geoservices.d.ts.map +1 -0
- package/query-planner/geoservices.js +667 -0
- package/query-planner/grpc.d.ts +100 -0
- package/query-planner/grpc.d.ts.map +1 -0
- package/query-planner/grpc.js +736 -0
- package/query-planner/index.d.ts +44 -0
- package/query-planner/index.d.ts.map +1 -0
- package/query-planner/index.js +30 -0
- package/query-planner/ir.d.ts +20 -0
- package/query-planner/ir.d.ts.map +1 -0
- package/query-planner/ir.js +417 -0
- package/query-planner/local-aggregate.d.ts +5 -0
- package/query-planner/local-aggregate.d.ts.map +1 -0
- package/query-planner/local-aggregate.js +126 -0
- package/query-planner/odata.d.ts +48 -0
- package/query-planner/odata.d.ts.map +1 -0
- package/query-planner/odata.js +662 -0
- package/query-planner/ogc-compiler.d.ts +55 -0
- package/query-planner/ogc-compiler.d.ts.map +1 -0
- package/query-planner/ogc-compiler.js +480 -0
- package/query-planner/ogc-features.d.ts +53 -0
- package/query-planner/ogc-features.d.ts.map +1 -0
- package/query-planner/ogc-features.js +617 -0
- package/query-planner/planner.d.ts +17 -0
- package/query-planner/planner.d.ts.map +1 -0
- package/query-planner/planner.js +818 -0
- package/query-planner/resource.d.ts +94 -0
- package/query-planner/resource.d.ts.map +1 -0
- package/query-planner/resource.js +526 -0
- package/query-planner/semantic-canonical.d.ts +15 -0
- package/query-planner/semantic-canonical.d.ts.map +1 -0
- package/query-planner/semantic-canonical.js +62 -0
- package/query-planner/semantic-compat.d.ts +10 -0
- package/query-planner/semantic-compat.d.ts.map +1 -0
- package/query-planner/semantic-compat.js +35 -0
- package/query-planner/semantic-compiler.d.ts +64 -0
- package/query-planner/semantic-compiler.d.ts.map +1 -0
- package/query-planner/semantic-compiler.js +81 -0
- package/query-planner/semantic-literals.d.ts +30 -0
- package/query-planner/semantic-literals.d.ts.map +1 -0
- package/query-planner/semantic-literals.js +133 -0
- package/query-planner/semantic-types.d.ts +265 -0
- package/query-planner/semantic-types.d.ts.map +1 -0
- package/query-planner/semantic-types.js +1 -0
- package/query-planner/semantic.d.ts +54 -0
- package/query-planner/semantic.d.ts.map +1 -0
- package/query-planner/semantic.js +1421 -0
- package/query-planner/types.d.ts +560 -0
- package/query-planner/types.d.ts.map +1 -0
- package/query-planner/types.js +49 -0
- package/query-planner/wfs-semantic.d.ts +58 -0
- package/query-planner/wfs-semantic.d.ts.map +1 -0
- package/query-planner/wfs-semantic.js +547 -0
- package/query-planner/wfs.d.ts +6 -0
- package/query-planner/wfs.d.ts.map +1 -0
- package/query-planner/wfs.js +71 -0
- package/react/external-map.d.ts +67 -0
- package/react/external-map.d.ts.map +1 -0
- package/react/external-map.js +63 -0
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +3 -2
- package/react/honua-source-layer.d.ts +110 -0
- package/react/honua-source-layer.d.ts.map +1 -0
- package/react/honua-source-layer.js +101 -0
- package/react/hooks.d.ts +28 -0
- package/react/hooks.d.ts.map +1 -1
- package/react/hooks.js +26 -4
- package/react/index.d.ts +8 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +5 -0
- package/react/query-cache.d.ts +26 -4
- package/react/query-cache.d.ts.map +1 -1
- package/react/query-cache.js +51 -8
- package/react/selection.d.ts +177 -0
- package/react/selection.d.ts.map +1 -0
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +69 -0
- package/react/use-mounted-source.d.ts.map +1 -0
- package/react/use-mounted-source.js +346 -0
- package/runtime/edit-snapping.d.ts +107 -0
- package/runtime/edit-snapping.d.ts.map +1 -0
- package/runtime/edit-snapping.js +190 -0
- package/runtime/errors.d.ts +3 -2
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +19 -2
- package/runtime/index.d.ts +8 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +4 -4
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +8 -0
- package/runtime/map-package-validation.d.ts +0 -13
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +0 -12
- package/runtime/map-package.d.ts +1 -1
- package/runtime/map-package.d.ts.map +1 -1
- package/runtime/maplibre-renderer.d.ts +41 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -0
- package/runtime/maplibre-renderer.js +322 -0
- package/runtime/pmtiles-protocol.d.ts +77 -0
- package/runtime/pmtiles-protocol.d.ts.map +1 -0
- package/runtime/pmtiles-protocol.js +120 -0
- package/runtime/query-tiles.d.ts +2 -1
- package/runtime/query-tiles.d.ts.map +1 -1
- package/runtime/query-tiles.js +6 -2
- package/runtime/source-bridge.d.ts +4 -4
- package/runtime/source-bridge.d.ts.map +1 -1
- package/runtime/source-bridge.js +36 -56
- package/runtime/style-interactions.d.ts +3 -2
- package/runtime/style-interactions.d.ts.map +1 -1
- package/runtime/style-interactions.js +11 -3
- package/runtime/terra-draw-sketch.d.ts +275 -0
- package/runtime/terra-draw-sketch.d.ts.map +1 -0
- package/runtime/terra-draw-sketch.js +404 -0
- package/source-capability-registry.d.ts +1 -0
- package/source-capability-registry.js +1 -0
- package/source-capability-types.d.ts +186 -0
- package/source-capability-types.js +7 -0
- package/style/index.d.ts +2 -0
- package/style/index.d.ts.map +1 -1
- package/style/index.js +2 -0
- package/style/renderers.d.ts +326 -0
- package/style/renderers.d.ts.map +1 -0
- package/style/renderers.js +517 -0
- package/webmap/convert-renderer.d.ts +24 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +93 -112
- package/webmap/index.d.ts +1 -1
- package/webmap/index.d.ts.map +1 -1
- package/webmap/index.js +1 -1
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import { validateExecutableCrsBinding } from "../contract/schema.js";
|
|
2
|
+
import { hashSemanticQuery } from "./semantic-canonical.js";
|
|
3
|
+
import { prepareSemanticCompilerQuery, runSemanticCompiler, sameCrsDefinition, sameExecutableCrs, semanticSchemaField, semanticUnsupported, } from "./semantic-compiler.js";
|
|
4
|
+
import { exactDecimalText, finiteDecimalNumber, quotedSemanticString, recordSemanticFieldMapping, semanticRequestFingerprint, sortSemanticFieldMappings, sql92Identifier, verifiedSemanticNativeText, verifiedSemanticSourceText, verifiedSemanticSourceVersion, } from "./semantic-literals.js";
|
|
5
|
+
import { HonuaQueryPlanningError } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Compile canonical query IR to the existing FeatureServer `query` request
|
|
8
|
+
* vocabulary without performing network or metadata I/O.
|
|
9
|
+
*/
|
|
10
|
+
export function compileGeoServicesQuery(source, query) {
|
|
11
|
+
if (source.protocol !== "geoservices-feature-service") {
|
|
12
|
+
throw new HonuaQueryPlanningError("unsupported-compiler", `geoservices-rest-query-v1 does not compile protocol "${source.protocol}"`);
|
|
13
|
+
}
|
|
14
|
+
if (source.serviceId === undefined || source.layerId === undefined) {
|
|
15
|
+
throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.serviceId and locator.layerId for GeoServices planning`);
|
|
16
|
+
}
|
|
17
|
+
if (query.aggregation?.histogram || query.aggregation?.timeSeries) {
|
|
18
|
+
throw new HonuaQueryPlanningError("unsupported-query", "The first GeoServices planner slice supports metrics and groupBy; histogram/timeSeries compilers remain follow-on work");
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
compiler: "geoservices-rest-query-v1",
|
|
22
|
+
serviceId: source.serviceId,
|
|
23
|
+
layerId: source.layerId,
|
|
24
|
+
...(query.where ? { where: query.where.expression } : {}),
|
|
25
|
+
...(query.outFields && query.outFields.length > 0 ? { outFields: query.outFields } : {}),
|
|
26
|
+
...(query.returnGeometry !== undefined ? { returnGeometry: query.returnGeometry } : {}),
|
|
27
|
+
...(query.outSr !== undefined ? { outSr: query.outSr } : {}),
|
|
28
|
+
...(query.orderBy && query.orderBy.length > 0
|
|
29
|
+
? {
|
|
30
|
+
orderByFields: query.orderBy
|
|
31
|
+
.map((sort) => `${sort.field}${sort.direction === "desc" ? " DESC" : ""}`)
|
|
32
|
+
.join(","),
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
...(query.spatialFilter
|
|
36
|
+
? {
|
|
37
|
+
geometry: query.spatialFilter.geometry,
|
|
38
|
+
geometryType: query.spatialFilter.geometryType,
|
|
39
|
+
...(query.spatialFilter.spatialRel ? { spatialRel: query.spatialFilter.spatialRel } : {}),
|
|
40
|
+
}
|
|
41
|
+
: {}),
|
|
42
|
+
...(query.pagination?.offset !== undefined ? { resultOffset: query.pagination.offset } : {}),
|
|
43
|
+
...(query.pagination?.limit !== undefined ? { resultRecordCount: query.pagination.limit } : {}),
|
|
44
|
+
...(query.aggregation ? compileAggregation(query.aggregation) : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function compileAggregation(aggregation) {
|
|
48
|
+
return {
|
|
49
|
+
outStatistics: aggregation.metrics.map((metric) => ({
|
|
50
|
+
statisticType: geoServicesStatistic(metric.fn),
|
|
51
|
+
onStatisticField: metric.field,
|
|
52
|
+
outStatisticFieldName: metric.alias ?? `${metric.fn}_${metric.field}`,
|
|
53
|
+
})),
|
|
54
|
+
...(aggregation.groupBy && aggregation.groupBy.length > 0
|
|
55
|
+
? { groupByFieldsForStatistics: aggregation.groupBy.join(",") }
|
|
56
|
+
: {}),
|
|
57
|
+
returnGeometry: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function geoServicesStatistic(fn) {
|
|
61
|
+
return fn === "var" ? "var" : fn;
|
|
62
|
+
}
|
|
63
|
+
/** Compile a validated semantic AST to an exact GeoServices SQL-92 request. */
|
|
64
|
+
export function compileSemanticGeoServicesQuery(options) {
|
|
65
|
+
return runSemanticCompiler(() => {
|
|
66
|
+
const source = verifiedGeoServicesSource(options.source);
|
|
67
|
+
const { query, schema } = prepareSemanticCompilerQuery(options.query, options.schema, source.protocol);
|
|
68
|
+
const state = { schema, source, fieldMappings: [], usesNativeFilter: false };
|
|
69
|
+
const parts = query.filter
|
|
70
|
+
? compileGeoServicesFilterParts(query.filter, state, "$.filter")
|
|
71
|
+
: {};
|
|
72
|
+
const projection = geoServicesProjection(query, state);
|
|
73
|
+
const spatial = parts.spatial
|
|
74
|
+
? compileGeoServicesSpatial(parts.spatial.filter, state, parts.spatial.path)
|
|
75
|
+
: undefined;
|
|
76
|
+
const orderByFields = geoServicesOrderBy(query, state);
|
|
77
|
+
const aggregation = query.kind === "aggregate" ? geoServicesSemanticAggregation(query, state) : {};
|
|
78
|
+
const page = geoServicesPage(query, state);
|
|
79
|
+
const outSr = geoServicesOutputCrs(query, projection.returnGeometry, state);
|
|
80
|
+
const queryFingerprint = hashSemanticQuery(query, { schema, protocol: source.protocol });
|
|
81
|
+
const request = {
|
|
82
|
+
protocol: source.protocol,
|
|
83
|
+
serviceId: source.serviceId,
|
|
84
|
+
layerId: source.layerId,
|
|
85
|
+
...(source.sourceVersion ? { sourceVersion: source.sourceVersion } : {}),
|
|
86
|
+
sqlFormat: "standard",
|
|
87
|
+
...(parts.where ? { where: parts.where } : {}),
|
|
88
|
+
...projection,
|
|
89
|
+
...(outSr ? { outSr } : {}),
|
|
90
|
+
...(orderByFields ? { orderByFields } : {}),
|
|
91
|
+
...page,
|
|
92
|
+
...spatial,
|
|
93
|
+
...aggregation,
|
|
94
|
+
fieldMappings: sortSemanticFieldMappings(state.fieldMappings),
|
|
95
|
+
usesNativeFilter: state.usesNativeFilter,
|
|
96
|
+
};
|
|
97
|
+
const artifact = {
|
|
98
|
+
compiler: "geoservices-sql92-semantic-query-v1",
|
|
99
|
+
dialect: "geoservices-sql92",
|
|
100
|
+
schemaFingerprint: schema.fingerprint,
|
|
101
|
+
queryFingerprint,
|
|
102
|
+
requestFingerprint: semanticRequestFingerprint("honua:query-request:geoservices-sql92:1", {
|
|
103
|
+
schemaFingerprint: schema.fingerprint,
|
|
104
|
+
queryFingerprint,
|
|
105
|
+
sourceVersion: source.sourceVersion ?? null,
|
|
106
|
+
request,
|
|
107
|
+
}),
|
|
108
|
+
...request,
|
|
109
|
+
};
|
|
110
|
+
return { artifact };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function verifiedGeoServicesSource(value) {
|
|
114
|
+
if (!value || typeof value !== "object") {
|
|
115
|
+
throw new HonuaQueryPlanningError("invalid-query", "Semantic GeoServices source identity is invalid");
|
|
116
|
+
}
|
|
117
|
+
if (value.protocol !== "geoservices-feature-service" && value.protocol !== "geoservices-map-service") {
|
|
118
|
+
throw new HonuaQueryPlanningError("invalid-query", "options.source.protocol is invalid");
|
|
119
|
+
}
|
|
120
|
+
const serviceId = verifiedSemanticSourceText(value.serviceId, "options.source.serviceId");
|
|
121
|
+
if (!Number.isSafeInteger(value.layerId) || value.layerId < 0 || value.layerId > 2_147_483_647) {
|
|
122
|
+
throw new HonuaQueryPlanningError("invalid-query", "options.source.layerId is invalid");
|
|
123
|
+
}
|
|
124
|
+
const sourceVersion = verifiedSemanticSourceVersion(value.sourceVersion, "options.source.sourceVersion");
|
|
125
|
+
for (const capability of [
|
|
126
|
+
"supportsAdvancedQueries",
|
|
127
|
+
"supportsPagination",
|
|
128
|
+
"supportsStatistics",
|
|
129
|
+
"supportsPaginationOnAggregatedQueries",
|
|
130
|
+
]) {
|
|
131
|
+
if (value[capability] !== undefined && typeof value[capability] !== "boolean") {
|
|
132
|
+
throw new HonuaQueryPlanningError("invalid-query", `options.source.${capability} is invalid`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const relationships = value.supportedSpatialRelationships ?? [];
|
|
136
|
+
if (!Array.isArray(relationships)) {
|
|
137
|
+
throw new HonuaQueryPlanningError("invalid-query", "options.source.supportedSpatialRelationships is invalid");
|
|
138
|
+
}
|
|
139
|
+
const allowed = new Set([
|
|
140
|
+
"esriSpatialRelIntersects",
|
|
141
|
+
"esriSpatialRelContains",
|
|
142
|
+
"esriSpatialRelWithin",
|
|
143
|
+
"esriSpatialRelCrosses",
|
|
144
|
+
"esriSpatialRelTouches",
|
|
145
|
+
"esriSpatialRelOverlaps",
|
|
146
|
+
]);
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
for (const relationship of relationships) {
|
|
149
|
+
if (!allowed.has(relationship) || seen.has(relationship)) {
|
|
150
|
+
throw new HonuaQueryPlanningError("invalid-query", "options.source.supportedSpatialRelationships is invalid");
|
|
151
|
+
}
|
|
152
|
+
seen.add(relationship);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
protocol: value.protocol,
|
|
156
|
+
serviceId,
|
|
157
|
+
layerId: value.layerId,
|
|
158
|
+
...(sourceVersion ? { sourceVersion } : {}),
|
|
159
|
+
supportedSpatialRelationships: [...relationships].sort(),
|
|
160
|
+
supportsAdvancedQueries: value.supportsAdvancedQueries === true,
|
|
161
|
+
supportsPagination: value.supportsPagination === true,
|
|
162
|
+
supportsStatistics: value.supportsStatistics === true,
|
|
163
|
+
supportsPaginationOnAggregatedQueries: value.supportsPaginationOnAggregatedQueries === true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function geoServicesProjection(query, state) {
|
|
167
|
+
if (query.kind === "aggregate")
|
|
168
|
+
return { returnGeometry: false };
|
|
169
|
+
const primary = primaryGeometryField(state.schema);
|
|
170
|
+
const geometryField = query.geometry && typeof query.geometry === "object"
|
|
171
|
+
? query.geometry.field
|
|
172
|
+
: query.geometry === "omit"
|
|
173
|
+
? undefined
|
|
174
|
+
: primary;
|
|
175
|
+
if (query.geometry === "include" && !primary) {
|
|
176
|
+
semanticUnsupported("unsupported-projection", "$.geometry", "GeoServices geometry inclusion requires a known primary geometry field");
|
|
177
|
+
}
|
|
178
|
+
if (geometryField && geometryField !== primary) {
|
|
179
|
+
semanticUnsupported("unsupported-projection", "$.geometry", "A GeoServices layer can return only its primary geometry field");
|
|
180
|
+
}
|
|
181
|
+
const requested = query.select ?? state.schema.fields.map((field) => field.name);
|
|
182
|
+
const outFields = [];
|
|
183
|
+
requested.forEach((name, index) => {
|
|
184
|
+
const path = query.select ? `$.select[${index}]` : "$.schema.fields";
|
|
185
|
+
const field = semanticSchemaField(state.schema, name, path);
|
|
186
|
+
if (field.type.kind === "geometry") {
|
|
187
|
+
if (query.select && field.name !== geometryField) {
|
|
188
|
+
semanticUnsupported("unsupported-projection", path, "A geometry field cannot be returned as a GeoServices attribute");
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
outFields.push(geoServicesRequestField(field, state, path));
|
|
193
|
+
});
|
|
194
|
+
if (outFields.length === 0) {
|
|
195
|
+
semanticUnsupported("unsupported-projection", query.select ? "$.select" : "$.schema.fields", "GeoServices cannot prove an exact geometry-only attribute projection");
|
|
196
|
+
}
|
|
197
|
+
if (!geometryField)
|
|
198
|
+
return { outFields, returnGeometry: false };
|
|
199
|
+
const geometryPath = query.geometry === undefined ? "$.schema.geometry" : "$.geometry";
|
|
200
|
+
const field = semanticSchemaField(state.schema, geometryField, geometryPath);
|
|
201
|
+
geoServicesPhysicalField(field, state, geometryPath);
|
|
202
|
+
const metadata = sourceGeometryField(state.schema, geometryField, geometryPath);
|
|
203
|
+
switch (metadata.layout) {
|
|
204
|
+
case "xy":
|
|
205
|
+
return { outFields, returnGeometry: true, returnZ: false, returnM: false };
|
|
206
|
+
case "xyz":
|
|
207
|
+
return { outFields, returnGeometry: true, returnZ: true, returnM: false };
|
|
208
|
+
case "xym":
|
|
209
|
+
return { outFields, returnGeometry: true, returnZ: false, returnM: true };
|
|
210
|
+
case "xyzm":
|
|
211
|
+
return { outFields, returnGeometry: true, returnZ: true, returnM: true };
|
|
212
|
+
default:
|
|
213
|
+
semanticUnsupported("unsupported-projection", geometryPath, "GeoServices geometry output requires a known xy, xyz, xym, or xyzm source layout");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function geoServicesOutputCrs(query, returnGeometry, state) {
|
|
217
|
+
if (!query.outputCrs)
|
|
218
|
+
return undefined;
|
|
219
|
+
if (!returnGeometry) {
|
|
220
|
+
semanticUnsupported("unsupported-projection", "$.outputCrs", "GeoServices cannot apply an output CRS when geometry is omitted");
|
|
221
|
+
}
|
|
222
|
+
const primary = primaryGeometryField(state.schema);
|
|
223
|
+
if (!primary) {
|
|
224
|
+
semanticUnsupported("unsupported-source", "$.outputCrs", "GeoServices output CRS requires a primary geometry field");
|
|
225
|
+
}
|
|
226
|
+
const metadata = sourceGeometryField(state.schema, primary, "$.outputCrs");
|
|
227
|
+
const sourceCrs = executableGeoServicesCrs(metadata.crs, "$.outputCrs");
|
|
228
|
+
if (!sameCrsDefinition(sourceCrs.definition, query.outputCrs)) {
|
|
229
|
+
semanticUnsupported("crs-transform-required", "$.outputCrs", "GeoServices output reprojection requires explicit datum-transformation evidence");
|
|
230
|
+
}
|
|
231
|
+
return geoServicesSpatialReference(query.outputCrs, "$.outputCrs");
|
|
232
|
+
}
|
|
233
|
+
function geoServicesOrderBy(query, state) {
|
|
234
|
+
if (!query.sort || query.sort.length === 0)
|
|
235
|
+
return undefined;
|
|
236
|
+
if (!state.source.supportsAdvancedQueries) {
|
|
237
|
+
semanticUnsupported("unsupported-source", "$.sort", "GeoServices orderByFields requires explicit supportsAdvancedQueries layer evidence");
|
|
238
|
+
}
|
|
239
|
+
if (query.kind === "aggregate") {
|
|
240
|
+
const groups = new Set(query.groupBy);
|
|
241
|
+
const index = query.sort.findIndex((sort) => !groups.has(sort.field));
|
|
242
|
+
if (index >= 0) {
|
|
243
|
+
semanticUnsupported("unsupported-sort", `$.sort[${index}].field`, "GeoServices statistics can sort only by grouped source fields");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return query.sort
|
|
247
|
+
.map((sort, index) => {
|
|
248
|
+
if (sort.nulls && sort.nulls !== "native") {
|
|
249
|
+
semanticUnsupported("unsupported-sort", `$.sort[${index}].nulls`, "GeoServices SQL-92 does not expose a portable explicit null order");
|
|
250
|
+
}
|
|
251
|
+
const path = `$.sort[${index}].field`;
|
|
252
|
+
const field = semanticSchemaField(state.schema, sort.field, path);
|
|
253
|
+
const requestField = geoServicesRequestField(field, state, path);
|
|
254
|
+
return `${sql92Identifier(requestField, path)} ${sort.direction === "desc" ? "DESC" : "ASC"}`;
|
|
255
|
+
})
|
|
256
|
+
.join(", ");
|
|
257
|
+
}
|
|
258
|
+
function geoServicesPage(query, state) {
|
|
259
|
+
if (!query.page)
|
|
260
|
+
return {};
|
|
261
|
+
if (!state.source.supportsPagination) {
|
|
262
|
+
semanticUnsupported("unsupported-source", "$.page", "GeoServices pagination requires explicit supportsPagination layer evidence");
|
|
263
|
+
}
|
|
264
|
+
if (query.kind === "aggregate" && !state.source.supportsPaginationOnAggregatedQueries) {
|
|
265
|
+
semanticUnsupported("unsupported-source", "$.page", "GeoServices aggregate pagination requires explicit layer capability evidence");
|
|
266
|
+
}
|
|
267
|
+
if (query.page.limit !== undefined && query.page.limit > 2_147_483_647) {
|
|
268
|
+
semanticUnsupported("unsupported-node", "$.page.limit", "GeoServices resultRecordCount is int32-bounded");
|
|
269
|
+
}
|
|
270
|
+
if (query.page.kind === "offset" && query.page.offset > 2_147_483_647) {
|
|
271
|
+
semanticUnsupported("unsupported-node", "$.page.offset", "GeoServices resultOffset is int32-bounded");
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
...(query.page.kind === "offset" ? { resultOffset: query.page.offset } : {}),
|
|
275
|
+
...(query.page.limit !== undefined ? { resultRecordCount: query.page.limit } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function geoServicesSemanticAggregation(query, state) {
|
|
279
|
+
if (!state.source.supportsStatistics) {
|
|
280
|
+
semanticUnsupported("unsupported-source", "$.metrics", "GeoServices outStatistics requires explicit supportsStatistics layer evidence");
|
|
281
|
+
}
|
|
282
|
+
if (query.outputCrs) {
|
|
283
|
+
semanticUnsupported("unsupported-projection", "$.outputCrs", "GeoServices aggregate rows do not carry geometry");
|
|
284
|
+
}
|
|
285
|
+
const groupBy = query.groupBy.map((name, index) => geoServicesRequestField(semanticSchemaField(state.schema, name, `$.groupBy[${index}]`), state, `$.groupBy[${index}]`));
|
|
286
|
+
const names = new Set(groupBy.map((name) => name.toLocaleLowerCase("en-US")));
|
|
287
|
+
const outStatistics = query.metrics.map((metric, index) => {
|
|
288
|
+
if (!metric.field) {
|
|
289
|
+
semanticUnsupported("unsupported-node", `$.metrics[${index}].field`, "GeoServices statistics require a named physical source field");
|
|
290
|
+
}
|
|
291
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(metric.as)) {
|
|
292
|
+
semanticUnsupported("unsupported-node", `$.metrics[${index}].as`, "GeoServices statistic aliases require an ASCII identifier");
|
|
293
|
+
}
|
|
294
|
+
const folded = metric.as.toLocaleLowerCase("en-US");
|
|
295
|
+
if (names.has(folded)) {
|
|
296
|
+
semanticUnsupported("unsupported-node", `$.metrics[${index}].as`, "GeoServices statistic result fields must be unique under case folding");
|
|
297
|
+
}
|
|
298
|
+
names.add(folded);
|
|
299
|
+
const field = semanticSchemaField(state.schema, metric.field, `$.metrics[${index}].field`);
|
|
300
|
+
return {
|
|
301
|
+
statisticType: (metric.fn === "variance" ? "var" : metric.fn),
|
|
302
|
+
onStatisticField: geoServicesRequestField(field, state, `$.metrics[${index}].field`),
|
|
303
|
+
outStatisticFieldName: metric.as,
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
outStatistics,
|
|
308
|
+
...(groupBy.length > 0 ? { groupByFieldsForStatistics: groupBy.join(",") } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function compileGeoServicesFilterParts(filter, state, path) {
|
|
312
|
+
if (filter.kind === "spatial")
|
|
313
|
+
return { spatial: { filter, path } };
|
|
314
|
+
if (filter.kind === "boolean" && filter.operator === "and") {
|
|
315
|
+
const where = [];
|
|
316
|
+
let spatial;
|
|
317
|
+
filter.args.forEach((entry, index) => {
|
|
318
|
+
const child = compileGeoServicesFilterParts(entry, state, `${path}.args[${index}]`);
|
|
319
|
+
if (child.where)
|
|
320
|
+
where.push(child.where);
|
|
321
|
+
if (child.spatial) {
|
|
322
|
+
if (spatial) {
|
|
323
|
+
semanticUnsupported("unsupported-node", child.spatial.path, "A GeoServices query request can carry only one spatial predicate");
|
|
324
|
+
}
|
|
325
|
+
spatial = child.spatial;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
...(where.length > 0 ? { where: where.map(parenthesized).join(" AND ") } : {}),
|
|
330
|
+
...(spatial ? { spatial } : {}),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (containsSpatialFilter(filter)) {
|
|
334
|
+
semanticUnsupported("unsupported-node", path, "GeoServices cannot preserve a spatial predicate inside OR or NOT");
|
|
335
|
+
}
|
|
336
|
+
return { where: compileGeoServicesAttributeFilter(filter, state, path) };
|
|
337
|
+
}
|
|
338
|
+
function compileGeoServicesAttributeFilter(filter, state, path) {
|
|
339
|
+
switch (filter.kind) {
|
|
340
|
+
case "comparison": {
|
|
341
|
+
const field = semanticSchemaField(state.schema, filter.left.name, `${path}.left.name`);
|
|
342
|
+
const operator = { eq: "=", ne: "<>", lt: "<", lte: "<=", gt: ">", gte: ">=" }[filter.operator];
|
|
343
|
+
return `${geoServicesSqlField(field, state, `${path}.left.name`)} ${operator} ${geoServicesLiteral(filter.right.value, field, state, `${path}.right.value`)}`;
|
|
344
|
+
}
|
|
345
|
+
case "list": {
|
|
346
|
+
const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
|
|
347
|
+
const values = filter.values.map((literal, index) => geoServicesLiteral(literal.value, field, state, `${path}.values[${index}].value`));
|
|
348
|
+
return `${geoServicesSqlField(field, state, `${path}.operand.name`)} IN (${values.join(", ")})`;
|
|
349
|
+
}
|
|
350
|
+
case "range": {
|
|
351
|
+
const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
|
|
352
|
+
return `${geoServicesSqlField(field, state, `${path}.operand.name`)} BETWEEN ${geoServicesLiteral(filter.lower.value, field, state, `${path}.lower.value`)} AND ${geoServicesLiteral(filter.upper.value, field, state, `${path}.upper.value`)}`;
|
|
353
|
+
}
|
|
354
|
+
case "null": {
|
|
355
|
+
const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
|
|
356
|
+
return `${geoServicesSqlField(field, state, `${path}.operand.name`)} IS ${filter.operator === "is-not-null" ? "NOT " : ""}NULL`;
|
|
357
|
+
}
|
|
358
|
+
case "pattern": {
|
|
359
|
+
if (filter.caseSensitive === false) {
|
|
360
|
+
semanticUnsupported("unsupported-node", `${path}.caseSensitive`, "GeoServices SQL-92 cannot prove case-insensitive LIKE semantics across data stores");
|
|
361
|
+
}
|
|
362
|
+
const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
|
|
363
|
+
return `${geoServicesSqlField(field, state, `${path}.operand.name`)} LIKE ${quotedSemanticString(filter.pattern, `${path}.pattern`)}`;
|
|
364
|
+
}
|
|
365
|
+
case "boolean":
|
|
366
|
+
return filter.args
|
|
367
|
+
.map((entry, index) => parenthesized(compileGeoServicesAttributeFilter(entry, state, `${path}.args[${index}]`)))
|
|
368
|
+
.join(filter.operator === "and" ? " AND " : " OR ");
|
|
369
|
+
case "not":
|
|
370
|
+
return `NOT ${parenthesized(compileGeoServicesAttributeFilter(filter.arg, state, `${path}.arg`))}`;
|
|
371
|
+
case "temporal": {
|
|
372
|
+
if (filter.operator === "during" || filter.operator === "time-intersects") {
|
|
373
|
+
semanticUnsupported("unsupported-node", `${path}.operator`, `GeoServices SQL-92 cannot represent the OGC ${filter.operator} temporal-topology predicate exactly`);
|
|
374
|
+
}
|
|
375
|
+
const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
|
|
376
|
+
const name = geoServicesSqlField(field, state, `${path}.operand.name`);
|
|
377
|
+
return `${name} ${filter.operator === "before" ? "<" : ">"} ${geoServicesLiteral(filter.value.value, field, state, `${path}.value.value`)}`;
|
|
378
|
+
}
|
|
379
|
+
case "native": {
|
|
380
|
+
if (filter.dialect !== "geoservices-sql92" || filter.payload.format !== "text") {
|
|
381
|
+
semanticUnsupported("unsupported-native-filter", path, "GeoServices native filters require geoservices-sql92 text");
|
|
382
|
+
}
|
|
383
|
+
state.usesNativeFilter = true;
|
|
384
|
+
return verifiedSemanticNativeText(filter.payload.text, `${path}.payload.text`, "GeoServices SQL-92");
|
|
385
|
+
}
|
|
386
|
+
case "spatial":
|
|
387
|
+
semanticUnsupported("unsupported-node", path, "Spatial predicates use GeoServices geometry request parameters");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function geoServicesLiteral(value, field, state, path) {
|
|
391
|
+
switch (field.type.kind) {
|
|
392
|
+
case "integer":
|
|
393
|
+
case "decimal":
|
|
394
|
+
return typeof value === "number" ? finiteDecimalNumber(value, path) : exactDecimalText(value, path);
|
|
395
|
+
case "float":
|
|
396
|
+
return finiteDecimalNumber(value, path);
|
|
397
|
+
case "string":
|
|
398
|
+
case "uuid":
|
|
399
|
+
return quotedSemanticString(value, path);
|
|
400
|
+
case "date": {
|
|
401
|
+
requireGeoServicesNativeTemporalType(field, state.source.protocol, "esriFieldTypeDateOnly", path);
|
|
402
|
+
return `DATE ${quotedSemanticString(value, path)}`;
|
|
403
|
+
}
|
|
404
|
+
case "time": {
|
|
405
|
+
requireGeoServicesNativeTemporalType(field, state.source.protocol, "esriFieldTypeTimeOnly", path);
|
|
406
|
+
if (value.includes(".")) {
|
|
407
|
+
semanticUnsupported("unsupported-field-type", path, "GeoServices Time Only SQL syntax does not preserve fractional seconds");
|
|
408
|
+
}
|
|
409
|
+
return `TIME ${quotedSemanticString(value, path)}`;
|
|
410
|
+
}
|
|
411
|
+
case "timestamp": {
|
|
412
|
+
const nativeType = geoServicesNativeTemporalType(field, state.source.protocol, path);
|
|
413
|
+
if (nativeType === "esriFieldTypeDate") {
|
|
414
|
+
if (field.type.timezone !== "utc") {
|
|
415
|
+
semanticUnsupported("unsupported-field-type", path, "GeoServices Date TIMESTAMP literals require explicit UTC field metadata");
|
|
416
|
+
}
|
|
417
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:[0-5]\d)Z$/.exec(value);
|
|
418
|
+
if (!match) {
|
|
419
|
+
semanticUnsupported("unsupported-field-type", path, "GeoServices Date TIMESTAMP syntax cannot preserve fractional seconds or leap seconds");
|
|
420
|
+
}
|
|
421
|
+
return `TIMESTAMP ${quotedSemanticString(`${match[1]} ${match[2]}`, path)}`;
|
|
422
|
+
}
|
|
423
|
+
if (nativeType !== "esriFieldTypeTimestampOffset" || field.type.timezone !== "offset") {
|
|
424
|
+
semanticUnsupported("unsupported-field-type", path, "GeoServices offset TIMESTAMP literals require matching Timestamp Offset field metadata");
|
|
425
|
+
}
|
|
426
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:[0-5]\d(?:\.\d{1,3})?)(Z|[+-]\d{2}:\d{2})$/.exec(value);
|
|
427
|
+
if (!match) {
|
|
428
|
+
semanticUnsupported("unsupported-field-type", path, "GeoServices Timestamp Offset SQL syntax supports at most millisecond precision and no leap seconds");
|
|
429
|
+
}
|
|
430
|
+
const normalized = `${match[1]} ${match[2]} ${match[3] === "Z" ? "+00:00" : match[3]}`;
|
|
431
|
+
return `TIMESTAMP ${quotedSemanticString(normalized, path)}`;
|
|
432
|
+
}
|
|
433
|
+
default:
|
|
434
|
+
semanticUnsupported("unsupported-field-type", path, `GeoServices SQL-92 cannot represent ${field.type.kind} literals exactly`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const GEO_SERVICES_TEMPORAL_TYPES = new Set([
|
|
438
|
+
"esriFieldTypeDate",
|
|
439
|
+
"esriFieldTypeDateOnly",
|
|
440
|
+
"esriFieldTypeTimeOnly",
|
|
441
|
+
"esriFieldTypeTimestampOffset",
|
|
442
|
+
]);
|
|
443
|
+
function geoServicesNativeTemporalType(field, protocol, path) {
|
|
444
|
+
const references = field.native.filter((reference) => reference.protocol === protocol);
|
|
445
|
+
if (references.length !== 1 ||
|
|
446
|
+
!GEO_SERVICES_TEMPORAL_TYPES.has(references[0]?.name)) {
|
|
447
|
+
semanticUnsupported("unsupported-source", path, "GeoServices temporal literals require one exact native layer field type");
|
|
448
|
+
}
|
|
449
|
+
return references[0]?.name;
|
|
450
|
+
}
|
|
451
|
+
function requireGeoServicesNativeTemporalType(field, protocol, expected, path) {
|
|
452
|
+
if (geoServicesNativeTemporalType(field, protocol, path) !== expected) {
|
|
453
|
+
semanticUnsupported("unsupported-field-type", path, `GeoServices ${field.type.kind} literal requires native ${expected} metadata`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function compileGeoServicesSpatial(filter, state, path) {
|
|
457
|
+
if (filter.operator === "within-distance" || filter.operator === "beyond-distance") {
|
|
458
|
+
semanticUnsupported("unsupported-distance", `${path}.distance`, "GeoServices distance parameters cannot preserve every semantic distance mode and unit");
|
|
459
|
+
}
|
|
460
|
+
const propertyName = filter.property?.name ?? primaryGeometryField(state.schema);
|
|
461
|
+
if (!propertyName) {
|
|
462
|
+
semanticUnsupported("unsupported-geometry", `${path}.property`, "GeoServices requires a primary geometry field");
|
|
463
|
+
}
|
|
464
|
+
const primary = primaryGeometryField(state.schema);
|
|
465
|
+
if (propertyName !== primary) {
|
|
466
|
+
semanticUnsupported("unsupported-geometry", `${path}.property`, "A GeoServices layer spatial filter can target only its primary geometry field");
|
|
467
|
+
}
|
|
468
|
+
const property = semanticSchemaField(state.schema, propertyName, `${path}.property.name`);
|
|
469
|
+
geoServicesPhysicalField(property, state, `${path}.property.name`);
|
|
470
|
+
const geometryMetadata = sourceGeometryField(state.schema, propertyName, `${path}.property`);
|
|
471
|
+
const sourceCrs = executableGeoServicesCrs(geometryMetadata.crs, `${path}.property`);
|
|
472
|
+
const relationship = geoServicesRelationship(filter.operator, path);
|
|
473
|
+
if (!state.source.supportedSpatialRelationships.includes(relationship)) {
|
|
474
|
+
semanticUnsupported("unsupported-source", `${path}.operator`, `Layer metadata does not explicitly advertise ${relationship}`);
|
|
475
|
+
}
|
|
476
|
+
if (filter.operator === "bbox-intersects") {
|
|
477
|
+
verifyGeoServicesBbox(filter.bbox, geometryMetadata.layout, sourceCrs, path);
|
|
478
|
+
const [xmin, ymin, xmax, ymax] = filter.bbox.box.bounds;
|
|
479
|
+
return {
|
|
480
|
+
geometry: { xmin, ymin, xmax, ymax },
|
|
481
|
+
geometryType: "esriGeometryEnvelope",
|
|
482
|
+
inSr: geoServicesSpatialReference(filter.bbox.crs.definition, `${path}.bbox.crs`),
|
|
483
|
+
spatialRel: relationship,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
verifyGeoServicesGeometry(filter.geometry, geometryMetadata.layout, sourceCrs, path);
|
|
487
|
+
const compiled = geoServicesGeometry(filter.geometry, path);
|
|
488
|
+
return {
|
|
489
|
+
...compiled,
|
|
490
|
+
inSr: geoServicesSpatialReference(filter.geometry.crs.definition, `${path}.geometry.crs`),
|
|
491
|
+
spatialRel: relationship,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function geoServicesRelationship(operator, path) {
|
|
495
|
+
switch (operator) {
|
|
496
|
+
case "intersects":
|
|
497
|
+
case "bbox-intersects":
|
|
498
|
+
return "esriSpatialRelIntersects";
|
|
499
|
+
case "within":
|
|
500
|
+
return "esriSpatialRelWithin";
|
|
501
|
+
case "contains":
|
|
502
|
+
return "esriSpatialRelContains";
|
|
503
|
+
case "crosses":
|
|
504
|
+
return "esriSpatialRelCrosses";
|
|
505
|
+
case "touches":
|
|
506
|
+
return "esriSpatialRelTouches";
|
|
507
|
+
case "overlaps":
|
|
508
|
+
return "esriSpatialRelOverlaps";
|
|
509
|
+
default:
|
|
510
|
+
semanticUnsupported("unsupported-node", `${path}.operator`, `GeoServices cannot prove exact ${operator} relationship semantics`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function verifyGeoServicesGeometry(value, sourceLayout, sourceCrs, path) {
|
|
514
|
+
assertEastNorth(value.crs, `${path}.geometry.crs`);
|
|
515
|
+
if (!sameExecutableCrs(sourceCrs, value.crs)) {
|
|
516
|
+
semanticUnsupported("crs-transform-required", `${path}.geometry.crs`, "Spatial geometry CRS differs from the GeoServices layer geometry CRS");
|
|
517
|
+
}
|
|
518
|
+
if ((value.layout !== "xy" && value.layout !== "xyz") || sourceLayout !== value.layout) {
|
|
519
|
+
semanticUnsupported("unsupported-geometry", `${path}.geometry.layout`, "GeoServices spatial filters require a known matching xy or xyz layer layout");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function verifyGeoServicesBbox(value, sourceLayout, sourceCrs, path) {
|
|
523
|
+
assertEastNorth(value.crs, `${path}.bbox.crs`);
|
|
524
|
+
if (!sameExecutableCrs(sourceCrs, value.crs)) {
|
|
525
|
+
semanticUnsupported("crs-transform-required", `${path}.bbox.crs`, "Bounding-box CRS differs from the GeoServices layer geometry CRS");
|
|
526
|
+
}
|
|
527
|
+
if (value.box.layout !== "xy" || sourceLayout !== "xy") {
|
|
528
|
+
semanticUnsupported("unsupported-geometry", `${path}.bbox.box.layout`, "GeoServices envelope filters require a known xy layer layout");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function geoServicesGeometry(value, path) {
|
|
532
|
+
const dimension = value.layout === "xyz" ? 3 : 2;
|
|
533
|
+
switch (value.geometry.type) {
|
|
534
|
+
case "Point":
|
|
535
|
+
return {
|
|
536
|
+
geometry: {
|
|
537
|
+
x: value.geometry.coordinates[0],
|
|
538
|
+
y: value.geometry.coordinates[1],
|
|
539
|
+
...(dimension === 3 ? { z: value.geometry.coordinates[2] } : {}),
|
|
540
|
+
},
|
|
541
|
+
geometryType: "esriGeometryPoint",
|
|
542
|
+
};
|
|
543
|
+
case "MultiPoint":
|
|
544
|
+
return {
|
|
545
|
+
geometry: { points: value.geometry.coordinates.map((position) => coordinate(position, dimension)) },
|
|
546
|
+
geometryType: "esriGeometryMultipoint",
|
|
547
|
+
};
|
|
548
|
+
case "LineString":
|
|
549
|
+
return {
|
|
550
|
+
geometry: { paths: [value.geometry.coordinates.map((position) => coordinate(position, dimension))] },
|
|
551
|
+
geometryType: "esriGeometryPolyline",
|
|
552
|
+
};
|
|
553
|
+
case "MultiLineString":
|
|
554
|
+
return {
|
|
555
|
+
geometry: {
|
|
556
|
+
paths: value.geometry.coordinates.map((line) => line.map((position) => coordinate(position, dimension))),
|
|
557
|
+
},
|
|
558
|
+
geometryType: "esriGeometryPolyline",
|
|
559
|
+
};
|
|
560
|
+
case "Polygon":
|
|
561
|
+
return {
|
|
562
|
+
geometry: {
|
|
563
|
+
rings: value.geometry.coordinates.map((ring, index) => esriRing(ring, index === 0, dimension)),
|
|
564
|
+
},
|
|
565
|
+
geometryType: "esriGeometryPolygon",
|
|
566
|
+
};
|
|
567
|
+
case "MultiPolygon":
|
|
568
|
+
return {
|
|
569
|
+
geometry: {
|
|
570
|
+
rings: value.geometry.coordinates.flatMap((polygon) => polygon.map((ring, index) => esriRing(ring, index === 0, dimension))),
|
|
571
|
+
},
|
|
572
|
+
geometryType: "esriGeometryPolygon",
|
|
573
|
+
};
|
|
574
|
+
case "GeometryCollection":
|
|
575
|
+
semanticUnsupported("unsupported-geometry", `${path}.geometry.geometry`, "GeoServices query geometry has no GeometryCollection representation");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function esriRing(ring, exterior, dimension) {
|
|
579
|
+
const coordinates = ring.map((position) => coordinate(position, dimension));
|
|
580
|
+
const signedArea = ring.reduce((area, position, index) => {
|
|
581
|
+
const next = ring[(index + 1) % ring.length];
|
|
582
|
+
return area + position[0] * next[1] - next[0] * position[1];
|
|
583
|
+
}, 0);
|
|
584
|
+
const clockwise = signedArea < 0;
|
|
585
|
+
return clockwise === exterior ? coordinates : [...coordinates].reverse();
|
|
586
|
+
}
|
|
587
|
+
function coordinate(position, dimension) {
|
|
588
|
+
return dimension === 3 ? [position[0], position[1], position[2]] : [position[0], position[1]];
|
|
589
|
+
}
|
|
590
|
+
function geoServicesSpatialReference(value, path) {
|
|
591
|
+
if (value.kind === "authority" && value.authority.toUpperCase() === "EPSG" && /^\d+$/.test(value.code)) {
|
|
592
|
+
const wkid = Number(value.code);
|
|
593
|
+
if (Number.isSafeInteger(wkid) && wkid > 0 && wkid <= 2_147_483_647)
|
|
594
|
+
return { wkid };
|
|
595
|
+
}
|
|
596
|
+
if (value.kind === "wkt" && value.validation === "engine")
|
|
597
|
+
return { wkt: value.wkt };
|
|
598
|
+
semanticUnsupported("unsupported-crs", path, "GeoServices requests require an EPSG code or engine-validated WKT");
|
|
599
|
+
}
|
|
600
|
+
function executableGeoServicesCrs(value, path) {
|
|
601
|
+
try {
|
|
602
|
+
const crs = validateExecutableCrsBinding(value, path);
|
|
603
|
+
if (crs.coordinateEpoch !== undefined) {
|
|
604
|
+
semanticUnsupported("unsupported-crs", path, "GeoServices requests cannot preserve coordinate epochs");
|
|
605
|
+
}
|
|
606
|
+
assertEastNorth(crs, path);
|
|
607
|
+
geoServicesSpatialReference(crs.definition, path);
|
|
608
|
+
return crs;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
if (error instanceof HonuaQueryPlanningError)
|
|
612
|
+
throw error;
|
|
613
|
+
semanticUnsupported("unsupported-crs", path, "GeoServices requires a resolved executable source CRS");
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function assertEastNorth(value, path) {
|
|
617
|
+
const [x, y] = value.coordinateOrder.axes;
|
|
618
|
+
if (x?.direction !== "east" || y?.direction !== "north") {
|
|
619
|
+
semanticUnsupported("unsupported-crs", path, "GeoServices geometry requires east/north x/y payload axes");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function sourceGeometryField(schema, name, path) {
|
|
623
|
+
if (schema.geometry.state !== "known") {
|
|
624
|
+
semanticUnsupported("unsupported-source", path, "Source geometry metadata is unavailable");
|
|
625
|
+
}
|
|
626
|
+
const field = schema.geometry.fields.find((candidate) => candidate.field === name);
|
|
627
|
+
if (!field)
|
|
628
|
+
semanticUnsupported("unsupported-source", path, "Source geometry field metadata is unavailable");
|
|
629
|
+
return field;
|
|
630
|
+
}
|
|
631
|
+
function primaryGeometryField(schema) {
|
|
632
|
+
return schema.geometry.state === "known" && schema.geometry.primaryField.state === "known"
|
|
633
|
+
? schema.geometry.primaryField.field
|
|
634
|
+
: undefined;
|
|
635
|
+
}
|
|
636
|
+
function geoServicesSqlField(field, state, path) {
|
|
637
|
+
return sql92Identifier(geoServicesPhysicalField(field, state, path), path);
|
|
638
|
+
}
|
|
639
|
+
function geoServicesRequestField(field, state, path) {
|
|
640
|
+
const requestField = geoServicesPhysicalField(field, state, path);
|
|
641
|
+
if (!GEO_SERVICES_REST_FIELD_NAME.test(requestField)) {
|
|
642
|
+
semanticUnsupported("unsupported-source", path, "GeoServices raw field parameters require one Unicode identifier without delimiters or whitespace");
|
|
643
|
+
}
|
|
644
|
+
return requestField;
|
|
645
|
+
}
|
|
646
|
+
const GEO_SERVICES_REST_FIELD_NAME = /^[_\p{ID_Start}][_\p{ID_Continue}]*$/u;
|
|
647
|
+
function geoServicesPhysicalField(field, state, path) {
|
|
648
|
+
if (field.path.length !== 1) {
|
|
649
|
+
semanticUnsupported("unsupported-source", path, "GeoServices fields require exactly one physical layer-field path segment");
|
|
650
|
+
}
|
|
651
|
+
const requestField = field.path[0];
|
|
652
|
+
sql92Identifier(requestField, path);
|
|
653
|
+
recordSemanticFieldMapping(state.fieldMappings, field, requestField);
|
|
654
|
+
return requestField;
|
|
655
|
+
}
|
|
656
|
+
function containsSpatialFilter(filter) {
|
|
657
|
+
if (filter.kind === "spatial")
|
|
658
|
+
return true;
|
|
659
|
+
if (filter.kind === "boolean")
|
|
660
|
+
return filter.args.some((entry) => containsSpatialFilter(entry));
|
|
661
|
+
if (filter.kind === "not")
|
|
662
|
+
return containsSpatialFilter(filter.arg);
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
function parenthesized(value) {
|
|
666
|
+
return `(${value})`;
|
|
667
|
+
}
|