@honua/react 0.1.0-beta.0 → 0.1.2-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,662 @@
1
+ import { validateExecutableCrsBinding } from "../contract/schema.js";
2
+ import { buildOdataSpatialFilter, rewriteWhereToOdataFilter } from "../core/odata.js";
3
+ import { hashSemanticQuery } from "./semantic-canonical.js";
4
+ import { prepareSemanticCompilerQuery, runSemanticCompiler, sameCrsDefinition, sameExecutableCrs, semanticSchemaField, semanticUnsupported, } from "./semantic-compiler.js";
5
+ import { exactDecimalText, finiteDecimalNumber, odataPropertyPath, quotedSemanticString, recordSemanticFieldMapping, semanticRequestFingerprint, sortSemanticFieldMappings, verifiedSemanticNativeText, verifiedSemanticSourceText, verifiedSemanticSourceVersion, } from "./semantic-literals.js";
6
+ import { HonuaQueryPlanningError } from "./types.js";
7
+ /** Compile canonical query IR to a deterministic OData v4 entity-set request. */
8
+ export function compileOdataQuery(source, query) {
9
+ if (source.protocol !== "odata") {
10
+ throw new HonuaQueryPlanningError("unsupported-compiler", `odata-v4-query-v1 does not compile protocol "${source.protocol}"`);
11
+ }
12
+ if (!source.entitySet) {
13
+ throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.entitySet for OData planning`);
14
+ }
15
+ if (query.aggregation) {
16
+ throw new HonuaQueryPlanningError("unsupported-query", "Canonical OData aggregation is not portable; use the typed $apply escape hatch");
17
+ }
18
+ if (query.outSr !== undefined) {
19
+ throw new HonuaQueryPlanningError("unsupported-query", "OData v4 has no portable output-CRS query option; omit outSr or use the typed OData escape hatch");
20
+ }
21
+ const filterParts = [];
22
+ try {
23
+ if (query.where) {
24
+ assertSupportedWhere(query.where.expression);
25
+ const rewritten = rewriteWhereToOdataFilter(query.where.expression);
26
+ if (rewritten)
27
+ filterParts.push(rewritten);
28
+ }
29
+ if (query.spatialFilter) {
30
+ if (!source.geometryProperty) {
31
+ throw new Error("descriptor schema does not identify the OData geometry column");
32
+ }
33
+ filterParts.push(buildOdataSpatialFilter({
34
+ geometry: query.spatialFilter.geometry,
35
+ geometryType: query.spatialFilter.geometryType,
36
+ ...(query.spatialFilter.spatialRel ? { spatialRel: query.spatialFilter.spatialRel } : {}),
37
+ }, { geometryColumn: source.geometryProperty }));
38
+ }
39
+ }
40
+ catch (error) {
41
+ throw new HonuaQueryPlanningError("unsupported-query", `OData query cannot preserve the requested predicate: ${error instanceof Error ? error.message : String(error)}`);
42
+ }
43
+ let projection;
44
+ if (query.outFields && query.outFields.length > 0) {
45
+ projection = splitProjection(query.outFields, query.returnGeometry === false ? source.geometryProperty : undefined);
46
+ if (query.returnGeometry !== false &&
47
+ source.geometryProperty &&
48
+ !projection.select.includes(source.geometryProperty)) {
49
+ projection = { ...projection, select: [...projection.select, source.geometryProperty] };
50
+ }
51
+ }
52
+ else if (query.returnGeometry === false) {
53
+ throw new HonuaQueryPlanningError("unsupported-query", "Metadata-free OData planning requires explicit outFields to prove returnGeometry=false on the wire");
54
+ }
55
+ return {
56
+ compiler: "odata-v4-query-v1",
57
+ entitySet: source.entitySet,
58
+ ...(filterParts.length > 0 ? { filter: filterParts.join(" and ") } : {}),
59
+ ...(projection && projection.select.length > 0 ? { select: projection.select } : {}),
60
+ ...(projection && projection.expand.length > 0 ? { expand: projection.expand } : {}),
61
+ ...(query.orderBy && query.orderBy.length > 0
62
+ ? { orderBy: query.orderBy.map((sort) => `${sort.field}${sort.direction === "desc" ? " desc" : ""}`) }
63
+ : {}),
64
+ ...(query.pagination?.offset !== undefined ? { skip: query.pagination.offset } : {}),
65
+ ...(query.pagination?.limit !== undefined ? { top: query.pagination.limit } : {}),
66
+ };
67
+ }
68
+ function assertSupportedWhere(where) {
69
+ const code = outsideQuotedStrings(where);
70
+ if (code === undefined)
71
+ throw new Error("unterminated OData string literal");
72
+ const unsupported = [
73
+ { expression: /\bLIKE\b/i, name: "LIKE" },
74
+ { expression: /\bBETWEEN\b/i, name: "BETWEEN" },
75
+ { expression: /==/, name: "==" },
76
+ { expression: /!=/, name: "!=" },
77
+ { expression: /;/, name: ";" },
78
+ ];
79
+ for (const candidate of unsupported) {
80
+ if (candidate.expression.test(code)) {
81
+ throw new Error(`operator ${candidate.name} is not translated by the OData compiler`);
82
+ }
83
+ }
84
+ }
85
+ /** Return only non-literal spans, or undefined for an unterminated literal. */
86
+ function outsideQuotedStrings(input) {
87
+ let output = "";
88
+ let quoted = false;
89
+ for (let index = 0; index < input.length; index += 1) {
90
+ const character = input[index];
91
+ if (character !== "'") {
92
+ if (!quoted)
93
+ output += character;
94
+ continue;
95
+ }
96
+ if (quoted && input[index + 1] === "'") {
97
+ index += 1;
98
+ continue;
99
+ }
100
+ quoted = !quoted;
101
+ }
102
+ return quoted ? undefined : output;
103
+ }
104
+ function splitProjection(fields, excludedGeometry) {
105
+ const root = { select: [], children: new Map() };
106
+ for (const field of fields) {
107
+ if (excludedGeometry && field === excludedGeometry)
108
+ continue;
109
+ const segments = field.split(".").filter(Boolean);
110
+ if (segments.length === 0)
111
+ continue;
112
+ let cursor = root;
113
+ for (let index = 0; index < segments.length - 1; index += 1) {
114
+ const segment = segments[index];
115
+ if (!segment)
116
+ continue;
117
+ let child = cursor.children.get(segment);
118
+ if (!child) {
119
+ child = { select: [], children: new Map() };
120
+ cursor.children.set(segment, child);
121
+ }
122
+ cursor = child;
123
+ }
124
+ const leaf = segments.at(-1);
125
+ if (leaf)
126
+ cursor.select.push(leaf);
127
+ }
128
+ const serialize = (node) => {
129
+ const parts = [];
130
+ if (node.select.length > 0)
131
+ parts.push(`$select=${node.select.join(",")}`);
132
+ if (node.children.size > 0) {
133
+ const children = [...node.children.entries()].map(([name, child]) => {
134
+ const inner = serialize(child);
135
+ return inner ? `${name}(${inner})` : name;
136
+ });
137
+ parts.push(`$expand=${children.join(",")}`);
138
+ }
139
+ return parts.join(";");
140
+ };
141
+ return {
142
+ select: root.select,
143
+ expand: [...root.children.entries()].map(([name, child]) => {
144
+ const inner = serialize(child);
145
+ return inner ? `${name}(${inner})` : name;
146
+ }),
147
+ };
148
+ }
149
+ const ODATA_COMPARISON_OPERATOR = {
150
+ eq: "eq",
151
+ ne: "ne",
152
+ lt: "lt",
153
+ lte: "le",
154
+ gt: "gt",
155
+ gte: "ge",
156
+ };
157
+ /** Compile a validated semantic AST to exact OData v4 system query options. */
158
+ export function compileSemanticOdataQuery(options) {
159
+ return runSemanticCompiler(() => {
160
+ const source = verifiedOdataSource(options.source);
161
+ const { query, schema } = prepareSemanticCompilerQuery(options.query, options.schema, "odata");
162
+ const state = { schema, source, fieldMappings: [], usesNativeFilter: false };
163
+ if (query.kind === "aggregate") {
164
+ semanticUnsupported("unsupported-node", "$.kind", "Portable OData v4 aggregation requires a separate typed $apply semantic model");
165
+ }
166
+ const projection = odataProjection(query, state);
167
+ const filter = query.filter
168
+ ? compileOdataSemanticFilter(query.filter, state, "$.filter")
169
+ : undefined;
170
+ const orderBy = odataOrderBy(query, state);
171
+ const page = odataPage(query);
172
+ const queryFingerprint = hashSemanticQuery(query, { schema, protocol: "odata" });
173
+ const request = {
174
+ entitySet: source.entitySet,
175
+ ...(source.sourceVersion ? { sourceVersion: source.sourceVersion } : {}),
176
+ ...(filter ? { filter } : {}),
177
+ ...projection,
178
+ ...(orderBy ? { orderBy } : {}),
179
+ ...page,
180
+ fieldMappings: sortSemanticFieldMappings(state.fieldMappings),
181
+ usesNativeFilter: state.usesNativeFilter,
182
+ };
183
+ const artifact = {
184
+ compiler: "odata-v4-semantic-query-v1",
185
+ dialect: "odata-4.0",
186
+ schemaFingerprint: schema.fingerprint,
187
+ queryFingerprint,
188
+ requestFingerprint: semanticRequestFingerprint("honua:query-request:odata-4.0:1", {
189
+ schemaFingerprint: schema.fingerprint,
190
+ queryFingerprint,
191
+ sourceVersion: source.sourceVersion ?? null,
192
+ request,
193
+ }),
194
+ ...request,
195
+ };
196
+ return { artifact };
197
+ });
198
+ }
199
+ function verifiedOdataSource(value) {
200
+ if (!value || typeof value !== "object") {
201
+ throw new HonuaQueryPlanningError("invalid-query", "Semantic OData source identity is invalid");
202
+ }
203
+ const entitySet = verifiedSemanticSourceText(value.entitySet, "options.source.entitySet");
204
+ try {
205
+ odataPropertyPath([entitySet], "options.source.entitySet");
206
+ }
207
+ catch {
208
+ throw new HonuaQueryPlanningError("invalid-query", "options.source.entitySet is invalid");
209
+ }
210
+ const sourceVersion = verifiedSemanticSourceVersion(value.sourceVersion, "options.source.sourceVersion");
211
+ const functions = value.supportedSpatialFunctions ?? [];
212
+ if (!Array.isArray(functions)) {
213
+ throw new HonuaQueryPlanningError("invalid-query", "options.source.supportedSpatialFunctions is invalid");
214
+ }
215
+ const allowed = new Set(["geo.intersects", "geo.distance"]);
216
+ const seen = new Set();
217
+ for (const name of functions) {
218
+ if (!allowed.has(name) || seen.has(name)) {
219
+ throw new HonuaQueryPlanningError("invalid-query", "options.source.supportedSpatialFunctions is invalid");
220
+ }
221
+ seen.add(name);
222
+ }
223
+ return {
224
+ entitySet,
225
+ ...(sourceVersion ? { sourceVersion } : {}),
226
+ supportedSpatialFunctions: [...functions].sort(),
227
+ };
228
+ }
229
+ function odataProjection(query, state) {
230
+ const primary = primaryOdataGeometryField(state.schema);
231
+ const geometryField = query.geometry && typeof query.geometry === "object"
232
+ ? query.geometry.field
233
+ : query.geometry === "omit"
234
+ ? undefined
235
+ : primary;
236
+ if (query.geometry === "include" && !primary) {
237
+ semanticUnsupported("unsupported-projection", "$.geometry", "OData geometry inclusion requires a known primary geometry property");
238
+ }
239
+ const requested = query.select ?? state.schema.fields.map((field) => field.name);
240
+ const select = [];
241
+ let outputGeometry;
242
+ requested.forEach((name, index) => {
243
+ const path = query.select ? `$.select[${index}]` : "$.schema.fields";
244
+ const field = semanticSchemaField(state.schema, name, path);
245
+ if (field.type.kind !== "geometry") {
246
+ select.push(odataRequestField(field, state, path));
247
+ return;
248
+ }
249
+ if (query.select && field.name !== geometryField) {
250
+ semanticUnsupported("unsupported-projection", path, "An OData geometry property selected as data must match the geometry projection");
251
+ }
252
+ if (field.name !== geometryField)
253
+ return;
254
+ outputGeometry = odataOutputGeometry(field, state, path);
255
+ select.push(outputGeometry.propertyPath);
256
+ });
257
+ if (geometryField && !outputGeometry) {
258
+ const field = semanticSchemaField(state.schema, geometryField, "$.geometry");
259
+ outputGeometry = odataOutputGeometry(field, state, "$.geometry");
260
+ if (!select.includes(outputGeometry.propertyPath))
261
+ select.push(outputGeometry.propertyPath);
262
+ }
263
+ if (query.outputCrs) {
264
+ if (!outputGeometry) {
265
+ semanticUnsupported("unsupported-projection", "$.outputCrs", "OData cannot apply an output CRS when geometry is omitted");
266
+ }
267
+ if (!sameCrsDefinition(outputGeometry.crs.definition, query.outputCrs)) {
268
+ semanticUnsupported("crs-transform-required", "$.outputCrs", "OData v4 has no portable output geometry transform option");
269
+ }
270
+ }
271
+ if (select.length === 0) {
272
+ semanticUnsupported("unsupported-projection", "$.select", "OData requires at least one selected property");
273
+ }
274
+ return { select, ...(outputGeometry ? { outputGeometry } : {}) };
275
+ }
276
+ function odataOutputGeometry(field, state, path) {
277
+ const propertyPath = odataRequestField(field, state, path);
278
+ const metadata = sourceOdataGeometryField(state.schema, field.name, path);
279
+ const crs = executableOdataCrs(metadata.crs, path);
280
+ if (metadata.layout !== "xy") {
281
+ semanticUnsupported("unsupported-geometry", path, "OData geometry output requires explicit xy layout metadata");
282
+ }
283
+ const spatialType = odataSpatialType(field, metadata, path);
284
+ assertOdataSpatialCategoryCrs(spatialType, crs, path);
285
+ return {
286
+ field: field.name,
287
+ propertyPath,
288
+ spatialType,
289
+ crs,
290
+ layout: "xy",
291
+ };
292
+ }
293
+ function odataOrderBy(query, state) {
294
+ if (!query.sort || query.sort.length === 0)
295
+ return undefined;
296
+ return query.sort.map((sort, index) => {
297
+ if (sort.nulls && sort.nulls !== "native") {
298
+ semanticUnsupported("unsupported-sort", `$.sort[${index}].nulls`, "OData v4 does not expose portable explicit null ordering");
299
+ }
300
+ const path = `$.sort[${index}].field`;
301
+ const field = semanticSchemaField(state.schema, sort.field, path);
302
+ return `${odataRequestField(field, state, path)} ${sort.direction}`;
303
+ });
304
+ }
305
+ function odataPage(query) {
306
+ if (!query.page)
307
+ return {};
308
+ if (query.page.limit !== undefined && query.page.limit > 2_147_483_647) {
309
+ semanticUnsupported("unsupported-node", "$.page.limit", "OData $top is int32-bounded");
310
+ }
311
+ if (query.page.kind === "offset" && query.page.offset > 2_147_483_647) {
312
+ semanticUnsupported("unsupported-node", "$.page.offset", "OData $skip is int32-bounded");
313
+ }
314
+ return {
315
+ ...(query.page.kind === "offset" ? { skip: query.page.offset } : {}),
316
+ ...(query.page.limit !== undefined ? { top: query.page.limit } : {}),
317
+ };
318
+ }
319
+ function compileOdataSemanticFilter(filter, state, path) {
320
+ switch (filter.kind) {
321
+ case "comparison": {
322
+ const field = semanticSchemaField(state.schema, filter.left.name, `${path}.left.name`);
323
+ return `${odataRequestField(field, state, `${path}.left.name`)} ${ODATA_COMPARISON_OPERATOR[filter.operator]} ${odataLiteral(filter.right.value, field, `${path}.right.value`)}`;
324
+ }
325
+ case "list": {
326
+ const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
327
+ const name = odataRequestField(field, state, `${path}.operand.name`);
328
+ return filter.values
329
+ .map((literal, index) => `(${name} eq ${odataLiteral(literal.value, field, `${path}.values[${index}].value`)})`)
330
+ .join(" or ");
331
+ }
332
+ case "range": {
333
+ const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
334
+ const name = odataRequestField(field, state, `${path}.operand.name`);
335
+ return `(${name} ge ${odataLiteral(filter.lower.value, field, `${path}.lower.value`)}) and (${name} le ${odataLiteral(filter.upper.value, field, `${path}.upper.value`)})`;
336
+ }
337
+ case "null": {
338
+ const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
339
+ return `${odataRequestField(field, state, `${path}.operand.name`)} ${filter.operator === "is-not-null" ? "ne" : "eq"} null`;
340
+ }
341
+ case "pattern":
342
+ return compileOdataPattern(filter, state, path);
343
+ case "boolean":
344
+ return filter.args
345
+ .map((entry, index) => odataParenthesized(compileOdataSemanticFilter(entry, state, `${path}.args[${index}]`)))
346
+ .join(filter.operator === "and" ? " and " : " or ");
347
+ case "not":
348
+ return `not ${odataParenthesized(compileOdataSemanticFilter(filter.arg, state, `${path}.arg`))}`;
349
+ case "temporal": {
350
+ if (filter.operator === "during" || filter.operator === "time-intersects") {
351
+ semanticUnsupported("unsupported-node", `${path}.operator`, `OData v4 cannot represent the OGC ${filter.operator} temporal-topology predicate exactly`);
352
+ }
353
+ const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
354
+ const name = odataRequestField(field, state, `${path}.operand.name`);
355
+ return `${name} ${filter.operator === "before" ? "lt" : "gt"} ${odataLiteral(filter.value.value, field, `${path}.value.value`)}`;
356
+ }
357
+ case "spatial":
358
+ return compileOdataSpatial(filter, state, path);
359
+ case "native": {
360
+ if (filter.dialect !== "odata-4.0" || filter.payload.format !== "text") {
361
+ semanticUnsupported("unsupported-native-filter", path, "OData native filters require odata-4.0 text");
362
+ }
363
+ state.usesNativeFilter = true;
364
+ return verifiedSemanticNativeText(filter.payload.text, `${path}.payload.text`, "OData 4.0");
365
+ }
366
+ }
367
+ }
368
+ function compileOdataPattern(filter, state, path) {
369
+ if (filter.caseSensitive === false) {
370
+ semanticUnsupported("unsupported-node", `${path}.caseSensitive`, "OData v4 cannot prove case-insensitive pattern semantics across services");
371
+ }
372
+ const field = semanticSchemaField(state.schema, filter.operand.name, `${path}.operand.name`);
373
+ const name = odataRequestField(field, state, `${path}.operand.name`);
374
+ const pattern = filter.pattern;
375
+ if (pattern.includes("_")) {
376
+ semanticUnsupported("unsupported-node", `${path}.pattern`, "OData string functions cannot represent the LIKE single-character wildcard");
377
+ }
378
+ const percentCount = [...pattern].filter((character) => character === "%").length;
379
+ if (percentCount === 0)
380
+ return `${name} eq ${quotedSemanticString(pattern, `${path}.pattern`)}`;
381
+ if (pattern === "%") {
382
+ semanticUnsupported("unsupported-node", `${path}.pattern`, "An always-true LIKE pattern is not emitted");
383
+ }
384
+ const starts = pattern.startsWith("%");
385
+ const ends = pattern.endsWith("%");
386
+ if (percentCount !== Number(starts) + Number(ends)) {
387
+ semanticUnsupported("unsupported-node", `${path}.pattern`, "OData string functions cannot represent an interior LIKE wildcard exactly");
388
+ }
389
+ const value = pattern.slice(starts ? 1 : 0, ends ? -1 : undefined);
390
+ if (value.length === 0 && starts && ends) {
391
+ semanticUnsupported("unsupported-node", `${path}.pattern`, "An always-true LIKE pattern is not emitted");
392
+ }
393
+ const literal = quotedSemanticString(value, `${path}.pattern`);
394
+ if (starts && ends)
395
+ return `contains(${name},${literal})`;
396
+ if (starts)
397
+ return `endswith(${name},${literal})`;
398
+ return `startswith(${name},${literal})`;
399
+ }
400
+ function odataLiteral(value, field, path) {
401
+ switch (field.type.kind) {
402
+ case "boolean":
403
+ return value ? "true" : "false";
404
+ case "integer":
405
+ case "decimal":
406
+ return typeof value === "number" ? finiteDecimalNumber(value, path) : exactDecimalText(value, path);
407
+ case "float":
408
+ return finiteDecimalNumber(value, path);
409
+ case "string":
410
+ return quotedSemanticString(value, path);
411
+ case "uuid":
412
+ case "date":
413
+ case "time":
414
+ return value;
415
+ case "timestamp":
416
+ if (/:60(?:[.Z+-]|$)/.test(value)) {
417
+ semanticUnsupported("unsupported-field-type", path, "OData Edm.DateTimeOffset does not support leap seconds");
418
+ }
419
+ return value;
420
+ case "duration":
421
+ return `duration${quotedSemanticString(value, path)}`;
422
+ case "union":
423
+ return odataUnionLiteral(value, field, path);
424
+ default:
425
+ semanticUnsupported("unsupported-field-type", path, `OData v4 cannot represent ${field.type.kind} literals exactly in $filter`);
426
+ }
427
+ }
428
+ function odataUnionLiteral(value, field, path) {
429
+ const specialFloat = field.type.kind === "union" &&
430
+ field.type.members.length === 2 &&
431
+ field.type.members.some((member) => member.kind === "float") &&
432
+ field.type.members.some((member) => member.kind === "string" && member.encoding === "odata-special-float");
433
+ const nativeFloat = field.native.some((reference) => reference.protocol === "odata" && (reference.name === "Edm.Single" || reference.name === "Edm.Double"));
434
+ if (!specialFloat || !nativeFloat) {
435
+ semanticUnsupported("unsupported-field-type", path, "OData union literal metadata is ambiguous");
436
+ }
437
+ if (typeof value === "number")
438
+ return finiteDecimalNumber(value, path);
439
+ if (value === "NaN" || value === "INF" || value === "-INF")
440
+ return value;
441
+ semanticUnsupported("unsupported-field-type", path, "OData special floats require NaN, INF, or -INF");
442
+ }
443
+ function compileOdataSpatial(filter, state, path) {
444
+ if (filter.operator === "within-distance" || filter.operator === "beyond-distance") {
445
+ semanticUnsupported("unsupported-distance", `${path}.distance`, "OData geo.distance cannot preserve semantic planar/geodesic mode and portable units");
446
+ }
447
+ if (filter.operator !== "intersects" && filter.operator !== "bbox-intersects") {
448
+ semanticUnsupported("unsupported-node", `${path}.operator`, `OData v4 has no portable function for exact ${filter.operator} semantics`);
449
+ }
450
+ if (!state.source.supportedSpatialFunctions.includes("geo.intersects")) {
451
+ semanticUnsupported("unsupported-source", `${path}.operator`, "OData source evidence does not explicitly advertise geo.intersects");
452
+ }
453
+ const fieldName = filter.property?.name ?? primaryOdataGeometryField(state.schema);
454
+ if (!fieldName) {
455
+ semanticUnsupported("unsupported-geometry", `${path}.property`, "OData requires an explicit geometry property");
456
+ }
457
+ const field = semanticSchemaField(state.schema, fieldName, `${path}.property.name`);
458
+ const propertyPath = odataRequestField(field, state, `${path}.property.name`);
459
+ const metadata = sourceOdataGeometryField(state.schema, fieldName, `${path}.property`);
460
+ const propertySpatial = odataFilterSpatialType(field, metadata, `${path}.property.name`);
461
+ const sourceCrs = executableOdataCrs(metadata.crs, `${path}.property`);
462
+ assertOdataSpatialCategoryCrs(propertySpatial.spatialType, sourceCrs, `${path}.property`);
463
+ if (metadata.layout !== "xy") {
464
+ semanticUnsupported("unsupported-geometry", `${path}.property`, "OData spatial filtering requires explicit xy geometry-property layout metadata");
465
+ }
466
+ if (filter.operator === "bbox-intersects") {
467
+ if (propertySpatial.geometryKind !== "Point") {
468
+ semanticUnsupported("unsupported-geometry", `${path}.property.name`, "OData bbox intersection requires an exact Point property for the Point,Polygon signature");
469
+ }
470
+ const literal = odataBboxLiteral(filter.bbox, sourceCrs, propertySpatial.spatialType, path);
471
+ return `geo.intersects(${propertyPath},${literal})`;
472
+ }
473
+ const literalKind = filter.geometry.geometry.type;
474
+ if (propertySpatial.geometryKind === "Point" && literalKind === "Polygon") {
475
+ const literal = odataGeometryLiteral(filter.geometry, sourceCrs, propertySpatial.spatialType, path);
476
+ return `geo.intersects(${propertyPath},${literal})`;
477
+ }
478
+ if (propertySpatial.geometryKind === "Polygon" && literalKind === "Point") {
479
+ const literal = odataGeometryLiteral(filter.geometry, sourceCrs, propertySpatial.spatialType, path);
480
+ return `geo.intersects(${literal},${propertyPath})`;
481
+ }
482
+ semanticUnsupported("unsupported-geometry", `${path}.geometry.geometry`, `OData geo.intersects requires Point,Polygon operands; property is ${propertySpatial.geometryKind} and literal is ${literalKind}`);
483
+ }
484
+ function odataGeometryLiteral(value, sourceCrs, spatialType, path) {
485
+ if (value.layout !== "xy") {
486
+ semanticUnsupported("unsupported-geometry", `${path}.geometry.layout`, "OData v4 spatial literals require an xy coordinate layout");
487
+ }
488
+ assertOdataEastNorth(value.crs, `${path}.geometry.crs`);
489
+ if (!sameExecutableCrs(sourceCrs, value.crs)) {
490
+ semanticUnsupported("crs-transform-required", `${path}.geometry.crs`, "Spatial literal CRS differs from the OData geometry property CRS");
491
+ }
492
+ return odataSpatialLiteral(spatialType, value.crs, odataWkt(value.geometry, path), `${path}.geometry.crs`);
493
+ }
494
+ function odataBboxLiteral(value, sourceCrs, spatialType, path) {
495
+ if (value.box.layout !== "xy") {
496
+ semanticUnsupported("unsupported-geometry", `${path}.bbox.box.layout`, "OData v4 bounding-box literals require an xy layout");
497
+ }
498
+ assertOdataEastNorth(value.crs, `${path}.bbox.crs`);
499
+ if (!sameExecutableCrs(sourceCrs, value.crs)) {
500
+ semanticUnsupported("crs-transform-required", `${path}.bbox.crs`, "Bounding-box CRS differs from the OData geometry property CRS");
501
+ }
502
+ const [xmin, ymin, xmax, ymax] = value.box.bounds;
503
+ const wkt = `POLYGON ((${wktPosition([xmin, ymin])}, ${wktPosition([xmax, ymin])}, ${wktPosition([
504
+ xmax,
505
+ ymax,
506
+ ])}, ${wktPosition([xmin, ymax])}, ${wktPosition([xmin, ymin])}))`;
507
+ return odataSpatialLiteral(spatialType, value.crs, wkt, `${path}.bbox.crs`);
508
+ }
509
+ function odataSpatialLiteral(spatialType, crs, wkt, path) {
510
+ const srid = odataSrid(crs, path);
511
+ return `${spatialType}'SRID=${srid};${wkt}'`;
512
+ }
513
+ function odataWkt(geometry, path) {
514
+ switch (geometry.type) {
515
+ case "Point":
516
+ return `POINT (${wktPosition(geometry.coordinates)})`;
517
+ case "MultiPoint":
518
+ return `MULTIPOINT (${geometry.coordinates.map((position) => `(${wktPosition(position)})`).join(", ")})`;
519
+ case "LineString":
520
+ return `LINESTRING (${wktLine(geometry.coordinates)})`;
521
+ case "MultiLineString":
522
+ return `MULTILINESTRING (${geometry.coordinates.map((line) => `(${wktLine(line)})`).join(", ")})`;
523
+ case "Polygon":
524
+ return `POLYGON (${geometry.coordinates.map((ring) => `(${wktLine(ring)})`).join(", ")})`;
525
+ case "MultiPolygon":
526
+ return `MULTIPOLYGON (${geometry.coordinates
527
+ .map((polygon) => `(${polygon.map((ring) => `(${wktLine(ring)})`).join(", ")})`)
528
+ .join(", ")})`;
529
+ case "GeometryCollection":
530
+ semanticUnsupported("unsupported-geometry", `${path}.geometry.geometry`, "OData v4 spatial literals do not compile GeometryCollection in this exact slice");
531
+ }
532
+ }
533
+ function wktLine(positions) {
534
+ return positions.map(wktPosition).join(", ");
535
+ }
536
+ function wktPosition(position) {
537
+ return `${finiteDecimalNumber(position[0], "$.geometry.coordinates")} ${finiteDecimalNumber(position[1], "$.geometry.coordinates")}`;
538
+ }
539
+ function executableOdataCrs(value, path) {
540
+ let crs;
541
+ try {
542
+ crs = validateExecutableCrsBinding(value, path);
543
+ }
544
+ catch {
545
+ semanticUnsupported("unsupported-crs", path, "OData requires a resolved executable geometry-property CRS");
546
+ }
547
+ if (crs.coordinateEpoch !== undefined) {
548
+ semanticUnsupported("unsupported-crs", path, "OData spatial literals cannot preserve coordinate epochs");
549
+ }
550
+ assertOdataEastNorth(crs, path);
551
+ odataSrid(crs, path);
552
+ return crs;
553
+ }
554
+ function odataSrid(value, path) {
555
+ const definition = value.definition;
556
+ if (definition.kind === "authority" &&
557
+ definition.authority.toUpperCase() === "EPSG" &&
558
+ /^\d+$/.test(definition.code)) {
559
+ const srid = Number(definition.code);
560
+ if (Number.isSafeInteger(srid) && srid > 0 && srid <= 2_147_483_647)
561
+ return srid;
562
+ }
563
+ semanticUnsupported("unsupported-crs", path, "OData spatial literals require a positive EPSG SRID");
564
+ }
565
+ function assertOdataEastNorth(value, path) {
566
+ const [x, y] = value.coordinateOrder.axes;
567
+ if (x?.direction !== "east" || y?.direction !== "north") {
568
+ semanticUnsupported("unsupported-crs", path, "OData spatial literals require east/north x/y payload axes");
569
+ }
570
+ }
571
+ const ODATA_SPATIAL_NATIVE_TYPES = {
572
+ "Edm.Geography": { spatialType: "geography" },
573
+ "Edm.GeographyPoint": { spatialType: "geography", geometryKind: "Point" },
574
+ "Edm.GeographyLineString": { spatialType: "geography", geometryKind: "LineString" },
575
+ "Edm.GeographyPolygon": { spatialType: "geography", geometryKind: "Polygon" },
576
+ "Edm.GeographyMultiPoint": { spatialType: "geography", geometryKind: "MultiPoint" },
577
+ "Edm.GeographyMultiLineString": { spatialType: "geography", geometryKind: "MultiLineString" },
578
+ "Edm.GeographyMultiPolygon": { spatialType: "geography", geometryKind: "MultiPolygon" },
579
+ "Edm.GeographyCollection": { spatialType: "geography", geometryKind: "GeometryCollection" },
580
+ "Edm.Geometry": { spatialType: "geometry" },
581
+ "Edm.GeometryPoint": { spatialType: "geometry", geometryKind: "Point" },
582
+ "Edm.GeometryLineString": { spatialType: "geometry", geometryKind: "LineString" },
583
+ "Edm.GeometryPolygon": { spatialType: "geometry", geometryKind: "Polygon" },
584
+ "Edm.GeometryMultiPoint": { spatialType: "geometry", geometryKind: "MultiPoint" },
585
+ "Edm.GeometryMultiLineString": { spatialType: "geometry", geometryKind: "MultiLineString" },
586
+ "Edm.GeometryMultiPolygon": { spatialType: "geometry", geometryKind: "MultiPolygon" },
587
+ "Edm.GeometryCollection": { spatialType: "geometry", geometryKind: "GeometryCollection" },
588
+ };
589
+ function odataSpatialType(field, metadata, path) {
590
+ return odataSpatialTypeEvidence(field, metadata, path).spatialType;
591
+ }
592
+ function odataFilterSpatialType(field, metadata, path) {
593
+ const evidence = odataSpatialTypeEvidence(field, metadata, path);
594
+ if (evidence.geometryKind !== "Point" && evidence.geometryKind !== "Polygon") {
595
+ semanticUnsupported("unsupported-source", path, "OData geo.intersects requires an exact Edm Point or Polygon property type");
596
+ }
597
+ if (metadata.geometryTypes.state !== "known" || metadata.geometryTypes.type !== evidence.geometryKind) {
598
+ semanticUnsupported("unsupported-source", path, "OData geo.intersects requires matching, single-kind SourceSchemaV2 geometry evidence");
599
+ }
600
+ return { spatialType: evidence.spatialType, geometryKind: evidence.geometryKind };
601
+ }
602
+ function odataSpatialTypeEvidence(field, metadata, path) {
603
+ const references = field.native.filter((reference) => reference.protocol === "odata");
604
+ if (references.length !== 1 || !Object.hasOwn(ODATA_SPATIAL_NATIVE_TYPES, references[0]?.name)) {
605
+ semanticUnsupported("unsupported-source", path, "OData geometry properties require one exact OData v4 Edm.Geography or Edm.Geometry primitive type");
606
+ }
607
+ const type = references[0]?.name;
608
+ const evidence = ODATA_SPATIAL_NATIVE_TYPES[type];
609
+ if (!evidence) {
610
+ semanticUnsupported("unsupported-source", path, "OData spatial native type evidence is unavailable");
611
+ }
612
+ const geometryKind = evidence.geometryKind;
613
+ if (geometryKind && (metadata.geometryTypes.state !== "known" || metadata.geometryTypes.type !== geometryKind)) {
614
+ semanticUnsupported("unsupported-source", path, "OData native spatial primitive requires matching, single-kind SourceSchemaV2 geometry evidence");
615
+ }
616
+ return evidence;
617
+ }
618
+ const ODATA_ANGULAR_AXIS_UNITS = new Set(["degree", "radian"]);
619
+ const ODATA_LINEAR_AXIS_UNITS = new Set([
620
+ "metre",
621
+ "meter",
622
+ "kilometre",
623
+ "kilometer",
624
+ "foot",
625
+ "us-survey-foot",
626
+ "mile",
627
+ "nautical-mile",
628
+ "yard",
629
+ ]);
630
+ function assertOdataSpatialCategoryCrs(spatialType, crs, path) {
631
+ const units = crs.coordinateOrder.axes.slice(0, 2).map((axis) => axis.unit?.toLowerCase());
632
+ const category = units.every((unit) => unit !== undefined && ODATA_ANGULAR_AXIS_UNITS.has(unit))
633
+ ? "geography"
634
+ : units.every((unit) => unit !== undefined && ODATA_LINEAR_AXIS_UNITS.has(unit))
635
+ ? "geometry"
636
+ : undefined;
637
+ if (category !== spatialType) {
638
+ semanticUnsupported("unsupported-crs", path, `OData ${spatialType} requires ${spatialType === "geography" ? "angular" : "linear"} horizontal CRS axes`);
639
+ }
640
+ }
641
+ function sourceOdataGeometryField(schema, name, path) {
642
+ if (schema.geometry.state !== "known") {
643
+ semanticUnsupported("unsupported-source", path, "OData source geometry metadata is unavailable");
644
+ }
645
+ const field = schema.geometry.fields.find((candidate) => candidate.field === name);
646
+ if (!field)
647
+ semanticUnsupported("unsupported-source", path, "OData geometry-property metadata is unavailable");
648
+ return field;
649
+ }
650
+ function primaryOdataGeometryField(schema) {
651
+ return schema.geometry.state === "known" && schema.geometry.primaryField.state === "known"
652
+ ? schema.geometry.primaryField.field
653
+ : undefined;
654
+ }
655
+ function odataRequestField(field, state, path) {
656
+ const requestField = odataPropertyPath(field.path, path);
657
+ recordSemanticFieldMapping(state.fieldMappings, field, requestField);
658
+ return requestField;
659
+ }
660
+ function odataParenthesized(value) {
661
+ return `(${value})`;
662
+ }