@nika-js/onlymap 0.6.2 → 0.6.7
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 +81 -0
- package/README.md +9 -7
- package/dist/{LercDecode.es-BOJJr6Gx.js → LercDecode.es-BzboSQ2U.js} +1 -1
- package/dist/attribute-resolution.d.ts +84 -6
- package/dist/{basemap-BofXgXxy.js → basemap-pWXbFGjp.js} +1 -1
- package/dist/ctx.d.ts +3 -1
- package/dist/data-layer.d.ts +26 -4
- package/dist/declarative-filter.d.ts +14 -5
- package/dist/elements/om-map.d.ts +1 -0
- package/dist/geodesy.d.ts +3 -0
- package/dist/{geoparquet-DapHATA0.js → geoparquet-BPl1T2Or.js} +1 -1
- package/dist/{index-SPJVn_n_.js → index-B9a6f006.js} +1 -1
- package/dist/{index-BF9iO8Tq.js → index-Bgx3CJru.js} +1 -1
- package/dist/{index-7B-6Cbzu.js → index-CAuq7wRs.js} +17565 -16782
- package/dist/{index-xmJjZxQJ.js → index-CqsQz9Bp.js} +2 -2
- package/dist/{index-BmX6IId3.js → index-iVMlGZS9.js} +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/ir-snapshot.d.ts +2 -1
- package/dist/ir.d.ts +47 -9
- package/dist/layer-registry.d.ts +7 -0
- package/dist/layers/marker-icons.d.ts +19 -0
- package/dist/layers/route-layer.d.ts +71 -0
- package/dist/layers/tracking-layer.d.ts +52 -0
- package/dist/{lerc-C6k7EzSN.js → lerc-BgzbFAc7.js} +2 -2
- package/dist/license.d.ts +13 -2
- package/dist/measure-controller.d.ts +28 -4
- package/dist/onlymap.standalone.js +50150 -49367
- package/dist/onlymapjs.js +65 -61
- package/dist/programmatic.d.ts +24 -4
- package/dist/providers.d.ts +48 -0
- package/dist/{raster-Cuzhibe2.js → raster-DtyMY54Z.js} +2 -2
- package/dist/{raster-pipeline-DLrnJK8y.js → raster-pipeline-BmHRCUEb.js} +1 -1
- package/dist/react/om-layer.d.ts +6 -2
- package/dist/react.js +54 -50
- package/dist/tracking-interpolation.d.ts +22 -0
- package/dist/version.d.ts +1 -1
- package/dist/{zarr-CUzEX8fB.js → zarr-CTpFZo_m.js} +2 -2
- package/docs/3d-assets.md +1 -1
- package/docs/live-data.md +17 -0
- package/docs/react.md +9 -1
- package/docs/routing.md +92 -0
- package/llms.txt +6 -5
- package/onlymapjs.html-data.json +61 -3
- package/package.json +1 -1
- package/skills/onlymapjs/SKILL.md +6 -4
- package/skills/onlymapjs/references/react.md +1 -1
- package/skills/onlymapjs/references/syntax.md +83 -5
- package/skills/onlymapjs/references/testing.md +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,87 @@ 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.7 — 2026-08-17
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Routing & tracking layer types** — `<om-layer type="Route" geometry='{"type":"LineString","coordinates":[...]}'>` draws a styled route (casing, colored line, origin/destination markers) from geometry you already have, resolving synchronously with no network call; `origin`/`destination` (+ `provider`, default `"nika"`, + optional `waypoints`/`profile`) resolve one asynchronously instead via a registered `RoutingProvider` (`OmMap.registerRoutingProvider`). `follow="fit-route"` auto-fits the camera once the route resolves. `<om-layer type="Tracking" get-position="[$lng,$lat]">` renders one moving entity with bearing-derived icon rotation (`bearing-field`, default `"bearing"`) that glides smoothly between position updates (`interpolate-ms`, default `1000`) instead of jumping; `follow="follow"` eases the camera along with it. Live position data arrives through the ordinary `data`/`source` mechanism — no separate tracking-subscription API. `Route`/`Tracking` expand into ordinary `PathLayer`/`IconLayer` instances internally, the same pattern `BIMLayer` already uses for `Tile3DLayer`.
|
|
15
|
+
- The bundled `nika` routing provider is registered by default so `type="Route"` works out of the box once NIKA's routing service exists — its endpoint is currently an **unverified placeholder** (no such backend is live yet); register a working provider with `OmMap.registerRoutingProvider(name, provider)` for anything that needs to resolve routes today, or author `geometry` directly.
|
|
16
|
+
- Two gallery examples: **Routing & Tracking** (direct-geometry route + a simulated live GPS feed gliding a tracking marker) and **Compute a Route** (click two points anywhere and a ~15-line page-script `RoutingProvider` adapter over OSRM's keyless public demo server computes the street-following drive — the click handler only writes `origin`/`destination` attributes; reconcile, the provider round-trip, and the camera re-fit are all library machinery). The OSRM adapter recipe also ships in the skill's syntax reference.
|
|
17
|
+
|
|
18
|
+
## 0.6.6 — 2026-08-14
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Multi-dimension categorical GPU filtering** — `filter-category-fields='[{"field":"fuel","categories":["Coal","Gas"]},{"field":"region","categories":["West"]}]'` filters on up to 4 categorical fields at once, AND'd together and combinable with an active numeric filter (a row must pass every dimension of both kinds). `filter-category`/`filter-categories` (single dimension) keep working unchanged. The built-in `<om-widget type="filter">` now auto-renders a checkbox list (one per distinct value present in the data, with its row count) instead of a slider when its `field` is declared categorically — the mode is inferred from the layer's own filter, never a separate widget attribute. The React/programmatic front-ends get the equivalent `filterCategoryFields` prop.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `ctx.stats`/`ctx.dataInViewport` now respect an active categorical filter (`filter-category`/`filter-categories`) — previously only numeric filters were coherence-rule-aware, so a chart could silently disagree with a categorically-filtered map.
|
|
25
|
+
- Story scrubbing and undo/redo now correctly restore an active categorical filter — previously `filter-category`/`filter-categories` weren't captured at all, so rewinding past a categorical `filter-layer` step silently dropped it.
|
|
26
|
+
|
|
27
|
+
## 0.6.5 — 2026-08-13
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **Multi-dimension GPU filtering** — `filter-fields='[{"field":"magnitude","range":[4,10]},{"field":"time","range":[…]}]'` filters on up to 4 fields at once (`DataFilterExtension`'s own ceiling), AND'd together (a row must pass every dimension). `filter-field`/`filter-range` (single dimension) keep working unchanged — `filter-fields` is additive, not a replacement. Place one `<om-widget type="filter" layer="…" field="…">` per dimension; the `filter-layer` action merges range updates onto the matching dimension instead of replacing the whole filter, so independent widgets never clobber each other. `ctx.stats`/`ctx.dataInViewport`'s filter-aware coherence rule applies the same AND-across-dimensions test CPU-side. The React/programmatic front-ends get the equivalent `filterFields` prop.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- `populate` on a layer with more than one authored filter dimension now sweeps the first and holds the rest at their authored range, instead of a type error.
|
|
34
|
+
|
|
35
|
+
## 0.6.4 — 2026-08-12
|
|
36
|
+
|
|
37
|
+
React Native core prerequisites. 0.6.3 was already tagged for release when this
|
|
38
|
+
work landed, so it carries its own version rather than redefining that one.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **Descriptor-owned data transports**: fetches, polling loops, and WebSockets
|
|
42
|
+
are shared by transport identity and reference-counted across active map
|
|
43
|
+
owners. Layer removal or option changes release stale handles; map disposal
|
|
44
|
+
releases all handles; `MapController.suspend()` and `resume()` provide an
|
|
45
|
+
app-background lifecycle without discarding canonical descriptors. A release
|
|
46
|
+
the owner means to reverse — `suspend()`, removing or re-pointing a layer —
|
|
47
|
+
leaves its last rows as a cold snapshot, so resuming or re-adding repaints
|
|
48
|
+
immediately instead of flashing empty; a permanent teardown (`destroy()`, an
|
|
49
|
+
`<om-map>` leaving the document) keeps nothing.
|
|
50
|
+
- `releaseDataOwner(owner, {retain})` plus an optional `owner` argument on
|
|
51
|
+
`descriptorToIR` — descriptor-owned transport lifetime for hosts that drive
|
|
52
|
+
the IR directly instead of through `MapController`.
|
|
53
|
+
- **JSON-safe programmatic descriptors**: schema-declared accessor props may
|
|
54
|
+
use restricted OnlyMap expression strings, and
|
|
55
|
+
`snapshotDescriptorIR(descriptors)` produces deterministic, fetch-free IR
|
|
56
|
+
snapshots for native/cross-process parity tests. The React layer adapter now
|
|
57
|
+
exposes a pure descriptor conversion path and mirrors dashed-line options.
|
|
58
|
+
- **App-scoped packaged licenses**: signed license tokens may now declare exact
|
|
59
|
+
native application identifiers in an `apps` claim, independently or together
|
|
60
|
+
with web domains. Native hosts pass platform-derived identity to
|
|
61
|
+
`configureLicense(key, {appId})`; page or bridge input must never supply it.
|
|
62
|
+
Only the `domains` claim is pinned by the browser — `apps` is asserted by the
|
|
63
|
+
host, so scope a native key by both where you can.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
- **Live transports no longer live for the page.** A `ws(s)://` socket, a
|
|
67
|
+
`refresh` poll loop, and an in-flight `data` fetch previously outlived the
|
|
68
|
+
layer that opened them; they are now stopped when their last descriptor owner
|
|
69
|
+
releases them — layer removal, a change to `data`/`source`/`key`/`flush`/
|
|
70
|
+
`refresh`, `MapController.destroy()`/`suspend()`, or an `<om-map>` leaving the
|
|
71
|
+
document. Pages that relied on a connection surviving layer removal must keep
|
|
72
|
+
the layer mounted (`visible="false"` does not release) or re-add it, which now
|
|
73
|
+
repaints from the retained cold snapshot. Re-parenting a live `<om-map>` in
|
|
74
|
+
the DOM does **not** drop its transports: the release is deferred a microtask,
|
|
75
|
+
so a synchronous disconnect→reconnect keeps the socket open.
|
|
76
|
+
- The data cache is keyed by **transport identity** (URL plus live-source
|
|
77
|
+
options) rather than URL alone. Two layers on the same URL still share one
|
|
78
|
+
transport when their stream/poll options match, and now correctly get separate
|
|
79
|
+
ones when they do not.
|
|
80
|
+
|
|
81
|
+
## 0.6.3 — 2026-08-12
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
- **Measure labels no longer render painted onto the terrain** (reported readability bug). The distance/area badges were `terrain="drape"`, and draping renders a layer INTO the terrain's own texture — so a badge came out flat on the ground, stretching and skewing with the slope and going edge-on to a pitched camera. They now use `terrain="offset"`, which keeps the badge a screen-facing billboard and only lifts its anchor to the surface, plus a 14px screen-space lift so a pill never reads as half-buried in a rise.
|
|
85
|
+
- **`PopupLayer`'s default terrain mode is now `offset` rather than `drape`**, for the same reason and following the rule the library already applied to 3D models: billboarded content anchors ON the surface instead of being painted onto it. This affects any `PopupLayer` under an active `terrain` that did not set the attribute explicitly; `terrain="drape"` still does the old thing for anyone who genuinely wants text painted onto the ground like a road marking.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
- **The elevation profile marks the footprint's own corners.** `profileSeries` samples that ARE a drawn vertex now carry `vertexIndex` (0-based, in draw order; the closing sample back at the start carries the last index), and interpolated samples omit the field entirely so a Vega-Lite spec separates them with `isValid(datum.vertexIndex)`. The measure widget's built-in profile chart uses this: a clean line, a distinct point on each real corner with a `Vertex / Distance / Elevation` tooltip, and vertex 0 labelled **"1 · Start"** (the exact badge text used on the map) — replacing the old `point: true`, which dotted all ~50 interpolated samples equally and so said nothing about which points were corners. New `ProfilePoint` type, exported.
|
|
89
|
+
- **Direction badges on the map: `1 · Start` and `2`.** Two problems, one marker. The profile runs around a closed loop, so its chart has a leftmost point but the map had no cue for WHICH corner that was; and marking only the start is still ambiguous, because from that corner the ring could run either way and clockwise vs counter-clockwise produce mirror-image profiles. The first two vertices are badged in draw order — the minimum that fixes a direction, and a constant cost: one badge per vertex would grow the clutter precisely as a footprint got complicated enough to need the cue. Draw order IS the order the profile walks, so badge N and the chart's Vertex N are the same point by construction. The chart still marks every corner. Displayed 1-based; `ProfilePoint.vertexIndex` stays 0-based, being an array index. Shown only with `profile` on, and cleared with the footprint.
|
|
90
|
+
- **`resamplePathWithVertices(points, count)`** in `geodesy.ts` — `resamplePath`'s evenly-spaced curve with the path's own vertices merged in by distance and tagged, coinciding samples replaced rather than duplicated. This is what makes a profile addressable back to the geometry that produced it.
|
|
91
|
+
|
|
11
92
|
## 0.6.2 — 2026-08-12
|
|
12
93
|
|
|
13
94
|
### Added
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Or with no build step at all, straight from a CDN:
|
|
|
72
72
|
|
|
73
73
|
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.
|
|
74
74
|
|
|
75
|
-
Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 1,
|
|
75
|
+
Then `npx @nika-js/onlymap init` wires up VS Code IntelliSense and `!`-prefixed manifest snippets for your project. The library ships with 1,208 unit/behavioral tests and 64 Playwright GPU tests.
|
|
76
76
|
|
|
77
77
|
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.
|
|
78
78
|
|
|
@@ -83,8 +83,8 @@ A handful of elements, one rule: **attributes are kebab-case versions of deck.gl
|
|
|
83
83
|
| Element | Role |
|
|
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
|
-
| `<om-layer>` | Any of **
|
|
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. 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">`. |
|
|
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">`. |
|
|
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,10 +149,12 @@ 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
|
-
GPU filtering is declarative — `filter-field="magnitude" filter-range="[4,10]"` — updates live from the built-in `filter` slider
|
|
152
|
+
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
153
|
|
|
154
154
|
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
155
|
|
|
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).
|
|
157
|
+
|
|
156
158
|
### Widgets get a real runtime API
|
|
157
159
|
|
|
158
160
|
Custom widget scripts receive `ctx`: layer metadata, `viewport` (bounds/zoom/project), the current `selection`, `emit()`, and data access — `ctx.data(id)`, `ctx.dataInViewport(id)`, `ctx.stats(id, field)` (count/min/max/mean/stddev/percentiles/histogram, viewport-scoped on request). Declare `watch` tokens (`data:quakes viewport selection layers`) and the runtime re-renders you only when relevant state changes. `vegaEmbed` and `d3` are available as globals for charts.
|
|
@@ -317,7 +319,7 @@ Packaged native hosts can verify an app-scoped key by passing platform-derived i
|
|
|
317
319
|
await OmMap.configureLicense("om_live_…", { appId: "com.example.riders" });
|
|
318
320
|
```
|
|
319
321
|
|
|
320
|
-
The `appId` must come from trusted native build metadata, never page/bridge input. Keys are self-verifying signed tokens (no network round-trip, works offline and in CI) bound to declared domains and/or exact app identifiers. Licensing: https://www.nikaplanet.com/onlymap.
|
|
322
|
+
The `appId` must come from trusted native build metadata, never page/bridge input — the library cannot verify where it came from. Unlike `domains`, which the browser pins, the `apps` claim is asserted by the caller, so prefer keys minted with **both** claims (include the packaged host's own origins) and treat app scoping as a deterrent rather than enforcement. Keys are self-verifying signed tokens (no network round-trip, works offline and in CI) bound to declared domains and/or exact app identifiers. Licensing: https://www.nikaplanet.com/onlymap.
|
|
321
323
|
|
|
322
324
|
## Telemetry
|
|
323
325
|
|
|
@@ -325,13 +327,13 @@ The library reports one **deployment-scoped** usage snapshot per map per page lo
|
|
|
325
327
|
|
|
326
328
|
## Not implemented yet (honestly)
|
|
327
329
|
|
|
328
|
-
Mapbox GL basemaps, depth-interleaved 3D compositing, globe projection, SSE transport,
|
|
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).
|
|
329
331
|
|
|
330
332
|
## Going deeper
|
|
331
333
|
|
|
332
334
|
| | |
|
|
333
335
|
|---|---|
|
|
334
|
-
| [docs/react.md](docs/react.md) · [docs/basemaps.md](docs/basemaps.md) · [docs/testing.md](docs/testing.md) · [docs/live-data.md](docs/live-data.md) · [docs/image-overlays.md](docs/image-overlays.md) · [docs/3d-assets.md](docs/3d-assets.md) · [docs/stories.md](docs/stories.md) · [docs/telemetry.md](docs/telemetry.md) | Consumer guides |
|
|
336
|
+
| [docs/react.md](docs/react.md) · [docs/basemaps.md](docs/basemaps.md) · [docs/testing.md](docs/testing.md) · [docs/live-data.md](docs/live-data.md) · [docs/image-overlays.md](docs/image-overlays.md) · [docs/3d-assets.md](docs/3d-assets.md) · [docs/stories.md](docs/stories.md) · [docs/routing.md](docs/routing.md) · [docs/telemetry.md](docs/telemetry.md) | Consumer guides |
|
|
335
337
|
| [CHANGELOG.md](CHANGELOG.md) | Version-by-version release notes |
|
|
336
338
|
| [llms.txt](llms.txt) | The agent-facing quick reference |
|
|
337
339
|
| `skills/onlymapjs` | Installable LLM skill for OnlyMapJS authoring |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayerMeta, LayerFilter } from "./ir";
|
|
1
|
+
import type { LayerMeta, LayerFilter, LayerCategoryFilter } from "./ir";
|
|
2
2
|
import type { LayerSchema, PropDescriptor } from "./layer-registry";
|
|
3
3
|
import type { Shape } from "./expr";
|
|
4
4
|
/** Sweeps the shared caches down to exactly the keys still referenced by the current manifest (Q4). */
|
|
@@ -19,9 +19,12 @@ export declare const RESERVED_ATTRS: Set<string>;
|
|
|
19
19
|
* Filtering (spec: "Filtering") is universal across every layer type — not
|
|
20
20
|
* tied to one schema's props like `pickable` — so it's resolved directly
|
|
21
21
|
* here rather than via `schema.props`, and excluded from the typo-check the
|
|
22
|
-
* same way `RESERVED_ATTRS` is.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* same way `RESERVED_ATTRS` is. `filter-field`/`filter-range`/
|
|
23
|
+
* `filter-soft-range` are the single-dimension surface; `filter-fields` is
|
|
24
|
+
* the multi-dimension one (up to 4 — `DataFilterExtension`'s own ceiling),
|
|
25
|
+
* a JSON array of `{field, range, softRange?}` objects. Categorical
|
|
26
|
+
* filtering (`filter-category`/`filter-categories`) is a separate slot,
|
|
27
|
+
* unrelated to the numeric dimension count.
|
|
25
28
|
*/
|
|
26
29
|
export declare const FILTER_ATTRS: Set<string>;
|
|
27
30
|
/**
|
|
@@ -51,14 +54,51 @@ export interface LayerDashSpec {
|
|
|
51
54
|
* ignore it otherwise — the invisible-attribute class of confusion).
|
|
52
55
|
*/
|
|
53
56
|
export declare function applyDashWiring(props: Record<string, unknown>, spec: LayerDashSpec, layerType: string, warnLabel: string): void;
|
|
54
|
-
/**
|
|
57
|
+
/** One numeric filter dimension, already-coerced (attribute strings or typed programmatic values). */
|
|
58
|
+
export interface LayerFilterFieldSpec {
|
|
59
|
+
field: string;
|
|
60
|
+
range?: [number, number];
|
|
61
|
+
softRange?: [number, number];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Already-coerced filter declaration — attribute strings (DOM front-end) or
|
|
65
|
+
* typed values (programmatic front-end). `filterField`/`filterRange`/
|
|
66
|
+
* `filterSoftRange` are the original single-dimension surface (still fully
|
|
67
|
+
* supported, zero migration needed); `filterFields` is the multi-dimension
|
|
68
|
+
* one. If both are present `filterFields` wins outright (validation warns
|
|
69
|
+
* separately about the ambiguity) — `applyFilterWiring` never merges them
|
|
70
|
+
* into a combined set, since which slot the singular field would occupy is
|
|
71
|
+
* not a well-defined question.
|
|
72
|
+
*/
|
|
55
73
|
export interface LayerFilterSpec {
|
|
56
74
|
filterField?: string;
|
|
57
75
|
filterRange?: [number, number];
|
|
58
76
|
filterSoftRange?: [number, number];
|
|
77
|
+
filterFields?: LayerFilterFieldSpec[];
|
|
59
78
|
filterCategoryField?: string;
|
|
60
79
|
filterCategories?: unknown[];
|
|
80
|
+
filterCategoryFields?: LayerCategoryFilterFieldSpec[];
|
|
61
81
|
}
|
|
82
|
+
/** One categorical filter dimension, already-coerced. `categories` absent = declared but not yet ranged, mirroring the numeric case — dropped from the active filter with a warning, since deck.gl's category mechanism has no "wide open" value to fall back to (unlike a numeric [-Infinity, Infinity] range). */
|
|
83
|
+
export interface LayerCategoryFilterFieldSpec {
|
|
84
|
+
field: string;
|
|
85
|
+
categories?: (string | number)[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parses the DOM front-end's `filter-fields` JSON attribute — an array of
|
|
89
|
+
* `{field, range, softRange?}` objects — dropping (with a warning, never a
|
|
90
|
+
* throw) any entry that isn't shaped like one, so a single malformed entry
|
|
91
|
+
* degrades to "that dimension is missing" rather than breaking the whole
|
|
92
|
+
* layer's filter or crashing the reconcile.
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseFilterFieldsAttr(raw: string, warnLabel: string): LayerFilterFieldSpec[] | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Parses the DOM front-end's `filter-category-fields` JSON attribute — an
|
|
97
|
+
* array of `{field, categories}` objects — same defensive shape as
|
|
98
|
+
* `parseFilterFieldsAttr`: a malformed entry is dropped with a warning, never
|
|
99
|
+
* a throw.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parseFilterCategoryFieldsAttr(raw: string, warnLabel: string): LayerCategoryFilterFieldSpec[] | undefined;
|
|
62
102
|
/**
|
|
63
103
|
* GPU filter wiring (spec: "Filtering"), shared by both front-ends (HU2).
|
|
64
104
|
* `filterRange` is a plain prop (not an accessor): the filter-layer action
|
|
@@ -78,6 +118,23 @@ export interface LayerFilterSpec {
|
|
|
78
118
|
* category filter re-mounts the extension — churn accepted for the rare
|
|
79
119
|
* case, never for the common numeric one.
|
|
80
120
|
*
|
|
121
|
+
* `filterSize` (1-4, the count of numeric dimensions) is a deck.gl
|
|
122
|
+
* *constructor* option that compiles straight into a GLSL define
|
|
123
|
+
* (`DATAFILTER_TYPE float|vec2|vec3|vec4` — verified against
|
|
124
|
+
* `@deck.gl/extensions`' own source, not assumed), the same category of
|
|
125
|
+
* shader-recompile-on-change as `categorySize` above — and empirically
|
|
126
|
+
* confirmed safe to change on an already-mounted layer the same way
|
|
127
|
+
* categorySize already is (a `DataFilterExtension` config-value change,
|
|
128
|
+
* NOT an `extensions` array composition change — the latter is the
|
|
129
|
+
* documented-elsewhere case that corrupts state, e.g. adding a
|
|
130
|
+
* `ClipBoxExtension` alongside an already-mounted one; this isn't that).
|
|
131
|
+
* So `filterSize` is simply `min(dims.length, 4)`, recomputed fresh every
|
|
132
|
+
* reconcile, no special remount handling needed. The one thing v1
|
|
133
|
+
* deliberately does NOT support is an author changing the number of
|
|
134
|
+
* *declared* dimensions through the `filter-layer` action's merge path —
|
|
135
|
+
* that's a `filter-layer` contract choice (see `actions.ts`), not a
|
|
136
|
+
* limitation of this function.
|
|
137
|
+
*
|
|
81
138
|
* The unfiltered baseline is a FUNCTION accessor (a buffer of zeros, 4
|
|
82
139
|
* bytes/row) plus a baseline trigger — never deck's constant-0 default.
|
|
83
140
|
* Two empirically-found deck behaviors force this: trigger keys absent
|
|
@@ -88,8 +145,27 @@ export interface LayerFilterSpec {
|
|
|
88
145
|
* one transition that recomputes reliably, so every state a layer can
|
|
89
146
|
* reach — unfiltered, authored filter, runtime populate sweep — is
|
|
90
147
|
* buffer-backed.
|
|
148
|
+
*
|
|
149
|
+
* `categorySize` (0-4) mirrors `filterSize` structurally — same packed
|
|
150
|
+
* single-value-at-1/array-at->1 accessor and prop shape, same truncate-past-4
|
|
151
|
+
* behavior — but is a GENUINELY different mechanism (deck.gl's discrete
|
|
152
|
+
* bitmask membership test, `FilterCategory = number | string`, NOT a range
|
|
153
|
+
* comparison), so it's wired independently and returned separately: a layer
|
|
154
|
+
* can have an active numeric filter, an active categorical filter, both, or
|
|
155
|
+
* neither, and `ctx`'s coherence rule ANDs whichever are present. Unlike
|
|
156
|
+
* `filterSize` there is no floor of 1 — 0 (unmounted) is category's true
|
|
157
|
+
* idle state, so introducing or clearing categorical filtering re-mounts the
|
|
158
|
+
* extension (see the doc comment above); a category dimension with no
|
|
159
|
+
* `categories` list is dropped from the active set entirely (warned), since
|
|
160
|
+
* there's no "wide open" value the way a numeric range has [-Infinity,
|
|
161
|
+
* Infinity] — an empty/absent keep-list has no honest non-excluding meaning
|
|
162
|
+
* in a membership test.
|
|
91
163
|
*/
|
|
92
|
-
export
|
|
164
|
+
export interface LayerFilterResult {
|
|
165
|
+
filter?: LayerFilter;
|
|
166
|
+
categoryFilter?: LayerCategoryFilter;
|
|
167
|
+
}
|
|
168
|
+
export declare function applyFilterWiring(props: Record<string, unknown>, updateTriggers: Record<string, string>, shape: Shape, spec: LayerFilterSpec, warnLabel: string): LayerFilterResult;
|
|
93
169
|
/**
|
|
94
170
|
* Standard global HTML attributes an author (or the library itself, e.g.
|
|
95
171
|
* OmLayerElement setting `style.display = "none"`) may set for reasons
|
|
@@ -135,5 +211,7 @@ export interface ResolvedAttributes {
|
|
|
135
211
|
liveKeys: Set<string>;
|
|
136
212
|
/** The layer's active declarative filter, if `filter-field`/`filter-range` are present. */
|
|
137
213
|
filter?: LayerFilter;
|
|
214
|
+
/** The layer's active categorical filter, if `filter-category`/`filter-category-fields` are present. */
|
|
215
|
+
categoryFilter?: LayerCategoryFilter;
|
|
138
216
|
}
|
|
139
217
|
export declare function resolveLayerAttributes(el: Element, schema: LayerSchema, shape: Shape): ResolvedAttributes;
|
|
@@ -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-CAuq7wRs.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;
|
package/dist/ctx.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* viewport (for projection) crosses that boundary, per the spec's own
|
|
6
6
|
* rationale.
|
|
7
7
|
*/
|
|
8
|
-
import type { LayerIR, LayerFilter } from "./ir";
|
|
8
|
+
import type { LayerIR, LayerFilter, LayerCategoryFilter } from "./ir";
|
|
9
9
|
import type { RuntimeCore } from "./runtime-core";
|
|
10
10
|
import type { Selection } from "./selection";
|
|
11
11
|
import { type Stats } from "./stats";
|
|
@@ -21,6 +21,8 @@ export interface LayerMetaSnapshot {
|
|
|
21
21
|
dataUrl?: string;
|
|
22
22
|
/** The layer's active declarative filter, if any (spec: "Filtering") — read by the built-in `filter` widget. */
|
|
23
23
|
filter?: LayerFilter;
|
|
24
|
+
/** The layer's active categorical filter, if any — independent of `filter`, read by the built-in `filter` widget's categorical mode. */
|
|
25
|
+
categoryFilter?: LayerCategoryFilter;
|
|
24
26
|
}
|
|
25
27
|
export interface DataOpts {
|
|
26
28
|
/** Set `false` to bypass the layer's active declarative filter (spec: "Filter-aware stats & data (the coherence rule)"). Default `true`. */
|
package/dist/data-layer.d.ts
CHANGED
|
@@ -94,10 +94,24 @@ export declare function registerFormat(format: DataFormat): void;
|
|
|
94
94
|
export type DataTransportOwner = object;
|
|
95
95
|
/** Start one descriptor reconciliation transaction for an owner. */
|
|
96
96
|
export declare function beginDataOwnerUpdate(owner: DataTransportOwner): void;
|
|
97
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Release transports no longer named by the owner's complete descriptor
|
|
99
|
+
* document. Retains their rows: a layer removed or re-pointed is a routine
|
|
100
|
+
* edit, and re-adding it (or undoing the edit) should repaint, not blink.
|
|
101
|
+
*/
|
|
98
102
|
export declare function endDataOwnerUpdate(owner: DataTransportOwner): void;
|
|
99
|
-
/**
|
|
100
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Release every fetch/poller/socket held by an owner.
|
|
105
|
+
*
|
|
106
|
+
* Defaults to a permanent teardown — dispose, unmount, an <om-map> leaving the
|
|
107
|
+
* document — which drops the rows too, because an owner that cannot come back
|
|
108
|
+
* has nothing to repaint. Pass `{ retain: true }` for a pause the owner intends
|
|
109
|
+
* to reverse (`MapController.suspend()`), and resuming repaints the last rows
|
|
110
|
+
* while the reopened transport catches up.
|
|
111
|
+
*/
|
|
112
|
+
export declare function releaseDataOwner(owner: DataTransportOwner, opts?: {
|
|
113
|
+
retain?: boolean;
|
|
114
|
+
}): void;
|
|
101
115
|
/**
|
|
102
116
|
* A source plugin supplies domain decoding (and optionally a subscription
|
|
103
117
|
* handshake); the transport (WebSocket, reconnect, flush) is generic.
|
|
@@ -171,7 +185,15 @@ export declare function getDrawData(url: string): LayerData;
|
|
|
171
185
|
* caller re-reconciles from there).
|
|
172
186
|
*/
|
|
173
187
|
export declare function getData(url: string, onLoaded: () => void, opts?: StreamOptions, owner?: DataTransportOwner): LayerData;
|
|
174
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* Is a fetch for this URL still in flight? Feeds the `om-map-ready` signal —
|
|
190
|
+
* errored fetches count as settled (readiness must not hang on a bad URL).
|
|
191
|
+
*
|
|
192
|
+
* With an owner this asks only about that owner's OWN transports: a map must
|
|
193
|
+
* never wait on — or be released by — a sibling map's fetch. An owner with no
|
|
194
|
+
* acquired identities has nothing pending by definition, so a missing state is
|
|
195
|
+
* an empty set, not "match anything".
|
|
196
|
+
*/
|
|
175
197
|
export declare function isPendingData(url: string, owner?: DataTransportOwner): boolean;
|
|
176
198
|
/**
|
|
177
199
|
* Minimal feature-shape detection (spec: "Feature Field Resolution & Data
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CPU-side mirror of the GPU `DataFilterExtension` test (spec: "Filter-aware
|
|
3
3
|
* stats & data (the coherence rule)") — `ctx.stats`/`ctx.dataInViewport`
|
|
4
|
-
* apply the SAME field+range test by default, so a chart/stat
|
|
5
|
-
* the map visibly shows. A row whose field isn't a finite
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* apply the SAME field+range/category test(s) by default, so a chart/stat
|
|
5
|
+
* reflects what the map visibly shows. A row whose field isn't a finite
|
|
6
|
+
* number (numeric) or isn't in the keep-list (categorical) is treated as
|
|
7
|
+
* filtered-out, matching the GPU path. With more than one dimension of
|
|
8
|
+
* EITHER kind a row passes only if every dimension's test passes (AND
|
|
9
|
+
* across dimensions) — the same semantics deck.gl's own packed
|
|
10
|
+
* `filterRange`/`filterCategories` arrays apply GPU-side. Numeric and
|
|
11
|
+
* categorical are independent mechanisms (see `applyFilterWiring`'s doc
|
|
12
|
+
* comment) but combine the same way: a row must pass BOTH kinds when both
|
|
13
|
+
* are active on a layer.
|
|
8
14
|
*/
|
|
9
|
-
import type { Shape, LayerFilter } from "./ir";
|
|
15
|
+
import type { Shape, LayerFilter, LayerCategoryFilter } from "./ir";
|
|
10
16
|
export declare function applyDeclarativeFilter(rows: readonly unknown[], shape: Shape, filter: LayerFilter): unknown[];
|
|
11
17
|
/** The same test as a reusable predicate — the columnar viewport filter runs it inside its index-aligned loop. */
|
|
12
18
|
export declare function declarativeFilterPredicate(shape: Shape, filter: LayerFilter): (row: unknown) => boolean;
|
|
19
|
+
export declare function applyDeclarativeCategoryFilter(rows: readonly unknown[], shape: Shape, categoryFilter: LayerCategoryFilter): unknown[];
|
|
20
|
+
/** Categorical twin of `declarativeFilterPredicate` — set membership instead of a range test. */
|
|
21
|
+
export declare function declarativeCategoryFilterPredicate(shape: Shape, categoryFilter: LayerCategoryFilter): (row: unknown) => boolean;
|
|
@@ -51,6 +51,7 @@ export declare class OmMapElement extends HTMLElementBase {
|
|
|
51
51
|
private mount;
|
|
52
52
|
private reconcilePending;
|
|
53
53
|
private readonly dataOwner;
|
|
54
|
+
private dataReleasePending;
|
|
54
55
|
/** Decoded property tables per pick-features layer — see RuntimeCoreCallbacks.onFeatureTable. */
|
|
55
56
|
private featureTables;
|
|
56
57
|
private layerIRs;
|
package/dist/geodesy.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface PathSample {
|
|
|
27
27
|
position: LngLat;
|
|
28
28
|
/** Cumulative distance (m) from the path's start. */
|
|
29
29
|
distance: number;
|
|
30
|
+
/** Index into the ORIGINAL input points when this sample IS one of them (see `resamplePathWithVertices`); absent for interpolated samples. */
|
|
31
|
+
vertexIndex?: number;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* `count` evenly-spaced points along a multi-segment polyline, by cumulative
|
|
@@ -39,6 +41,7 @@ export interface PathSample {
|
|
|
39
41
|
* coincident) returns `count` copies of the start point at distance 0.
|
|
40
42
|
*/
|
|
41
43
|
export declare function resamplePath(points: LngLat[], count: number): PathSample[];
|
|
44
|
+
export declare function resamplePathWithVertices(points: LngLat[], count: number): PathSample[];
|
|
42
45
|
/**
|
|
43
46
|
* Spherical polygon area in m² via the Chamberlain–Duquette line integral (the
|
|
44
47
|
* exact index form Turf's `ringArea` uses, so results match Turf's fixtures).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ag as Be, ah as le, ai as ce, aj as De, ak as Oe, al as ke } from "./index-
|
|
1
|
+
import { ag as Be, ah as le, ai as ce, aj as De, ak as Oe, al as ke } from "./index-CAuq7wRs.js";
|
|
2
2
|
import { y as ve, a as he, z as G, R as J, A as ue, C as Fe, F as je, n as xe, S as Ie, E as Ne } from "./recordbatch-Bpc0uxFn.js";
|
|
3
3
|
import { g as V, a as fe, b as Le, c as Ue, d as Me, e as qe, m as Ve } from "./table-accessors-CYWTzpQI.js";
|
|
4
4
|
import { c as $e } from "./convert-arrow-schema-DrAihRf9.js";
|