@nika-js/onlymap 0.6.7 → 0.6.13
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/CHANGELOG.md +36 -0
- package/README.md +17 -3
- package/dist/{LercDecode.es-BzboSQ2U.js → LercDecode.es-DB1mYTrm.js} +1 -1
- package/dist/attribute-resolution.d.ts +2 -0
- package/dist/{basemap-pWXbFGjp.js → basemap-CDp9EsvL.js} +1 -1
- package/dist/classify.d.ts +62 -0
- package/dist/draw-controller.d.ts +4 -1
- package/dist/elements/om-map.d.ts +21 -1
- package/dist/{geoparquet-BPl1T2Or.js → geoparquet-CI3o8X5-.js} +1 -1
- package/dist/html-data.d.ts +1 -1
- package/dist/{index-CqsQz9Bp.js → index-BJh0t_nt.js} +2 -2
- package/dist/{index-iVMlGZS9.js → index-Bue-53Xo.js} +1 -1
- package/dist/{index-CAuq7wRs.js → index-CnKC1yuV.js} +13565 -13225
- package/dist/{index-Bgx3CJru.js → index-DMu0qL9C.js} +1 -1
- package/dist/{index-B9a6f006.js → index-Dio4lUCZ.js} +1 -1
- package/dist/layers/marker-icons.d.ts +12 -1
- package/dist/layers/route-layer.d.ts +32 -0
- package/dist/layers/tracking-layer.d.ts +3 -0
- package/dist/{lerc-BgzbFAc7.js → lerc-B1LFm9ra.js} +2 -2
- package/dist/onlymap.standalone.js +31212 -30872
- package/dist/onlymapjs.js +1 -1
- package/dist/programmatic.d.ts +19 -0
- package/dist/{raster-DtyMY54Z.js → raster-DQ1dS7mZ.js} +2 -2
- package/dist/{raster-pipeline-BmHRCUEb.js → raster-pipeline-mXzwFf0A.js} +1 -1
- package/dist/runtime-core.d.ts +22 -1
- package/dist/testing.d.ts +4 -1
- package/dist/version.d.ts +1 -1
- package/dist/{zarr-CTpFZo_m.js → zarr-BSoVUvcs.js} +2 -2
- package/docs/routing.md +23 -4
- package/docs/testing.md +1 -1
- package/llms.txt +7 -5
- package/onlymapjs.html-data.json +48 -1
- package/package.json +3 -2
- package/skills/onlymapjs/SKILL.md +6 -5
- package/skills/onlymapjs/references/syntax.md +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,42 @@ Note: npm collapsed a few closely-spaced releases — the GPX/FlatGeobuf (0.5.4)
|
|
|
8
8
|
and GeoParquet (0.5.5) work shipped to npm together as **0.5.6**, so npm's
|
|
9
9
|
version list jumps 0.5.3 → 0.5.6. Each logical version is listed here regardless.
|
|
10
10
|
|
|
11
|
+
## 0.6.13 — 2026-08-20
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Line/polygon completion now works on iOS WebViews** (issue #17) — iOS never synthesizes `dblclick` from a double-tap while touch handlers are active, so double-tap added two more vertices instead of committing (Android committed fine — platform-inconsistent). The draw controller now detects the tap pair itself (two touch/pen taps within 350ms and ~12px) and completes the shape; mouse input keeps the native `dblclick` path, and synthetic/programmatic picks (no `pointerType`) opt into nothing. Applies wherever the draw stack runs, including the measure widget's footprints.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Finish button on the draw toolbar** — an explicit, discoverable completion affordance (double-click/double-tap/Enter are conventions the user must already know); harmless no-op when nothing is pending.
|
|
18
|
+
- **`mapPoint(coord, kind, pointerType?)`** on the test harness — simulate a specific input modality (`"touch"`/`"pen"`/`"mouse"`) through the real pick path.
|
|
19
|
+
|
|
20
|
+
## 0.6.12 — 2026-08-19
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Classified symbology** (issue #12) — `classify-by="magnitude" classify-scale="quantile|equal-interval|jenks" classify-classes="5" classify-ramp="viridis"` computes class breaks *from the data* at reconcile time, installs the fill-color accessor, and auto-generates the classes legend. Fourteen named ramps (matplotlib + ColorBrewer). An authored `get-fill-color`/`color` always wins (validation names the conflict); URL-backed layers classify when their data arrives; results are memoized per data reference so deck recomputes exactly when the classification genuinely changes. Mirrored on the programmatic front-end as `classifyBy`/`classifyScale`/`classifyClasses`/`classifyRamp`.
|
|
24
|
+
- **`time-slider` widget** (issue #18) — play/pause/scrub playback over a layer's numeric/epoch-ms time field: `<om-widget type="time-slider" layer="quakes" field="time" duration="20s" window="86400000" loop format="date">`. Drives the ordinary `filter-layer` action (cumulative from the domain start, or a sliding `window` in field units), so widget statistics stay coherent and undo/stories/external filter edits re-sync the thumb; date labels reuse the filter widget's `format`/`date-style`/`time-zone` contract.
|
|
25
|
+
|
|
26
|
+
## 0.6.11 — 2026-08-18
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **Tracking markers tween smoothly instead of jittering.** Two causes, both fixed: inline-JSON layer data is now reference-stable across reconciles (it was re-parsed into a fresh array every pass, so any unrelated manifest edit — another rider's fix, a story step — looked like a new position fix to every Tracking layer and snapped its glide to the endpoint; this also stops spurious `data:<id>` watch-token fires for every inline layer on every reconcile), and a glide interrupted by a genuinely new fix now resumes from the marker's **current animated position** rather than the previous fix's endpoint, keeping motion continuous under any fix cadence.
|
|
30
|
+
|
|
31
|
+
## 0.6.10 — 2026-08-18
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **`om-route-resolved` consumer event** — whenever a `Route` layer resolves (direct `geometry` or a `RoutingProvider` round-trip), the map dispatches `om-route-resolved` with `detail = { layerId, route }`, where `route` carries the normalized `geometry`, `distanceMeters`, `durationSec`, `legs`, and fitted `bounds`; re-fired on every re-resolve. `MapController` mirrors it as `onRouteResolved(layerId, route)`. This closes the "route metadata has no consumer surface" gap natively: the Delivery Riders simulation and the Compute a Route readout now run entirely on this event, with no page-side fetching or adapter-to-UI plumbing.
|
|
35
|
+
|
|
36
|
+
## 0.6.9 — 2026-08-18
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **Tracking marker shapes** — `icon="arrow|car|motorcycle"` on a `Tracking` layer picks the marker (default arrow, unchanged). All shapes are drawn nose-up and baked in the layer's `color`, so bearing rotation and per-rider tinting apply to every shape identically; unknown names fall back to the arrow with a validation warning. The Delivery Riders example now rides color-matched motorcycles.
|
|
40
|
+
|
|
41
|
+
## 0.6.8 — 2026-08-18
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **Route tail modes** — link a `Route` layer to its `Tracking` layer with `progress-from="<tracking-layer-id>"` and choose how the traveled portion renders: `tail="none"` (client view — only current position → destination renders, and the origin pin drops with the traveled line) or `tail="dim"` (operator view — the traveled portion darkens, `tail-color` overrides the default derivation, while current position → destination keeps the live color). The split point is the tracking marker's interpolated position projected onto the nearest point of the route line, advancing smoothly per frame with the marker's glide — not stepping per GPS fix. Default (`tail="full"`) keeps 0.6.7 behavior exactly. Validation warns on partial wiring: `tail` without `progress-from`, `progress-from` without a tail mode, `tail-color` outside `dim`, and a `progress-from` naming no layer.
|
|
45
|
+
- The **Delivery Riders** example shows the operator view (three dimmed trails growing behind the riders); the **Routing & Tracking** example shows the client view (the route line shrinks to what's ahead of the rider).
|
|
46
|
+
|
|
11
47
|
## 0.6.7 — 2026-08-17
|
|
12
48
|
|
|
13
49
|
### Added
|
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ A handful of elements, one rule: **attributes are kebab-case versions of deck.gl
|
|
|
84
84
|
|---|---|
|
|
85
85
|
| `<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. |
|
|
86
86
|
| `<om-layer>` | Any of **39 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, the native `COGLayer` for GeoTIFF rasters, `ZarrLayer` for chunked Zarr/GeoZarr rasters, `ImageOverlay` for georeferenced drone JPEGs, `BIMLayer` for BIM source files (`.ifc` today) loaded straight in the browser with no pre-conversion step, and `Route`/`Tracking` for styled routes and live position tracking. `id` required; `label`/`color` feed the legend. |
|
|
87
|
-
| `<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`, `clip-box`, `zoom-controls`, `undo-redo`, `scale-bar` (metric/imperial/nautical `units`), `attribution`, `filter`, `draw` (point/line/polygon sketch capture, GeoJSON save/autosave; `export-3d` adds an "Export 3D" button that clips loaded `Tile3DLayer`/`BIMLayer` content to the drawn footprint and downloads it as a portable GLB — re-framed to a local coordinate frame at the footprint's own centroid, each triangle carrying its own source color as vertex colors, no textures — or, with `export-3d="b3dm"`, the same mesh wrapped for Cesium/3D-Tiles pipelines; only currently-visible 3D Tiles/BIM layers are included — a layer hidden via `visible="false"` or the `toggle-layer` action is skipped, with a distinct console warning for "nothing loaded" vs. "everything hidden"), `measure` (geodesic distance + area + cut/fill volume — live labels, `units` toggle, an `om-measure` readout event; `modes="distance area volume"` adds a footprint-then-extrude tool: outline a polygon, double-click to close it — it turns solid teal, ready — then drag the double-headed arrow gizmo that appears at its centroid up to fill or down to cut, reading out Cut/Fill/Net (signed, fill−cut)/Total (unsigned, cut+fill) volume plus Area/Perimeter, each labelled with its own sign convention. The math is a REAL per-cell grid integration: closing a footprint bulk-loads its covering DEM tiles, lays a metric grid in a local tangent frame at the ring centroid (cell size = the DEM's ground-sample distance; scanline point-in-polygon; bilinear, tile-seam-correct sampling; worker-offloaded), and integrates terrain-vs-base per cell — mixed cut AND fill within one footprint on undulating ground, with a published ±error (per-cell cellArea × 1.5 × GSD, summed per side), the cell size used, and a no-data warning all carried on the `om-measure` readout (`cellSizeM`/`gsdM`/`cutErrorM3`/`fillErrorM3`/`nodataFraction`). A `base-surface` attribute picks the reference surface: `custom` (default — the gizmo's draggable target plane), or boundary-derived stockpile strategies with no gizmo (`triangulated` boundary TIN, `plane` least-squares fit, `lowest`/`highest`/`average`). On `custom`, the extruded prism's own boundary hugs each corner's real ground elevation by default (so it doesn't visibly float above or sink below sloped terrain) — a "Flat target plane" toggle button appears above the readout once a footprint closes, switching that rendering to a single level plane instead (a rendering choice only; the underlying cut/fill numbers were already computed against one flat target elevation either way). Requires `terrain` on `<om-map>` — validation warns if `volume` is in `modes` with none (without terrain a flat-plane fallback runs, with no error figures — nothing honest to quote). A `profile` attribute alongside `modes` — not a mode of its own — samples elevation around a volume footprint's own perimeter as it's drawn and closed, live, and dispatches it on `om-measure`'s `profileSeries` field for a `dynamic-chart` widget to plot. Each sample is `{x: distance-from-start-m, y: elevation-m}`, and samples that ARE one of the drawn footprint's own corners additionally carry `vertexIndex` (0-based, in draw order) — so a chart can mark the real corners instead of every interpolated sample, and the widget's built-in profile chart marks each corner, labelling the first `1 · Start`. The map badges the first two vertices in draw order (`1 · Start`, `2`), which states the ring's direction outright — a start marker alone leaves clockwise vs counter-clockwise ambiguous, and the two wind to mirror-image profiles. Two badges is the minimum that fixes a direction and a constant cost regardless of how many corners the footprint has. Also volume-only: `deadband` (m³) zeroes out a Cut/Fill figure below the threshold, filtering drag noise near zero height (Cut/Fill/Net/Total are always RAW geometric volumes — deliberately unaffected by `swell`/`shrink`, so they keep answering "does this reach target elevation" regardless of what material's configured); `density` (t/m³ metric, lb/yd³ imperial) and `swell`/`shrink` (multipliers, default 1×) instead populate a separate Material section, standard Bank/Loose/Compacted earthworks convention — Adjusted Cut = raw × swell (loose/haul volume, bigger — excavating adds air voids), Adjusted Fill = raw ÷ shrink (loose/borrow volume needed, also bigger — the raw fill is already the compacted target void), plus Cut/Fill tonnage computed from the raw (not adjusted) volume, since swell/shrink change volume, not mass; the section only appears once at least one of `density`/`swell`/`shrink` is actually configured, no separate toggle. A `stale` field on the `om-measure` readout flags the brief window between a footprint committing and its elevation sample resolving, so a consumer doesn't read numbers left over from a prior footprint as current), `vega-lite` (live charts bound to a layer's data), `dynamic-chart` (the same Vega-Lite rendering, but data-driven by a live DOM event instead of a layer — `on="<event-name>"` + `series-field="<name>"` reads `event.detail[seriesField]` as the chart's `values` on every matching event, redrawing at a fixed `width`; a feature "freezes" it for free by simply omitting that field the next time it fires, no separate pause API needed), and the BIM set `ifc-browser` / `feature-inspector` / `ifc-loader` / `ifc-clash`. 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">`. |
|
|
87
|
+
| `<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`, `clip-box`, `zoom-controls`, `undo-redo`, `scale-bar` (metric/imperial/nautical `units`), `attribution`, `filter`, `draw` (point/line/polygon sketch capture, GeoJSON save/autosave; lines/polygons complete via double-click, touch double-tap — detected by the library itself, since iOS WebViews never synthesize `dblclick` from taps — Enter, or the toolbar's Finish button; `export-3d` adds an "Export 3D" button that clips loaded `Tile3DLayer`/`BIMLayer` content to the drawn footprint and downloads it as a portable GLB — re-framed to a local coordinate frame at the footprint's own centroid, each triangle carrying its own source color as vertex colors, no textures — or, with `export-3d="b3dm"`, the same mesh wrapped for Cesium/3D-Tiles pipelines; only currently-visible 3D Tiles/BIM layers are included — a layer hidden via `visible="false"` or the `toggle-layer` action is skipped, with a distinct console warning for "nothing loaded" vs. "everything hidden"), `measure` (geodesic distance + area + cut/fill volume — live labels, `units` toggle, an `om-measure` readout event; `modes="distance area volume"` adds a footprint-then-extrude tool: outline a polygon, double-click (double-tap on touch) to close it — it turns solid teal, ready — then drag the double-headed arrow gizmo that appears at its centroid up to fill or down to cut, reading out Cut/Fill/Net (signed, fill−cut)/Total (unsigned, cut+fill) volume plus Area/Perimeter, each labelled with its own sign convention. The math is a REAL per-cell grid integration: closing a footprint bulk-loads its covering DEM tiles, lays a metric grid in a local tangent frame at the ring centroid (cell size = the DEM's ground-sample distance; scanline point-in-polygon; bilinear, tile-seam-correct sampling; worker-offloaded), and integrates terrain-vs-base per cell — mixed cut AND fill within one footprint on undulating ground, with a published ±error (per-cell cellArea × 1.5 × GSD, summed per side), the cell size used, and a no-data warning all carried on the `om-measure` readout (`cellSizeM`/`gsdM`/`cutErrorM3`/`fillErrorM3`/`nodataFraction`). A `base-surface` attribute picks the reference surface: `custom` (default — the gizmo's draggable target plane), or boundary-derived stockpile strategies with no gizmo (`triangulated` boundary TIN, `plane` least-squares fit, `lowest`/`highest`/`average`). On `custom`, the extruded prism's own boundary hugs each corner's real ground elevation by default (so it doesn't visibly float above or sink below sloped terrain) — a "Flat target plane" toggle button appears above the readout once a footprint closes, switching that rendering to a single level plane instead (a rendering choice only; the underlying cut/fill numbers were already computed against one flat target elevation either way). Requires `terrain` on `<om-map>` — validation warns if `volume` is in `modes` with none (without terrain a flat-plane fallback runs, with no error figures — nothing honest to quote). A `profile` attribute alongside `modes` — not a mode of its own — samples elevation around a volume footprint's own perimeter as it's drawn and closed, live, and dispatches it on `om-measure`'s `profileSeries` field for a `dynamic-chart` widget to plot. Each sample is `{x: distance-from-start-m, y: elevation-m}`, and samples that ARE one of the drawn footprint's own corners additionally carry `vertexIndex` (0-based, in draw order) — so a chart can mark the real corners instead of every interpolated sample, and the widget's built-in profile chart marks each corner, labelling the first `1 · Start`. The map badges the first two vertices in draw order (`1 · Start`, `2`), which states the ring's direction outright — a start marker alone leaves clockwise vs counter-clockwise ambiguous, and the two wind to mirror-image profiles. Two badges is the minimum that fixes a direction and a constant cost regardless of how many corners the footprint has. Also volume-only: `deadband` (m³) zeroes out a Cut/Fill figure below the threshold, filtering drag noise near zero height (Cut/Fill/Net/Total are always RAW geometric volumes — deliberately unaffected by `swell`/`shrink`, so they keep answering "does this reach target elevation" regardless of what material's configured); `density` (t/m³ metric, lb/yd³ imperial) and `swell`/`shrink` (multipliers, default 1×) instead populate a separate Material section, standard Bank/Loose/Compacted earthworks convention — Adjusted Cut = raw × swell (loose/haul volume, bigger — excavating adds air voids), Adjusted Fill = raw ÷ shrink (loose/borrow volume needed, also bigger — the raw fill is already the compacted target void), plus Cut/Fill tonnage computed from the raw (not adjusted) volume, since swell/shrink change volume, not mass; the section only appears once at least one of `density`/`swell`/`shrink` is actually configured, no separate toggle. A `stale` field on the `om-measure` readout flags the brief window between a footprint committing and its elevation sample resolving, so a consumer doesn't read numbers left over from a prior footprint as current), `vega-lite` (live charts bound to a layer's data), `dynamic-chart` (the same Vega-Lite rendering, but data-driven by a live DOM event instead of a layer — `on="<event-name>"` + `series-field="<name>"` reads `event.detail[seriesField]` as the chart's `values` on every matching event, redrawing at a fixed `width`; a feature "freezes" it for free by simply omitting that field the next time it fires, no separate pause API needed), and the BIM set `ifc-browser` / `feature-inspector` / `ifc-loader` / `ifc-clash`. 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">`. |
|
|
88
88
|
| `<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`. Selection-anchored overlays scope with `layer` (one layer's picks) and `selection-type="click"`/`"hover"` (one pick type — give a click-opened popup `selection-type="click"` so hovering elsewhere doesn't drag it along). `{{field}}` interpolates the picked feature, HTML-escaped by default; `{{z}}` is a real depth-picked elevation when some layer under the cursor set `pickable="3d"` (terrain does this itself, so hovering the ground always has one) — empty otherwise, never a misleading 0. `clip-to-map` (opt-in) hides the overlay when its own box would spill past the map viewport, not just when its anchor leaves — for small transient tips that track the cursor, where an overhanging box would otherwise inflate the page's scroll overflow and cause visible view jitter. |
|
|
89
89
|
| `<om-behavior>` | Declarative interactions: `on="click|hover|drag|load|data-loaded"` → a named action. |
|
|
90
90
|
| `<om-story>` | A storyboard: `<om-step>` children fire actions on a timeline. Controlled by the `player` widget, behaviors, or `storyEl.play()/pause()/seek()`. |
|
|
@@ -149,11 +149,13 @@ Built-in actions wire to picks, widget buttons (`data-emit`), or script (`ctx.em
|
|
|
149
149
|
|
|
150
150
|
**Map stories:** a guided tour as markup — `<om-story>` holds `<om-step>` children that fire the same actions behaviors use, on a timeline (`duration`, `delay`, `parallel`); the built-in `type="player"` widget gives play/pause/scrub, seeking restores the scene's captured initial state, one story is active per map, and grabbing the map pauses playback. Effect verbs — `fade`, `pulse`, `trace` (progressive TripsLayer draw-on; with `feature-id`, a single polygon draws itself on inside its own layer), and `populate` (rows drop in one by one via a GPU filter sweep) — work as step shorthands or plain actions. The story is a sibling that references layers by id — delete it and the map is unchanged. Guide: [docs/stories.md](docs/stories.md).
|
|
151
151
|
|
|
152
|
+
**Classified symbology:** `classify-by="magnitude" classify-scale="jenks" classify-classes="5" classify-ramp="viridis"` computes class breaks *from the data* (quantile, equal-interval, or Jenks natural breaks), styles the layer with a named ramp, and auto-generates the classes legend — no domains or colors to hand-author; an explicit `get-fill-color`/`color` always wins. **Temporal playback:** `<om-widget type="time-slider" layer="…" field="time" duration="20s" window="86400000" format="date">` is a play/pause/scrub control over a layer's time field, driving the same `filter-layer` mechanics as the filter widget (stats stay coherent; `loop` restarts the sweep).
|
|
153
|
+
|
|
152
154
|
GPU filtering is declarative — `filter-field="magnitude" filter-range="[4,10]"` — updates live from the built-in `filter` widget (a slider), and widget statistics stay coherent with what the map shows (`ctx.stats` respects the active filter unless you opt out). Up to 4 numeric dimensions at once via `filter-fields='[{"field":"magnitude","range":[4,10]},{"field":"time","range":[…]}]'` — a widget per field, each moved independently; a row must pass every active dimension. Categorical filtering is the same shape, a separate mechanism — `filter-category="fuel" filter-categories="[...]"` (or `filter-category-fields` for up to 4) renders the same `filter` widget as a checkbox list instead of a slider, and combines with any active numeric filter (a row must pass both).
|
|
153
155
|
|
|
154
156
|
Dashed lines are one attribute — `dash="[6, 3]"` (or `dash="6 3"`, plus optional `dash-justified`) on a `PathLayer`/`GeoJsonLayer`/`PolygonLayer`/`TripsLayer` — wired through deck's `PathStyleExtension` under the hood.
|
|
155
157
|
|
|
156
|
-
**Routing & tracking:** `<om-layer type="Route" geometry='{"type":"LineString","coordinates":[...]}'>` draws a styled route (casing + line + origin/destination markers) from geometry you already have; `origin`/`destination` (+ `provider`, default `"nika"`) resolve one asynchronously instead via `OmMap.registerRoutingProvider`. `follow="fit-route"` auto-fits the camera once it resolves. `<om-layer type="Tracking" get-position="[$lng,$lat]">` renders one moving entity with bearing-derived rotation (`bearing-field`, default `"bearing"`) and glides smoothly between position updates (`interpolate-ms`, default `1000`) rather than jumping — `follow="follow"` eases the camera along with it. Live position data arrives through the ordinary `data`/`source` mechanism, same as any layer — no separate tracking subscription API to learn. Guide (incl. a keyless OSRM adapter recipe and per-engine notes): [docs/routing.md](docs/routing.md).
|
|
158
|
+
**Routing & tracking:** `<om-layer type="Route" geometry='{"type":"LineString","coordinates":[...]}'>` draws a styled route (casing + line + origin/destination markers) from geometry you already have; `origin`/`destination` (+ `provider`, default `"nika"`) resolve one asynchronously instead via `OmMap.registerRoutingProvider`. `follow="fit-route"` auto-fits the camera once it resolves. `<om-layer type="Tracking" get-position="[$lng,$lat]">` renders one moving entity with bearing-derived rotation (`bearing-field`, default `"bearing"`; `icon="arrow|car|motorcycle"` picks the marker shape, tinted by `color`) and glides smoothly between position updates (`interpolate-ms`, default `1000`) rather than jumping — `follow="follow"` eases the camera along with it. Live position data arrives through the ordinary `data`/`source` mechanism, same as any layer — no separate tracking subscription API to learn. Link a route to its rider with `progress-from` and pick a tail mode: `tail="none"` (client view — only current position to destination renders) or `tail="dim"` (operator view — the traveled portion darkens), split at the marker's interpolated position per frame. Guide (incl. a keyless OSRM adapter recipe and per-engine notes): [docs/routing.md](docs/routing.md).
|
|
157
159
|
|
|
158
160
|
### Widgets get a real runtime API
|
|
159
161
|
|
|
@@ -195,6 +197,18 @@ That opt-in command updates `.vscode/settings.json` and copies the `!`-prefixed
|
|
|
195
197
|
|
|
196
198
|
The package also ships `!`-prefixed manifest snippets (`node_modules/@nika-js/onlymap/.vscode/onlymap.code-snippets`) — type `!starter`, `!map`, `!layer`, `!draw`, etc. to scaffold a well-formed element.
|
|
197
199
|
|
|
200
|
+
## React Native / mobile
|
|
201
|
+
|
|
202
|
+
The same runtime ships as an Expo React Native adapter,
|
|
203
|
+
[`@nika-js/onlymap-native`](https://github.com/NikaGeospatial/onlymap-native)
|
|
204
|
+
— the complete library hosted in a secure WebView and driven from JSX over a
|
|
205
|
+
JSON bridge. The native surface keeps OnlyMap's vocabulary with two rule
|
|
206
|
+
changes: accessors are **expression strings** (`getPosition="[$lon, $lat]"` —
|
|
207
|
+
functions cannot cross the bridge) and everything on a descriptor is plain
|
|
208
|
+
JSON. A 20-fixture parity gate keeps the native lane byte-identical to this
|
|
209
|
+
repo's React adapter at the IR level. Guide:
|
|
210
|
+
[docs.nikaplanet.com → OnlyMap JS → React Native](https://docs.nikaplanet.com/onlymap/react-native).
|
|
211
|
+
|
|
198
212
|
## Quick-start guide
|
|
199
213
|
|
|
200
214
|
### Use OnlyMapJS with Claude Code and Codex
|
|
@@ -327,7 +341,7 @@ The library reports one **deployment-scoped** usage snapshot per map per page lo
|
|
|
327
341
|
|
|
328
342
|
## Not implemented yet (honestly)
|
|
329
343
|
|
|
330
|
-
Mapbox GL basemaps, depth-interleaved 3D compositing, globe projection, SSE transport, `dblclick` behaviors, the `transform` data pipeline, the typed fluent builder, and stories/draw as React components (both work via the HTML manifest). On `pick-features`: instanced (i3dm) tiles keep tile-granularity picking, and `feature-styles`' `opacity` below 1 is still being validated. On BIM: clash detection is bounding-box only (no mesh-level test, and it cannot tell a resolved penetration from a collision), there is no BCF export, no equivalent of Navisworks' Auto Reveal, and prepared tilesets converted before the `bbox*` columns existed cannot take part in a clash pass — `strength` tinting and `opacity: 0` (hide, a shader discard) work. On the measure widget's `volume` mode: the per-cell grid integration measures against the map's active terrain DEM only — measuring against an imported design surface or a previous survey (period-over-period reconciliation) and a cut/fill heat-map overlay are not implemented yet, and a snapped footprint vertex takes the matched feature's horizontal position only — its exact elevation is not threaded through (see **XY snapping** above).
|
|
344
|
+
Mapbox GL basemaps, depth-interleaved 3D compositing, globe projection, SSE transport, `dblclick` behaviors, the `transform` data pipeline, the typed fluent builder, and stories/draw as React components (both work via the HTML manifest). On React Native (`@nika-js/onlymap-native`): stories, draw, widgets, and overlays as native components (build native UI beside the map instead), and bare (non-Expo) React Native until its `0.2.0`. On `pick-features`: instanced (i3dm) tiles keep tile-granularity picking, and `feature-styles`' `opacity` below 1 is still being validated. On BIM: clash detection is bounding-box only (no mesh-level test, and it cannot tell a resolved penetration from a collision), there is no BCF export, no equivalent of Navisworks' Auto Reveal, and prepared tilesets converted before the `bbox*` columns existed cannot take part in a clash pass — `strength` tinting and `opacity: 0` (hide, a shader discard) work. On the measure widget's `volume` mode: the per-cell grid integration measures against the map's active terrain DEM only — measuring against an imported design surface or a previous survey (period-over-period reconciliation) and a cut/fill heat-map overlay are not implemented yet, and a snapped footprint vertex takes the matched feature's horizontal position only — its exact elevation is not threaded through (see **XY snapping** above).
|
|
331
345
|
|
|
332
346
|
## Going deeper
|
|
333
347
|
|
|
@@ -35,6 +35,8 @@ export declare const FILTER_ATTRS: Set<string>;
|
|
|
35
35
|
* applies to layers that stroke a path.
|
|
36
36
|
*/
|
|
37
37
|
export declare const DASH_ATTRS: Set<string>;
|
|
38
|
+
/** Classified symbology (spec: issue #12; src/classify.ts) — universal layer attributes like `filter-*`, resolved where the data is in hand (parse-manifest / descriptorToIR), never per-schema deck props. */
|
|
39
|
+
export declare const CLASSIFY_ATTRS: Set<string>;
|
|
38
40
|
/** Layer types whose deck class strokes a path, so PathStyleExtension's dash takes effect (GeoJsonLayer/PolygonLayer dash their line/stroke sublayers). */
|
|
39
41
|
export declare const DASH_CAPABLE_LAYERS: Set<string>;
|
|
40
42
|
/** A dash pattern authored as a JSON array (`[6, 3]`) OR an SVG-style token list (`6 3`, `6,3`) → `[dashLength, gapLength]`; undefined if it isn't two finite positive numbers. */
|
|
@@ -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-
|
|
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-CnKC1yuV.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;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classified symbology (issue #12 — "categorized/graduated styling, named
|
|
3
|
+
* ramps + auto-generated legend"). The half `scale()` deliberately does NOT
|
|
4
|
+
* cover: class breaks computed FROM THE DATA. An auto-domain expression was
|
|
5
|
+
* rejected long ago as impure ("an auto-domain scale() broke the cache"), so
|
|
6
|
+
* classification lives at the layer level instead, resolved where the rows
|
|
7
|
+
* are already in hand (parse-manifest / descriptorToIR — the same "resolved
|
|
8
|
+
* once data is present" seam `highlighted-id` and the CityJSON defaults
|
|
9
|
+
* use). URL-backed layers classify on the reparse their data arrival
|
|
10
|
+
* triggers; until then the layer renders unclassified, not blank.
|
|
11
|
+
*
|
|
12
|
+
* Authoring surface (universal layer attributes, like filter-*):
|
|
13
|
+
* classify-by="magnitude" the numeric field
|
|
14
|
+
* classify-scale="quantile" quantile (default) | equal-interval | jenks
|
|
15
|
+
* classify-classes="5" 2–12, default 5
|
|
16
|
+
* classify-ramp="viridis" a named ramp below, default viridis
|
|
17
|
+
*
|
|
18
|
+
* An authored fill-color source (get-fill-color, a color shorthand, or a
|
|
19
|
+
* script-block export) always wins — classification silently defers at
|
|
20
|
+
* runtime and validation names the conflict at author time. The generated
|
|
21
|
+
* accessor is a plain function with an updateTriggers key derived from the
|
|
22
|
+
* actual computed breaks, so deck recomputes exactly when the classification
|
|
23
|
+
* genuinely changes and never otherwise; results are memoized per data
|
|
24
|
+
* reference (inline data is reference-stable since 0.6.11).
|
|
25
|
+
*/
|
|
26
|
+
import type { LayerData, LayerMeta } from "./ir";
|
|
27
|
+
import type { Shape } from "./expr";
|
|
28
|
+
/**
|
|
29
|
+
* Named ramps — standard matplotlib / ColorBrewer stop sets, the same
|
|
30
|
+
* vocabulary COGLayer's raster colormaps use where the names overlap.
|
|
31
|
+
*/
|
|
32
|
+
export declare const CLASSIFY_RAMPS: Record<string, string[]>;
|
|
33
|
+
export declare const CLASSIFY_SCALES: readonly ["quantile", "equal-interval", "jenks"];
|
|
34
|
+
export type ClassifyScale = (typeof CLASSIFY_SCALES)[number];
|
|
35
|
+
/** k colors sampled evenly along a ramp's stops (linear RGB interpolation). */
|
|
36
|
+
export declare function rampColors(name: string, k: number): string[];
|
|
37
|
+
/** k-1 ascending upper boundaries for k classes: value < b[0] → class 0, …, ≥ b[k-2] → class k-1. */
|
|
38
|
+
export declare function quantileBreaks(sorted: readonly number[], k: number): number[];
|
|
39
|
+
export declare function equalIntervalBreaks(sorted: readonly number[], k: number): number[];
|
|
40
|
+
/**
|
|
41
|
+
* Jenks natural breaks via the classic Fisher–Jenks dynamic program. O(k·n²),
|
|
42
|
+
* so inputs above 600 values are evenly strided down to 600 first — the
|
|
43
|
+
* breaks of a stride-sampled sorted vector are statistically
|
|
44
|
+
* indistinguishable for styling purposes, and this keeps a 25k-row layer's
|
|
45
|
+
* reconcile pass flat.
|
|
46
|
+
*/
|
|
47
|
+
export declare function jenksBreaks(sortedInput: readonly number[], k: number): number[];
|
|
48
|
+
export interface ClassifySpec {
|
|
49
|
+
classifyBy?: string;
|
|
50
|
+
classifyScale?: string;
|
|
51
|
+
classifyClasses?: number;
|
|
52
|
+
classifyRamp?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Computes the classification for a layer and installs it as `getFillColor`
|
|
56
|
+
* + updateTrigger + legend, shared verbatim by both front-ends. No-ops (and
|
|
57
|
+
* leaves everything untouched) when: no `classifyBy`; a fill-color source is
|
|
58
|
+
* already authored (it wins — validation names the conflict); the data
|
|
59
|
+
* hasn't arrived yet or has under two distinct numeric values (the URL case
|
|
60
|
+
* — the data-arrival reparse re-enters here with real rows).
|
|
61
|
+
*/
|
|
62
|
+
export declare function applyClassification(props: Record<string, unknown>, meta: LayerMeta, updateTriggers: Record<string, string>, spec: ClassifySpec, data: LayerData, shape: Shape, warnLabel: string): void;
|
|
@@ -65,6 +65,9 @@ export declare class DrawController {
|
|
|
65
65
|
* Consumed (cleared) by the very next click regardless of whether it matched.
|
|
66
66
|
*/
|
|
67
67
|
private lastClosePos;
|
|
68
|
+
/** The previous click's coordinate + timestamp — the first half of a possible double-tap (see DOUBLE_TAP_MS). */
|
|
69
|
+
private lastTapPos;
|
|
70
|
+
private lastTapTime;
|
|
68
71
|
constructor(mapEl: Element, core: RuntimeCore);
|
|
69
72
|
/** A draw tool is active — om-map routes points here and suppresses selection. */
|
|
70
73
|
isCapturing(): boolean;
|
|
@@ -109,7 +112,7 @@ export declare class DrawController {
|
|
|
109
112
|
private ensureCommittedLayer;
|
|
110
113
|
setMode(target: string, mode: DrawMode | null): void;
|
|
111
114
|
private deactivate;
|
|
112
|
-
point(coord: [number, number] | null, kind: "click" | "hover"): void;
|
|
115
|
+
point(coord: [number, number] | null, kind: "click" | "hover", pointerType?: string): void;
|
|
113
116
|
/** Drop a queued hover-coalesced render (a following immediate render already supersedes it). */
|
|
114
117
|
private cancelHoverRaf;
|
|
115
118
|
/** `CLOSE_GUARD_PIXELS` converted to real meters at the current zoom/latitude (standard Web Mercator meters-per-pixel). */
|
|
@@ -278,7 +278,7 @@ export declare class OmMapElement extends HTMLElementBase {
|
|
|
278
278
|
/** Harness map-point injection (spec: "Consumer Testing Surface") — the
|
|
279
279
|
* same path a real deck click/hover coordinate takes, so the om-map-point
|
|
280
280
|
* event and custom capture tools are testable without a GPU. */
|
|
281
|
-
injectMapPointInternal(coordinate: [number, number] | null, kind?: "click" | "hover"): void;
|
|
281
|
+
injectMapPointInternal(coordinate: [number, number] | null, kind?: "click" | "hover", pointerType?: string): void;
|
|
282
282
|
/** Harness setView (spec: "Consumer Testing Surface") — the one path that reaches pitch/bearing. */
|
|
283
283
|
setViewInternal(partial: {
|
|
284
284
|
longitude?: number;
|
|
@@ -416,6 +416,26 @@ declare global {
|
|
|
416
416
|
layerId: string;
|
|
417
417
|
tileset: unknown;
|
|
418
418
|
}>;
|
|
419
|
+
/** A Route layer finished resolving its route (direct geometry or a
|
|
420
|
+
* RoutingProvider round-trip) — detail carries the authored layer id and
|
|
421
|
+
* the normalized route (geometry/distanceMeters/durationSec/legs/bounds),
|
|
422
|
+
* so a page can read a provider-resolved route without re-fetching it. */
|
|
423
|
+
"om-route-resolved": CustomEvent<{
|
|
424
|
+
layerId: string;
|
|
425
|
+
route: {
|
|
426
|
+
geometry?: {
|
|
427
|
+
type: "LineString";
|
|
428
|
+
coordinates: [number, number][];
|
|
429
|
+
};
|
|
430
|
+
distanceMeters?: number;
|
|
431
|
+
durationSec?: number;
|
|
432
|
+
legs?: {
|
|
433
|
+
distanceMeters: number;
|
|
434
|
+
durationSec: number;
|
|
435
|
+
}[];
|
|
436
|
+
bounds?: [[number, number], [number, number]];
|
|
437
|
+
};
|
|
438
|
+
}>;
|
|
419
439
|
}
|
|
420
440
|
}
|
|
421
441
|
export {};
|
package/dist/html-data.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface TagData {
|
|
|
17
17
|
/** Hand-authored action list — validated against hasAction() in html-data.test.ts. */
|
|
18
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", "export-region-3d", "measure-mode", "measure-clear", "measure-units", "set-basemap", "set-lighting", "set-terrain", "set-clip-box", "clip-box-edit", "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", "dynamic-chart", "player", "draw", "measure", "basemap-switcher", "lighting", "clip-box", "undo-redo", "widgets-toggle", "ifc-browser", "ifc-legend", "feature-inspector", "ifc-inspector", "ifc-loader", "ifc-clash"];
|
|
20
|
+
export declare const BUILTIN_WIDGETS: readonly ["legend", "layer-switcher", "zoom-controls", "scale-bar", "attribution", "filter", "vega-lite", "dynamic-chart", "player", "draw", "measure", "basemap-switcher", "lighting", "clip-box", "undo-redo", "widgets-toggle", "time-slider", "ifc-browser", "ifc-legend", "feature-inspector", "ifc-inspector", "ifc-loader", "ifc-clash"];
|
|
21
21
|
export declare function buildHtmlCustomData(): {
|
|
22
22
|
version: number;
|
|
23
23
|
tags: TagData[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as E, j as S, k as R, o as N, p as h, q as w, s as O, t as W, v as x, w as I, x as B, y as _, A as P, B as k, E as $, F as z, H as v, I as F, J as C, K as U, N as p, O as M } from "./index-
|
|
2
|
-
import { P as $e, R as ze, _ as ve, Q as Ce, S as Ue, T as Me, U as je, X as De, Y as Je, Z as qe, $ as He, a0 as Ve, a1 as Ge, a2 as Ke, a3 as Qe, a4 as Xe, a5 as Ye, a6 as Ze, a7 as et, a8 as tt, a9 as nt, aa as rt, ab as at, ac as st, ad as ot } from "./index-
|
|
1
|
+
import { i as E, j as S, k as R, o as N, p as h, q as w, s as O, t as W, v as x, w as I, x as B, y as _, A as P, B as k, E as $, F as z, H as v, I as F, J as C, K as U, N as p, O as M } from "./index-CnKC1yuV.js";
|
|
2
|
+
import { P as $e, R as ze, _ as ve, Q as Ce, S as Ue, T as Me, U as je, X as De, Y as Je, Z as qe, $ as He, a0 as Ve, a1 as Ge, a2 as Ke, a3 as Qe, a4 as Xe, a5 as Ye, a6 as Ze, a7 as et, a8 as tt, a9 as nt, aa as rt, ab as at, ac as st, ad as ot } from "./index-CnKC1yuV.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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ak as Bt, ax as Xt, ay as Yt, az as Us } from "./index-
|
|
1
|
+
import { ak as Bt, ax as Xt, ay as Yt, az as Us } from "./index-CnKC1yuV.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";
|