@nika-js/onlymap 0.3.0 → 0.3.2

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 (47) hide show
  1. package/.vscode/onlymap.code-snippets +13 -0
  2. package/LICENSE.md +4 -0
  3. package/README.md +11 -6
  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-BMi87Jez.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-D_BgGpIa.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-CDvm17M5.js +1446 -0
  17. package/dist/index-C_zLohC_.js +3108 -0
  18. package/dist/{index-C9tgnPNw.js → index-CaLhpYyz.js} +4 -4
  19. package/dist/{index-CsicbycJ.js → index-DYtODPDb.js} +21383 -21872
  20. package/dist/index-idvzm9MK.js +594 -0
  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-DgHmuuRe.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/onlymap.standalone.js +133480 -0
  30. package/dist/onlymapjs.css +1 -1
  31. package/dist/onlymapjs.js +24 -23
  32. package/dist/raster-D-MVtlA7.js +8843 -0
  33. package/dist/raster.d.ts +61 -0
  34. package/dist/{recordbatch-HRu0SMKp.js → recordbatch-Bpc0uxFn.js} +200 -200
  35. package/dist/style.css +1 -0
  36. package/dist/{table-accessors-DBjWgN0C.js → table-accessors-CYWTzpQI.js} +71 -76
  37. package/dist/version.d.ts +1 -1
  38. package/dist/zstd-jXobGRcq.js +14 -0
  39. package/llms.txt +10 -3
  40. package/onlymapjs.html-data.json +23 -0
  41. package/package.json +27 -15
  42. package/skills/onlymapjs/SKILL.md +3 -1
  43. package/skills/onlymapjs/references/syntax.md +32 -4
  44. package/dist/index-CiuGqS0i.js +0 -605
  45. package/dist/index-DXoRERAy.js +0 -1457
  46. package/dist/index-Ztkd30f8.js +0 -3907
  47. 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.2";
@@ -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
@@ -4,21 +4,28 @@
4
4
 
5
5
  OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the delta from what you already assume — following them produces correct manifests on the first pass. Validate with `OmMap.validate(htmlString)` (structured errors, each with a `fix` instruction) and inspect resolved output with `OmMap.snapshotIR(htmlString)` before finalizing.
6
6
 
7
+ ## Loading the library
8
+
9
+ - npm projects: `import "@nika-js/onlymap"` + `import "@nika-js/onlymap/onlymapjs.css"`.
10
+ - No-build/CDN pages (any HTML file that travels as an artifact): load the single-file standalone bundle from a raw-file CDN — `<script type="module" src="https://unpkg.com/@nika-js/onlymap"></script>` (resolves to `dist/onlymap.standalone.js`; pin `@<version>` in files meant to be shared) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap/dist/onlymapjs.css">`. The stylesheet is `dist/onlymapjs.css`, NOT `dist/style.css` (an alias ships for that guess, but write the canonical path).
11
+ - NEVER load from a rebundling CDN (esm.sh, skypack, jspm): re-bundling the code-split dist duplicates the deck.gl/luma.gl runtime across lazy chunks, and every layer then fails shader compilation (`DECKGL_FILTER_*: no matching overloaded function`) while the basemap still renders. Raw-file CDNs (unpkg, jsDelivr) are fine, for the code-split `dist/onlymapjs.js` too.
12
+
7
13
  ## Rules that differ from your training priors
8
14
 
9
15
  - Always write explicit closing tags: `<om-layer ...></om-layer>`. NEVER self-close (`<om-layer ... />`) — HTML5 ignores the slash on custom elements and every following sibling silently becomes a child.
10
16
  - Attributes are kebab-case, not camelCase: `get-fill-color`, `radius-units`, `line-width-min-pixels`. Every hyphenated attribute maps to the camelCase deck.gl prop.
11
17
  - 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`.
18
+ - A literal color inside a `get-*` accessor is a string IN the expression — quote it: `get-line-color="'#ffffff'"` or an RGBA array `get-line-color="[255,255,255,200]"`. Bare `get-line-color="#ffffff"` is an expression parse error (plain attributes like `color="#dc2626"` take bare hex, accessors do not).
12
19
  - 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
20
  - `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).
21
+ - `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
22
  - 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
23
 
17
24
  ## Element vocabulary
18
25
 
19
26
  - `<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.
27
+ - `<om-layer id="..." type="ScatterplotLayer" data="./points.json">` — any deck.gl layer class by `type` (all 33 bundled, plus the native `COGLayer` raster type), 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).
28
+ - `<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
29
  - `<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
30
  - `<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
31
  - 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.2",
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"
@@ -47,6 +47,9 @@
47
47
  "module": "./dist/onlymapjs.js",
48
48
  "types": "./dist/index.d.ts",
49
49
  "style": "./dist/onlymapjs.css",
50
+ "comment:cdn-fields": "Bare CDN URLs (https://unpkg.com/@nika-js/onlymap) must serve the single-file standalone bundle, never the code-split entry: rebundling CDNs duplicate the deck.gl/luma.gl runtime across lazy chunks and break shader compilation (see vite.standalone.config.ts).",
51
+ "unpkg": "./dist/onlymap.standalone.js",
52
+ "jsdelivr": "./dist/onlymap.standalone.js",
50
53
  "bin": {
51
54
  "onlymapjs": "bin/onlymapjs.mjs"
52
55
  },
@@ -54,7 +57,6 @@
54
57
  ".": {
55
58
  "types": "./dist/index.d.ts",
56
59
  "import": "./dist/onlymapjs.js",
57
- "require": "./dist/onlymapjs.umd.cjs",
58
60
  "default": "./dist/onlymapjs.js"
59
61
  },
60
62
  "./react": {
@@ -67,7 +69,13 @@
67
69
  "import": "./dist/deck.js",
68
70
  "default": "./dist/deck.js"
69
71
  },
72
+ "./standalone": {
73
+ "types": "./dist/index.d.ts",
74
+ "import": "./dist/onlymap.standalone.js",
75
+ "default": "./dist/onlymap.standalone.js"
76
+ },
70
77
  "./onlymapjs.css": "./dist/onlymapjs.css",
78
+ "./style.css": "./dist/style.css",
71
79
  "./onlymapjs.html-data.json": "./onlymapjs.html-data.json",
72
80
  "./onlymap.code-snippets": "./.vscode/onlymap.code-snippets",
73
81
  "./package.json": "./package.json"
@@ -90,6 +98,7 @@
90
98
  "dist",
91
99
  "README.md",
92
100
  "LICENSE.md",
101
+ "THIRD-PARTY-LICENSES.md",
93
102
  "docs",
94
103
  "llms.txt",
95
104
  "onlymapjs.html-data.json",
@@ -97,7 +106,7 @@
97
106
  ],
98
107
  "scripts": {
99
108
  "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",
109
+ "build": "npm run typecheck && npm run gen:licenses && vite build && vite build --config vite.react.config.ts && vite build --config vite.deck.config.ts && vite build --config vite.standalone.config.ts && npm run build:types",
101
110
  "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
102
111
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.e2e.json --noEmit && tsc -p cloud/workers/telemetry/tsconfig.json",
103
112
  "test": "vitest run",
@@ -108,17 +117,19 @@
108
117
  "gen:public": "npm run gen:html-data && vite-node dev/build-public.ts",
109
118
  "test:public": "npm run gen:public -- --dry-run --strict",
110
119
  "deploy:telemetry": "wrangler deploy --config cloud/workers/telemetry/wrangler.toml",
111
- "dev:telemetry": "wrangler dev --config cloud/workers/telemetry/wrangler.toml"
120
+ "dev:telemetry": "wrangler dev --config cloud/workers/telemetry/wrangler.toml",
121
+ "gen:licenses": "node dev/gen-third-party-licenses.mjs"
112
122
  },
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.",
123
+ "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
124
  "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",
125
+ "@deck.gl/aggregation-layers": "9.3.5",
126
+ "@deck.gl/core": "9.3.5",
127
+ "@deck.gl/extensions": "9.3.5",
128
+ "@deck.gl/geo-layers": "9.3.5",
129
+ "@deck.gl/layers": "9.3.5",
130
+ "@deck.gl/mapbox": "9.3.5",
131
+ "@deck.gl/mesh-layers": "9.3.5",
132
+ "@developmentseed/deck.gl-geotiff": "^0.7.0",
122
133
  "@loaders.gl/csv": "^4.4.3",
123
134
  "@loaders.gl/kml": "^4.4.3",
124
135
  "@loaders.gl/shapefile": "^4.4.3",
@@ -149,5 +160,6 @@
149
160
  "vitest": "^4.1.9",
150
161
  "wrangler": "^4.110.0",
151
162
  "ws": "^8.21.0"
152
- }
153
- }
163
+ },
164
+ "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."
165
+ }
@@ -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 pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.3.2` (the bare package URL serves `dist/onlymap.standalone.js`) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.3.2/dist/onlymapjs.css">`. Never a rebundling CDN (esm.sh, skypack): re-bundling duplicates the deck.gl/luma.gl runtime and every layer fails shader compilation.
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">`.
@@ -13,10 +13,11 @@ Vite/npm project:
13
13
  </script>
14
14
  ```
15
15
 
16
- Static CDN page:
16
+ Static CDN page (raw-file CDNs only — unpkg/jsDelivr; never esm.sh or another rebundling CDN, which duplicates the WebGL runtime and breaks layer shaders):
17
17
 
18
18
  ```html
19
- <script type="module" src="https://esm.sh/@nika-js/onlymap@0.1.0"></script>
19
+ <link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.3.2/dist/onlymapjs.css">
20
+ <script type="module" src="https://unpkg.com/@nika-js/onlymap@0.3.2"></script>
20
21
  ```
21
22
 
22
23
  Always include `onlymapjs.css` — it carries the MapLibre basemap styles and the no-JS fallback rules (`<om-fallback>` / default banner). For the fallback to work in script-disabled previews it must load without JavaScript: a real `<link rel="stylesheet">` or inlined `<style>` on no-build pages (a bundler-emitted stylesheet is fine in npm projects).
@@ -100,7 +101,7 @@ filter-field="magnitude" filter-range="[4, 10]"
100
101
 
101
102
  Use the `type` value exactly:
102
103
 
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`.
104
+ `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
105
 
105
106
  Common choices:
106
107
 
@@ -110,6 +111,22 @@ Common choices:
110
111
  - Aggregation: `HeatmapLayer`, `HexagonLayer`, `GridLayer`, `ScreenGridLayer`.
111
112
  - Tiles: `TileLayer`, `MVTLayer`, `Tile3DLayer`.
112
113
  - 3D models: `ScenegraphLayer`, `SimpleMeshLayer`, `PointCloudLayer`, `Tile3DLayer`.
114
+ - GeoTIFF/COG rasters: `COGLayer`.
115
+
116
+ ### COGLayer (GeoTIFF / COG rasters)
117
+
118
+ ```html
119
+ <om-layer id="dem" type="COGLayer" label="Elevation"
120
+ src="./elevation.tif" min="0" max="1900" colormap="viridis" nodata="-9999"></om-layer>
121
+ ```
122
+
123
+ - `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).
124
+ - Sources must be Cloud-Optimized GeoTIFFs (`gdal_translate -of COG` otherwise).
125
+ - `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).
126
+ - `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.
127
+ - `nodata` — overrides the source's nodata sentinel; nodata pixels render transparent.
128
+ - Plain 8-bit RGB COGs (satellite truecolor) need no styling attributes at all.
129
+ - 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
130
 
114
131
  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
132
 
@@ -183,7 +200,7 @@ Do not use full-JS blocks on columnar/Arrow layers.
183
200
 
184
201
  Built-ins:
185
202
 
186
- - `legend`
203
+ - `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
204
  - `layer-switcher`
188
205
  - `zoom-controls`
189
206
  - `scale-bar`
@@ -198,6 +215,17 @@ Built-ins:
198
215
 
199
216
  Positions: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
200
217
 
218
+ Theming: built-in widgets read `--om-widget-*` CSS custom properties, which inherit through their shadow roots — so plain page CSS themes them, no JS:
219
+
220
+ ```css
221
+ /* map-wide (every widget) */
222
+ om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }
223
+ /* or one widget */
224
+ om-widget[type="legend"] { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }
225
+ ```
226
+
227
+ 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.
228
+
201
229
  Examples:
202
230
 
203
231
  ```html