@honua/react 0.1.0-beta.0 → 0.1.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/contract/discovery.d.ts +231 -0
  2. package/contract/discovery.d.ts.map +1 -0
  3. package/contract/discovery.js +554 -0
  4. package/contract/edit-sketch.d.ts +8 -0
  5. package/contract/edit-sketch.d.ts.map +1 -1
  6. package/contract/edit-sketch.js +15 -0
  7. package/contract/edit-snapping.d.ts +160 -0
  8. package/contract/edit-snapping.d.ts.map +1 -0
  9. package/contract/edit-snapping.js +675 -0
  10. package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
  11. package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
  12. package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
  13. package/contract/index.d.ts +11 -2
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -1
  16. package/contract/pmtiles.d.ts +31 -0
  17. package/contract/pmtiles.d.ts.map +1 -1
  18. package/contract/pmtiles.js +60 -20
  19. package/contract/protocol-module.d.ts +70 -0
  20. package/contract/protocol-module.d.ts.map +1 -0
  21. package/contract/protocol-module.js +1 -0
  22. package/contract/schema-envelope.d.ts +26 -0
  23. package/contract/schema-envelope.d.ts.map +1 -0
  24. package/contract/schema-envelope.js +25 -0
  25. package/contract/schema.d.ts +582 -0
  26. package/contract/schema.d.ts.map +1 -0
  27. package/contract/schema.js +2483 -0
  28. package/contract/source-capability-support.d.ts +6 -0
  29. package/contract/source-capability-support.d.ts.map +1 -0
  30. package/contract/source-capability-support.js +154 -0
  31. package/contract/source.d.ts +35 -17
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +267 -71
  34. package/contract/types.d.ts +140 -4
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +6 -14
  37. package/core/bounded-json.d.ts +4 -0
  38. package/core/bounded-json.d.ts.map +1 -0
  39. package/core/bounded-json.js +38 -0
  40. package/core/cache-state.d.ts +9 -0
  41. package/core/cache-state.d.ts.map +1 -1
  42. package/core/cache-state.js +10 -0
  43. package/core/capabilities-xml.d.ts +33 -0
  44. package/core/capabilities-xml.d.ts.map +1 -0
  45. package/core/capabilities-xml.js +331 -0
  46. package/core/client.d.ts +4 -1
  47. package/core/client.d.ts.map +1 -1
  48. package/core/client.js +115 -29
  49. package/core/error-classifications.d.ts +153 -0
  50. package/core/error-classifications.d.ts.map +1 -0
  51. package/core/error-classifications.js +140 -0
  52. package/core/error-code-registry.d.ts +158 -0
  53. package/core/error-code-registry.d.ts.map +1 -0
  54. package/core/error-code-registry.js +143 -0
  55. package/core/error-envelope.d.ts +68 -0
  56. package/core/error-envelope.d.ts.map +1 -0
  57. package/core/error-envelope.js +357 -0
  58. package/core/errors.d.ts +45 -29
  59. package/core/errors.d.ts.map +1 -1
  60. package/core/errors.js +97 -50
  61. package/core/geoparquet-sql.d.ts +30 -2
  62. package/core/geoparquet-sql.d.ts.map +1 -1
  63. package/core/geoparquet-sql.js +38 -3
  64. package/core/odata-key-path.d.ts +12 -0
  65. package/core/odata-key-path.d.ts.map +1 -0
  66. package/core/odata-key-path.js +28 -0
  67. package/core/odata-write-codec.d.ts +66 -0
  68. package/core/odata-write-codec.d.ts.map +1 -0
  69. package/core/odata-write-codec.js +1083 -0
  70. package/core/odata.d.ts +59 -7
  71. package/core/odata.d.ts.map +1 -1
  72. package/core/odata.js +431 -99
  73. package/core/ogc-features.d.ts.map +1 -1
  74. package/core/ogc-features.js +3 -0
  75. package/core/ogc-maps.d.ts +7 -0
  76. package/core/ogc-maps.d.ts.map +1 -1
  77. package/core/ogc-maps.js +46 -7
  78. package/core/ogc-processes.d.ts +2 -1
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +33 -5
  81. package/core/ogc-records.d.ts +9 -0
  82. package/core/ogc-records.d.ts.map +1 -1
  83. package/core/ogc-records.js +78 -29
  84. package/core/ogc-tiles.d.ts +10 -1
  85. package/core/ogc-tiles.d.ts.map +1 -1
  86. package/core/ogc-tiles.js +56 -17
  87. package/core/path-utils.d.ts +9 -0
  88. package/core/path-utils.d.ts.map +1 -1
  89. package/core/path-utils.js +19 -0
  90. package/core/protocol-transport.d.ts +1 -0
  91. package/core/protocol-transport.d.ts.map +1 -1
  92. package/core/request-pipeline.d.ts.map +1 -1
  93. package/core/request-pipeline.js +1 -1
  94. package/core/spatial-filter.d.ts +8 -4
  95. package/core/spatial-filter.d.ts.map +1 -1
  96. package/core/spatial-filter.js +206 -16
  97. package/core/stac-static.d.ts +6 -2
  98. package/core/stac-static.d.ts.map +1 -1
  99. package/core/stac-static.js +260 -69
  100. package/core/surfaces.d.ts.map +1 -1
  101. package/core/surfaces.js +69 -5
  102. package/core/types.d.ts +125 -7
  103. package/core/types.d.ts.map +1 -1
  104. package/core/wfs-capabilities.d.ts +2 -0
  105. package/core/wfs-capabilities.d.ts.map +1 -1
  106. package/core/wfs-capabilities.js +8 -1
  107. package/core/wfs.d.ts.map +1 -1
  108. package/core/wfs.js +10 -0
  109. package/core/wms-capabilities.d.ts +56 -84
  110. package/core/wms-capabilities.d.ts.map +1 -1
  111. package/core/wms-capabilities.js +212 -509
  112. package/core/wmts-capabilities.d.ts +62 -62
  113. package/core/wmts-capabilities.d.ts.map +1 -1
  114. package/core/wmts-capabilities.js +224 -371
  115. package/esri-compat/editor.d.ts +8 -0
  116. package/esri-compat/editor.d.ts.map +1 -1
  117. package/esri-compat/editor.js +13 -0
  118. package/esri-compat/feature-layer.d.ts +2 -0
  119. package/esri-compat/feature-layer.d.ts.map +1 -1
  120. package/esri-compat/feature-layer.js +1 -0
  121. package/esri-compat/layer-list.d.ts +11 -0
  122. package/esri-compat/layer-list.d.ts.map +1 -1
  123. package/esri-compat/layer-list.js +49 -0
  124. package/esri-compat/legend.d.ts +10 -0
  125. package/esri-compat/legend.d.ts.map +1 -1
  126. package/esri-compat/legend.js +27 -0
  127. package/esri-compat/portal.d.ts +197 -0
  128. package/esri-compat/portal.d.ts.map +1 -0
  129. package/esri-compat/portal.js +251 -0
  130. package/esri-compat/renderer-objects.d.ts +40 -0
  131. package/esri-compat/renderer-objects.d.ts.map +1 -0
  132. package/esri-compat/renderer-objects.js +62 -0
  133. package/esri-compat/sketch.d.ts +43 -0
  134. package/esri-compat/sketch.d.ts.map +1 -1
  135. package/esri-compat/sketch.js +46 -0
  136. package/esri-compat/snapping.d.ts +43 -0
  137. package/esri-compat/snapping.d.ts.map +1 -0
  138. package/esri-compat/snapping.js +45 -0
  139. package/esri-compat/url.d.ts.map +1 -1
  140. package/esri-compat/url.js +1 -1
  141. package/esri-compat/widget-host.d.ts +84 -0
  142. package/esri-compat/widget-host.d.ts.map +1 -0
  143. package/esri-compat/widget-host.js +159 -0
  144. package/filter-registry/index.d.ts +145 -0
  145. package/filter-registry/index.d.ts.map +1 -0
  146. package/filter-registry/index.js +531 -0
  147. package/generated-app/errors.d.ts +18 -2
  148. package/generated-app/errors.d.ts.map +1 -1
  149. package/generated-app/errors.js +44 -11
  150. package/map/automatic-mount-integration.d.ts +134 -0
  151. package/map/automatic-mount-integration.d.ts.map +1 -0
  152. package/map/automatic-mount-integration.js +305 -0
  153. package/map/automatic-source-strategy.d.ts +125 -0
  154. package/map/automatic-source-strategy.d.ts.map +1 -0
  155. package/map/automatic-source-strategy.js +494 -0
  156. package/map/data-to-map-bridge.d.ts +293 -0
  157. package/map/data-to-map-bridge.d.ts.map +1 -0
  158. package/map/data-to-map-bridge.js +817 -0
  159. package/map/geojson-projection.d.ts +33 -0
  160. package/map/geojson-projection.d.ts.map +1 -0
  161. package/map/geojson-projection.js +118 -0
  162. package/map/index.d.ts +14 -0
  163. package/map/index.d.ts.map +1 -1
  164. package/map/index.js +8 -0
  165. package/map/raster-source-spec.d.ts +30 -0
  166. package/map/raster-source-spec.d.ts.map +1 -0
  167. package/map/raster-source-spec.js +190 -0
  168. package/map/raster-source-strategy.d.ts +65 -0
  169. package/map/raster-source-strategy.d.ts.map +1 -0
  170. package/map/raster-source-strategy.js +232 -0
  171. package/map/source-to-maplibre.d.ts +91 -0
  172. package/map/source-to-maplibre.d.ts.map +1 -0
  173. package/map/source-to-maplibre.js +454 -0
  174. package/map/temporal-playback.d.ts +139 -0
  175. package/map/temporal-playback.d.ts.map +1 -0
  176. package/map/temporal-playback.js +247 -0
  177. package/package.json +14 -1
  178. package/query-planner/canonical.d.ts +7 -0
  179. package/query-planner/canonical.d.ts.map +1 -0
  180. package/query-planner/canonical.js +145 -0
  181. package/query-planner/cql2-json.d.ts +17 -0
  182. package/query-planner/cql2-json.d.ts.map +1 -0
  183. package/query-planner/cql2-json.js +646 -0
  184. package/query-planner/diagnostics.d.ts +35 -0
  185. package/query-planner/diagnostics.d.ts.map +1 -0
  186. package/query-planner/diagnostics.js +373 -0
  187. package/query-planner/duckdb.d.ts +79 -0
  188. package/query-planner/duckdb.d.ts.map +1 -0
  189. package/query-planner/duckdb.js +758 -0
  190. package/query-planner/execution-context.d.ts +5 -0
  191. package/query-planner/execution-context.d.ts.map +1 -0
  192. package/query-planner/execution-context.js +50 -0
  193. package/query-planner/executor.d.ts +5 -0
  194. package/query-planner/executor.d.ts.map +1 -0
  195. package/query-planner/executor.js +223 -0
  196. package/query-planner/geoservices.d.ts +91 -0
  197. package/query-planner/geoservices.d.ts.map +1 -0
  198. package/query-planner/geoservices.js +667 -0
  199. package/query-planner/grpc.d.ts +100 -0
  200. package/query-planner/grpc.d.ts.map +1 -0
  201. package/query-planner/grpc.js +736 -0
  202. package/query-planner/index.d.ts +44 -0
  203. package/query-planner/index.d.ts.map +1 -0
  204. package/query-planner/index.js +30 -0
  205. package/query-planner/ir.d.ts +20 -0
  206. package/query-planner/ir.d.ts.map +1 -0
  207. package/query-planner/ir.js +417 -0
  208. package/query-planner/local-aggregate.d.ts +5 -0
  209. package/query-planner/local-aggregate.d.ts.map +1 -0
  210. package/query-planner/local-aggregate.js +126 -0
  211. package/query-planner/odata.d.ts +48 -0
  212. package/query-planner/odata.d.ts.map +1 -0
  213. package/query-planner/odata.js +662 -0
  214. package/query-planner/ogc-compiler.d.ts +55 -0
  215. package/query-planner/ogc-compiler.d.ts.map +1 -0
  216. package/query-planner/ogc-compiler.js +480 -0
  217. package/query-planner/ogc-features.d.ts +53 -0
  218. package/query-planner/ogc-features.d.ts.map +1 -0
  219. package/query-planner/ogc-features.js +617 -0
  220. package/query-planner/planner.d.ts +17 -0
  221. package/query-planner/planner.d.ts.map +1 -0
  222. package/query-planner/planner.js +818 -0
  223. package/query-planner/resource.d.ts +94 -0
  224. package/query-planner/resource.d.ts.map +1 -0
  225. package/query-planner/resource.js +526 -0
  226. package/query-planner/semantic-canonical.d.ts +15 -0
  227. package/query-planner/semantic-canonical.d.ts.map +1 -0
  228. package/query-planner/semantic-canonical.js +62 -0
  229. package/query-planner/semantic-compat.d.ts +10 -0
  230. package/query-planner/semantic-compat.d.ts.map +1 -0
  231. package/query-planner/semantic-compat.js +35 -0
  232. package/query-planner/semantic-compiler.d.ts +64 -0
  233. package/query-planner/semantic-compiler.d.ts.map +1 -0
  234. package/query-planner/semantic-compiler.js +81 -0
  235. package/query-planner/semantic-literals.d.ts +30 -0
  236. package/query-planner/semantic-literals.d.ts.map +1 -0
  237. package/query-planner/semantic-literals.js +133 -0
  238. package/query-planner/semantic-types.d.ts +265 -0
  239. package/query-planner/semantic-types.d.ts.map +1 -0
  240. package/query-planner/semantic-types.js +1 -0
  241. package/query-planner/semantic.d.ts +54 -0
  242. package/query-planner/semantic.d.ts.map +1 -0
  243. package/query-planner/semantic.js +1421 -0
  244. package/query-planner/types.d.ts +560 -0
  245. package/query-planner/types.d.ts.map +1 -0
  246. package/query-planner/types.js +49 -0
  247. package/query-planner/wfs-semantic.d.ts +58 -0
  248. package/query-planner/wfs-semantic.d.ts.map +1 -0
  249. package/query-planner/wfs-semantic.js +547 -0
  250. package/query-planner/wfs.d.ts +6 -0
  251. package/query-planner/wfs.d.ts.map +1 -0
  252. package/query-planner/wfs.js +71 -0
  253. package/react/external-map.d.ts +67 -0
  254. package/react/external-map.d.ts.map +1 -0
  255. package/react/external-map.js +63 -0
  256. package/react/honua-map.d.ts.map +1 -1
  257. package/react/honua-map.js +3 -2
  258. package/react/honua-source-layer.d.ts +110 -0
  259. package/react/honua-source-layer.d.ts.map +1 -0
  260. package/react/honua-source-layer.js +101 -0
  261. package/react/hooks.d.ts +28 -0
  262. package/react/hooks.d.ts.map +1 -1
  263. package/react/hooks.js +26 -4
  264. package/react/index.d.ts +8 -0
  265. package/react/index.d.ts.map +1 -1
  266. package/react/index.js +5 -0
  267. package/react/query-cache.d.ts +26 -4
  268. package/react/query-cache.d.ts.map +1 -1
  269. package/react/query-cache.js +51 -8
  270. package/react/selection.d.ts +177 -0
  271. package/react/selection.d.ts.map +1 -0
  272. package/react/selection.js +0 -0
  273. package/react/use-mounted-source.d.ts +69 -0
  274. package/react/use-mounted-source.d.ts.map +1 -0
  275. package/react/use-mounted-source.js +346 -0
  276. package/runtime/edit-snapping.d.ts +107 -0
  277. package/runtime/edit-snapping.d.ts.map +1 -0
  278. package/runtime/edit-snapping.js +190 -0
  279. package/runtime/errors.d.ts +3 -2
  280. package/runtime/errors.d.ts.map +1 -1
  281. package/runtime/errors.js +19 -2
  282. package/runtime/index.d.ts +6 -0
  283. package/runtime/index.d.ts.map +1 -1
  284. package/runtime/index.js +3 -0
  285. package/runtime/maplibre-renderer.d.ts +41 -0
  286. package/runtime/maplibre-renderer.d.ts.map +1 -0
  287. package/runtime/maplibre-renderer.js +322 -0
  288. package/runtime/query-tiles.d.ts +2 -1
  289. package/runtime/query-tiles.d.ts.map +1 -1
  290. package/runtime/query-tiles.js +6 -2
  291. package/runtime/source-bridge.d.ts +4 -4
  292. package/runtime/source-bridge.d.ts.map +1 -1
  293. package/runtime/source-bridge.js +9 -56
  294. package/runtime/style-interactions.d.ts +3 -2
  295. package/runtime/style-interactions.d.ts.map +1 -1
  296. package/runtime/style-interactions.js +11 -3
  297. package/runtime/terra-draw-sketch.d.ts +275 -0
  298. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  299. package/runtime/terra-draw-sketch.js +404 -0
  300. package/source-capability-registry.d.ts +1 -0
  301. package/source-capability-registry.js +1 -0
  302. package/source-capability-types.d.ts +186 -0
  303. package/source-capability-types.js +7 -0
  304. package/style/index.d.ts +2 -0
  305. package/style/index.d.ts.map +1 -1
  306. package/style/index.js +2 -0
  307. package/style/renderers.d.ts +326 -0
  308. package/style/renderers.d.ts.map +1 -0
  309. package/style/renderers.js +517 -0
  310. package/webmap/convert-renderer.d.ts +24 -3
  311. package/webmap/convert-renderer.d.ts.map +1 -1
  312. package/webmap/convert-renderer.js +93 -112
  313. package/webmap/index.d.ts +1 -1
  314. package/webmap/index.d.ts.map +1 -1
  315. package/webmap/index.js +1 -1
@@ -0,0 +1,517 @@
1
+ /**
2
+ * First-class renderer objects (issue #497): serializable, renderer-neutral
3
+ * descriptions of smart-mapping styles (class breaks, unique values, heatmap,
4
+ * clustering) that compile deterministically to MapLibre style fragments
5
+ * through the `/expr` builder.
6
+ *
7
+ * A renderer object is a pure value: construct it from options (or revive it
8
+ * from JSON with {@link rendererFromJSON}), read legend metadata with
9
+ * `legendItems()`, and compile MapLibre layer fragments with
10
+ * `toMapLibre(geometryType)`. Compilation is a pure function of the
11
+ * descriptor — no caching, no map access — so the same descriptor always
12
+ * produces byte-identical style JSON.
13
+ *
14
+ * The WebMap JSON converter (`src/webmap/convert-renderer.ts`) and the
15
+ * esri-compat renderer shims emit these objects instead of private
16
+ * transforms, so there is exactly one class-breaks/unique-value compiler in
17
+ * the SDK.
18
+ *
19
+ * @module
20
+ */
21
+ import { concat, get, heatmapDensity, interpolate, linear, matchExpr, step, toNumber, } from "../expr/expression.js";
22
+ /**
23
+ * Create a graduated (class-breaks) renderer: a `step` expression over a
24
+ * numeric field.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const renderer = classBreaksRenderer({
29
+ * field: "magnitude",
30
+ * breaks: [
31
+ * { min: 0, max: 3, label: "Minor" },
32
+ * { min: 3, max: 5, label: "Moderate" },
33
+ * { min: 5, label: "Strong" },
34
+ * ],
35
+ * colors: ["#fed976", "#fd8d3c", "#b10026"],
36
+ * defaultColor: "#cccccc",
37
+ * });
38
+ * const [fragment] = renderer.toMapLibre("point");
39
+ * // fragment.paint["circle-color"] → ["step", ["get", "magnitude"], "#cccccc", 0, "#fed976", …]
40
+ * ```
41
+ *
42
+ * @experimental
43
+ */
44
+ export function classBreaksRenderer(options) {
45
+ const descriptor = cloneDescriptor({ ...options, kind: "class-breaks" });
46
+ if (typeof descriptor.field !== "string" || descriptor.field.length === 0) {
47
+ throw new TypeError("classBreaksRenderer requires a non-empty field.");
48
+ }
49
+ if (!Array.isArray(descriptor.breaks) || descriptor.breaks.length === 0) {
50
+ throw new TypeError("classBreaksRenderer requires at least one break.");
51
+ }
52
+ descriptor.breaks.forEach((entry, index) => {
53
+ if (entry.style !== undefined)
54
+ return; // advanced entries are trusted as-is
55
+ if (entry.min === undefined && entry.max === undefined) {
56
+ throw new TypeError(`breaks[${index}] needs a min or max threshold.`);
57
+ }
58
+ if (entry.color === undefined && descriptor.colors?.[index] === undefined) {
59
+ throw new TypeError(`breaks[${index}] needs a color, a positional colors[] entry, or a style override.`);
60
+ }
61
+ });
62
+ return Object.freeze({
63
+ kind: "class-breaks",
64
+ toJSON: () => cloneDescriptor(descriptor),
65
+ legendItems: () => classBreaksLegend(descriptor),
66
+ toMapLibre: (geometry) => {
67
+ const type = descriptor.layerType ?? layerTypeFor(geometry);
68
+ const colorProperty = colorPropertyFor(type);
69
+ const entries = descriptor.breaks.map((entry, index) => ({
70
+ key: entry.min ?? entry.max,
71
+ style: resolveEntryStyle(entry, index, descriptor.colors, colorProperty),
72
+ }));
73
+ const defaultStyle = resolveDefaultStyle(descriptor, colorProperty);
74
+ const compiled = compileDataDrivenStyle("step", get(descriptor.field), entries, defaultStyle);
75
+ return Object.freeze([
76
+ Object.freeze({ role: "symbolizer", type, paint: compiled.paint, layout: compiled.layout }),
77
+ ]);
78
+ },
79
+ });
80
+ }
81
+ function classBreaksLegend(descriptor) {
82
+ const items = descriptor.breaks.map((entry, index) => Object.freeze({
83
+ kind: "class-break",
84
+ label: entry.label ?? formatRange(entry.min, entry.max),
85
+ color: legendColor(entry.color, index, descriptor.colors, entry.style),
86
+ ...(entry.min !== undefined ? { minValue: entry.min } : {}),
87
+ ...(entry.max !== undefined ? { maxValue: entry.max } : {}),
88
+ }));
89
+ const defaultItem = legendDefaultItem(descriptor);
90
+ if (defaultItem)
91
+ items.push(defaultItem);
92
+ return Object.freeze(items);
93
+ }
94
+ /**
95
+ * Create a categorical (unique-value) renderer: a `match` expression over a
96
+ * feature property (or a delimited concatenation of up to three).
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const renderer = uniqueValueRenderer({
101
+ * field: "priority",
102
+ * values: [
103
+ * { value: "high", color: "#b91c1c", label: "High priority" },
104
+ * { value: "low", color: "#0f766e", label: "Low priority" },
105
+ * ],
106
+ * defaultColor: "#334155",
107
+ * });
108
+ * renderer.legendItems(); // [{ kind: "unique-value", label: "High priority", … }, …]
109
+ * ```
110
+ *
111
+ * @experimental
112
+ */
113
+ export function uniqueValueRenderer(options) {
114
+ const descriptor = cloneDescriptor({ ...options, kind: "unique-value" });
115
+ if (typeof descriptor.field !== "string") {
116
+ throw new TypeError("uniqueValueRenderer requires a field.");
117
+ }
118
+ if (!Array.isArray(descriptor.values) || descriptor.values.length === 0) {
119
+ throw new TypeError("uniqueValueRenderer requires at least one value entry.");
120
+ }
121
+ descriptor.values.forEach((entry, index) => {
122
+ if (entry.value === undefined)
123
+ throw new TypeError(`values[${index}] needs a value.`);
124
+ if (entry.color === undefined && entry.style === undefined) {
125
+ throw new TypeError(`values[${index}] needs a color or a style override.`);
126
+ }
127
+ });
128
+ return Object.freeze({
129
+ kind: "unique-value",
130
+ toJSON: () => cloneDescriptor(descriptor),
131
+ legendItems: () => uniqueValueLegend(descriptor),
132
+ toMapLibre: (geometry) => {
133
+ const type = descriptor.layerType ?? layerTypeFor(geometry);
134
+ const colorProperty = colorPropertyFor(type);
135
+ const entries = descriptor.values.map((entry) => ({
136
+ key: entry.value,
137
+ style: resolveEntryStyle(entry, undefined, undefined, colorProperty),
138
+ }));
139
+ const defaultStyle = resolveDefaultStyle(descriptor, colorProperty);
140
+ const compiled = compileDataDrivenStyle("match", uniqueValueFieldExpression(descriptor), entries, defaultStyle);
141
+ return Object.freeze([
142
+ Object.freeze({ role: "symbolizer", type, paint: compiled.paint, layout: compiled.layout }),
143
+ ]);
144
+ },
145
+ });
146
+ }
147
+ function uniqueValueFieldExpression(descriptor) {
148
+ if (!descriptor.field2 && !descriptor.field3)
149
+ return get(descriptor.field);
150
+ const delimiter = descriptor.fieldDelimiter ?? ",";
151
+ const parts = [];
152
+ if (descriptor.field)
153
+ parts.push(get(descriptor.field));
154
+ if (descriptor.field2) {
155
+ parts.push(delimiter);
156
+ parts.push(get(descriptor.field2));
157
+ }
158
+ if (descriptor.field3) {
159
+ parts.push(delimiter);
160
+ parts.push(get(descriptor.field3));
161
+ }
162
+ return concat(...parts);
163
+ }
164
+ function uniqueValueLegend(descriptor) {
165
+ const items = descriptor.values.map((entry) => Object.freeze({
166
+ kind: "unique-value",
167
+ label: entry.label ?? String(entry.value),
168
+ color: legendColor(entry.color, undefined, undefined, entry.style),
169
+ value: entry.value,
170
+ }));
171
+ const defaultItem = legendDefaultItem(descriptor);
172
+ if (defaultItem)
173
+ items.push(defaultItem);
174
+ return Object.freeze(items);
175
+ }
176
+ const DEFAULT_HEATMAP_RAMP = Object.freeze([
177
+ Object.freeze({ stop: 0, color: "rgba(33,102,172,0)" }),
178
+ Object.freeze({ stop: 0.2, color: "rgb(103,169,207)" }),
179
+ Object.freeze({ stop: 0.4, color: "rgb(209,229,240)" }),
180
+ Object.freeze({ stop: 0.6, color: "rgb(253,219,199)" }),
181
+ Object.freeze({ stop: 0.8, color: "rgb(239,138,98)" }),
182
+ Object.freeze({ stop: 1, color: "rgb(178,24,43)" }),
183
+ ]);
184
+ /**
185
+ * Create a MapLibre-native heatmap renderer. Compiles to a single `heatmap`
186
+ * layer fragment regardless of the requested geometry (heatmaps render
187
+ * point data).
188
+ *
189
+ * @experimental
190
+ */
191
+ export function heatmapRenderer(options = {}) {
192
+ const descriptor = cloneDescriptor({ ...options, kind: "heatmap" });
193
+ const ramp = normalizeHeatmapRamp(descriptor.colorRamp);
194
+ return Object.freeze({
195
+ kind: "heatmap",
196
+ toJSON: () => cloneDescriptor(descriptor),
197
+ legendItems: () => Object.freeze(ramp.map((entry) => Object.freeze({
198
+ kind: "heatmap-stop",
199
+ label: String(entry.stop),
200
+ color: entry.color,
201
+ value: entry.stop,
202
+ }))),
203
+ toMapLibre: () => {
204
+ const paint = {
205
+ "heatmap-radius": descriptor.radius ?? 30,
206
+ "heatmap-intensity": descriptor.intensity ?? 1,
207
+ "heatmap-color": interpolate(linear(), heatmapDensity(), ...ramp.map((entry) => [entry.stop, entry.color])).toJSON(),
208
+ };
209
+ if (descriptor.weightField !== undefined) {
210
+ paint["heatmap-weight"] = toNumber(get(descriptor.weightField), 0).toJSON();
211
+ }
212
+ if (descriptor.opacity !== undefined)
213
+ paint["heatmap-opacity"] = descriptor.opacity;
214
+ return Object.freeze([Object.freeze({ role: "symbolizer", type: "heatmap", paint, layout: {} })]);
215
+ },
216
+ });
217
+ }
218
+ function normalizeHeatmapRamp(ramp) {
219
+ if (ramp === undefined || ramp.length === 0)
220
+ return DEFAULT_HEATMAP_RAMP;
221
+ if (typeof ramp[0] === "string") {
222
+ const colors = ramp;
223
+ if (colors.length === 1)
224
+ return [{ stop: 1, color: colors[0] }];
225
+ return colors.map((color, index) => ({
226
+ stop: roundStop(index / (colors.length - 1)),
227
+ color,
228
+ }));
229
+ }
230
+ const stops = [...ramp];
231
+ for (let i = 1; i < stops.length; i++) {
232
+ if (stops[i].stop <= stops[i - 1].stop) {
233
+ throw new TypeError("heatmapRenderer colorRamp stops must be strictly ascending.");
234
+ }
235
+ }
236
+ return stops;
237
+ }
238
+ function roundStop(value) {
239
+ return Math.round(value * 1000) / 1000;
240
+ }
241
+ /**
242
+ * Create a MapLibre-native cluster renderer. Compiles to three fragments —
243
+ * cluster circles, cluster count labels, and unclustered points — plus
244
+ * GeoJSON source cluster options via {@link ClusterRenderer.toMapLibreSource}.
245
+ *
246
+ * @experimental
247
+ */
248
+ export function clusterRenderer(options) {
249
+ const descriptor = cloneDescriptor({ ...options, kind: "cluster" });
250
+ if (!Array.isArray(descriptor.steps) || descriptor.steps.length === 0) {
251
+ throw new TypeError("clusterRenderer requires at least one step.");
252
+ }
253
+ for (let i = 1; i < descriptor.steps.length; i++) {
254
+ if (descriptor.steps[i].threshold <= descriptor.steps[i - 1].threshold) {
255
+ throw new TypeError("clusterRenderer steps must have strictly ascending thresholds.");
256
+ }
257
+ }
258
+ const sumProperty = descriptor.countField !== undefined ? `${descriptor.countField}_sum` : undefined;
259
+ const countExpression = () => get(sumProperty ?? "point_count");
260
+ return Object.freeze({
261
+ kind: "cluster",
262
+ toJSON: () => cloneDescriptor(descriptor),
263
+ toMapLibreSource: () => ({
264
+ cluster: true,
265
+ clusterRadius: descriptor.radius ?? 50,
266
+ clusterMaxZoom: descriptor.maxZoom ?? 14,
267
+ ...(descriptor.countField !== undefined && sumProperty !== undefined
268
+ ? { clusterProperties: { [sumProperty]: ["+", ["get", descriptor.countField]] } }
269
+ : {}),
270
+ }),
271
+ legendItems: () => clusterLegend(descriptor),
272
+ toMapLibre: () => {
273
+ const steps = descriptor.steps;
274
+ const color = steps.length === 1
275
+ ? steps[0].color
276
+ : step(countExpression(), steps[0].color, ...steps.slice(1).map((entry) => [entry.threshold, entry.color])).toJSON();
277
+ const radiusFor = (entry, index) => entry.radius ?? 14 + 6 * index;
278
+ const radius = steps.length === 1
279
+ ? radiusFor(steps[0], 0)
280
+ : step(countExpression(), radiusFor(steps[0], 0), ...steps
281
+ .slice(1)
282
+ .map((entry, index) => [entry.threshold, radiusFor(entry, index + 1)])).toJSON();
283
+ const clusters = Object.freeze({
284
+ role: "clusters",
285
+ type: "circle",
286
+ filter: ["has", "point_count"],
287
+ paint: {
288
+ "circle-color": color,
289
+ "circle-radius": radius,
290
+ "circle-stroke-color": "#ffffff",
291
+ "circle-stroke-width": 1.5,
292
+ },
293
+ layout: {},
294
+ });
295
+ const count = Object.freeze({
296
+ role: "cluster-count",
297
+ type: "symbol",
298
+ filter: ["has", "point_count"],
299
+ paint: { "text-color": "#ffffff" },
300
+ layout: {
301
+ "text-field": sumProperty !== undefined ? ["to-string", ["get", sumProperty]] : ["get", "point_count_abbreviated"],
302
+ "text-size": 12,
303
+ },
304
+ });
305
+ const unclustered = Object.freeze({
306
+ role: "unclustered",
307
+ type: "circle",
308
+ filter: ["!", ["has", "point_count"]],
309
+ paint: {
310
+ "circle-color": descriptor.unclusteredColor ?? "#16735b",
311
+ "circle-radius": descriptor.unclusteredRadius ?? 5,
312
+ "circle-stroke-color": "#ffffff",
313
+ "circle-stroke-width": 1,
314
+ },
315
+ layout: {},
316
+ });
317
+ return Object.freeze([clusters, count, unclustered]);
318
+ },
319
+ });
320
+ }
321
+ function clusterLegend(descriptor) {
322
+ const items = descriptor.steps.map((entry, index) => {
323
+ const next = descriptor.steps[index + 1];
324
+ return Object.freeze({
325
+ kind: "cluster-step",
326
+ label: entry.label ?? (next ? `${entry.threshold}–${next.threshold}` : `≥ ${entry.threshold}`),
327
+ color: entry.color,
328
+ minValue: entry.threshold,
329
+ ...(next ? { maxValue: next.threshold } : {}),
330
+ });
331
+ });
332
+ items.push(Object.freeze({
333
+ kind: "default",
334
+ label: "Individual features",
335
+ color: descriptor.unclusteredColor ?? "#16735b",
336
+ }));
337
+ return Object.freeze(items);
338
+ }
339
+ // ── JSON revival ─────────────────────────────────────────────────
340
+ /**
341
+ * Revive a renderer object from its serializable descriptor
342
+ * (`renderer.toJSON()` output).
343
+ *
344
+ * @experimental
345
+ */
346
+ export function rendererFromJSON(descriptor) {
347
+ switch (descriptor.kind) {
348
+ case "class-breaks":
349
+ return classBreaksRenderer(descriptor);
350
+ case "unique-value":
351
+ return uniqueValueRenderer(descriptor);
352
+ case "heatmap":
353
+ return heatmapRenderer(descriptor);
354
+ case "cluster":
355
+ return clusterRenderer(descriptor);
356
+ default:
357
+ throw new TypeError(`Unknown renderer kind "${String(descriptor.kind)}".`);
358
+ }
359
+ }
360
+ /** True when a value looks like a renderer object from this module. @experimental */
361
+ export function isRenderer(value) {
362
+ if (value === null || typeof value !== "object")
363
+ return false;
364
+ const candidate = value;
365
+ return (typeof candidate.toMapLibre === "function" &&
366
+ typeof candidate.legendItems === "function" &&
367
+ (candidate.kind === "class-breaks" ||
368
+ candidate.kind === "unique-value" ||
369
+ candidate.kind === "heatmap" ||
370
+ candidate.kind === "cluster"));
371
+ }
372
+ // ── Shared compilation core (single implementation, REQ-002) ─────
373
+ /**
374
+ * MapLibre properties that live in `layout` rather than `paint`. Shared with
375
+ * the WebMap converter so both route properties identically.
376
+ *
377
+ * @internal
378
+ */
379
+ export const RENDERER_LAYOUT_PROPERTIES = new Set([
380
+ "icon-image",
381
+ "icon-size",
382
+ "icon-offset",
383
+ "icon-anchor",
384
+ "icon-rotate",
385
+ "text-field",
386
+ "text-font",
387
+ "text-size",
388
+ "text-offset",
389
+ "text-anchor",
390
+ "text-max-width",
391
+ "text-letter-spacing",
392
+ "text-justify",
393
+ "text-rotate",
394
+ "symbol-placement",
395
+ "symbol-spacing",
396
+ "visibility",
397
+ ]);
398
+ /**
399
+ * The single class-breaks/unique-value property compiler (issue #497
400
+ * REQ-002). For every paint/layout property present on any class it builds
401
+ * one `match` (categorical) or `step` (graduated) expression through the
402
+ * `/expr` builder. When `defaultStyle` is omitted the first entry's style
403
+ * provides per-property defaults; missing values fall back to
404
+ * `"transparent"`.
405
+ *
406
+ * @internal
407
+ */
408
+ export function compileDataDrivenStyle(mode, input, entries, defaultStyle) {
409
+ const paint = {};
410
+ const layout = {};
411
+ if (entries.length === 0)
412
+ return { paint, layout };
413
+ const keys = new Set();
414
+ for (const entry of entries) {
415
+ for (const key of Object.keys(entry.style.paint))
416
+ keys.add(key);
417
+ for (const key of Object.keys(entry.style.layout))
418
+ keys.add(key);
419
+ }
420
+ const fallbackStyle = defaultStyle ?? entries[0].style;
421
+ for (const key of keys) {
422
+ const defaultValue = (fallbackStyle.paint[key] ?? fallbackStyle.layout[key] ?? "transparent");
423
+ // Classes that do not define this property fall back to the default so
424
+ // the emitted match/step expression never contains `undefined` branches.
425
+ const valueFor = (entry) => (entry.style.paint[key] ?? entry.style.layout[key] ?? defaultValue);
426
+ let expression;
427
+ if (mode === "match") {
428
+ expression = matchExpr(input, ...entries.map((entry) => [entry.key, valueFor(entry)]), defaultValue).toJSON();
429
+ }
430
+ else {
431
+ expression = step(input, defaultValue, ...entries.map((entry) => [entry.key, valueFor(entry)])).toJSON();
432
+ }
433
+ if (RENDERER_LAYOUT_PROPERTIES.has(key))
434
+ layout[key] = expression;
435
+ else
436
+ paint[key] = expression;
437
+ }
438
+ return { paint, layout };
439
+ }
440
+ // ── Internal helpers ─────────────────────────────────────────────
441
+ function layerTypeFor(geometry) {
442
+ return geometry === "point" ? "circle" : geometry === "line" ? "line" : "fill";
443
+ }
444
+ function colorPropertyFor(layerType) {
445
+ switch (layerType) {
446
+ case "circle":
447
+ return "circle-color";
448
+ case "line":
449
+ return "line-color";
450
+ case "fill":
451
+ return "fill-color";
452
+ case "symbol":
453
+ return "icon-color";
454
+ default:
455
+ return `${layerType}-color`;
456
+ }
457
+ }
458
+ function resolveEntryStyle(entry, index, colors, colorProperty) {
459
+ if (entry.style !== undefined) {
460
+ return { paint: { ...(entry.style.paint ?? {}) }, layout: { ...(entry.style.layout ?? {}) } };
461
+ }
462
+ const color = entry.color ?? (index !== undefined ? colors?.[index] : undefined);
463
+ return { paint: color !== undefined ? { [colorProperty]: color } : {}, layout: {} };
464
+ }
465
+ function resolveDefaultStyle(descriptor, colorProperty) {
466
+ if (descriptor.defaultStyle !== undefined) {
467
+ return {
468
+ paint: { ...(descriptor.defaultStyle.paint ?? {}) },
469
+ layout: { ...(descriptor.defaultStyle.layout ?? {}) },
470
+ };
471
+ }
472
+ if (descriptor.defaultColor !== undefined) {
473
+ return { paint: { [colorProperty]: descriptor.defaultColor }, layout: {} };
474
+ }
475
+ return undefined;
476
+ }
477
+ function legendColor(color, index, colors, style) {
478
+ const resolved = color ?? (index !== undefined ? colors?.[index] : undefined) ?? styleColor(style);
479
+ return resolved ?? "transparent";
480
+ }
481
+ function styleColor(style) {
482
+ if (!style)
483
+ return undefined;
484
+ for (const bag of [style.paint, style.layout]) {
485
+ if (!bag)
486
+ continue;
487
+ for (const [key, value] of Object.entries(bag)) {
488
+ if (key.endsWith("-color") && typeof value === "string")
489
+ return value;
490
+ }
491
+ }
492
+ return undefined;
493
+ }
494
+ function legendDefaultItem(descriptor) {
495
+ if (descriptor.defaultColor === undefined &&
496
+ descriptor.defaultLabel === undefined &&
497
+ descriptor.defaultStyle === undefined) {
498
+ return undefined;
499
+ }
500
+ return Object.freeze({
501
+ kind: "default",
502
+ label: descriptor.defaultLabel ?? "Other",
503
+ color: descriptor.defaultColor ?? styleColor(descriptor.defaultStyle) ?? "transparent",
504
+ });
505
+ }
506
+ function formatRange(min, max) {
507
+ if (min !== undefined && max !== undefined)
508
+ return `${min}–${max}`;
509
+ if (min !== undefined)
510
+ return `≥ ${min}`;
511
+ if (max !== undefined)
512
+ return `≤ ${max}`;
513
+ return "";
514
+ }
515
+ function cloneDescriptor(value) {
516
+ return structuredClone(value);
517
+ }
@@ -1,12 +1,16 @@
1
1
  /**
2
2
  * Converts Esri renderer JSON to MapLibre paint/layout properties.
3
3
  *
4
- * Handles simple, uniqueValue, and classBreaks renderers. Produces
5
- * data-driven expressions (match, step) for categorized/graduated renderers.
4
+ * Since issue #497 this module emits first-class renderer objects
5
+ * (`src/style/renderers.ts`) for uniqueValue/classBreaks renderers and
6
+ * compiles them through the shared `/expr`-based compiler, so WebMap
7
+ * conversion, esri-compat, and standalone renderer objects share a single
8
+ * implementation. The compiled style output is unchanged.
6
9
  *
7
10
  * @module
8
11
  */
9
- import type { WebMapRenderer } from "./types.js";
12
+ import type { ClassBreaksRenderer, UniqueValueRenderer } from "../style/renderers.js";
13
+ import type { WebMapClassBreaksRenderer, WebMapRenderer, WebMapUniqueValueRenderer } from "./types.js";
10
14
  import type { WarningCollector } from "./warnings.js";
11
15
  export interface RendererConversionResult {
12
16
  layerType: string;
@@ -14,4 +18,21 @@ export interface RendererConversionResult {
14
18
  layout: Record<string, unknown>;
15
19
  }
16
20
  export declare function convertRenderer(renderer: WebMapRenderer | undefined, warn: WarningCollector): RendererConversionResult | undefined;
21
+ /**
22
+ * Build a first-class unique-value renderer object from WebMap renderer
23
+ * JSON. Returns `undefined` when the renderer has no value infos or the
24
+ * first symbol cannot be converted (matching `convertRenderer` behavior).
25
+ *
26
+ * @experimental
27
+ */
28
+ export declare function uniqueValueRendererFromWebMap(renderer: WebMapUniqueValueRenderer, warn: WarningCollector): UniqueValueRenderer | undefined;
29
+ /**
30
+ * Build a first-class class-breaks renderer object from WebMap renderer
31
+ * JSON. Returns `undefined` when the renderer has no break infos, no field,
32
+ * or the first symbol cannot be converted (matching `convertRenderer`
33
+ * behavior).
34
+ *
35
+ * @experimental
36
+ */
37
+ export declare function classBreaksRendererFromWebMap(renderer: WebMapClassBreaksRenderer, warn: WarningCollector): ClassBreaksRenderer | undefined;
17
38
  //# sourceMappingURL=convert-renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convert-renderer.d.ts","sourceRoot":"","sources":["../../../src/webmap/convert-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAA6B,cAAc,EAA2C,MAAM,YAAY,CAAC;AACrH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,IAAI,EAAE,gBAAgB,GACrB,wBAAwB,GAAG,SAAS,CActC"}
1
+ {"version":3,"file":"convert-renderer.d.ts","sourceRoot":"","sources":["../../../src/webmap/convert-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAmC,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGvH,OAAO,KAAK,EAAE,yBAAyB,EAAE,cAAc,EAAgB,yBAAyB,EAAE,MAAM,YAAY,CAAC;AACrH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,IAAI,EAAE,gBAAgB,GACrB,wBAAwB,GAAG,SAAS,CActC;AAmCD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,yBAAyB,EACnC,IAAI,EAAE,gBAAgB,GACrB,mBAAmB,GAAG,SAAS,CA6BjC;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,yBAAyB,EACnC,IAAI,EAAE,gBAAgB,GACrB,mBAAmB,GAAG,SAAS,CAgCjC"}