@nika-js/onlymap 0.5.2 → 0.5.3

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.
package/README.md CHANGED
@@ -69,7 +69,7 @@ Or with no build step at all, straight from a CDN:
69
69
 
70
70
  The bare package URL serves `dist/onlymap.standalone.js`, a single-file bundle built for exactly this (jsDelivr too). Use a CDN that serves the package's raw files — **not** a rebundling CDN like esm.sh, which re-splits the bundle into duplicate copies of the deck.gl/luma.gl runtime and breaks every layer's shader compilation.
71
71
 
72
- Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 595 unit/behavioral tests and 58 Playwright GPU tests.
72
+ Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 628 unit/behavioral tests and 58 Playwright GPU tests.
73
73
 
74
74
  The [examples](https://github.com/NikaGeospatial/onlymapjs/tree/main/examples) are the best tour: widgets, behaviors & overlays, basemaps, columnar/Arrow data, manual drawing, 3D models, scene lighting (with the native lighting widget), DEM terrain, a live WebSocket ship feed, and a polled driver fleet.
75
75
 
@@ -81,7 +81,7 @@ A handful of elements, one rule: **attributes are kebab-case versions of deck.gl
81
81
  |---|---|
82
82
  | `<om-map>` | The map. `center`, `zoom`, `pitch`, `bearing`; `basemap` takes a free preset (`positron`, `liberty`, `dark-matter`, `osm`, …), a style URL, or `"none"` (standalone canvas) — and switches **live**; `validate` for a live on-page error panel. Give it a height: a custom element is `display:inline` by default, so the library injects a `display:block` default (fills a sized parent, else a 400px floor) to keep a bare map visible, but set an explicit height (`om-map { height: 100vh }`) for real layout — any height you set wins outright, including one below the floor. A map that still collapses warns in the console; `hidden` and `display:none` maps stay hidden and stay quiet. |
83
83
  | `<om-layer>` | Any of **34 layer types** by name — all of deck.gl's core, geo, aggregation, and mesh layers (Scatterplot, GeoJson, Arc, Path, Heatmap, Hexagon, Trips, Tile, Tile3D, Scenegraph, …) plus the built-in `PopupLayer` for WebGL badges/labels at scale and the native `COGLayer` for GeoTIFF rasters. `id` required; `label`/`color` feed the legend. |
84
- | `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar`, `attribution`, `filter`, `draw`, `vega-lite` (live charts). Or write your own inline with HTML + a `<script type="om/widget">`. Adjacent compact button widgets (`zoom-controls`, `undo-redo`, `widgets-toggle`) **auto-cluster** into one control group (opt out per widget with `cluster="false"`), and `<om-map widgets-hidden>` / the `set-widgets-visible` action / `<om-widget type="widgets-toggle">` hide all authored chrome without destroying it — provider attribution and the license badge never hide. **Placement is managed**: `position` takes one of 8 logical, RTL-aware slots (`top-start`, `top-center`, `top-end`, `center-start`, `center-end`, `bottom-start`, `bottom-center`, `bottom-end`; legacy corner names alias) — same-slot widgets stack with flush edges and a shared gap, `order` sets in-slot ordering, and `position="manual"` opts out entirely (a plain block you style yourself, even outside the map). At map widths ≤640px, managed widgets automatically move into accessible top/end/bottom/start drawers; `fold="never"` keeps an essential control out, `widgets-fold="off"` disables folding, and `--om-widget-fold-breakpoint` changes the map-width threshold. Provider attribution is an in-flow member of `bottom-end` and the license badge of `bottom-start`, so neither covers a widget. A slot dims automatically while an open popup covers it (`widgets-dim="off"` to disable), except slots containing required chrome. Themeable from plain page CSS via custom properties: `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` (also `-muted`, `-border`, `-hover-bg`, `-accent`), plus layout tokens (`--om-widget-inset-x/-y`, `--om-widget-gap-x/-y`, `--om-widget-opacity`, `--om-widget-radius`) or the no-CSS sugar `<om-map widget-style="gap:10 opacity:0.9">`. |
84
+ | `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar` (metric/imperial/nautical `units`), `attribution`, `filter`, `draw`, `measure` (geodesic distance + area — live labels, `units` toggle, an `om-measure` readout event), `vega-lite` (live charts). Or write your own inline with HTML + a `<script type="om/widget">`. Adjacent compact button widgets (`zoom-controls`, `undo-redo`, `widgets-toggle`) **auto-cluster** into one control group (opt out per widget with `cluster="false"`), and `<om-map widgets-hidden>` / the `set-widgets-visible` action / `<om-widget type="widgets-toggle">` hide all authored chrome without destroying it — provider attribution and the license badge never hide. **Placement is managed**: `position` takes one of 8 logical, RTL-aware slots (`top-start`, `top-center`, `top-end`, `center-start`, `center-end`, `bottom-start`, `bottom-center`, `bottom-end`; legacy corner names alias) — same-slot widgets stack with flush edges and a shared gap, `order` sets in-slot ordering, and `position="manual"` opts out entirely (a plain block you style yourself, even outside the map). At map widths ≤640px, managed widgets automatically move into accessible top/end/bottom/start drawers; `fold="never"` keeps an essential control out, `widgets-fold="off"` disables folding, and `--om-widget-fold-breakpoint` changes the map-width threshold. Provider attribution is an in-flow member of `bottom-end` and the license badge of `bottom-start`, so neither covers a widget. A slot dims automatically while an open popup covers it (`widgets-dim="off"` to disable), except slots containing required chrome. Themeable from plain page CSS via custom properties: `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` (also `-muted`, `-border`, `-hover-bg`, `-accent`), plus layout tokens (`--om-widget-inset-x/-y`, `--om-widget-gap-x/-y`, `--om-widget-opacity`, `--om-widget-radius`) or the no-CSS sugar `<om-map widget-style="gap:10 opacity:0.9">`. |
85
85
  | `<om-overlay>` | Rich HTML anchored to a map location — a static `anchor="[lng, lat]"`, the current selection, or a feature's own geometry via `anchor-layer`/`anchor-feature-id`. `{{field}}` interpolates the picked feature, HTML-escaped by default. |
86
86
  | `<om-behavior>` | Declarative interactions: `on="click|hover|drag|load|data-loaded"` → a named action. |
87
87
  | `<om-story>` | A storyboard: `<om-step>` children fire actions on a timeline. Controlled by the `player` widget, behaviors, or `storyEl.play()/pause()/seek()`. |
@@ -19,7 +19,7 @@ async function v(N = {}) {
19
19
  if (n) {
20
20
  const { createRequire: I } = await import(
21
21
  /*webpackIgnore:true*/
22
- "./lerc-C_VBg2kh.js"
22
+ "./lerc-n0GaWEmL.js"
23
23
  ).then((g) => g._);
24
24
  var O = I(import.meta.url);
25
25
  }
@@ -1,4 +1,4 @@
1
- import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-ytnYzWRW.js";
1
+ import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-BoAq5XsU.js";
2
2
  const Cd = Math.PI / 180, gy = 180 / Math.PI;
3
3
  function em(Pe, Q = 0) {
4
4
  const me = Math.min(180, Pe) * Cd;
@@ -10,7 +10,7 @@
10
10
  * `draw-*` actions (src/actions.ts) plus keyboard — so the widget stays pure
11
11
  * UI and no "widget injects a layer" capability is needed.
12
12
  */
13
- import { type DrawMode } from "./draw";
13
+ import { DrawSession, type DrawMode } from "./draw";
14
14
  import type { RuntimeCore } from "./runtime-core";
15
15
  export type SaveMode = "download" | "file-system" | "both";
16
16
  /** `sketch` → `draw:sketch` (idempotent if already prefixed). */
@@ -20,10 +20,19 @@ export declare class DrawController {
20
20
  private readonly core;
21
21
  private readonly sessions;
22
22
  private readonly autosaveKeys;
23
+ private readonly observers;
23
24
  private active;
24
25
  constructor(mapEl: Element, core: RuntimeCore);
25
26
  /** A draw tool is active — om-map routes points here and suppresses selection. */
26
27
  isCapturing(): boolean;
28
+ /**
29
+ * Watch a target's session (spec: issue #20 measure widget). The callback
30
+ * fires after every mutation of that target's session — vertex added, cursor
31
+ * moved, commit, cancel, clear — so the measure controller can recompute
32
+ * geodesic readouts + labels live off the shared draw stack. `null` unsubs.
33
+ */
34
+ setObserver(target: string, cb: ((session: DrawSession) => void) | null): void;
35
+ private notify;
27
36
  /** Enable localStorage autosave for a target and restore any saved sketch (draw-config action). */
28
37
  configure(target: string, opts: {
29
38
  autosave?: string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Geodesy (spec: issue #20 "Measure widget — geodesic distance and area").
3
+ *
4
+ * Pure spherical math on a sphere of the WGS84 MEAN radius (2a + b)/3 =
5
+ * 6 371 008.7714 m. A sphere, not the ellipsoid: documented accuracy vs. the
6
+ * true WGS84 geodesic is ≤ 0.56% worst case (near-meridional lines), typically
7
+ * < 0.3%. Deliberately NOT Vincenty (fails to converge near antipodes) and NOT
8
+ * a Karney/GeographicLib port (overkill for v1) — but every signature takes the
9
+ * radius, so an ellipsoidal `geodesic="ellipsoidal"` opt-in can slot in later
10
+ * without touching callers. Haversine uses GL's numerically-guarded form
11
+ * `2R·asin(min(1, √h))`. Area is the Chamberlain–Duquette line integral (the
12
+ * JPL formulation Turf's area uses), returning |signed area| so self-
13
+ * intersecting lobes cancel.
14
+ */
15
+ /** WGS84 mean radius (2a + b)/3, with a = 6378137 m, 1/f = 298.257223563. */
16
+ export declare const WGS84_MEAN_RADIUS_M = 6371008.7714;
17
+ /** The sphere radius (m) current measurements resolve against. */
18
+ export declare function getMeasureRadiusMeters(): number;
19
+ /** Override the measure radius (e.g. a non-Earth body). Non-positive values reset to WGS84. */
20
+ export declare function setMeasureRadiusMeters(radiusMeters: number): void;
21
+ export type LngLat = [number, number];
22
+ /** Haversine great-circle distance in meters (guarded `2R·asin(min(1, √h))`). */
23
+ export declare function distanceMeters(a: LngLat, b: LngLat, radius?: number): number;
24
+ /** Total haversine length of a polyline in meters (0 for < 2 points). */
25
+ export declare function pathLengthMeters(points: LngLat[], radius?: number): number;
26
+ /**
27
+ * Spherical polygon area in m² via the Chamberlain–Duquette line integral (the
28
+ * exact index form Turf's `ringArea` uses, so results match Turf's fixtures).
29
+ * Returns the ABSOLUTE value; ring winding and self-intersection lobes cancel.
30
+ * Pass the OPEN ring (a trailing duplicate of the first point is tolerated).
31
+ */
32
+ export declare function ringAreaMeters2(ring: LngLat[], radius?: number): number;
33
+ /** Perimeter of a ring in meters (closes the ring implicitly). */
34
+ export declare function ringPerimeterMeters(ring: LngLat[], radius?: number): number;
35
+ /**
36
+ * Does this ring wind around a pole? The Chamberlain–Duquette integral
37
+ * misreports a pole-enclosing ring, so the widget refuses an area for one.
38
+ * Test: sum the per-edge longitude steps normalized to (−180, 180]; a ring that
39
+ * does NOT enclose a pole nets ~0, one that circles a pole nets ~±360.
40
+ */
41
+ export declare function ringEnclosesPole(ring: LngLat[]): boolean;
42
+ /**
43
+ * Great-circle midpoint of a segment — antimeridian-safe (converts to Cartesian
44
+ * so a segment crossing ±180° gets its label on the shorter arc, not the middle
45
+ * of the wrong way round). Returns [lng (normalized to (−180, 180]), lat].
46
+ */
47
+ export declare function midpoint(a: LngLat, b: LngLat): LngLat;
@@ -15,9 +15,9 @@ interface TagData {
15
15
  }[];
16
16
  }
17
17
  /** Hand-authored action list — validated against hasAction() in html-data.test.ts. */
18
- export declare const BUILTIN_ACTIONS: readonly ["toggle-layer", "zoom-in", "zoom-out", "filter-layer", "highlight-feature", "fade", "pulse", "populate", "trace", "story-play", "story-pause", "story-seek", "fly-to", "zoom-to-feature", "show-overlay", "hide-overlay", "show-tooltip", "hide-tooltip", "draw-mode", "draw-commit", "draw-cancel", "draw-delete", "draw-clear", "draw-config", "draw-save", "set-basemap", "set-lighting", "set-terrain", "set-widgets-visible", "undo", "redo"];
18
+ export declare const BUILTIN_ACTIONS: readonly ["toggle-layer", "zoom-in", "zoom-out", "filter-layer", "highlight-feature", "fade", "pulse", "populate", "trace", "story-play", "story-pause", "story-seek", "fly-to", "zoom-to-feature", "show-overlay", "hide-overlay", "show-tooltip", "hide-tooltip", "draw-mode", "draw-commit", "draw-cancel", "draw-delete", "draw-clear", "draw-config", "draw-save", "measure-mode", "measure-clear", "measure-units", "set-basemap", "set-lighting", "set-terrain", "set-widgets-visible", "undo", "redo"];
19
19
  /** Hand-authored widget types — validated against the widget registry in html-data.test.ts. */
20
- export declare const BUILTIN_WIDGETS: readonly ["legend", "layer-switcher", "zoom-controls", "scale-bar", "attribution", "filter", "vega-lite", "player", "draw", "basemap-switcher", "lighting", "undo-redo", "widgets-toggle"];
20
+ export declare const BUILTIN_WIDGETS: readonly ["legend", "layer-switcher", "zoom-controls", "scale-bar", "attribution", "filter", "vega-lite", "player", "draw", "measure", "basemap-switcher", "lighting", "undo-redo", "widgets-toggle"];
21
21
  export declare function buildHtmlCustomData(): {
22
22
  version: number;
23
23
  tags: TagData[];
@@ -1,4 +1,4 @@
1
- import { ai as Bt, av as Xt, aw as Yt, ax as Us } from "./index-ytnYzWRW.js";
1
+ import { ai as Bt, av as Xt, aw as Yt, ax as Us } from "./index-BoAq5XsU.js";
2
2
  import { w as gs, t as Hs, f as Ws, m as Qs } from "./mgrs-BY9bIvp4.js";
3
3
  import { c as Xs } from "./convert-arrow-schema-DrAihRf9.js";
4
4
  import { y as Ys, A as bt, a as Ks, z as Vs, R as Zs } from "./recordbatch-Bpc0uxFn.js";
@@ -1,5 +1,5 @@
1
- import { i as E, j as S, k as R, n as N, o as h, p as w, q as O, s as W, r as x, t as I, v as B, w as _, x as P, y as k, A as $, B as z, E as v, F, H as C, I as U, J as p, K as M } from "./index-ytnYzWRW.js";
2
- import { N as $e, R as ze, _ as ve, O as Ce, P as Ue, Q as Me, S as je, T as De, U as Je, X as qe, Y as He, Z as Ve, $ as Ge, a0 as Ke, a1 as Qe, a2 as Xe, a3 as Ye, a4 as Ze, a5 as et, a6 as tt, a7 as nt, a8 as rt, a9 as at, aa as st, ab as ot } from "./index-ytnYzWRW.js";
1
+ import { i as E, j as S, k as R, n as N, o as h, p as w, q as O, s as W, r as x, t as I, v as B, w as _, x as P, y as k, A as $, B as z, E as v, F, H as C, I as U, J as p, K as M } from "./index-BoAq5XsU.js";
2
+ import { N as $e, R as ze, _ as ve, O as Ce, P as Ue, Q as Me, S as je, T as De, U as Je, X as qe, Y as He, Z as Ve, $ as Ge, a0 as Ke, a1 as Qe, a2 as Xe, a3 as Ye, a4 as Ze, a5 as et, a6 as tt, a7 as nt, a8 as rt, a9 as at, aa as st, ab as ot } from "./index-BoAq5XsU.js";
3
3
  import { g as j, i as D } from "./table-accessors-CYWTzpQI.js";
4
4
  async function J(t, e, n = {}, r = {}) {
5
5
  const a = E(t), s = S.getWorkerFarm(n), { source: o } = n, c = { name: a, source: o };