@nika-js/onlymap 0.3.0 → 0.3.1

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 (45) hide show
  1. package/.vscode/onlymap.code-snippets +13 -0
  2. package/LICENSE.md +4 -0
  3. package/README.md +4 -3
  4. package/THIRD-PARTY-LICENSES.md +7874 -0
  5. package/dist/{Arrow.dom-7AXne1TU.js → Arrow.dom-BNjbQ9jb.js} +23 -23
  6. package/dist/LercDecode.es-CVmNpXBp.js +303 -0
  7. package/dist/assets/LercDecode.es-BJb2dZu5.js +305 -0
  8. package/dist/assets/__vite-browser-external-DDArsqdc.js +29 -0
  9. package/dist/assets/lerc-wasm.wasm +0 -0
  10. package/dist/assets/lzw-BY5a_5nb.js +83 -0
  11. package/dist/assets/worker-DtIrTJB7.js +305 -0
  12. package/dist/assets/zstd-QGAv3PMm.js +383 -0
  13. package/dist/{basemap-DrK6zcu8.js → basemap-BSb-g03Z.js} +6228 -6247
  14. package/dist/{convert-arrow-schema-CvZ3cT5m.js → convert-arrow-schema-DrAihRf9.js} +31 -31
  15. package/dist/ctx.d.ts +3 -0
  16. package/dist/index-CIEkseeV.js +3108 -0
  17. package/dist/index-D67FynN5.js +594 -0
  18. package/dist/{index-C9tgnPNw.js → index-QSkZjzIL.js} +4 -4
  19. package/dist/index-WeMwLoaQ.js +1446 -0
  20. package/dist/{index-CsicbycJ.js → index-lV5eG2Xt.js} +21442 -21860
  21. package/dist/index.d.ts +2 -0
  22. package/dist/ir.d.ts +3 -0
  23. package/dist/layer-registry.d.ts +15 -1
  24. package/dist/legend-spec.d.ts +66 -0
  25. package/dist/lerc-DJzRIT6z.js +30 -0
  26. package/dist/lerc-wasm.wasm +0 -0
  27. package/dist/lzw-BY5a_5nb.js +83 -0
  28. package/dist/mgrs-BY9bIvp4.js +799 -0
  29. package/dist/onlymapjs.css +1 -1
  30. package/dist/onlymapjs.js +24 -23
  31. package/dist/raster-BKf1dx54.js +8843 -0
  32. package/dist/raster.d.ts +61 -0
  33. package/dist/{recordbatch-HRu0SMKp.js → recordbatch-Bpc0uxFn.js} +200 -200
  34. package/dist/{table-accessors-DBjWgN0C.js → table-accessors-CYWTzpQI.js} +71 -76
  35. package/dist/version.d.ts +1 -1
  36. package/dist/zstd-jXobGRcq.js +14 -0
  37. package/llms.txt +3 -3
  38. package/onlymapjs.html-data.json +23 -0
  39. package/package.json +18 -15
  40. package/skills/onlymapjs/SKILL.md +3 -1
  41. package/skills/onlymapjs/references/syntax.md +29 -2
  42. package/dist/index-CiuGqS0i.js +0 -605
  43. package/dist/index-DXoRERAy.js +0 -1457
  44. package/dist/index-Ztkd30f8.js +0 -3907
  45. package/dist/onlymapjs.umd.cjs +0 -8708
@@ -1,6 +1,5 @@
1
- function w(e) {
2
- var a;
3
- switch (typeof e == "object" && (e == null ? void 0 : e.shape)) {
1
+ function d(e) {
2
+ switch (typeof e == "object" && e?.shape) {
4
3
  case "array-row-table":
5
4
  case "object-row-table":
6
5
  return Array.isArray(e.data);
@@ -9,12 +8,12 @@ function w(e) {
9
8
  case "columnar-table":
10
9
  return e.data && typeof e.data == "object";
11
10
  case "arrow-table":
12
- return ((a = e == null ? void 0 : e.data) == null ? void 0 : a.numRows) !== void 0;
11
+ return e?.data?.numRows !== void 0;
13
12
  default:
14
13
  return !1;
15
14
  }
16
15
  }
17
- function l(e) {
16
+ function i(e) {
18
17
  switch (e.shape) {
19
18
  case "array-row-table":
20
19
  case "object-row-table":
@@ -24,8 +23,8 @@ function l(e) {
24
23
  case "arrow-table":
25
24
  return e.data.numRows;
26
25
  case "columnar-table":
27
- for (const a of Object.values(e.data))
28
- return a.length || 0;
26
+ for (const t of Object.values(e.data))
27
+ return t.length || 0;
29
28
  return 0;
30
29
  default:
31
30
  throw new Error("table");
@@ -34,7 +33,7 @@ function l(e) {
34
33
  function g(e) {
35
34
  if (e.schema)
36
35
  return e.schema.fields.length;
37
- if (l(e) === 0)
36
+ if (i(e) === 0)
38
37
  throw new Error("empty table");
39
38
  switch (e.shape) {
40
39
  case "array-row-table":
@@ -51,154 +50,150 @@ function g(e) {
51
50
  throw new Error("table");
52
51
  }
53
52
  }
54
- function j(e, r, a) {
55
- var o;
53
+ function j(e, r, t) {
56
54
  switch (e.shape) {
57
55
  case "array-row-table":
58
- const n = m(e, a);
59
- return e.data[r][n];
56
+ const o = m(e, t);
57
+ return e.data[r][o];
60
58
  case "object-row-table":
61
- return e.data[r][a];
59
+ return e.data[r][t];
62
60
  case "geojson-table":
63
- return e.features[r][a];
61
+ return e.features[r][t];
64
62
  case "columnar-table":
65
- return e.data[a][r];
63
+ return e.data[t][r];
66
64
  case "arrow-table":
67
- const c = e.data, u = c.schema.fields.findIndex((s) => s.name === a);
68
- return (o = c.getChildAt(u)) == null ? void 0 : o.get(r);
65
+ const n = e.data, f = n.schema.fields.findIndex((c) => c.name === t);
66
+ return n.getChildAt(f)?.get(r);
69
67
  default:
70
68
  throw new Error("todo");
71
69
  }
72
70
  }
73
- function y(e, r, a) {
74
- var o;
71
+ function y(e, r, t) {
75
72
  switch (e.shape) {
76
73
  case "array-row-table":
77
- return e.data[r][a];
74
+ return e.data[r][t];
78
75
  case "object-row-table":
79
- const n = i(e, a);
80
- return e.data[r][n];
76
+ const o = l(e, t);
77
+ return e.data[r][o];
81
78
  case "geojson-table":
82
- const f = i(e, a);
83
- return e.features[r][f];
79
+ const u = l(e, t);
80
+ return e.features[r][u];
84
81
  case "columnar-table":
85
- const c = i(e, a);
86
- return e.data[c][r];
82
+ const n = l(e, t);
83
+ return e.data[n][r];
87
84
  case "arrow-table":
88
- return (o = e.data.getChildAt(a)) == null ? void 0 : o.get(r);
85
+ return e.data.getChildAt(t)?.get(r);
89
86
  default:
90
87
  throw new Error("todo");
91
88
  }
92
89
  }
93
90
  function m(e, r) {
94
- var o;
95
- const a = (o = e.schema) == null ? void 0 : o.fields.findIndex((n) => n.name === r);
96
- if (a === void 0)
91
+ const t = e.schema?.fields.findIndex((o) => o.name === r);
92
+ if (t === void 0)
97
93
  throw new Error(r);
98
- return a;
94
+ return t;
99
95
  }
100
- function i(e, r) {
101
- var o, n;
102
- const a = (n = (o = e.schema) == null ? void 0 : o.fields[r]) == null ? void 0 : n.name;
103
- if (!a)
96
+ function l(e, r) {
97
+ const t = e.schema?.fields[r]?.name;
98
+ if (!t)
104
99
  throw new Error(`${r}`);
105
- return a;
100
+ return t;
106
101
  }
107
- function T(e, r, a, o) {
102
+ function T(e, r, t, o) {
108
103
  switch (e.shape) {
109
104
  case "object-row-table":
110
105
  return o ? Object.fromEntries(Object.entries(e.data[r])) : e.data[r];
111
106
  case "array-row-table":
112
107
  if (e.schema) {
113
- const s = a || {};
114
- for (let t = 0; t < e.schema.fields.length; t++)
115
- s[e.schema.fields[t].name] = e.data[r][t];
108
+ const s = t || {};
109
+ for (let a = 0; a < e.schema.fields.length; a++)
110
+ s[e.schema.fields[a].name] = e.data[r][a];
116
111
  return s;
117
112
  }
118
113
  throw new Error("no schema");
119
114
  case "geojson-table":
120
115
  if (e.schema) {
121
- const s = a || {};
122
- for (let t = 0; t < e.schema.fields.length; t++)
123
- s[e.schema.fields[t].name] = e.features[r][t];
116
+ const s = t || {};
117
+ for (let a = 0; a < e.schema.fields.length; a++)
118
+ s[e.schema.fields[a].name] = e.features[r][a];
124
119
  return s;
125
120
  }
126
121
  throw new Error("no schema");
127
122
  case "columnar-table":
128
123
  if (e.schema) {
129
- const s = a || {};
130
- for (let t = 0; t < e.schema.fields.length; t++)
131
- s[e.schema.fields[t].name] = e.data[e.schema.fields[t].name][r];
124
+ const s = t || {};
125
+ for (let a = 0; a < e.schema.fields.length; a++)
126
+ s[e.schema.fields[a].name] = e.data[e.schema.fields[a].name][r];
132
127
  return s;
133
128
  } else {
134
- const s = a || {};
135
- for (const [t, h] of Object.entries(e.data))
136
- s[t] = h[r];
129
+ const s = t || {};
130
+ for (const [a, h] of Object.entries(e.data))
131
+ s[a] = h[r];
137
132
  return s;
138
133
  }
139
134
  case "arrow-table":
140
- const n = e.data, f = a || {}, c = n.get(r), u = n.schema;
141
- for (let s = 0; s < u.fields.length; s++)
142
- f[u.fields[s].name] = c == null ? void 0 : c[u.fields[s].name];
143
- return f;
135
+ const u = e.data, n = t || {}, f = u.get(r), c = u.schema;
136
+ for (let s = 0; s < c.fields.length; s++)
137
+ n[c.fields[s].name] = f?.[c.fields[s].name];
138
+ return n;
144
139
  default:
145
140
  throw new Error("shape");
146
141
  }
147
142
  }
148
- function d(e, r, a, o) {
143
+ function w(e, r, t, o) {
149
144
  switch (e.shape) {
150
145
  case "array-row-table":
151
146
  return o ? Array.from(e.data[r]) : e.data[r];
152
147
  case "object-row-table":
153
148
  if (e.schema) {
154
- const s = a || [];
155
- for (let t = 0; t < e.schema.fields.length; t++)
156
- s[t] = e.data[r][e.schema.fields[t].name];
149
+ const s = t || [];
150
+ for (let a = 0; a < e.schema.fields.length; a++)
151
+ s[a] = e.data[r][e.schema.fields[a].name];
157
152
  return s;
158
153
  }
159
154
  return Object.values(e.data[r]);
160
155
  case "geojson-table":
161
156
  if (e.schema) {
162
- const s = a || [];
163
- for (let t = 0; t < e.schema.fields.length; t++)
164
- s[t] = e.features[r][e.schema.fields[t].name];
157
+ const s = t || [];
158
+ for (let a = 0; a < e.schema.fields.length; a++)
159
+ s[a] = e.features[r][e.schema.fields[a].name];
165
160
  return s;
166
161
  }
167
162
  return Object.values(e.features[r]);
168
163
  case "columnar-table":
169
164
  if (e.schema) {
170
- const s = a || [];
171
- for (let t = 0; t < e.schema.fields.length; t++)
172
- s[t] = e.data[e.schema.fields[t].name][r];
165
+ const s = t || [];
166
+ for (let a = 0; a < e.schema.fields.length; a++)
167
+ s[a] = e.data[e.schema.fields[a].name][r];
173
168
  return s;
174
169
  } else {
175
- const s = a || [];
176
- let t = 0;
170
+ const s = t || [];
171
+ let a = 0;
177
172
  for (const h of Object.values(e.data))
178
- s[t] = h[r], t++;
173
+ s[a] = h[r], a++;
179
174
  return s;
180
175
  }
181
176
  case "arrow-table":
182
- const n = e.data, f = a || [], c = n.get(r), u = n.schema;
183
- for (let s = 0; s < u.fields.length; s++)
184
- f[s] = c == null ? void 0 : c[u.fields[s].name];
185
- return f;
177
+ const u = e.data, n = t || [], f = u.get(r), c = u.schema;
178
+ for (let s = 0; s < c.fields.length; s++)
179
+ n[s] = f?.[c.fields[s].name];
180
+ return n;
186
181
  default:
187
182
  throw new Error("shape");
188
183
  }
189
184
  }
190
185
  function* p(e, r = []) {
191
- const a = l(e);
192
- for (let o = 0; o < a; o++)
193
- yield d(e, o, r);
186
+ const t = i(e);
187
+ for (let o = 0; o < t; o++)
188
+ yield w(e, o, r);
194
189
  }
195
190
  export {
196
191
  g as a,
197
192
  y as b,
198
193
  j as c,
199
- d,
194
+ w as d,
200
195
  T as e,
201
- l as g,
202
- w as i,
196
+ i as g,
197
+ d as i,
203
198
  p as m
204
199
  };
package/dist/version.d.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  * the build rootDir, and a `define` would need repeating across vite/vitest/
6
6
  * vite-node configs.
7
7
  */
8
- export declare const LIBRARY_VERSION = "0.3.0";
8
+ export declare const LIBRARY_VERSION = "0.3.1";
@@ -0,0 +1,14 @@
1
+ import { decompress as r } from "./index-2v2NXF_n.js";
2
+ async function o(t) {
3
+ const e = r(new Uint8Array(t));
4
+ return n(e);
5
+ }
6
+ function n(t) {
7
+ if (t.byteOffset === 0 && t.byteLength === t.buffer.byteLength)
8
+ return t.buffer;
9
+ const e = new Uint8Array(t.byteLength);
10
+ return e.set(t), e.buffer;
11
+ }
12
+ export {
13
+ o as decode
14
+ };
package/llms.txt CHANGED
@@ -11,14 +11,14 @@ OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the d
11
11
  - Accessors are expressions in attributes, not JS functions: `get-position="[$lon, $lat]"`, `get-radius="$population * 0.001"`. `$field` reads a datum field; the library resolves flat vs. GeoJSON shape for you — never write `d.properties.x`.
12
12
  - Inline event handlers (`onclick="..."`) are rejected. Use `data-emit` attributes (`<span data-emit="hide-overlay" data-target="popup1">`) or `addEventListener` inside a `<script type="om/widget">` block where `ctx` is in scope.
13
13
  - `scale()` requires an explicit `domain=`: `get-fill-color="scale($depth, sequential, ['#ffffcc','#800026'], domain=[0,700])"`. A missing domain is a validation error.
14
- - `id` is required on every `<om-layer>`. `label` and `color` feed the legend; an explicit `get-fill-color` overrides `color` for rendering (both together is valid and common).
14
+ - `id` is required on every `<om-layer>`. `label` and `color` feed the legend; an explicit `get-fill-color` overrides `color` for rendering (both together is valid and common). The legend widget also reads `get-fill-color` itself: a `sequential`/`diverging` scale renders as a gradient ramp with domain labels, a `threshold` scale as discrete class ranges, and an equality ternary chain (`$f == 'a' ? '#c1' : '#c2'`) as a category palette — write those canonical shapes and the legend describes the symbology automatically.
15
15
  - Full JavaScript in accessor blocks needs the `js` attribute on the layer (`<om-layer js>` + `<script type="om/accessors">`). Without it, blocks are restricted to `export const name = d => <expression>` — no statements, no loops, no nested functions.
16
16
 
17
17
  ## Element vocabulary
18
18
 
19
19
  - `<om-map center="[lng, lat]" zoom="11" pitch="55" bearing="20" basemap="positron">` — the root. `basemap` accepts a free preset (`liberty`, `bright`, `positron`, `dark-matter`, `voyager`, `osm` — no keys; or `maptiler-streets`/`maptiler-dataviz`/`maptiler-satellite` with `basemap-key="…"` or `OmMap.configureBasemap({ maptilerKey })`), `maplibre` (bare demo style), a style URL (e.g. a MapTiler-customized style; any scheme fetch supports, including desktop asset protocols like Tauri's, query strings fine), or `none` (standalone canvas). The attribute is LIVE — writing it switches the basemap in place (camera + layers survive); the `set-basemap {basemap}` action and `<om-widget type="basemap-switcher" options="positron dark-matter osm">` do the same. Register more via `OmMap.registerBasemap(name, { style })`. Required provider attribution renders automatically (`attribution="false"` opts out). `pitch`/`bearing` tilt the initial camera (use for 3D content). Terrain: `terrain="terrarium"` (keyless AWS DEM; also `maptiler-terrain` with a key, a raw `{z}/{x}/{y}` DEM URL + `terrain-decoder="terrarium|mapbox-rgb|<JSON>"`, or `off`) raises a 3D surface — geographic layers DRAPE onto it automatically (per-layer `terrain="drape|offset|off"` overrides; 3D-model layers sit ON it), `terrain-exaggeration` scales relief, `terrain-texture` drapes imagery; terrain REPLACES an active basemap while on (restored when off); `set-terrain` action, `terrain` watch token, `OmMap.registerTerrain(name, {...})` for more sources. Scene lighting for 3D content: `lighting="daylight|studio|flat|custom"` (absent = deck defaults) with `lighting-ambient`/`lighting-sun`/`lighting-sun-azimuth`/`lighting-sun-elevation`/`lighting-camera` overrides and `lighting-sun-date` (ISO/epoch — solar-position sun computed at the map center, wins over azimuth/elevation); attribute-backed (undoable, live-editable), story-steppable via the `set-lighting {lighting, sunAzimuth, ...}` action (`lighting="default"` removes the attribute set; a bare preset is a clean reset — stale overrides clear), native UI via `<om-widget type="lighting">` (preset radios + tuning sliders), widget watch token `lighting`. GeoJsonLayer extrudes polygons declaratively: `extruded get-elevation="$height"` (+ `wireframe`). `validate` attribute enables live validation + on-page error panel. Include a `map-id="<random UUID>"` on complete pages (identifies the map artifact for usage telemetry — not a visitor id; the page author deletes it to opt out); `telemetry="off"` disables usage telemetry + library-error reporting for the map (global: `OmMap.configureTelemetry({ disabled: true })`; schema: docs/telemetry.md). Free plan limits: 5 layers, 25k rows per layer — layers past a limit don't render and validation says why; a `license-key="om_live_…"` attribute (publishable, origin-restricted) or `OmMap.configureLicense(key)` lifts limits and removes the corner badge. When a user's map exceeds free limits, say so and point at the validation message rather than silently restructuring their data. Events on the element: `om-map-ready` (boot complete; `await mapEl.ready` is the promise twin), `om-validation-error`, `om-view-changed` (camera settled after a move, debounced; `detail = {longitude, latitude, zoom, pitch, bearing}` — the camera-persistence hook). `await mapEl.snapshot()` returns a canvas-only PNG dataURL of the scene (basemap + layers at device pixels; `{as:"blob"}` for files) — DOM widgets/overlays/attribution are NOT captured, so exports must render provider credits themselves.
20
- - `<om-layer id="..." type="ScatterplotLayer" data="./points.json">` — any deck.gl layer class by `type` (all 33 bundled), plus `PopupLayer` (WebGL badges/labels at scale: `layout="badge|pin-label|card"`, `min-zoom`/`max-zoom`). External layer classes register via `OmMap.registerLayer({type, deckClass, props})` — build them on `@nika-js/onlymap/deck` (the bundled `CompositeLayer`/`TileLayer`/… re-exports), never a separately-installed deck.gl (different class hierarchy, breaks in the renderer); function-valued props ride the subclass's `static defaultProps`; register at module top level BEFORE the manifest mounts (see docs/custom-layers.md). Data: `data` URL (JSON, GeoJSON, CSV/TSV `.csv` — parsed to typed columns — Shapefile `.shp` (+`.dbf` attributes) and KML `.kml` as GeoJSON features, or Arrow IPC `.arrow`/`.feather` — large point datasets stay columnar, GeoArrow line/polygon geometry becomes GeoJSON features, zstd-compressed IPC is handled; other formats plug in via `OmMap.registerFormat({match, parse})`; data URLs accept any scheme the runtime's fetch supports — desktop webviews (Tauri, Electron) pass asset-protocol URLs straight in), inline `<script type="application/json">` (row arrays or column-oriented `{"columns": {"lon": [...], "lat": [...]}}`), or `wss://` streaming (`key="mmsi"` upserts entities in place, `flush="250ms"` coalesces bursts, `source="name"` selects a `OmMap.registerSource` decoder plugin), or a polled REST snapshot (`refresh="5s"` re-fetches and replaces — for live endpoints that return the full current state). Authenticated endpoints: call `OmMap.configureData({ headers: {...} })` in a script — never put tokens in attributes. `$field` accessors work identically on all of them — never write column-index code yourself. One columnar restriction: the `js` full-JS opt-in is not allowed on Arrow/columnar layers (validation will tell you; use `$field` accessors instead). For 3D models use `type="ScenegraphLayer"` with `scenegraph="./model.glb"` (required) and `get-orientation="[0, $heading, 90]"` — the roll of 90 stands Y-up glTF models upright; see docs/3d-assets.md.
21
- - `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite" position="bottom-right">` — static UI panels. No `type` + HTML + `<script type="om/widget">` = custom widget with `ctx` (`ctx.layers`, `ctx.data(id)`, `ctx.dataInViewport(id)`, `ctx.stats(id, field)`, `ctx.viewport`, `ctx.selection`, `ctx.emit(action, payload)`), `this.watch = ['data:<layerId>', 'viewport', 'selection', 'layers', 'history', 'basemap']` (`layers` also fires on visibility/filter changes; `basemap` on basemap switches; `history` on undo/redo availability), `this.$(sel)`, `vegaEmbed`/`d3` as globals.
20
+ - `<om-layer id="..." type="ScatterplotLayer" data="./points.json">` — any deck.gl layer class by `type` (all 33 bundled), plus `PopupLayer` (WebGL badges/labels at scale: `layout="badge|pin-label|card"`, `min-zoom`/`max-zoom`). External layer classes register via `OmMap.registerLayer({type, deckClass, props})` — build them on `@nika-js/onlymap/deck` (the bundled `CompositeLayer`/`TileLayer`/… re-exports), never a separately-installed deck.gl (different class hierarchy, breaks in the renderer); function-valued props ride the subclass's `static defaultProps`; register at module top level BEFORE the manifest mounts (see docs/custom-layers.md). Data: `data` URL (JSON, GeoJSON, CSV/TSV `.csv` — parsed to typed columns — Shapefile `.shp` (+`.dbf` attributes) and KML `.kml` as GeoJSON features, or Arrow IPC `.arrow`/`.feather` — large point datasets stay columnar, GeoArrow line/polygon geometry becomes GeoJSON features, zstd-compressed IPC is handled; other formats plug in via `OmMap.registerFormat({match, parse})`; data URLs accept any scheme the runtime's fetch supports — desktop webviews (Tauri, Electron) pass asset-protocol URLs straight in), inline `<script type="application/json">` (row arrays or column-oriented `{"columns": {"lon": [...], "lat": [...]}}`), or `wss://` streaming (`key="mmsi"` upserts entities in place, `flush="250ms"` coalesces bursts, `source="name"` selects a `OmMap.registerSource` decoder plugin), or a polled REST snapshot (`refresh="5s"` re-fetches and replaces — for live endpoints that return the full current state). Authenticated endpoints: call `OmMap.configureData({ headers: {...} })` in a script — never put tokens in attributes. `$field` accessors work identically on all of them — never write column-index code yourself. One columnar restriction: the `js` full-JS opt-in is not allowed on Arrow/columnar layers (validation will tell you; use `$field` accessors instead). For 3D models use `type="ScenegraphLayer"` with `scenegraph="./model.glb"` (required) and `get-orientation="[0, $heading, 90]"` — the roll of 90 stands Y-up glTF models upright; see docs/3d-assets.md. GeoTIFF/COG rasters use `type="COGLayer"` with `src="./dem.tif"` (NOT `data` — rasters stream tiles by Range request, they are not parsed rows): `min`/`max` set the rescale window (default 0–255; ALWAYS set them for float/16-bit data like DEMs), `colormap` picks a bundled ramp for single-band sources (gray, viridis, plasma, inferno, magma, cividis, rdylgn, rdbu, spectral, terrain, jet, turbo), `nodata` overrides the source sentinel (renders transparent); plain 8-bit RGB COGs need no styling attributes; restretch/recolor are GPU uniforms (no refetch) and the legend ramp derives from colormap+min/max automatically. Sources must be Cloud-Optimized (`gdal_translate -of COG` otherwise).
21
+ - `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite" position="bottom-right">` — static UI panels. Built-ins are themeable from page CSS via custom properties (they inherit through the shadow root): `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` — full set: `--om-widget-bg/-fg/-muted/-border/-hover-bg/-accent`; scope to a single widget with an `om-widget[type=legend]` selector instead. No `type` + HTML + `<script type="om/widget">` = custom widget with `ctx` (`ctx.layers`, `ctx.data(id)`, `ctx.dataInViewport(id)`, `ctx.stats(id, field)`, `ctx.viewport`, `ctx.selection`, `ctx.emit(action, payload)`), `this.watch = ['data:<layerId>', 'viewport', 'selection', 'layers', 'history', 'basemap']` (`layers` also fires on visibility/filter changes; `basemap` on basemap switches; `history` on undo/redo availability), `this.$(sel)`, `vegaEmbed`/`d3` as globals.
22
22
  - `<om-overlay id="..." anchor-from="selection">` — rich geo-anchored HTML (≤ ~20 per map). Anchors: `anchor="[lng, lat]"` (static), `anchor-from="selection"` (follows picks), or `anchor-layer="regions" anchor-feature-id="mission"` (anchored to a feature's own geometry — bbox center — no coordinates in markup; `{{field}}` interpolates that feature's attributes). `{{field}}` interpolates the picked feature HTML-escaped; `{{{field}}}` is raw (avoid). For labels on many features use `PopupLayer`, not overlays.
23
23
  - `<om-behavior on="click|hover|drag|load|data-loaded" layer="..." action="...">` — declarative interaction. Built-in actions: `show-overlay`, `hide-overlay`, `show-tooltip`, `hide-tooltip`, `toggle-layer`, `filter-layer`, `highlight-feature`, `zoom-to-feature`, `set-basemap`, `undo`, `redo`. One payload contract everywhere: `{ layer, target, feature, featureId, coordinate }`.
24
24
  - Undo/redo is built in: user-facing manifest changes (layer toggles, filter changes, basemap switches, element add/remove, drawn sketches) are recorded automatically — the manifest is the state. `<om-widget type="undo-redo">` renders the buttons; Cmd/Ctrl-Z, Shift-Cmd/Ctrl-Z, and Ctrl-Y work on any map (text inputs keep their native undo). Camera moves, hover effects, and story playback are deliberately NOT undo steps. Widget scripts: `ctx.history.canUndo/canRedo` with watch token `history`; `ctx.emit("undo")`/`ctx.emit("redo")`.
@@ -216,6 +216,9 @@
216
216
  {
217
217
  "name": "BitmapLayer"
218
218
  },
219
+ {
220
+ "name": "COGLayer"
221
+ },
219
222
  {
220
223
  "name": "ColumnLayer"
221
224
  },
@@ -559,6 +562,26 @@
559
562
  "name": "point-size",
560
563
  "description": "deck.gl pointSize."
561
564
  },
565
+ {
566
+ "name": "src",
567
+ "description": "deck.gl geotiff."
568
+ },
569
+ {
570
+ "name": "min",
571
+ "description": "deck.gl rescaleMin."
572
+ },
573
+ {
574
+ "name": "max",
575
+ "description": "deck.gl rescaleMax."
576
+ },
577
+ {
578
+ "name": "colormap",
579
+ "description": "deck.gl colormap."
580
+ },
581
+ {
582
+ "name": "nodata",
583
+ "description": "deck.gl nodataOverride."
584
+ },
562
585
  {
563
586
  "name": "operation",
564
587
  "description": "deck.gl operation."
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nika-js/onlymap",
3
- "version": "0.3.0",
4
- "description": "Declarative deck.gl maps for HTML and React interactive WebGL mapping with GeoJSON/CSV/Arrow data, MapLibre basemaps, widgets, popups, and live streams from a custom-element manifest or typed React components. TypeScript, no build step.",
3
+ "version": "0.3.1",
4
+ "description": "Declarative deck.gl maps for HTML and React \u2014 interactive WebGL mapping with GeoJSON/CSV/Arrow data, MapLibre basemaps, widgets, popups, and live streams from a custom-element manifest or typed React components. TypeScript, no build step.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -54,7 +54,6 @@
54
54
  ".": {
55
55
  "types": "./dist/index.d.ts",
56
56
  "import": "./dist/onlymapjs.js",
57
- "require": "./dist/onlymapjs.umd.cjs",
58
57
  "default": "./dist/onlymapjs.js"
59
58
  },
60
59
  "./react": {
@@ -90,6 +89,7 @@
90
89
  "dist",
91
90
  "README.md",
92
91
  "LICENSE.md",
92
+ "THIRD-PARTY-LICENSES.md",
93
93
  "docs",
94
94
  "llms.txt",
95
95
  "onlymapjs.html-data.json",
@@ -97,7 +97,7 @@
97
97
  ],
98
98
  "scripts": {
99
99
  "dev": "vite",
100
- "build": "npm run typecheck && vite build && vite build --config vite.react.config.ts && vite build --config vite.deck.config.ts && npm run build:types",
100
+ "build": "npm run typecheck && npm run gen:licenses && vite build && vite build --config vite.react.config.ts && vite build --config vite.deck.config.ts && npm run build:types",
101
101
  "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
102
102
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.e2e.json --noEmit && tsc -p cloud/workers/telemetry/tsconfig.json",
103
103
  "test": "vitest run",
@@ -108,17 +108,19 @@
108
108
  "gen:public": "npm run gen:html-data && vite-node dev/build-public.ts",
109
109
  "test:public": "npm run gen:public -- --dry-run --strict",
110
110
  "deploy:telemetry": "wrangler deploy --config cloud/workers/telemetry/wrangler.toml",
111
- "dev:telemetry": "wrangler dev --config cloud/workers/telemetry/wrangler.toml"
111
+ "dev:telemetry": "wrangler dev --config cloud/workers/telemetry/wrangler.toml",
112
+ "gen:licenses": "node dev/gen-third-party-licenses.mjs"
112
113
  },
113
- "comment:deps": "This library ships a fully self-contained bundle in dist/ (verified: zero external bare imports), so it has NO runtime dependencies deck.gl, loaders.gl, MapLibre, Arrow, d3, acorn, etc. are build-time-only and get bundled by Vite. Keeping them here (not in dependencies) is what stops every consumer install from pulling ~250 MB it never uses.",
114
+ "comment:deps": "This library ships a fully self-contained bundle in dist/ (verified: zero external bare imports), so it has NO runtime dependencies \u2014 deck.gl, loaders.gl, MapLibre, Arrow, d3, acorn, etc. are build-time-only and get bundled by Vite. Keeping them here (not in dependencies) is what stops every consumer install from pulling ~250 MB it never uses.",
114
115
  "devDependencies": {
115
- "@deck.gl/aggregation-layers": "^9.3.5",
116
- "@deck.gl/core": "^9.3.5",
117
- "@deck.gl/extensions": "^9.3.5",
118
- "@deck.gl/geo-layers": "^9.3.5",
119
- "@deck.gl/layers": "^9.3.5",
120
- "@deck.gl/mapbox": "^9.3.5",
121
- "@deck.gl/mesh-layers": "^9.3.5",
116
+ "@deck.gl/aggregation-layers": "9.3.5",
117
+ "@deck.gl/core": "9.3.5",
118
+ "@deck.gl/extensions": "9.3.5",
119
+ "@deck.gl/geo-layers": "9.3.5",
120
+ "@deck.gl/layers": "9.3.5",
121
+ "@deck.gl/mapbox": "9.3.5",
122
+ "@deck.gl/mesh-layers": "9.3.5",
123
+ "@developmentseed/deck.gl-geotiff": "^0.7.0",
122
124
  "@loaders.gl/csv": "^4.4.3",
123
125
  "@loaders.gl/kml": "^4.4.3",
124
126
  "@loaders.gl/shapefile": "^4.4.3",
@@ -149,5 +151,6 @@
149
151
  "vitest": "^4.1.9",
150
152
  "wrangler": "^4.110.0",
151
153
  "ws": "^8.21.0"
152
- }
153
- }
154
+ },
155
+ "comment:deck-pin": "deck.gl/luma.gl devDeps are EXACT pins: they get BUNDLED into dist, and both libraries hard-throw on duplicate-version detection \u2014 consumers that also ship their own deck.gl (nika-agent) must match these versions exactly, so bumps are breaking-coordination events, never side effects of a reinstall."
156
+ }
@@ -23,7 +23,7 @@ Use OnlyMapJS as a declarative HTML map library. Write custom elements such as `
23
23
  </script>
24
24
  ```
25
25
 
26
- For no-build CDN examples, use a published module URL such as `https://esm.sh/@nika-js/onlymap@0.3.0`.
26
+ For no-build CDN examples, use a published module URL such as `https://esm.sh/@nika-js/onlymap@0.3.1`.
27
27
 
28
28
  ## React Projects
29
29
 
@@ -51,6 +51,7 @@ Load the smallest reference needed for the task:
51
51
  - Attribute names are kebab-case: `get-fill-color`, `radius-units`, `line-width-min-pixels`.
52
52
  - Accessor values are expressions: `get-position="[$lon, $lat]"`.
53
53
  - `scale()` always needs an explicit `domain=`.
54
+ - Prefer canonical color expressions — a `sequential`/`diverging`/`threshold` `scale()` or an equality ternary chain — over hand-rolled arithmetic: the legend widget parses these shapes and renders a matching gradient ramp / class ranges / category palette automatically.
54
55
  - Inline handlers such as `onclick` are wrong. Use `data-emit`, `<om-behavior>`, or widget scripts.
55
56
  - Full JavaScript accessor blocks require the `js` attribute on `<om-layer>`.
56
57
  - Do not put secrets in markup. Use `OmMap.configureData({ headers, credentials, fetch })`.
@@ -63,6 +64,7 @@ Load the smallest reference needed for the task:
63
64
  - Guided tour or narrative sequence -> `<om-story>` with `<om-step>` siblings that reference existing layers/overlays by id.
64
65
  - Basemap choice or user-switchable basemaps -> `basemap` presets (`positron`, `liberty`, `dark-matter`, `osm`, ...) + `<om-widget type="basemap-switcher">`; MapTiler custom styles via a style URL or `basemap-key`.
65
66
  - Undoable UI (step back after layer toggles, filter changes, basemap switches, sketch edits) -> `<om-widget type="undo-redo">`; Cmd/Ctrl-Z works even without the widget. Camera moves and story playback are not undo steps.
67
+ - GeoTIFF/COG raster (DEM, satellite imagery, NDVI) -> `<om-layer type="COGLayer" src="…tif">` with `min`/`max`/`colormap` for single-band data (see syntax.md — `src`, not `data`).
66
68
  - Live entity updates -> `wss://` stream with `key` and optional `source` decoder.
67
69
  - REST snapshot that changes over time -> `refresh="5s"`.
68
70
  - User sketching -> `data="draw:sketch"` layer plus `<om-widget type="draw" target="sketch">`.
@@ -100,7 +100,7 @@ filter-field="magnitude" filter-range="[4, 10]"
100
100
 
101
101
  Use the `type` value exactly:
102
102
 
103
- `A5Layer`, `ArcLayer`, `BitmapLayer`, `ColumnLayer`, `ContourLayer`, `GeoJsonLayer`, `GeohashLayer`, `GreatCircleLayer`, `GridCellLayer`, `GridLayer`, `H3ClusterLayer`, `H3HexagonLayer`, `HeatmapLayer`, `HexagonLayer`, `IconLayer`, `LineLayer`, `MVTLayer`, `PathLayer`, `PointCloudLayer`, `PolygonLayer`, `PopupLayer`, `QuadkeyLayer`, `S2Layer`, `ScatterplotLayer`, `ScenegraphLayer`, `ScreenGridLayer`, `SimpleMeshLayer`, `SolidPolygonLayer`, `TerrainLayer`, `TextLayer`, `Tile3DLayer`, `TileLayer`, `TripsLayer`.
103
+ `A5Layer`, `ArcLayer`, `BitmapLayer`, `COGLayer`, `ColumnLayer`, `ContourLayer`, `GeoJsonLayer`, `GeohashLayer`, `GreatCircleLayer`, `GridCellLayer`, `GridLayer`, `H3ClusterLayer`, `H3HexagonLayer`, `HeatmapLayer`, `HexagonLayer`, `IconLayer`, `LineLayer`, `MVTLayer`, `PathLayer`, `PointCloudLayer`, `PolygonLayer`, `PopupLayer`, `QuadkeyLayer`, `S2Layer`, `ScatterplotLayer`, `ScenegraphLayer`, `ScreenGridLayer`, `SimpleMeshLayer`, `SolidPolygonLayer`, `TerrainLayer`, `TextLayer`, `Tile3DLayer`, `TileLayer`, `TripsLayer`.
104
104
 
105
105
  Common choices:
106
106
 
@@ -110,6 +110,22 @@ Common choices:
110
110
  - Aggregation: `HeatmapLayer`, `HexagonLayer`, `GridLayer`, `ScreenGridLayer`.
111
111
  - Tiles: `TileLayer`, `MVTLayer`, `Tile3DLayer`.
112
112
  - 3D models: `ScenegraphLayer`, `SimpleMeshLayer`, `PointCloudLayer`, `Tile3DLayer`.
113
+ - GeoTIFF/COG rasters: `COGLayer`.
114
+
115
+ ### COGLayer (GeoTIFF / COG rasters)
116
+
117
+ ```html
118
+ <om-layer id="dem" type="COGLayer" label="Elevation"
119
+ src="./elevation.tif" min="0" max="1900" colormap="viridis" nodata="-9999"></om-layer>
120
+ ```
121
+
122
+ - `src` (required) — the GeoTIFF URL. NOT `data`: rasters stream tiles by HTTP Range request through the layer's own reader; they are never parsed rows (`$field`, `ctx.data()`, `ctx.stats()`, filters do not apply).
123
+ - Sources must be Cloud-Optimized GeoTIFFs (`gdal_translate -of COG` otherwise).
124
+ - `min`/`max` — the rescale window mapped onto the colormap. Defaults to 0–255, so ALWAYS set them for float or 16-bit data (DEMs, NDVI, temperature).
125
+ - `colormap` — single-band ramps from the bundled sprite: `gray` (default), `viridis`, `plasma`, `inferno`, `magma`, `cividis`, `rdylgn`, `rdbu`, `spectral`, `terrain`, `jet`, `turbo`. Sources with 3+ bands composite as RGB and ignore it.
126
+ - `nodata` — overrides the source's nodata sentinel; nodata pixels render transparent.
127
+ - Plain 8-bit RGB COGs (satellite truecolor) need no styling attributes at all.
128
+ - Restretch/recolor (min/max/colormap edits) are GPU uniform updates — tiles are not refetched. The legend widget renders the colormap ramp automatically when `colormap` + `min`/`max` are authored.
113
129
 
114
130
  External layer classes become manifest types via `OmMap.registerLayer({type, deckClass, props})`. Build them on `@nika-js/onlymap/deck` (the bundled `CompositeLayer`/`TileLayer`/… re-exports — a separately-installed deck.gl is a different class hierarchy and breaks in the renderer); function-valued props ride the subclass's `static defaultProps`; register at module top level before the manifest mounts. Full recipe: docs/custom-layers.md.
115
131
 
@@ -183,7 +199,7 @@ Do not use full-JS blocks on columnar/Arrow layers.
183
199
 
184
200
  Built-ins:
185
201
 
186
- - `legend`
202
+ - `legend` — symbology-aware by default: it parses each layer's `get-fill-color`. A `sequential`/`diverging` `scale()` renders as a gradient ramp with the domain ends labeled; a `threshold` scale as discrete class ranges (`< b1`, `b1 – b2`, `≥ bN`); an equality ternary chain (`$f == 'a' ? '#c1' : $f == 'b' ? '#c2' : '#fallback'`) as a category palette with an "other" row. Any other expression falls back to the single `color` swatch — so writing the canonical shapes buys a self-describing legend for free.
187
203
  - `layer-switcher`
188
204
  - `zoom-controls`
189
205
  - `scale-bar`
@@ -198,6 +214,17 @@ Built-ins:
198
214
 
199
215
  Positions: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
200
216
 
217
+ Theming: built-in widgets read `--om-widget-*` CSS custom properties, which inherit through their shadow roots — so plain page CSS themes them, no JS:
218
+
219
+ ```css
220
+ /* map-wide (every widget) */
221
+ om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }
222
+ /* or one widget */
223
+ om-widget[type="legend"] { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }
224
+ ```
225
+
226
+ The full set: `--om-widget-bg` (panel/button background), `--om-widget-fg` (text), `--om-widget-muted` (secondary text: legend field/domain labels, disabled buttons, clocks), `--om-widget-border` (separators, button outlines), `--om-widget-hover-bg` (button hover), `--om-widget-accent` (player transport buttons). Unset properties fall back to the stock light palette.
227
+
201
228
  Examples:
202
229
 
203
230
  ```html