@nika-js/onlymap 0.5.1 → 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 +2 -2
- package/dist/{LercDecode.es-BNF7PeWz.js → LercDecode.es-S6KKhkuA.js} +1 -1
- package/dist/{basemap-7GQL0KSL.js → basemap-BSCq9hHZ.js} +1 -1
- package/dist/draw-controller.d.ts +10 -1
- package/dist/geodesy.d.ts +47 -0
- package/dist/html-data.d.ts +2 -2
- package/dist/{index-BKiYWYJz.js → index-BCgjJcZn.js} +1 -1
- package/dist/{index-9W_UXlPB.js → index-BTcRCrfX.js} +2 -2
- package/dist/{index-ppCRU8Eb.js → index-BoAq5XsU.js} +46502 -46181
- package/dist/{index-ULFXRQAo.js → index-D1swbykc.js} +1 -1
- package/dist/{index-7PZgoR0X.js → index-DfNGlG_k.js} +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/{lerc-C-D0ZLmp.js → lerc-n0GaWEmL.js} +2 -2
- package/dist/measure-controller.d.ts +41 -0
- package/dist/onlymap.standalone.js +91667 -91346
- package/dist/onlymapjs.js +82 -67
- package/dist/{raster-D43jYSib.js → raster-DO-Ex2Lb.js} +2 -2
- package/dist/units.d.ts +40 -0
- package/dist/validation.d.ts +10 -0
- package/dist/version.d.ts +1 -1
- package/llms.txt +3 -1
- package/onlymapjs.html-data.json +46 -1
- package/package.json +1 -1
- package/skills/onlymapjs/SKILL.md +3 -1
- package/skills/onlymapjs/references/patterns.md +38 -0
- package/skills/onlymapjs/references/syntax.md +4 -3
package/dist/onlymapjs.js
CHANGED
|
@@ -1,82 +1,97 @@
|
|
|
1
|
-
import { ay as e, az as r, aA as
|
|
1
|
+
import { ay as e, az as r, aA as t, aB as o, aC as b, aD as i, an as l, aE as n, aF as S, aG as L, aH as T, aI as E, aJ as g, aK as m, aL as A, aM as d, aN as p, aO as c, aP as _, aQ as y, aR as M, aS as u, aT as I, am as D, aU as O, au as f, aV as N, aW as P, aX as R, W as F, aY as h, aZ as C, a_ as B, a$ as G, b0 as U, b1 as W, b2 as x, b3 as v, b4 as w, b5 as Y, b6 as H, b7 as X, b8 as k, b9 as z, ba as J, bb as K, bc as V, bd as Q, be as j, bf as q, bg as Z, bh as $, bi as aa, bj as sa, bk as ea, bl as ra, bm as ta, bn as oa, bo as ba, bp as ia, bq as la, br as na, bs as Sa, bt as La, bu as Ta, bv as Ea, bw as ga, bx as ma, by as Aa, bz as da, bA as pa, bB as ca, bC as _a, bD as ya, bE as Ma, bF as ua, bG as Ia, bH as Da, bI as Oa, bJ as fa, bK as Na, bL as Pa, bM as Ra, bN as Fa, bO as ha, bP as Ca, bQ as Ba, bR as Ga, bS as Ua, bT as Wa, bU as xa, bV as va, bW as wa, bX as Ya } from "./index-BoAq5XsU.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ALL_POSITION_VALUES,
|
|
4
4
|
r as AUDIT_EXEMPTIONS,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
t as AccessorCache,
|
|
6
|
+
o as BitmapLayer,
|
|
7
|
+
b as COORDINATE_SYSTEM,
|
|
8
|
+
i as CompileError,
|
|
9
|
+
l as CompositeLayer,
|
|
10
10
|
n as DEFAULT_AUDIT_WIDTHS,
|
|
11
11
|
S as DEFAULT_FOLD_BREAKPOINT_PX,
|
|
12
12
|
L as EDGE_TOLERANCE_PX,
|
|
13
13
|
T as FOLD_HYSTERESIS_PX,
|
|
14
14
|
E as FOLD_SIDES,
|
|
15
15
|
g as GeoJsonLayer,
|
|
16
|
-
|
|
16
|
+
m as IconLayer,
|
|
17
17
|
A as LEGACY_POSITION_ALIASES,
|
|
18
18
|
d as LIGHTING_PRESET_NAMES,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
p as Layer,
|
|
20
|
+
c as LayerExtension,
|
|
21
|
+
_ as MANAGED_SLOTS,
|
|
22
|
+
y as MapController,
|
|
23
|
+
M as OmMap,
|
|
24
|
+
u as ScatterplotLayer,
|
|
25
|
+
I as ScenegraphLayer,
|
|
26
|
+
D as SimpleMeshLayer,
|
|
27
|
+
O as Tile3DLayer,
|
|
28
28
|
f as TileLayer,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
N as UNIT_SYSTEMS,
|
|
30
|
+
P as WGS84_MEAN_RADIUS_M,
|
|
31
|
+
R as WIDGET_STYLE_KEY_NAMES,
|
|
32
|
+
F as WebMercatorViewport,
|
|
33
|
+
h as arrowTableToColumnar,
|
|
34
|
+
C as asRows,
|
|
33
35
|
B as auditLayout,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
G as compileExpression,
|
|
37
|
+
U as compileFullJsAccessorBlockInSandbox,
|
|
38
|
+
W as computeTimeline,
|
|
39
|
+
x as configureBasemap,
|
|
40
|
+
v as configureData,
|
|
41
|
+
w as configureLicense,
|
|
42
|
+
Y as configureTelemetry,
|
|
41
43
|
H as descriptorToIR,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
la as
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Da as
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
44
|
+
X as destroySandbox,
|
|
45
|
+
k as distanceMeters,
|
|
46
|
+
z as evaluateInSandbox,
|
|
47
|
+
J as foldDrawerStyle,
|
|
48
|
+
K as foldPanelStyle,
|
|
49
|
+
V as foldSideForSlot,
|
|
50
|
+
Q as foldToggleSlot,
|
|
51
|
+
j as foldToggleStyle,
|
|
52
|
+
q as formatArea,
|
|
53
|
+
Z as formatLength,
|
|
54
|
+
$ as getBasemap,
|
|
55
|
+
aa as getEntitlements,
|
|
56
|
+
sa as getLayerSchema,
|
|
57
|
+
ea as getMeasureRadiusMeters,
|
|
58
|
+
ra as getTerrain,
|
|
59
|
+
ta as isBrowser,
|
|
60
|
+
oa as isColumnar,
|
|
61
|
+
ba as isStoreToken,
|
|
62
|
+
ia as loadRasterModule,
|
|
63
|
+
la as midpoint,
|
|
64
|
+
na as mountForTest,
|
|
65
|
+
Sa as niceNumber,
|
|
66
|
+
La as normalizeData,
|
|
67
|
+
Ta as parseDurationMs,
|
|
68
|
+
Ea as parseTerrainAttrs,
|
|
69
|
+
ga as parseUnitSystem,
|
|
70
|
+
ma as parseWidgetStyle,
|
|
71
|
+
Aa as pathLengthMeters,
|
|
72
|
+
da as registerAction,
|
|
73
|
+
pa as registerBasemap,
|
|
74
|
+
ca as registerFormat,
|
|
75
|
+
_a as registerLayer,
|
|
76
|
+
ya as registerSource,
|
|
77
|
+
Ma as registerTerrain,
|
|
78
|
+
ua as registerWidget,
|
|
79
|
+
Ia as resolveFoldBreakpointPx,
|
|
80
|
+
Da as resolveLighting,
|
|
81
|
+
Oa as resolveSlot,
|
|
82
|
+
fa as ringAreaMeters2,
|
|
83
|
+
Na as ringEnclosesPole,
|
|
84
|
+
Pa as ringPerimeterMeters,
|
|
85
|
+
Ra as rowAt,
|
|
86
|
+
Fa as sandboxHtml,
|
|
87
|
+
ha as scaleBarStep,
|
|
88
|
+
Ca as setMeasureRadiusMeters,
|
|
89
|
+
Ba as settleLayout,
|
|
90
|
+
Ga as shouldFoldAtWidth,
|
|
91
|
+
Ua as slotContainerStyle,
|
|
92
|
+
Wa as slotLayout,
|
|
93
|
+
xa as snapshotIR,
|
|
94
|
+
va as solarAzElDegrees,
|
|
95
|
+
wa as validateManifest,
|
|
96
|
+
Ya as validateManifestString
|
|
82
97
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ae from "./index-CW1n5LdO.js";
|
|
2
|
-
import { ak as Mt, al as Rt, am as tt, an as nt, ao as zt, b as Ft, ap as Dt, l as Ee, aq as Nt, d as Ut, ar as Bt, as as Gt, at as Kt, au as rt } from "./index-
|
|
2
|
+
import { ak as Mt, al as Rt, am as tt, an as nt, ao as zt, b as Ft, ap as Dt, l as Ee, aq as Nt, d as Ut, ar as Bt, as as Gt, at as Kt, au as rt } from "./index-BoAq5XsU.js";
|
|
3
3
|
import { w as _t } from "./mgrs-BY9bIvp4.js";
|
|
4
4
|
function Xt(e, t, n) {
|
|
5
5
|
const { projectedCorners: r } = t, { topLeft: o, topRight: i, bottomRight: s, bottomLeft: a } = r, l = n(o[0], o[1]), c = n(i[0], i[1]), u = n(s[0], s[1]), h = n(a[0], a[1]), p = [
|
|
@@ -2378,7 +2378,7 @@ D.set(P.Zstd, () => import("./zstd-jXobGRcq.js").then((e) => e.decode));
|
|
|
2378
2378
|
D.set(P.Jpeg, () => Promise.resolve(Ce));
|
|
2379
2379
|
D.set(P.Jpeg6, () => Promise.resolve(Ce));
|
|
2380
2380
|
D.set(P.Webp, () => Promise.resolve(Ce));
|
|
2381
|
-
D.set(P.Lerc, () => import("./lerc-
|
|
2381
|
+
D.set(P.Lerc, () => import("./lerc-n0GaWEmL.js").then((e) => e.l).then((e) => e.decode));
|
|
2382
2382
|
async function je(e, t, n) {
|
|
2383
2383
|
const r = D.get(t);
|
|
2384
2384
|
if (!r)
|
package/dist/units.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Units (spec: issue #20 "Measure widget"). The shared length/area unit system
|
|
3
|
+
* for the measure widget AND the scale-bar (which refactors onto this module
|
|
4
|
+
* and gains a `units` attribute — it was metric-only with a hardcoded steps
|
|
5
|
+
* table). Adopts GL's shared scale-units precedent: the exact metric constants
|
|
6
|
+
* (0.3048 m/ft, 1609.344 m/mi, 1852 m/nmi), denomination crossover (m→km at
|
|
7
|
+
* 1000, ft→mi at 5280 ft), and the log10-based 1/2/3/5/10 "nice number" — NOT
|
|
8
|
+
* MapLibre's digit-count trick, which mis-rounds sub-unit spans.
|
|
9
|
+
*/
|
|
10
|
+
export type UnitSystem = "metric" | "imperial" | "nautical";
|
|
11
|
+
export declare const UNIT_SYSTEMS: readonly UnitSystem[];
|
|
12
|
+
export declare const M_PER_FT = 0.3048;
|
|
13
|
+
export declare const M_PER_MI = 1609.344;
|
|
14
|
+
export declare const M_PER_NMI = 1852;
|
|
15
|
+
/** Validate a unit-system string; `null` for anything not in {@link UNIT_SYSTEMS}. */
|
|
16
|
+
export declare function parseUnitSystem(raw: string | null | undefined): UnitSystem | null;
|
|
17
|
+
/** Format a length (meters) for a unit system, with denomination crossover. */
|
|
18
|
+
export declare function formatLength(meters: number, system?: UnitSystem): string;
|
|
19
|
+
/**
|
|
20
|
+
* Format an area (m²), with denomination crossover. Metric: m²/ha/km².
|
|
21
|
+
* Imperial: ft²/acres/mi². Nautical falls back to metric for area (open
|
|
22
|
+
* question 1 — nmi² is not a denomination users read).
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatArea(m2: number, system?: UnitSystem): string;
|
|
25
|
+
/**
|
|
26
|
+
* The largest 1/2/3/5 ×10ⁿ value ≤ `value` (GL's log10 "nice number"). Used by
|
|
27
|
+
* the scale bar to snap its bar to a round distance. log10-based, so it is
|
|
28
|
+
* correct for sub-unit spans (`value < 1`) where a digit-count rule breaks.
|
|
29
|
+
*/
|
|
30
|
+
export declare function niceNumber(value: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* Scale-bar helper: given the max distance (meters) that fits the bar's pixel
|
|
33
|
+
* budget, return the nice round distance to actually draw and its label, in the
|
|
34
|
+
* chosen unit system. Metric snaps in meters/km, imperial in ft/mi, nautical in
|
|
35
|
+
* nmi — the crossover handled by {@link formatLength}.
|
|
36
|
+
*/
|
|
37
|
+
export declare function scaleBarStep(maxMeters: number, system?: UnitSystem): {
|
|
38
|
+
meters: number;
|
|
39
|
+
label: string;
|
|
40
|
+
};
|
package/dist/validation.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export interface ValidationResult {
|
|
|
12
12
|
errors: ValidationEntry[];
|
|
13
13
|
warnings: ValidationEntry[];
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A scalar/shorthand attribute value that is actually a data-driven accessor
|
|
17
|
+
* expression — a `$field` reference or a `scale()`/`clamp()`/`lerp()`/
|
|
18
|
+
* `colorRamp()` call. Scalar props take a CONSTANT; an expression here doesn't
|
|
19
|
+
* coerce (deck.gl gets a string/NaN and silently falls back to defaults — the
|
|
20
|
+
* "black squares" / invisible-layer class of bug), and the value belongs in a
|
|
21
|
+
* `get-*` accessor instead. Word-boundary on the function names so a literal
|
|
22
|
+
* like "grayscale(" doesn't false-match.
|
|
23
|
+
*/
|
|
24
|
+
export declare function looksLikeAccessorExpression(raw: string): boolean;
|
|
15
25
|
/**
|
|
16
26
|
* Validates a manifest already parsed into a DOM tree — a live `<om-map>`
|
|
17
27
|
* (mounted or not) or a detached `Document`/`DocumentFragment` (what
|
package/dist/version.d.ts
CHANGED
package/llms.txt
CHANGED
|
@@ -19,14 +19,16 @@ OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the d
|
|
|
19
19
|
- 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).
|
|
20
20
|
- 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.
|
|
21
21
|
- `scale()` requires an explicit `domain=`: `get-fill-color="scale($depth, sequential, ['#ffffcc','#800026'], domain=[0,700])"`. A missing domain is a validation error.
|
|
22
|
+
- `scale()`/`clamp()`/`lerp()`/`$field` expressions go ONLY in `get-*` accessors — NEVER in a scalar attribute (`radius-min-pixels`, `point-radius-min-pixels`, `opacity`, `line-width-min-pixels`, `color`, …). A data-driven expression in a scalar doesn't coerce, so the layer silently renders defaults (invisible / black points); validation errors on it. Data-driven size/width/opacity/color → the matching `get-*` accessor (`get-radius`, `get-fill-color`, …); scalars take a constant floor/value.
|
|
22
23
|
- `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.
|
|
23
24
|
- 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.
|
|
25
|
+
- To capture where the user CLICKS on the map (a measure tool, drop-a-pin, a custom rectangle/circle AOI, snap-to-feature), listen for the `om-map-point` event on `<om-map>`: `mapEl.addEventListener('om-map-point', e => { const { coordinate, kind } = e.detail; })` — `coordinate` is `[lng,lat]` (or `null` off-globe), `kind` is `"click"`|`"hover"`, and it fires on every click/hover including empty-map clicks. Do NOT reach for deck.gl internals (`mapEl.getMap()`, `.deckInstance`, `.deck.viewManager`) or unproject canvas pixels — those are not exposed on `<om-map>` and return nothing. The built-in `draw` widget handles polygon/line/point sketching; `om-map-point` is for tools it doesn't cover.
|
|
24
26
|
|
|
25
27
|
## Element vocabulary
|
|
26
28
|
|
|
27
29
|
- `<om-map center="[lng, lat]" zoom="11" pitch="55" bearing="20" basemap="positron">` — the root. Give it a height (`om-map { display:block; height:100vh }` with `html,body{height:100%}`, or a sized container) — a custom element is display:inline by default and collapses to zero size; with none set, the library falls back to display:block + a 400px floor so a bare map still shows (any height you DO set wins over the floor, including one below 400px), and a collapsed map logs a console warning. A map hidden on purpose (`hidden`, or inside a `display:none` panel) stays hidden and does not warn. `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`). `widgets-hidden` attribute (or the `set-widgets-visible {visible}` action / `<om-widget type="widgets-toggle">` button) hides every widget WITHOUT destroying state — attribution never hides (license); transient (not an undo step) but story-steppable, so a step can clear chrome for a cinematic take. Slots auto-dim while an open `<om-overlay>` popup covers them (position stability over the popup dodging; `widgets-dim="off"` disables). `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, origin}` where `origin` is `"user"` for gesture bursts vs `"programmatic"` for API/story moves — the camera-persistence hook, and the echo-suppression signal when syncing camera state to an app store), `om-map-point` (`detail = {coordinate: [lng,lat]|null, kind}` — every click/hover map coordinate incl. empty-map clicks, for custom capture tools beyond the draw widget), `om-tileset-load` (`detail = {layerId, tileset}` — a Tile3DLayer's live deck Tileset3D, for tools needing the real tileset like region export). `MapController` mirrors these as `onViewChange`/`onMapPoint`/`onTilesetLoad` options. `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.
|
|
28
30
|
- `<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`). Every type's full attribute list ships in the package's `onlymapjs.html-data.json` — consult it instead of guessing attribute names or reading the minified dist. TextLayer's default font atlas covers ASCII only: set `character-set` when label text carries other glyphs (`—`, `·`, accents) or deck warns and renders them blank. 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, CityJSON `.city.json` / CityJSONSeq `.city.jsonl` (semantic 3D city models — 3DBAG, PLATEAU — decoded to one of two shapes by the `data` URL, no CityJSON layer type: default → extruded footprints, `type="GeoJsonLayer" extruded get-elevation="$roof_height"`; `?om-surfaces=1` → one row PER FACE at its own real per-vertex height so a pitched LoD2.2 roof actually looks pitched, `type="SolidPolygonLayer" get-polygon="$polygon" full3d` (`extruded` stays at its ordinary `false` default) (flat-shaded — deck.gl only lights the `extruded` shader path — each row also carrying `surface_type`: RoofSurface/WallSurface/GroundSurface, and `fill_color`: a ninja-viewer-style default color per surface_type/cityobject_type, verified against cityjson-threejs-loader's own default palette — `get-fill-color` on `SolidPolygonLayer` reads it automatically when left unauthored, no color attribute required, and an authored `get-fill-color` still overrides it); derived properties (both modes) `roof_height` (area-weighted mean roof height above ground), `eaves_height`, `ridge_height`, `ground_height`, `roof_area`, `surface_count`, `lod`, `cityobject_id`, `cityobject_type`, `parent_id` win over same-named source attributes, plus surfaces-mode-only `polygon`/`outline`/`surface_type`/`fill_color` (`outline` is the face's outer ring flattened and closed — bind a companion `type="PathLayer" get-path="$outline"` layer to it for visible face edges, since surfaces mode is flat-shaded and `SolidPolygonLayer`'s own `wireframe` prop is a no-op when unextruded — always pair one, matching `filter-field`/`filter-range` to the fill layer), and a parent Building's attributes are inherited by its BuildingPart rows; national grids NL/CH/DE/JP/AT/SG reproject automatically including axis order, other EPSG codes fail with an error naming the code; highest LoD wins, pin one with `?om-lod=1.2` (combine as `?om-lod=1.2&om-surfaces=1`, cached independently); `.city.jsonl` fills in as it downloads in either mode — see docs/3d-assets.md), 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": [...]}}`; must be a DIRECT child of the `<om-layer>`, and when present it wins — omit the `data` attribute), 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). TILED layers: a `{z}/{x}/{y}` `data` template is deck's tile URL for `TileLayer`/`MVTLayer` (NOT rows) — passed through to deck verbatim, never fetched/parsed, so `<om-layer type="TileLayer" data="…/{z}/{x}/{y}.png">` works (raster gets a built-in BitmapLayer sublayer) and `type="MVTLayer" data="…/{z}/{x}/{y}.pbf"` self-renders vector tiles with `get-*` accessors applying to each decoded feature's properties; a tiled layer has no local rows so `ctx.data`/`ctx.stats`/`filter-*` don't apply. 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).
|
|
29
|
-
- `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite" position="bottom-end">` — static UI panels. `position` takes one of 8 managed slots (logical, RTL-aware: `top-start|top-center|top-end|center-start|center-end|bottom-start|bottom-center|bottom-end`; legacy corners `top-left` etc. alias) — same-slot widgets stack with flush edges and a shared gap (never overlap); provider attribution joins `bottom-end` and the license badge joins `bottom-start` as in-flow members, so required chrome never covers a widget; `order="1"` orders within a slot; adjacent compact button widgets (zoom-controls, undo-redo, widgets-toggle) auto-merge into ONE control group with dividers (`cluster="false"` opts a widget out); `position="manual"` renders a plain block you place with your own CSS (even outside the map). At map widths ≤640px managed widgets auto-fold into one accessible drawer per map side; `fold="never"` exempts an essential widget, `widgets-fold="off"` opts the map out, `--om-widget-fold-breakpoint` changes the threshold. Layout tokens on `<om-map>`: `widget-style="gap:10 opacity:0.9 inset:16"` (keys inset/gap/inset-x/-y/gap-x/-y/opacity/radius/size, px except opacity) or the `--om-widget-inset-x/-y/-gap-x/-gap-y/-opacity/-radius/-fold-breakpoint` custom properties. 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.
|
|
31
|
+
- `<om-widget type="legend|layer-switcher|basemap-switcher|lighting|zoom-controls|undo-redo|scale-bar|attribution|filter|vega-lite|measure" position="bottom-end">` — static UI panels. `position` takes one of 8 managed slots (logical, RTL-aware: `top-start|top-center|top-end|center-start|center-end|bottom-start|bottom-center|bottom-end`; legacy corners `top-left` etc. alias) — same-slot widgets stack with flush edges and a shared gap (never overlap); provider attribution joins `bottom-end` and the license badge joins `bottom-start` as in-flow members, so required chrome never covers a widget; `order="1"` orders within a slot; adjacent compact button widgets (zoom-controls, undo-redo, widgets-toggle) auto-merge into ONE control group with dividers (`cluster="false"` opts a widget out); `position="manual"` renders a plain block you place with your own CSS (even outside the map). At map widths ≤640px managed widgets auto-fold into one accessible drawer per map side; `fold="never"` exempts an essential widget, `widgets-fold="off"` opts the map out, `--om-widget-fold-breakpoint` changes the threshold. Layout tokens on `<om-map>`: `widget-style="gap:10 opacity:0.9 inset:16"` (keys inset/gap/inset-x/-y/gap-x/-y/opacity/radius/size, px except opacity) or the `--om-widget-inset-x/-y/-gap-x/-gap-y/-opacity/-radius/-fold-breakpoint` custom properties. 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. `measure` is a geodesic ruler: `modes="distance area"` (default both), `units="metric|imperial|nautical"` — click the map to place points, live per-segment + total labels render on the map, and it dispatches an `om-measure` event (`detail = {mode, units, totalMeters, segments, areaMeters2, perimeterMeters, poleWarning}`); it reuses the draw capture stack (measure/draw mutually exclusive) and its geometry is ephemeral (never saved, not an undo step). `scale-bar` now takes the same `units`. 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.
|
|
30
32
|
- `<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.
|
|
31
33
|
- `<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 }`.
|
|
32
34
|
- 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")`.
|
package/onlymapjs.html-data.json
CHANGED
|
@@ -1599,6 +1599,15 @@
|
|
|
1599
1599
|
{
|
|
1600
1600
|
"name": "draw-save"
|
|
1601
1601
|
},
|
|
1602
|
+
{
|
|
1603
|
+
"name": "measure-mode"
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
"name": "measure-clear"
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
"name": "measure-units"
|
|
1610
|
+
},
|
|
1602
1611
|
{
|
|
1603
1612
|
"name": "set-basemap"
|
|
1604
1613
|
},
|
|
@@ -1664,6 +1673,9 @@
|
|
|
1664
1673
|
{
|
|
1665
1674
|
"name": "draw"
|
|
1666
1675
|
},
|
|
1676
|
+
{
|
|
1677
|
+
"name": "measure"
|
|
1678
|
+
},
|
|
1667
1679
|
{
|
|
1668
1680
|
"name": "basemap-switcher"
|
|
1669
1681
|
},
|
|
@@ -1767,7 +1779,7 @@
|
|
|
1767
1779
|
},
|
|
1768
1780
|
{
|
|
1769
1781
|
"name": "modes",
|
|
1770
|
-
"description": "Draw widget: space-separated tools
|
|
1782
|
+
"description": "Draw widget: space-separated tools (point line polygon). Measure widget: space-separated modes (distance area).",
|
|
1771
1783
|
"values": [
|
|
1772
1784
|
{
|
|
1773
1785
|
"name": "point line polygon"
|
|
@@ -1780,6 +1792,15 @@
|
|
|
1780
1792
|
},
|
|
1781
1793
|
{
|
|
1782
1794
|
"name": "polygon"
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
"name": "distance area"
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
"name": "distance"
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
"name": "area"
|
|
1783
1804
|
}
|
|
1784
1805
|
]
|
|
1785
1806
|
},
|
|
@@ -1802,6 +1823,21 @@
|
|
|
1802
1823
|
"name": "autosave",
|
|
1803
1824
|
"description": "Draw widget: localStorage key — mirrors the sketch and restores it on reload."
|
|
1804
1825
|
},
|
|
1826
|
+
{
|
|
1827
|
+
"name": "units",
|
|
1828
|
+
"description": "Measure / scale-bar widget: unit system for length and area.",
|
|
1829
|
+
"values": [
|
|
1830
|
+
{
|
|
1831
|
+
"name": "metric"
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
"name": "imperial"
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
"name": "nautical"
|
|
1838
|
+
}
|
|
1839
|
+
]
|
|
1840
|
+
},
|
|
1805
1841
|
{
|
|
1806
1842
|
"name": "options",
|
|
1807
1843
|
"description": "Basemap-switcher: space-separated preset names to offer (default: every keyless registered preset)."
|
|
@@ -1937,6 +1973,15 @@
|
|
|
1937
1973
|
{
|
|
1938
1974
|
"name": "draw-save"
|
|
1939
1975
|
},
|
|
1976
|
+
{
|
|
1977
|
+
"name": "measure-mode"
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
"name": "measure-clear"
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
"name": "measure-units"
|
|
1984
|
+
},
|
|
1940
1985
|
{
|
|
1941
1986
|
"name": "set-basemap"
|
|
1942
1987
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nika-js/onlymap",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
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.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"publishConfig": {
|
|
@@ -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 pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.5.
|
|
26
|
+
For no-build CDN pages, use the single-file standalone bundle from a raw-file CDN — `https://unpkg.com/@nika-js/onlymap@0.5.3` (the bare package URL serves `dist/onlymap.standalone.js`) — plus `<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.5.3/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
|
|
|
@@ -74,6 +74,8 @@ Load the smallest reference needed for the task:
|
|
|
74
74
|
- Live entity updates -> `wss://` stream with `key` and optional `source` decoder.
|
|
75
75
|
- REST snapshot that changes over time -> `refresh="5s"`.
|
|
76
76
|
- User sketching -> `data="draw:sketch"` layer plus `<om-widget type="draw" target="sketch">`.
|
|
77
|
+
- Measure geodesic distance or area (a ruler / area tool) -> `<om-widget type="measure" modes="distance area" units="metric|imperial|nautical">`. Click the map to place points; it shows live per-segment + total labels and dispatches an `om-measure` event (`detail` = the readout). Reuses the draw capture stack, so measure and draw are mutually exclusive. Do NOT hand-roll distance math off canvas pixels; the `scale-bar` widget also takes `units` now.
|
|
78
|
+
- Capture raw map clicks/hovers yourself (measure distance, drop a pin where the user clicks, a custom rectangle/circle AOI, snap-to-feature) -> listen for the **`om-map-point`** DOM event on `<om-map>`: `mapEl.addEventListener('om-map-point', e => { const { coordinate, kind } = e.detail; /* [lng,lat] or null; kind is "click"|"hover" */ })`. It fires on EVERY click/hover including empty-map clicks. NEVER read deck.gl internals (`getMap()`, `deckInstance`, `deck.viewManager`) or unproject canvas pixels by hand — those are not on `<om-map>` and will silently return nothing. The built-in `draw` widget covers polygon/line/point sketching; `om-map-point` is for tools it doesn't. (`MapController` twin: the `onMapPoint` option. See patterns.md.)
|
|
77
79
|
- Page may travel as a file (shared, emailed, downloaded) or be embedded -> add an `<om-fallback>` child to `<om-map>`. Chat-app and email previews render HTML with JavaScript disabled (iOS QuickLook), so the map cannot boot there; the fallback is what recipients see instead. It is hidden automatically once the map boots. Good practice on every complete page — without one, the stylesheet shows a generic text-only banner.
|
|
78
80
|
|
|
79
81
|
## Output Expectations
|
|
@@ -192,6 +192,44 @@ Use `PopupLayer` instead of many `<om-overlay>` elements for labels/badges at sc
|
|
|
192
192
|
autosave="onlymapjs-sketch"></om-widget>
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
## Capture Raw Map Clicks (measure tool, drop-a-pin, custom AOI)
|
|
196
|
+
|
|
197
|
+
For anything the `draw` widget doesn't cover — a distance/area measurer, dropping
|
|
198
|
+
a pin at the clicked coordinate, a custom rectangle/circle AOI — read the
|
|
199
|
+
**`om-map-point`** event. It gives you the map coordinate of every click and
|
|
200
|
+
hover. Do NOT reach into deck.gl (`getMap()`, `deckInstance`, `deck.viewManager`)
|
|
201
|
+
or unproject canvas pixels — none of that is exposed on `<om-map>`.
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<om-map center="[103.8, 1.35]" zoom="11" basemap="positron">
|
|
205
|
+
<!-- The pins the user drops, driven by a draw store you write into. -->
|
|
206
|
+
<om-layer id="pins" type="GeoJsonLayer" data="draw:measure"
|
|
207
|
+
get-fill-color="[239,68,68,220]" point-radius-min-pixels="5"
|
|
208
|
+
get-line-color="[239,68,68,220]" line-width-min-pixels="2"
|
|
209
|
+
stroked filled></om-layer>
|
|
210
|
+
</om-map>
|
|
211
|
+
|
|
212
|
+
<script type="module">
|
|
213
|
+
import "@nika-js/onlymap";
|
|
214
|
+
const map = document.querySelector("om-map");
|
|
215
|
+
const pts = [];
|
|
216
|
+
|
|
217
|
+
map.addEventListener("om-map-point", (e) => {
|
|
218
|
+
const { coordinate, kind } = e.detail; // [lng, lat] | null ; "click" | "hover"
|
|
219
|
+
if (!coordinate || kind !== "click") return;
|
|
220
|
+
pts.push(coordinate);
|
|
221
|
+
// ...compute distance (haversine), update your own panel, etc.
|
|
222
|
+
// Push geometry into the "measure" draw store so it renders as a layer:
|
|
223
|
+
// OmMap.getStore("data:pins") / a draw controller, or your own <om-layer> data.
|
|
224
|
+
});
|
|
225
|
+
</script>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Notes: `coordinate` is `null` when the click lands off the globe; `kind` is
|
|
229
|
+
`"hover"` for the live pointer (use it for a rubber-band segment) and `"click"`
|
|
230
|
+
for a committed point. The programmatic twin is the `MapController` `onMapPoint`
|
|
231
|
+
option; test it headlessly with `h.mapPoint(coordinate, kind)`.
|
|
232
|
+
|
|
195
233
|
## Story/Tour
|
|
196
234
|
|
|
197
235
|
```html
|
|
@@ -16,8 +16,8 @@ Vite/npm project:
|
|
|
16
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
|
-
<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.5.
|
|
20
|
-
<script type="module" src="https://unpkg.com/@nika-js/onlymap@0.5.
|
|
19
|
+
<link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap@0.5.3/dist/onlymapjs.css">
|
|
20
|
+
<script type="module" src="https://unpkg.com/@nika-js/onlymap@0.5.3"></script>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
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).
|
|
@@ -266,10 +266,11 @@ Built-ins:
|
|
|
266
266
|
- `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.
|
|
267
267
|
- `layer-switcher`
|
|
268
268
|
- `zoom-controls`
|
|
269
|
-
- `scale-bar`
|
|
269
|
+
- `scale-bar` — `units="metric|imperial|nautical"` (default metric) picks the length system; snaps to a nice round distance.
|
|
270
270
|
- `attribution`
|
|
271
271
|
- `filter`
|
|
272
272
|
- `draw`
|
|
273
|
+
- `measure` — geodesic ruler: `modes="distance area"` (space-separated; default both), `units="metric|imperial|nautical"`. Click the map to place points; live per-segment + total labels render on the map, and a totals panel + a `units` toggle sit in the widget. Distance is haversine on the WGS84 mean sphere (≤0.56% vs. the true geodesic); area is the spherical-excess integral. Nautical shows nmi for length and falls back to metric for area. Reuses the draw capture stack (measure and draw are mutually exclusive); the geometry is ephemeral (never saved, never an undo step). Consume the reading programmatically via the `om-measure` event on `<om-map>` (`detail = {mode, units, totalMeters, segments, areaMeters2, perimeterMeters, poleWarning}`).
|
|
273
274
|
- `vega-lite`
|
|
274
275
|
- `player`
|
|
275
276
|
- `basemap-switcher` — radio list of presets; `options="positron dark-matter osm"` (default: every keyless registered preset)
|