@honua/react 0.1.0-beta.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.
Files changed (315) hide show
  1. package/contract/discovery.d.ts +231 -0
  2. package/contract/discovery.d.ts.map +1 -0
  3. package/contract/discovery.js +554 -0
  4. package/contract/edit-sketch.d.ts +8 -0
  5. package/contract/edit-sketch.d.ts.map +1 -1
  6. package/contract/edit-sketch.js +15 -0
  7. package/contract/edit-snapping.d.ts +160 -0
  8. package/contract/edit-snapping.d.ts.map +1 -0
  9. package/contract/edit-snapping.js +675 -0
  10. package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
  11. package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
  12. package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
  13. package/contract/index.d.ts +11 -2
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -1
  16. package/contract/pmtiles.d.ts +31 -0
  17. package/contract/pmtiles.d.ts.map +1 -1
  18. package/contract/pmtiles.js +60 -20
  19. package/contract/protocol-module.d.ts +70 -0
  20. package/contract/protocol-module.d.ts.map +1 -0
  21. package/contract/protocol-module.js +1 -0
  22. package/contract/schema-envelope.d.ts +26 -0
  23. package/contract/schema-envelope.d.ts.map +1 -0
  24. package/contract/schema-envelope.js +25 -0
  25. package/contract/schema.d.ts +582 -0
  26. package/contract/schema.d.ts.map +1 -0
  27. package/contract/schema.js +2483 -0
  28. package/contract/source-capability-support.d.ts +6 -0
  29. package/contract/source-capability-support.d.ts.map +1 -0
  30. package/contract/source-capability-support.js +154 -0
  31. package/contract/source.d.ts +35 -17
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +267 -71
  34. package/contract/types.d.ts +140 -4
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +6 -14
  37. package/core/bounded-json.d.ts +4 -0
  38. package/core/bounded-json.d.ts.map +1 -0
  39. package/core/bounded-json.js +38 -0
  40. package/core/cache-state.d.ts +9 -0
  41. package/core/cache-state.d.ts.map +1 -1
  42. package/core/cache-state.js +10 -0
  43. package/core/capabilities-xml.d.ts +33 -0
  44. package/core/capabilities-xml.d.ts.map +1 -0
  45. package/core/capabilities-xml.js +331 -0
  46. package/core/client.d.ts +4 -1
  47. package/core/client.d.ts.map +1 -1
  48. package/core/client.js +115 -29
  49. package/core/error-classifications.d.ts +153 -0
  50. package/core/error-classifications.d.ts.map +1 -0
  51. package/core/error-classifications.js +140 -0
  52. package/core/error-code-registry.d.ts +158 -0
  53. package/core/error-code-registry.d.ts.map +1 -0
  54. package/core/error-code-registry.js +143 -0
  55. package/core/error-envelope.d.ts +68 -0
  56. package/core/error-envelope.d.ts.map +1 -0
  57. package/core/error-envelope.js +357 -0
  58. package/core/errors.d.ts +45 -29
  59. package/core/errors.d.ts.map +1 -1
  60. package/core/errors.js +97 -50
  61. package/core/geoparquet-sql.d.ts +30 -2
  62. package/core/geoparquet-sql.d.ts.map +1 -1
  63. package/core/geoparquet-sql.js +38 -3
  64. package/core/odata-key-path.d.ts +12 -0
  65. package/core/odata-key-path.d.ts.map +1 -0
  66. package/core/odata-key-path.js +28 -0
  67. package/core/odata-write-codec.d.ts +66 -0
  68. package/core/odata-write-codec.d.ts.map +1 -0
  69. package/core/odata-write-codec.js +1083 -0
  70. package/core/odata.d.ts +59 -7
  71. package/core/odata.d.ts.map +1 -1
  72. package/core/odata.js +431 -99
  73. package/core/ogc-features.d.ts.map +1 -1
  74. package/core/ogc-features.js +3 -0
  75. package/core/ogc-maps.d.ts +7 -0
  76. package/core/ogc-maps.d.ts.map +1 -1
  77. package/core/ogc-maps.js +46 -7
  78. package/core/ogc-processes.d.ts +2 -1
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +33 -5
  81. package/core/ogc-records.d.ts +9 -0
  82. package/core/ogc-records.d.ts.map +1 -1
  83. package/core/ogc-records.js +78 -29
  84. package/core/ogc-tiles.d.ts +10 -1
  85. package/core/ogc-tiles.d.ts.map +1 -1
  86. package/core/ogc-tiles.js +56 -17
  87. package/core/path-utils.d.ts +9 -0
  88. package/core/path-utils.d.ts.map +1 -1
  89. package/core/path-utils.js +19 -0
  90. package/core/protocol-transport.d.ts +1 -0
  91. package/core/protocol-transport.d.ts.map +1 -1
  92. package/core/request-pipeline.d.ts.map +1 -1
  93. package/core/request-pipeline.js +1 -1
  94. package/core/spatial-filter.d.ts +8 -4
  95. package/core/spatial-filter.d.ts.map +1 -1
  96. package/core/spatial-filter.js +206 -16
  97. package/core/stac-static.d.ts +6 -2
  98. package/core/stac-static.d.ts.map +1 -1
  99. package/core/stac-static.js +260 -69
  100. package/core/surfaces.d.ts.map +1 -1
  101. package/core/surfaces.js +69 -5
  102. package/core/types.d.ts +125 -7
  103. package/core/types.d.ts.map +1 -1
  104. package/core/wfs-capabilities.d.ts +2 -0
  105. package/core/wfs-capabilities.d.ts.map +1 -1
  106. package/core/wfs-capabilities.js +8 -1
  107. package/core/wfs.d.ts.map +1 -1
  108. package/core/wfs.js +10 -0
  109. package/core/wms-capabilities.d.ts +56 -84
  110. package/core/wms-capabilities.d.ts.map +1 -1
  111. package/core/wms-capabilities.js +212 -509
  112. package/core/wmts-capabilities.d.ts +62 -62
  113. package/core/wmts-capabilities.d.ts.map +1 -1
  114. package/core/wmts-capabilities.js +224 -371
  115. package/esri-compat/editor.d.ts +8 -0
  116. package/esri-compat/editor.d.ts.map +1 -1
  117. package/esri-compat/editor.js +13 -0
  118. package/esri-compat/feature-layer.d.ts +2 -0
  119. package/esri-compat/feature-layer.d.ts.map +1 -1
  120. package/esri-compat/feature-layer.js +1 -0
  121. package/esri-compat/layer-list.d.ts +11 -0
  122. package/esri-compat/layer-list.d.ts.map +1 -1
  123. package/esri-compat/layer-list.js +49 -0
  124. package/esri-compat/legend.d.ts +10 -0
  125. package/esri-compat/legend.d.ts.map +1 -1
  126. package/esri-compat/legend.js +27 -0
  127. package/esri-compat/portal.d.ts +197 -0
  128. package/esri-compat/portal.d.ts.map +1 -0
  129. package/esri-compat/portal.js +251 -0
  130. package/esri-compat/renderer-objects.d.ts +40 -0
  131. package/esri-compat/renderer-objects.d.ts.map +1 -0
  132. package/esri-compat/renderer-objects.js +62 -0
  133. package/esri-compat/sketch.d.ts +43 -0
  134. package/esri-compat/sketch.d.ts.map +1 -1
  135. package/esri-compat/sketch.js +46 -0
  136. package/esri-compat/snapping.d.ts +43 -0
  137. package/esri-compat/snapping.d.ts.map +1 -0
  138. package/esri-compat/snapping.js +45 -0
  139. package/esri-compat/url.d.ts.map +1 -1
  140. package/esri-compat/url.js +1 -1
  141. package/esri-compat/widget-host.d.ts +84 -0
  142. package/esri-compat/widget-host.d.ts.map +1 -0
  143. package/esri-compat/widget-host.js +159 -0
  144. package/filter-registry/index.d.ts +145 -0
  145. package/filter-registry/index.d.ts.map +1 -0
  146. package/filter-registry/index.js +531 -0
  147. package/generated-app/errors.d.ts +18 -2
  148. package/generated-app/errors.d.ts.map +1 -1
  149. package/generated-app/errors.js +44 -11
  150. package/map/automatic-mount-integration.d.ts +134 -0
  151. package/map/automatic-mount-integration.d.ts.map +1 -0
  152. package/map/automatic-mount-integration.js +305 -0
  153. package/map/automatic-source-strategy.d.ts +125 -0
  154. package/map/automatic-source-strategy.d.ts.map +1 -0
  155. package/map/automatic-source-strategy.js +494 -0
  156. package/map/data-to-map-bridge.d.ts +293 -0
  157. package/map/data-to-map-bridge.d.ts.map +1 -0
  158. package/map/data-to-map-bridge.js +817 -0
  159. package/map/geojson-projection.d.ts +33 -0
  160. package/map/geojson-projection.d.ts.map +1 -0
  161. package/map/geojson-projection.js +118 -0
  162. package/map/index.d.ts +14 -0
  163. package/map/index.d.ts.map +1 -1
  164. package/map/index.js +8 -0
  165. package/map/raster-source-spec.d.ts +30 -0
  166. package/map/raster-source-spec.d.ts.map +1 -0
  167. package/map/raster-source-spec.js +190 -0
  168. package/map/raster-source-strategy.d.ts +65 -0
  169. package/map/raster-source-strategy.d.ts.map +1 -0
  170. package/map/raster-source-strategy.js +232 -0
  171. package/map/source-to-maplibre.d.ts +91 -0
  172. package/map/source-to-maplibre.d.ts.map +1 -0
  173. package/map/source-to-maplibre.js +454 -0
  174. package/map/temporal-playback.d.ts +139 -0
  175. package/map/temporal-playback.d.ts.map +1 -0
  176. package/map/temporal-playback.js +247 -0
  177. package/package.json +14 -1
  178. package/query-planner/canonical.d.ts +7 -0
  179. package/query-planner/canonical.d.ts.map +1 -0
  180. package/query-planner/canonical.js +145 -0
  181. package/query-planner/cql2-json.d.ts +17 -0
  182. package/query-planner/cql2-json.d.ts.map +1 -0
  183. package/query-planner/cql2-json.js +646 -0
  184. package/query-planner/diagnostics.d.ts +35 -0
  185. package/query-planner/diagnostics.d.ts.map +1 -0
  186. package/query-planner/diagnostics.js +373 -0
  187. package/query-planner/duckdb.d.ts +79 -0
  188. package/query-planner/duckdb.d.ts.map +1 -0
  189. package/query-planner/duckdb.js +758 -0
  190. package/query-planner/execution-context.d.ts +5 -0
  191. package/query-planner/execution-context.d.ts.map +1 -0
  192. package/query-planner/execution-context.js +50 -0
  193. package/query-planner/executor.d.ts +5 -0
  194. package/query-planner/executor.d.ts.map +1 -0
  195. package/query-planner/executor.js +223 -0
  196. package/query-planner/geoservices.d.ts +91 -0
  197. package/query-planner/geoservices.d.ts.map +1 -0
  198. package/query-planner/geoservices.js +667 -0
  199. package/query-planner/grpc.d.ts +100 -0
  200. package/query-planner/grpc.d.ts.map +1 -0
  201. package/query-planner/grpc.js +736 -0
  202. package/query-planner/index.d.ts +44 -0
  203. package/query-planner/index.d.ts.map +1 -0
  204. package/query-planner/index.js +30 -0
  205. package/query-planner/ir.d.ts +20 -0
  206. package/query-planner/ir.d.ts.map +1 -0
  207. package/query-planner/ir.js +417 -0
  208. package/query-planner/local-aggregate.d.ts +5 -0
  209. package/query-planner/local-aggregate.d.ts.map +1 -0
  210. package/query-planner/local-aggregate.js +126 -0
  211. package/query-planner/odata.d.ts +48 -0
  212. package/query-planner/odata.d.ts.map +1 -0
  213. package/query-planner/odata.js +662 -0
  214. package/query-planner/ogc-compiler.d.ts +55 -0
  215. package/query-planner/ogc-compiler.d.ts.map +1 -0
  216. package/query-planner/ogc-compiler.js +480 -0
  217. package/query-planner/ogc-features.d.ts +53 -0
  218. package/query-planner/ogc-features.d.ts.map +1 -0
  219. package/query-planner/ogc-features.js +617 -0
  220. package/query-planner/planner.d.ts +17 -0
  221. package/query-planner/planner.d.ts.map +1 -0
  222. package/query-planner/planner.js +818 -0
  223. package/query-planner/resource.d.ts +94 -0
  224. package/query-planner/resource.d.ts.map +1 -0
  225. package/query-planner/resource.js +526 -0
  226. package/query-planner/semantic-canonical.d.ts +15 -0
  227. package/query-planner/semantic-canonical.d.ts.map +1 -0
  228. package/query-planner/semantic-canonical.js +62 -0
  229. package/query-planner/semantic-compat.d.ts +10 -0
  230. package/query-planner/semantic-compat.d.ts.map +1 -0
  231. package/query-planner/semantic-compat.js +35 -0
  232. package/query-planner/semantic-compiler.d.ts +64 -0
  233. package/query-planner/semantic-compiler.d.ts.map +1 -0
  234. package/query-planner/semantic-compiler.js +81 -0
  235. package/query-planner/semantic-literals.d.ts +30 -0
  236. package/query-planner/semantic-literals.d.ts.map +1 -0
  237. package/query-planner/semantic-literals.js +133 -0
  238. package/query-planner/semantic-types.d.ts +265 -0
  239. package/query-planner/semantic-types.d.ts.map +1 -0
  240. package/query-planner/semantic-types.js +1 -0
  241. package/query-planner/semantic.d.ts +54 -0
  242. package/query-planner/semantic.d.ts.map +1 -0
  243. package/query-planner/semantic.js +1421 -0
  244. package/query-planner/types.d.ts +560 -0
  245. package/query-planner/types.d.ts.map +1 -0
  246. package/query-planner/types.js +49 -0
  247. package/query-planner/wfs-semantic.d.ts +58 -0
  248. package/query-planner/wfs-semantic.d.ts.map +1 -0
  249. package/query-planner/wfs-semantic.js +547 -0
  250. package/query-planner/wfs.d.ts +6 -0
  251. package/query-planner/wfs.d.ts.map +1 -0
  252. package/query-planner/wfs.js +71 -0
  253. package/react/external-map.d.ts +67 -0
  254. package/react/external-map.d.ts.map +1 -0
  255. package/react/external-map.js +63 -0
  256. package/react/honua-map.d.ts.map +1 -1
  257. package/react/honua-map.js +3 -2
  258. package/react/honua-source-layer.d.ts +110 -0
  259. package/react/honua-source-layer.d.ts.map +1 -0
  260. package/react/honua-source-layer.js +101 -0
  261. package/react/hooks.d.ts +28 -0
  262. package/react/hooks.d.ts.map +1 -1
  263. package/react/hooks.js +26 -4
  264. package/react/index.d.ts +8 -0
  265. package/react/index.d.ts.map +1 -1
  266. package/react/index.js +5 -0
  267. package/react/query-cache.d.ts +26 -4
  268. package/react/query-cache.d.ts.map +1 -1
  269. package/react/query-cache.js +51 -8
  270. package/react/selection.d.ts +177 -0
  271. package/react/selection.d.ts.map +1 -0
  272. package/react/selection.js +0 -0
  273. package/react/use-mounted-source.d.ts +69 -0
  274. package/react/use-mounted-source.d.ts.map +1 -0
  275. package/react/use-mounted-source.js +346 -0
  276. package/runtime/edit-snapping.d.ts +107 -0
  277. package/runtime/edit-snapping.d.ts.map +1 -0
  278. package/runtime/edit-snapping.js +190 -0
  279. package/runtime/errors.d.ts +3 -2
  280. package/runtime/errors.d.ts.map +1 -1
  281. package/runtime/errors.js +19 -2
  282. package/runtime/index.d.ts +6 -0
  283. package/runtime/index.d.ts.map +1 -1
  284. package/runtime/index.js +3 -0
  285. package/runtime/maplibre-renderer.d.ts +41 -0
  286. package/runtime/maplibre-renderer.d.ts.map +1 -0
  287. package/runtime/maplibre-renderer.js +322 -0
  288. package/runtime/query-tiles.d.ts +2 -1
  289. package/runtime/query-tiles.d.ts.map +1 -1
  290. package/runtime/query-tiles.js +6 -2
  291. package/runtime/source-bridge.d.ts +4 -4
  292. package/runtime/source-bridge.d.ts.map +1 -1
  293. package/runtime/source-bridge.js +9 -56
  294. package/runtime/style-interactions.d.ts +3 -2
  295. package/runtime/style-interactions.d.ts.map +1 -1
  296. package/runtime/style-interactions.js +11 -3
  297. package/runtime/terra-draw-sketch.d.ts +275 -0
  298. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  299. package/runtime/terra-draw-sketch.js +404 -0
  300. package/source-capability-registry.d.ts +1 -0
  301. package/source-capability-registry.js +1 -0
  302. package/source-capability-types.d.ts +186 -0
  303. package/source-capability-types.js +7 -0
  304. package/style/index.d.ts +2 -0
  305. package/style/index.d.ts.map +1 -1
  306. package/style/index.js +2 -0
  307. package/style/renderers.d.ts +326 -0
  308. package/style/renderers.d.ts.map +1 -0
  309. package/style/renderers.js +517 -0
  310. package/webmap/convert-renderer.d.ts +24 -3
  311. package/webmap/convert-renderer.d.ts.map +1 -1
  312. package/webmap/convert-renderer.js +93 -112
  313. package/webmap/index.d.ts +1 -1
  314. package/webmap/index.d.ts.map +1 -1
  315. package/webmap/index.js +1 -1
@@ -0,0 +1,617 @@
1
+ import { canonicalStringify, toJsonValue } from "./canonical.js";
2
+ import { bboxInDefinitionOrder, compiledRequestFingerprint, compilerEvidenceFingerprint, compilerField, cql2FieldName, cql2Identifier, cql2StringLiteral, geometryInDefinitionOrder, geometryToWkt, hasConformanceClass, normalizeConformanceUris, normalizeIdentifierUris, ogcAxisPlan, ogcCrsUri, ogcNumber, sharedFilterCrs, } from "./ogc-compiler.js";
3
+ import { hashSemanticQuery } from "./semantic-canonical.js";
4
+ import { prepareSemanticCompilerQuery, runSemanticCompiler, semanticUnsupported, } from "./semantic-compiler.js";
5
+ import { HonuaQueryPlanningError } from "./types.js";
6
+ const OGC_CAPABILITY_FINGERPRINT_DOMAIN = "honua.ogc-features.filter-capabilities.v1";
7
+ const OGC_REQUEST_FINGERPRINT_DOMAIN = "honua.ogc-features.semantic-request.v1";
8
+ const CQL2_DATE = /^\d{4}-\d{2}-\d{2}$/;
9
+ const CQL2_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
10
+ const EXACT_NUMERIC_TEXT = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
11
+ const COMPARISON_TEXT = {
12
+ eq: "=",
13
+ ne: "<>",
14
+ lt: "<",
15
+ lte: "<=",
16
+ gt: ">",
17
+ gte: ">=",
18
+ };
19
+ const SPATIAL_TEXT = {
20
+ equals: "S_EQUALS",
21
+ intersects: "S_INTERSECTS",
22
+ within: "S_WITHIN",
23
+ contains: "S_CONTAINS",
24
+ disjoint: "S_DISJOINT",
25
+ touches: "S_TOUCHES",
26
+ overlaps: "S_OVERLAPS",
27
+ crosses: "S_CROSSES",
28
+ };
29
+ const SPATIAL_JSON = {
30
+ equals: "s_equals",
31
+ intersects: "s_intersects",
32
+ within: "s_within",
33
+ contains: "s_contains",
34
+ disjoint: "s_disjoint",
35
+ touches: "s_touches",
36
+ overlaps: "s_overlaps",
37
+ crosses: "s_crosses",
38
+ };
39
+ const TEMPORAL_TEXT = {
40
+ before: "T_BEFORE",
41
+ after: "T_AFTER",
42
+ during: "T_DURING",
43
+ "time-intersects": "T_INTERSECTS",
44
+ };
45
+ const TEMPORAL_JSON = {
46
+ before: "t_before",
47
+ after: "t_after",
48
+ during: "t_during",
49
+ "time-intersects": "t_intersects",
50
+ };
51
+ /**
52
+ * Compile canonical query IR to an OGC API Features `/items` request. The
53
+ * compiler is side-effect free and deliberately supports only semantics the
54
+ * first-party OGC adapter can preserve exactly.
55
+ */
56
+ export function compileOgcApiFeaturesQuery(source, query) {
57
+ if (source.protocol !== "ogc-features") {
58
+ throw new HonuaQueryPlanningError("unsupported-compiler", `ogc-api-features-query-v1 does not compile protocol "${source.protocol}"`);
59
+ }
60
+ if (source.collectionId === undefined || source.collectionId === "") {
61
+ throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.collectionId for OGC API Features planning`);
62
+ }
63
+ if (query.aggregation) {
64
+ throw new HonuaQueryPlanningError("unsupported-query", "OGC API Features does not provide remote aggregation; use degraded policy with an explicit bounded-local fallback");
65
+ }
66
+ if (query.returnGeometry === false) {
67
+ throw new HonuaQueryPlanningError("unsupported-query", "OGC API Features /items has no portable geometry-suppression parameter; omit returnGeometry or select another protocol");
68
+ }
69
+ return {
70
+ compiler: "ogc-api-features-query-v1",
71
+ collectionId: source.collectionId,
72
+ ...(query.where ? { filter: query.where.expression, filterLang: "cql2-text" } : {}),
73
+ ...(query.outFields && query.outFields.length > 0 ? { properties: query.outFields } : {}),
74
+ ...(query.orderBy && query.orderBy.length > 0
75
+ ? { sortby: query.orderBy.map((sort) => `${sort.direction === "desc" ? "-" : ""}${sort.field}`).join(",") }
76
+ : {}),
77
+ ...(query.spatialFilter ? { bbox: compileBbox(query.spatialFilter, source.id) } : {}),
78
+ ...(query.outSr !== undefined ? { crs: String(query.outSr) } : {}),
79
+ ...(query.pagination?.offset !== undefined ? { offset: query.pagination.offset } : {}),
80
+ ...(query.pagination?.limit !== undefined ? { limit: query.pagination.limit } : {}),
81
+ };
82
+ }
83
+ /** Compile a typed semantic query using only explicitly discovered OGC/CQL2 support. */
84
+ export function compileSemanticOgcApiFeaturesQuery(options) {
85
+ return runSemanticCompiler(() => {
86
+ const { query, schema } = prepareSemanticCompilerQuery(options.query, options.schema, "ogc-features");
87
+ const source = verifiedSemanticOgcSource(options.source);
88
+ const conformance = normalizeOgcConformance(options.conformance);
89
+ const language = query.filter
90
+ ? selectCql2Language(query.filter, conformance, options.preferredFilterLanguage)
91
+ : undefined;
92
+ const state = language
93
+ ? { schema, conformance, language, usesNativeFilter: false }
94
+ : undefined;
95
+ const filter = state && query.filter ? compileCql2Filter(query.filter, state, "$.filter") : undefined;
96
+ const filterCrs = state?.filterCrs
97
+ ? compiledOgcFilterCrs(state.filterCrs, conformance, "$.filter")
98
+ : options.nativeFilterCrs && query.filter?.kind === "native"
99
+ ? compiledOgcFilterCrs(options.nativeFilterCrs, conformance, "options.nativeFilterCrs")
100
+ : undefined;
101
+ validateOgcSemanticProjection(query, schema);
102
+ rejectOgcSemanticSort(query);
103
+ const page = query.page;
104
+ if (page?.kind === "offset") {
105
+ semanticUnsupported("unsupported-node", "$.page.offset", "OGC API Features Core does not define a client-supplied offset parameter");
106
+ }
107
+ const crs = query.outputCrs ? compiledOgcOutputCrs(query.outputCrs, conformance, "$.outputCrs") : undefined;
108
+ const queryFingerprint = hashSemanticQuery(query, { schema, protocol: "ogc-features" });
109
+ const preimage = {
110
+ compiler: "ogc-api-features-semantic-query-v1",
111
+ ...(language ? { dialect: language } : {}),
112
+ schemaFingerprint: schema.fingerprint,
113
+ queryFingerprint,
114
+ capabilityFingerprint: conformance.fingerprint,
115
+ collectionId: source.collectionId,
116
+ ...(filter !== undefined ? { filter, filterLang: language } : {}),
117
+ ...(filterCrs ? { filterCrs } : {}),
118
+ ...(crs ? { crs } : {}),
119
+ ...(page?.limit !== undefined ? { limit: page.limit } : {}),
120
+ usesNativeFilter: state?.usesNativeFilter ?? false,
121
+ };
122
+ const artifact = {
123
+ ...preimage,
124
+ requestFingerprint: compiledRequestFingerprint(OGC_REQUEST_FINGERPRINT_DOMAIN, toJsonValue(preimage)),
125
+ };
126
+ return { artifact };
127
+ });
128
+ }
129
+ function verifiedSemanticOgcSource(value) {
130
+ if (!value || typeof value !== "object") {
131
+ throw new HonuaQueryPlanningError("invalid-query", "Semantic OGC source identity is invalid");
132
+ }
133
+ if ((typeof value.collectionId !== "string" && typeof value.collectionId !== "number") ||
134
+ (typeof value.collectionId === "string" &&
135
+ (value.collectionId.length === 0 || value.collectionId.length > 1_024 || hasControl(value.collectionId))) ||
136
+ (typeof value.collectionId === "number" && (!Number.isSafeInteger(value.collectionId) || value.collectionId < 0))) {
137
+ throw new HonuaQueryPlanningError("invalid-query", "options.source.collectionId is invalid");
138
+ }
139
+ return { collectionId: value.collectionId };
140
+ }
141
+ function normalizeOgcConformance(value) {
142
+ if (!value || typeof value !== "object") {
143
+ throw new HonuaQueryPlanningError("invalid-query", "options.conformance is invalid");
144
+ }
145
+ const conformsTo = normalizeConformanceUris(value.conformsTo, "options.conformance.conformsTo");
146
+ const supportedFilterCrs = normalizeIdentifierUris(value.supportedFilterCrs, "options.conformance.supportedFilterCrs");
147
+ const supportedOutputCrs = normalizeIdentifierUris(value.supportedOutputCrs, "options.conformance.supportedOutputCrs");
148
+ return {
149
+ conformsTo,
150
+ supportedFilterCrs,
151
+ supportedOutputCrs,
152
+ fingerprint: compilerEvidenceFingerprint(OGC_CAPABILITY_FINGERPRINT_DOMAIN, toJsonValue({ conformsTo, supportedFilterCrs, supportedOutputCrs })),
153
+ };
154
+ }
155
+ function selectCql2Language(filter, conformance, preferred) {
156
+ requireCqlConformance(conformance, "ogcapi-features-3/1.0", "features-filter", "$.filter");
157
+ requireCqlConformance(conformance, "cql2/1.0", "basic-cql2", "$.filter");
158
+ const advertised = ["cql2-json", "cql2-text"].filter((language) => hasConformanceClass(conformance.conformsTo, "cql2/1.0", language));
159
+ if (filter.kind === "native") {
160
+ const language = filter.dialect;
161
+ if (language !== "cql2-json" && language !== "cql2-text") {
162
+ semanticUnsupported("unsupported-native-filter", "$.filter.dialect", `unsupported native dialect ${language}`);
163
+ }
164
+ if (!advertised.includes(language)) {
165
+ semanticUnsupported("unsupported-native-filter", "$.filter.dialect", `native ${language} requires matching discovered conformance`);
166
+ }
167
+ return language;
168
+ }
169
+ if (preferred !== undefined && preferred !== "cql2-json" && preferred !== "cql2-text") {
170
+ throw new HonuaQueryPlanningError("invalid-query", "options.preferredFilterLanguage is invalid");
171
+ }
172
+ if (preferred !== undefined) {
173
+ if (!advertised.includes(preferred)) {
174
+ semanticUnsupported("unsupported-source", "options.preferredFilterLanguage", `${preferred} was not present in discovered conformance`);
175
+ }
176
+ return preferred;
177
+ }
178
+ const selected = advertised[0];
179
+ if (!selected) {
180
+ semanticUnsupported("unsupported-source", "options.conformance.conformsTo", "OGC filtering requires explicitly discovered cql2-json or cql2-text conformance");
181
+ }
182
+ return selected;
183
+ }
184
+ function compileCql2Filter(filter, state, path) {
185
+ if (filter.kind === "native") {
186
+ state.usesNativeFilter = true;
187
+ if (filter.dialect !== state.language) {
188
+ semanticUnsupported("unsupported-native-filter", `${path}.dialect`, `native ${filter.dialect} cannot compile as ${state.language}`);
189
+ }
190
+ return filter.payload.format === "json" ? canonicalStringify(filter.payload.value) : filter.payload.text;
191
+ }
192
+ if (state.language === "cql2-json") {
193
+ return canonicalStringify(compileCql2JsonNode(filter, state, path));
194
+ }
195
+ return compileCql2TextNode(filter, state, path);
196
+ }
197
+ function compileCql2JsonNode(filter, state, path) {
198
+ switch (filter.kind) {
199
+ case "comparison": {
200
+ const field = compilerField(state.schema, filter.left.name, `${path}.left.name`);
201
+ return {
202
+ op: COMPARISON_TEXT[filter.operator],
203
+ args: [
204
+ { property: cql2FieldName(state.schema, filter.left.name, `${path}.left.name`) },
205
+ cql2JsonLiteral(filter.right.value, field, `${path}.right.value`),
206
+ ],
207
+ };
208
+ }
209
+ case "boolean":
210
+ if (filter.args.length === 1) {
211
+ return compileCql2JsonNode(filter.args[0], state, `${path}.args[0]`);
212
+ }
213
+ return {
214
+ op: filter.operator,
215
+ args: filter.args.map((entry, index) => compileCql2JsonNode(entry, state, `${path}.args[${index}]`)),
216
+ };
217
+ case "not":
218
+ return { op: "not", args: [compileCql2JsonNode(filter.arg, state, `${path}.arg`)] };
219
+ case "null": {
220
+ const node = {
221
+ op: "isNull",
222
+ args: [{ property: cql2FieldName(state.schema, filter.operand.name, `${path}.operand.name`) }],
223
+ };
224
+ return filter.operator === "is-null" ? node : { op: "not", args: [node] };
225
+ }
226
+ case "list": {
227
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
228
+ const field = compilerField(state.schema, filter.operand.name, `${path}.operand.name`);
229
+ return {
230
+ op: "in",
231
+ args: [
232
+ { property: cql2FieldName(state.schema, filter.operand.name, `${path}.operand.name`) },
233
+ filter.values.map((entry, index) => cql2JsonLiteral(entry.value, field, `${path}.values[${index}].value`)),
234
+ ],
235
+ };
236
+ }
237
+ case "range": {
238
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
239
+ const field = compilerField(state.schema, filter.operand.name, `${path}.operand.name`);
240
+ return {
241
+ op: "between",
242
+ args: [
243
+ { property: cql2FieldName(state.schema, filter.operand.name, `${path}.operand.name`) },
244
+ cql2JsonLiteral(filter.lower.value, field, `${path}.lower.value`),
245
+ cql2JsonLiteral(filter.upper.value, field, `${path}.upper.value`),
246
+ ],
247
+ };
248
+ }
249
+ case "pattern": {
250
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
251
+ const property = {
252
+ property: cql2FieldName(state.schema, filter.operand.name, `${path}.operand.name`),
253
+ };
254
+ if (filter.caseSensitive === false) {
255
+ requireCqlConformance(state.conformance, "cql2/1.0", "case-insensitive-comparison", path);
256
+ return {
257
+ op: "like",
258
+ args: [
259
+ { op: "casei", args: [property] },
260
+ { op: "casei", args: [filter.pattern] },
261
+ ],
262
+ };
263
+ }
264
+ return { op: "like", args: [property, filter.pattern] };
265
+ }
266
+ case "spatial":
267
+ return compileCql2JsonSpatial(filter, state, path);
268
+ case "temporal":
269
+ rejectCql2During(filter.operator, path);
270
+ requireCqlConformance(state.conformance, "cql2/1.0", "temporal-functions", path);
271
+ return {
272
+ op: TEMPORAL_JSON[filter.operator],
273
+ args: [
274
+ { property: cql2FieldName(state.schema, filter.operand.name, `${path}.operand.name`) },
275
+ cql2JsonTemporal(filter.value, `${path}.value`),
276
+ ],
277
+ };
278
+ }
279
+ }
280
+ function compileCql2TextNode(filter, state, path) {
281
+ switch (filter.kind) {
282
+ case "comparison": {
283
+ const field = compilerField(state.schema, filter.left.name, `${path}.left.name`);
284
+ const property = cql2TextProperty(state.schema, filter.left.name, `${path}.left.name`);
285
+ return `${property} ${COMPARISON_TEXT[filter.operator]} ${cql2TextLiteral(filter.right.value, field, `${path}.right.value`)}`;
286
+ }
287
+ case "boolean":
288
+ if (filter.args.length === 1) {
289
+ return compileCql2TextNode(filter.args[0], state, `${path}.args[0]`);
290
+ }
291
+ return `(${filter.args
292
+ .map((entry, index) => compileCql2TextNode(entry, state, `${path}.args[${index}]`))
293
+ .join(` ${filter.operator.toUpperCase()} `)})`;
294
+ case "not":
295
+ return `NOT (${compileCql2TextNode(filter.arg, state, `${path}.arg`)})`;
296
+ case "null": {
297
+ const property = cql2TextProperty(state.schema, filter.operand.name, `${path}.operand.name`);
298
+ return `${property} IS ${filter.operator === "is-not-null" ? "NOT " : ""}NULL`;
299
+ }
300
+ case "list": {
301
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
302
+ const field = compilerField(state.schema, filter.operand.name, `${path}.operand.name`);
303
+ const values = filter.values
304
+ .map((entry, index) => cql2TextLiteral(entry.value, field, `${path}.values[${index}].value`))
305
+ .join(", ");
306
+ return `${cql2TextProperty(state.schema, filter.operand.name, `${path}.operand.name`)} IN (${values})`;
307
+ }
308
+ case "range": {
309
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
310
+ const field = compilerField(state.schema, filter.operand.name, `${path}.operand.name`);
311
+ return `${cql2TextProperty(state.schema, filter.operand.name, `${path}.operand.name`)} BETWEEN ${cql2TextLiteral(filter.lower.value, field, `${path}.lower.value`)} AND ${cql2TextLiteral(filter.upper.value, field, `${path}.upper.value`)}`;
312
+ }
313
+ case "pattern": {
314
+ requireCqlConformance(state.conformance, "cql2/1.0", "advanced-comparison-operators", path);
315
+ const property = cql2TextProperty(state.schema, filter.operand.name, `${path}.operand.name`);
316
+ const pattern = cql2StringLiteral(filter.pattern, `${path}.pattern`);
317
+ if (filter.caseSensitive === false) {
318
+ requireCqlConformance(state.conformance, "cql2/1.0", "case-insensitive-comparison", path);
319
+ return `CASEI(${property}) LIKE CASEI(${pattern})`;
320
+ }
321
+ return `${property} LIKE ${pattern}`;
322
+ }
323
+ case "spatial":
324
+ return compileCql2TextSpatial(filter, state, path);
325
+ case "temporal":
326
+ rejectCql2During(filter.operator, path);
327
+ requireCqlConformance(state.conformance, "cql2/1.0", "temporal-functions", path);
328
+ return `${TEMPORAL_TEXT[filter.operator]}(${cql2TextProperty(state.schema, filter.operand.name, `${path}.operand.name`)}, ${cql2TextTemporal(filter.value, `${path}.value`)})`;
329
+ }
330
+ }
331
+ function compileCql2JsonSpatial(filter, state, path) {
332
+ const property = {
333
+ property: cql2FieldName(state.schema, spatialProperty(filter, state.schema, path), `${path}.property.name`),
334
+ };
335
+ if (filter.operator === "within-distance" || filter.operator === "beyond-distance") {
336
+ semanticUnsupported("unsupported-distance", `${path}.operator`, "distance predicates are outside CQL2 1.0");
337
+ }
338
+ if (filter.operator === "bbox-intersects") {
339
+ requireCqlConformance(state.conformance, "cql2/1.0", "basic-spatial-functions", path);
340
+ state.filterCrs = sharedFilterCrs(state.filterCrs, filter.bbox.crs, path);
341
+ return { op: "s_intersects", args: [property, { bbox: bboxInDefinitionOrder(filter.bbox, `${path}.bbox`) }] };
342
+ }
343
+ requireCqlSpatialClass(state.conformance, filter.operator, filter.geometry.geometry.type, path);
344
+ state.filterCrs = sharedFilterCrs(state.filterCrs, filter.geometry.crs, path);
345
+ if (filter.geometry.layout !== "xy" && filter.geometry.layout !== "xyz") {
346
+ semanticUnsupported("unsupported-geometry", `${path}.geometry.layout`, "CQL2 JSON cannot preserve measured coordinate layouts");
347
+ }
348
+ const geometry = geometryInDefinitionOrder(filter.geometry, `${path}.geometry`);
349
+ assertCql2JsonGeometry(geometry, `${path}.geometry.geometry`);
350
+ return { op: SPATIAL_JSON[filter.operator], args: [property, toJsonValue(geometry)] };
351
+ }
352
+ function compileCql2TextSpatial(filter, state, path) {
353
+ const property = cql2TextProperty(state.schema, spatialProperty(filter, state.schema, path), `${path}.property.name`);
354
+ if (filter.operator === "within-distance" || filter.operator === "beyond-distance") {
355
+ semanticUnsupported("unsupported-distance", `${path}.operator`, "distance predicates are outside CQL2 1.0");
356
+ }
357
+ if (filter.operator === "bbox-intersects") {
358
+ requireCqlConformance(state.conformance, "cql2/1.0", "basic-spatial-functions", path);
359
+ state.filterCrs = sharedFilterCrs(state.filterCrs, filter.bbox.crs, path);
360
+ const bounds = bboxInDefinitionOrder(filter.bbox, `${path}.bbox`)
361
+ .map((entry, index) => ogcNumber(entry, `${path}.bbox.box.bounds[${index}]`))
362
+ .join(", ");
363
+ return `S_INTERSECTS(${property}, BBOX(${bounds}))`;
364
+ }
365
+ requireCqlSpatialClass(state.conformance, filter.operator, filter.geometry.geometry.type, path);
366
+ state.filterCrs = sharedFilterCrs(state.filterCrs, filter.geometry.crs, path);
367
+ if (filter.geometry.layout !== "xy" && filter.geometry.layout !== "xyz") {
368
+ semanticUnsupported("unsupported-geometry", `${path}.geometry.layout`, "CQL2 text cannot preserve measured coordinate layouts");
369
+ }
370
+ const geometry = geometryInDefinitionOrder(filter.geometry, `${path}.geometry`);
371
+ return `${SPATIAL_TEXT[filter.operator]}(${property}, ${geometryToWkt(geometry, filter.geometry.layout, `${path}.geometry.geometry`)})`;
372
+ }
373
+ function cql2JsonLiteral(value, field, path) {
374
+ requireCql2ScalarField(field, path);
375
+ if (field.type.kind === "date") {
376
+ if (typeof value !== "string" || !CQL2_DATE.test(value)) {
377
+ semanticUnsupported("unsupported-field-type", path, "CQL2 date comparisons require a full-date string");
378
+ }
379
+ return { date: value };
380
+ }
381
+ if (field.type.kind === "timestamp") {
382
+ if (typeof value !== "string" || !CQL2_TIMESTAMP.test(value)) {
383
+ semanticUnsupported("unsupported-field-type", path, "CQL2 timestamp comparisons require a UTC Z instant");
384
+ }
385
+ return { timestamp: value };
386
+ }
387
+ if ((field.type.kind === "integer" || field.type.kind === "decimal") && field.type.jsonEncoding === "string") {
388
+ semanticUnsupported("unsupported-field-type", path, "CQL2 JSON cannot preserve string-encoded numeric precision; use discovered cql2-text");
389
+ }
390
+ if (value === null || Array.isArray(value) || typeof value === "object") {
391
+ semanticUnsupported("unsupported-field-type", path, "CQL2 scalar literals must be string, number, or boolean");
392
+ }
393
+ return value;
394
+ }
395
+ function cql2TextLiteral(value, field, path) {
396
+ requireCql2ScalarField(field, path);
397
+ if (field.type.kind === "date") {
398
+ if (typeof value !== "string" || !CQL2_DATE.test(value)) {
399
+ semanticUnsupported("unsupported-field-type", path, "CQL2 date comparisons require a full-date string");
400
+ }
401
+ return `DATE(${cql2StringLiteral(value, path)})`;
402
+ }
403
+ if (field.type.kind === "timestamp") {
404
+ if (typeof value !== "string" || !CQL2_TIMESTAMP.test(value)) {
405
+ semanticUnsupported("unsupported-field-type", path, "CQL2 timestamp comparisons require a UTC Z instant");
406
+ }
407
+ return `TIMESTAMP(${cql2StringLiteral(value, path)})`;
408
+ }
409
+ if ((field.type.kind === "integer" || field.type.kind === "decimal") && field.type.jsonEncoding === "string") {
410
+ if (typeof value !== "string" || !EXACT_NUMERIC_TEXT.test(value)) {
411
+ semanticUnsupported("unsupported-field-type", path, "CQL2 numeric text is not an exact decimal token");
412
+ }
413
+ return value;
414
+ }
415
+ if (typeof value === "string")
416
+ return cql2StringLiteral(value, path);
417
+ if (typeof value === "number")
418
+ return ogcNumber(value, path);
419
+ if (typeof value === "boolean")
420
+ return value ? "TRUE" : "FALSE";
421
+ semanticUnsupported("unsupported-field-type", path, "CQL2 scalar literals must be string, number, or boolean");
422
+ }
423
+ function requireCql2ScalarField(field, path) {
424
+ switch (field.type.kind) {
425
+ case "boolean":
426
+ case "integer":
427
+ case "float":
428
+ case "decimal":
429
+ case "string":
430
+ case "uuid":
431
+ case "date":
432
+ case "timestamp":
433
+ return;
434
+ default:
435
+ semanticUnsupported("unsupported-field-type", path, `CQL2 has no exact scalar representation for logical field type ${field.type.kind}`);
436
+ }
437
+ }
438
+ function cql2JsonTemporal(value, path) {
439
+ if (value.valueType === "date") {
440
+ requireCql2Date(value.value, `${path}.value`);
441
+ return { date: value.value };
442
+ }
443
+ if (value.valueType === "instant") {
444
+ requireCql2Timestamp(value.value, `${path}.value`);
445
+ return { timestamp: value.value };
446
+ }
447
+ const interval = value;
448
+ interval.value.forEach((endpoint, index) => requireCql2IntervalEndpoint(endpoint, `${path}.value[${index}]`));
449
+ return { interval: [...interval.value] };
450
+ }
451
+ function cql2TextTemporal(value, path) {
452
+ if (value.valueType === "date") {
453
+ requireCql2Date(value.value, `${path}.value`);
454
+ return `DATE(${cql2StringLiteral(value.value, `${path}.value`)})`;
455
+ }
456
+ if (value.valueType === "instant") {
457
+ requireCql2Timestamp(value.value, `${path}.value`);
458
+ return `TIMESTAMP(${cql2StringLiteral(value.value, `${path}.value`)})`;
459
+ }
460
+ const interval = value;
461
+ interval.value.forEach((endpoint, index) => requireCql2IntervalEndpoint(endpoint, `${path}.value[${index}]`));
462
+ return `INTERVAL(${cql2StringLiteral(interval.value[0], `${path}.value[0]`)}, ${cql2StringLiteral(interval.value[1], `${path}.value[1]`)})`;
463
+ }
464
+ function rejectCql2During(operator, path) {
465
+ if (operator === "during") {
466
+ semanticUnsupported("unsupported-node", `${path}.operator`, "CQL2 T_DURING requires an interval-valued first operand; semantic temporal properties are instant-valued");
467
+ }
468
+ }
469
+ function requireCql2Date(value, path) {
470
+ if (!CQL2_DATE.test(value)) {
471
+ semanticUnsupported("unsupported-field-type", path, "CQL2 temporal dates require a full-date literal");
472
+ }
473
+ }
474
+ function requireCql2Timestamp(value, path) {
475
+ if (!CQL2_TIMESTAMP.test(value)) {
476
+ semanticUnsupported("unsupported-field-type", path, "CQL2 temporal timestamps require a UTC Z instant");
477
+ }
478
+ }
479
+ function requireCql2IntervalEndpoint(value, path) {
480
+ if (!CQL2_DATE.test(value) && !CQL2_TIMESTAMP.test(value)) {
481
+ semanticUnsupported("unsupported-field-type", path, "CQL2 temporal interval endpoints require a full-date or UTC Z timestamp");
482
+ }
483
+ }
484
+ function cql2TextProperty(schema, name, path) {
485
+ return cql2Identifier(cql2FieldName(schema, name, path), path);
486
+ }
487
+ function spatialProperty(filter, schema, path) {
488
+ if (filter.property)
489
+ return filter.property.name;
490
+ if (schema.geometry.state === "known" && schema.geometry.primaryField.state === "known") {
491
+ return schema.geometry.primaryField.field;
492
+ }
493
+ semanticUnsupported("unsupported-source", `${path}.property`, "implicit spatial property requires a known primary geometry field");
494
+ }
495
+ function requireCqlSpatialClass(conformance, operator, geometryType, path) {
496
+ if (operator !== "intersects") {
497
+ requireCqlConformance(conformance, "cql2/1.0", "spatial-functions", path);
498
+ return;
499
+ }
500
+ requireCqlConformance(conformance, "cql2/1.0", geometryType === "Point" ? "basic-spatial-functions" : "basic-spatial-functions-plus", path);
501
+ }
502
+ function requireCqlConformance(conformance, standard, name, path) {
503
+ if (!hasConformanceClass(conformance.conformsTo, standard, name)) {
504
+ semanticUnsupported("unsupported-node", path, `${name} was not present in explicit discovered conformance`);
505
+ }
506
+ }
507
+ function compiledOgcFilterCrs(binding, conformance, path) {
508
+ const dimensions = binding.coordinateOrder.axes.length >= 3 ? 3 : 2;
509
+ const plan = ogcAxisPlan(binding, dimensions, `${path}.crs`);
510
+ if (isDefaultFilterCrs(plan.srsName, dimensions))
511
+ return undefined;
512
+ requireOgcPart2Crs(conformance, `${path}.crs`);
513
+ if (!conformance.supportedFilterCrs.includes(plan.srsName)) {
514
+ semanticUnsupported("unsupported-crs", `${path}.crs`, `filter CRS ${plan.srsName} was not present in discovered source metadata`);
515
+ }
516
+ return plan.srsName;
517
+ }
518
+ function compiledOgcOutputCrs(definition, conformance, path) {
519
+ const uri = ogcCrsUri(definition, path);
520
+ if (isDefaultOutputCrs(uri))
521
+ return undefined;
522
+ requireOgcPart2Crs(conformance, path);
523
+ if (!conformance.supportedOutputCrs.includes(uri)) {
524
+ semanticUnsupported("unsupported-crs", path, `output CRS ${uri} was not present in discovered source metadata`);
525
+ }
526
+ return uri;
527
+ }
528
+ function requireOgcPart2Crs(conformance, path) {
529
+ if (!hasConformanceClass(conformance.conformsTo, "ogcapi-features-2/1.0", "crs")) {
530
+ semanticUnsupported("unsupported-crs", path, "OGC API Features CRS parameters require explicitly discovered Part 2 CRS conformance");
531
+ }
532
+ }
533
+ function validateOgcSemanticProjection(query, schema) {
534
+ if (query.kind === "aggregate") {
535
+ semanticUnsupported("unsupported-node", "$.kind", "OGC API Features has no portable remote aggregation");
536
+ }
537
+ if (query.geometry === "omit") {
538
+ semanticUnsupported("unsupported-projection", "$.geometry", "OGC API Features has no portable geometry-suppression request parameter");
539
+ }
540
+ if (query.geometry && typeof query.geometry === "object") {
541
+ const primary = schema.geometry.state === "known" && schema.geometry.primaryField.state === "known"
542
+ ? schema.geometry.primaryField.field
543
+ : undefined;
544
+ if (query.geometry.field !== primary) {
545
+ semanticUnsupported("unsupported-projection", "$.geometry.field", "OGC API Features can return only the source primary GeoJSON geometry");
546
+ }
547
+ }
548
+ if (query.select && query.select.length > 0) {
549
+ semanticUnsupported("unsupported-projection", "$.select", "OGC API Features Core does not define a portable properties projection parameter");
550
+ }
551
+ }
552
+ function rejectOgcSemanticSort(query) {
553
+ if (query.sort && query.sort.length > 0) {
554
+ semanticUnsupported("unsupported-sort", "$.sort", "OGC API Features Core does not define a portable sortby parameter");
555
+ }
556
+ }
557
+ function assertCql2JsonGeometry(value, path) {
558
+ if (value.type !== "GeometryCollection")
559
+ return;
560
+ if (value.geometries.length < 2) {
561
+ semanticUnsupported("unsupported-geometry", `${path}.geometries`, "CQL2 JSON GeometryCollection requires at least two members");
562
+ }
563
+ value.geometries.forEach((entry, index) => {
564
+ if (entry.type === "GeometryCollection") {
565
+ semanticUnsupported("unsupported-geometry", `${path}.geometries[${index}]`, "CQL2 JSON 1.0 does not permit nested GeometryCollection members");
566
+ }
567
+ });
568
+ }
569
+ function isDefaultFilterCrs(uri, dimensions) {
570
+ return dimensions === 3
571
+ ? uri === "http://www.opengis.net/def/crs/OGC/0/CRS84h"
572
+ : uri === "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
573
+ }
574
+ function isDefaultOutputCrs(uri) {
575
+ return (uri === "http://www.opengis.net/def/crs/OGC/1.3/CRS84" || uri === "http://www.opengis.net/def/crs/OGC/0/CRS84h");
576
+ }
577
+ function hasControl(value) {
578
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: source identifiers cannot contain controls
579
+ return /[\u0000-\u001f\u007f]/.test(value);
580
+ }
581
+ function compileBbox(spatialFilter, sourceId) {
582
+ if (spatialFilter.geometryType !== "esriGeometryEnvelope") {
583
+ throw new HonuaQueryPlanningError("unsupported-query", `Source "${sourceId}" cannot compile spatial geometry "${spatialFilter.geometryType}" to OGC bbox`);
584
+ }
585
+ if (spatialFilter.spatialRel !== undefined &&
586
+ spatialFilter.spatialRel !== "esriSpatialRelIntersects" &&
587
+ spatialFilter.spatialRel !== "esriSpatialRelEnvelopeIntersects") {
588
+ throw new HonuaQueryPlanningError("unsupported-query", `Source "${sourceId}" cannot weaken spatial relationship "${spatialFilter.spatialRel}" to OGC envelope-intersects`);
589
+ }
590
+ const geometry = spatialFilter.geometry;
591
+ assertDefaultBboxCrs(geometry.spatialReference, sourceId);
592
+ const coordinates = [geometry.xmin, geometry.ymin, geometry.xmax, geometry.ymax];
593
+ if (!coordinates.every((value) => typeof value === "number" && Number.isFinite(value))) {
594
+ throw new HonuaQueryPlanningError("invalid-query", `Source "${sourceId}" requires finite xmin, ymin, xmax, and ymax values for OGC bbox`);
595
+ }
596
+ const [xmin, ymin, xmax, ymax] = coordinates;
597
+ if (xmin > xmax || ymin > ymax) {
598
+ throw new HonuaQueryPlanningError("invalid-query", `Source "${sourceId}" requires an ordered OGC bbox where xmin <= xmax and ymin <= ymax`);
599
+ }
600
+ return `${xmin},${ymin},${xmax},${ymax}`;
601
+ }
602
+ function assertDefaultBboxCrs(spatialReference, sourceId) {
603
+ if (spatialReference === undefined)
604
+ return;
605
+ if (spatialReference === null || Array.isArray(spatialReference) || typeof spatialReference !== "object") {
606
+ throw unsupportedBboxCrs(sourceId);
607
+ }
608
+ const reference = spatialReference;
609
+ const declaredWkids = [reference.wkid, reference.latestWkid].filter((value) => value !== undefined);
610
+ if (declaredWkids.length > 0 && declaredWkids.every((value) => value === 4326) && reference.wkt === undefined) {
611
+ return;
612
+ }
613
+ throw unsupportedBboxCrs(sourceId);
614
+ }
615
+ function unsupportedBboxCrs(sourceId) {
616
+ return new HonuaQueryPlanningError("unsupported-query", `Source "${sourceId}" can only compile an unstamped or EPSG:4326 envelope to the default OGC bbox CRS`);
617
+ }
@@ -0,0 +1,17 @@
1
+ import { type ExplainGeoParquetQueryOptions, type ExplainQueryOptions, type QueryExecutionPlan, type QueryExecutionPlanV1, type QueryExecutionPlanV2 } from "./types.js";
2
+ export declare function explainQuery<T>(options: ExplainGeoParquetQueryOptions<T>): QueryExecutionPlanV2;
3
+ export declare function explainQuery<T>(options: ExplainQueryOptions<T>): QueryExecutionPlanV1;
4
+ export declare function hashQueryPlan(plan: QueryExecutionPlan): `sha256:${string}`;
5
+ /** @internal Lightweight v1 integrity path for stable consumers that cannot execute v2 resources. */
6
+ export declare function hashQueryPlanV1(plan: QueryExecutionPlanV1): `sha256:${string}` | undefined;
7
+ /** Internal executor boundary: validate once and continue with an immutable detached snapshot. */
8
+ export declare function validateQueryPlanSnapshot(plan: QueryExecutionPlan): QueryExecutionPlan;
9
+ /** Canonical persistence boundary for accepted v1/v2 query plans. */
10
+ export declare function serializeQueryPlan(plan: QueryExecutionPlan): string;
11
+ /** Parse a persisted plan without retaining invalid JSON or credential-bearing v1 locators. */
12
+ export declare function parseQueryPlan(serialized: string): QueryExecutionPlan;
13
+ /** Stable cache identity derived only after the plan passes the persistence guard. */
14
+ export declare function queryPlanCacheKey(plan: QueryExecutionPlan): string;
15
+ /** Explicitly migrate a credential-free v1 GeoParquet plan to opaque v2 identity. */
16
+ export declare function migrateGeoParquetQueryPlanV1(legacyPlan: QueryExecutionPlanV1, resourceValue: unknown): QueryExecutionPlanV2;
17
+ //# sourceMappingURL=planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../../../src/query-planner/planner.ts"],"names":[],"mappings":"AAwBA,OAAO,EAIL,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EAUxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAM1B,MAAM,YAAY,CAAC;AAGpB,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,6BAA6B,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC;AACjG,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC;AAqMvF,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,UAAU,MAAM,EAAE,CAE1E;AAED,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB,GAAG,UAAU,MAAM,EAAE,GAAG,SAAS,CAkB1F;AAED,kGAAkG;AAClG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAItF;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAInE;AAED,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAWrE;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAIlE;AAED,qFAAqF;AACrF,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,oBAAoB,EAChC,aAAa,EAAE,OAAO,GACrB,oBAAoB,CAiCtB"}