@honua/react 0.1.2-beta.0 → 0.1.7-beta.0

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