@nika-js/onlymap 0.6.1 → 0.6.4
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 +106 -1
- package/README.md +28 -15
- package/dist/{LercDecode.es-CJnypw8j.js → LercDecode.es-D5in29tf.js} +1 -1
- package/dist/{basemap-DrQ0-eyR.js → basemap-C0pFT3AO.js} +13 -3
- package/dist/basemap.d.ts +10 -1
- package/dist/clip-box-controller.d.ts +94 -0
- package/dist/clip-box.d.ts +109 -0
- package/dist/data-layer.d.ts +34 -4
- package/dist/draw-controller.d.ts +75 -1
- package/dist/draw.d.ts +22 -4
- package/dist/elements/om-map.d.ts +20 -0
- package/dist/elements/om-overlay.d.ts +12 -0
- package/dist/geodesy.d.ts +32 -3
- package/dist/{geoparquet-Dix4lTNy.js → geoparquet-By98JVB0.js} +1 -1
- package/dist/html-data.d.ts +2 -2
- package/dist/{index-BQMjW5w0.js → index-Bx9GFkrn.js} +1 -1
- package/dist/{index-CXPaeisL.js → index-CqC4sW_k.js} +1 -1
- package/dist/{index-1UgNlfGR.js → index-Cxo9mCw_.js} +30947 -27785
- package/dist/{index-B_1PPJgC.js → index-olfncHIq.js} +1 -1
- package/dist/{index-CAuT5j9Y.js → index-tnlYDALL.js} +2 -2
- package/dist/index.d.ts +8 -3
- package/dist/ir-snapshot.d.ts +3 -1
- package/dist/layers/bim-layer.d.ts +30 -7
- package/dist/layers/feature-mesh-layer.d.ts +1 -1
- package/dist/layers/popup-layer.d.ts +13 -0
- package/dist/legend-spec.d.ts +1 -1
- package/dist/{lerc-CbTjQ7uI.js → lerc-gKDDtc69.js} +2 -2
- package/dist/license.d.ts +25 -4
- package/dist/measure-controller.d.ts +482 -4
- package/dist/onlymap.standalone.js +53976 -50804
- package/dist/onlymapjs.js +42 -40
- package/dist/parse-manifest.d.ts +3 -0
- package/dist/programmatic.d.ts +34 -5
- package/dist/{raster-0b0nSHUh.js → raster-Cl5m3KsC.js} +2 -2
- package/dist/{raster-pipeline-D8siq7-4.js → raster-pipeline-hJGxIwYx.js} +1 -1
- package/dist/react/om-layer.d.ts +6 -1
- package/dist/react.js +167 -160
- package/dist/region-export-controller.d.ts +37 -0
- package/dist/region-export.d.ts +56 -0
- package/dist/runtime-core.d.ts +150 -0
- package/dist/selection.d.ts +11 -1
- package/dist/site-placement.d.ts +22 -0
- package/dist/snapping.d.ts +88 -0
- package/dist/terrain-heightfield.d.ts +53 -0
- package/dist/terrain-sample.d.ts +30 -0
- package/dist/terrain.d.ts +6 -1
- package/dist/units.d.ts +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/volumetrics-run.d.ts +13 -0
- package/dist/volumetrics-worker.d.ts +1 -0
- package/dist/volumetrics.d.ts +201 -0
- package/dist/{zarr-DCYro_Vs.js → zarr-hRDavRGV.js} +2 -2
- package/docs/3d-assets.md +49 -1
- package/docs/live-data.md +26 -1
- package/docs/react.md +9 -1
- package/docs/testing.md +2 -0
- package/llms.txt +10 -5
- package/onlymapjs.html-data.json +161 -2
- package/package.json +4 -2
- package/skills/onlymapjs/SKILL.md +11 -1
- package/skills/onlymapjs/references/react.md +2 -2
- package/skills/onlymapjs/references/syntax.md +20 -8
- package/skills/onlymapjs/references/testing.md +6 -0
|
@@ -1,6 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measure controller (spec: issue #20 "Measure widget — geodesic distance and
|
|
3
|
+
* area"). Capture is entirely the existing draw stack: it drives the shared
|
|
4
|
+
* DrawController on the runtime-internal target `__onlymapjs-measure`, so the
|
|
5
|
+
* one active-tool slot gives measure↔draw mutual exclusion for free, and the
|
|
6
|
+
* internal prefix already exempts the geometry from ctx.layers, license gates,
|
|
7
|
+
* telemetry, undo history, and save. This module adds only the geodesic math
|
|
8
|
+
* hookup (via a DrawController change observer), the live badge labels (an
|
|
9
|
+
* internal PopupLayer on the per-frame channel), and the `om-measure` readout
|
|
10
|
+
* event the widget renders. Owned per <om-map> (the draw/tooltip precedent),
|
|
11
|
+
* driven by `measure-*` actions.
|
|
12
|
+
*/
|
|
13
|
+
import type { PickingInfo } from "@deck.gl/core";
|
|
1
14
|
import { type UnitSystem } from "./units";
|
|
2
|
-
import type
|
|
3
|
-
|
|
15
|
+
import { type BaseSurfaceKind } from "./volumetrics";
|
|
16
|
+
import type { RuntimeCore, GizmoDragEvent } from "./runtime-core";
|
|
17
|
+
export type MeasureMode = "distance" | "area" | "volume";
|
|
18
|
+
/**
|
|
19
|
+
* One elevation-profile sample on the `om-measure` readout.
|
|
20
|
+
*
|
|
21
|
+
* `vertexIndex` is present ONLY on samples that ARE one of the drawn
|
|
22
|
+
* footprint's own vertices (0-based, in draw order; the closing sample back at
|
|
23
|
+
* the start carries the last index). Interpolated samples omit it entirely, so
|
|
24
|
+
* a Vega-Lite chart marks the real corners with `isValid(datum.vertexIndex)` —
|
|
25
|
+
* see `dev/examples/features/widgets/measure-cut-fill-volume.html`. Without
|
|
26
|
+
* this a reader cannot tell WHICH point on the map a place in the chart
|
|
27
|
+
* corresponds to; vertex 0 is the leftmost sample, and the measure tool marks
|
|
28
|
+
* that same point on the map with a "Start" badge.
|
|
29
|
+
*/
|
|
30
|
+
export interface ProfilePoint {
|
|
31
|
+
/** Distance along the footprint's perimeter from the first vertex, in meters. */
|
|
32
|
+
x: number;
|
|
33
|
+
/** Terrain elevation at this sample, in meters. */
|
|
34
|
+
y: number;
|
|
35
|
+
/** 0-based index into the drawn footprint's vertices — present only on real vertices. */
|
|
36
|
+
vertexIndex?: number;
|
|
37
|
+
}
|
|
4
38
|
/** Live readout dispatched as the `om-measure` event `detail` and consumed by the widget. */
|
|
5
39
|
export interface MeasureReadout {
|
|
6
40
|
mode: MeasureMode | null;
|
|
@@ -13,28 +47,472 @@ export interface MeasureReadout {
|
|
|
13
47
|
perimeterMeters: number;
|
|
14
48
|
/** area readout is unavailable because the ring winds around a pole (documented limitation). */
|
|
15
49
|
poleWarning: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* volume mode only (spec: "Cut/fill volume measurement"). `null` before the
|
|
52
|
+
* footprint is closed — cut/fill/net are meaningless without a base
|
|
53
|
+
* elevation. RAW geometric volumes (ground truth: "does this reach target
|
|
54
|
+
* elevation") — deliberately UNAFFECTED by `swell`/`shrink` (see
|
|
55
|
+
* `cutAdjustedMeters3`/`fillAdjustedMeters3` for the material-adjusted
|
|
56
|
+
* figures) so this pair never silently changes meaning depending on
|
|
57
|
+
* whether a density/factor attribute happens to be set. v1 samples ONE
|
|
58
|
+
* ground elevation at the footprint's centroid and treats it as flat: cut
|
|
59
|
+
* and fill are mutually exclusive per polygon (dragging the gizmo up is
|
|
60
|
+
* pure fill, down is pure cut) — real per-cell terrain-relative
|
|
61
|
+
* integration on sloped ground is a documented follow-up.
|
|
62
|
+
*/
|
|
63
|
+
cutMeters3?: number | null;
|
|
64
|
+
fillMeters3?: number | null;
|
|
65
|
+
/**
|
|
66
|
+
* volume mode only. SIGNED, from the RAW (not material-adjusted) cut/fill:
|
|
67
|
+
* `fillMeters3 − cutMeters3`. Positive = net material added.
|
|
68
|
+
*/
|
|
69
|
+
netMeters3?: number | null;
|
|
70
|
+
/**
|
|
71
|
+
* volume mode only (spec item D — "Reporting, units, factors"). Unsigned
|
|
72
|
+
* RAW `cutMeters3 + fillMeters3` — distinct from the SIGNED `netMeters3`:
|
|
73
|
+
* cut and fill are mutually exclusive per polygon in this v1 (dragging is
|
|
74
|
+
* one direction at a time), so this only ever equals whichever of cut/fill
|
|
75
|
+
* is currently nonzero, but it's a separate field so a "Total moved"
|
|
76
|
+
* readout doesn't have to infer that from the sign of net — same "both
|
|
77
|
+
* conventions labelled unambiguously" ask that motivates `netMeters3`'s
|
|
78
|
+
* own sign.
|
|
79
|
+
*/
|
|
80
|
+
totalMeters3?: number | null;
|
|
81
|
+
/**
|
|
82
|
+
* volume mode only (spec item D). Material-adjusted volumes, standard
|
|
83
|
+
* Bank/Loose/Compacted earthworks convention: `cutAdjustedMeters3` = raw
|
|
84
|
+
* cut × `swell` (bank → loose/haul volume — excavating adds air voids, so
|
|
85
|
+
* this is BIGGER than `cutMeters3`); `fillAdjustedMeters3` = raw fill ÷
|
|
86
|
+
* `shrink` (raw fill is already a compacted target-void volume; dividing
|
|
87
|
+
* by a <1 shrink factor gives the BIGGER loose/borrow volume actually
|
|
88
|
+
* needed on-site to achieve it after compaction settles it down). Each is
|
|
89
|
+
* `null` unless its own factor was actually configured away from the
|
|
90
|
+
* neutral default (1×) — the widget renders the "Material" section only
|
|
91
|
+
* when at least one of these (or `cutMassKg`/`fillMassKg`) is non-null, no
|
|
92
|
+
* separate show/hide toggle.
|
|
93
|
+
*/
|
|
94
|
+
cutAdjustedMeters3?: number | null;
|
|
95
|
+
fillAdjustedMeters3?: number | null;
|
|
96
|
+
/** volume mode only. The `swell`/`shrink` multipliers currently in effect (default 1×) — for labeling the Material section's rows with the actual factor applied, not just the adjusted number. */
|
|
97
|
+
swell?: number;
|
|
98
|
+
shrink?: number;
|
|
99
|
+
/**
|
|
100
|
+
* volume mode only (spec item D). Mass in kg — canonical regardless of
|
|
101
|
+
* unit system, format with `formatMass` at render time. Computed from the
|
|
102
|
+
* RAW (not material-adjusted) cut/fill volume: swell/shrink change VOLUME
|
|
103
|
+
* via added/removed air voids, not the actual mass of material, so basing
|
|
104
|
+
* tonnage on the raw figure is the only mass-conserving choice — a second
|
|
105
|
+
* "adjusted tonnage" would misrepresent the same dirt as weighing a
|
|
106
|
+
* different amount depending on how it's packed. `null` both before a
|
|
107
|
+
* footprint closes AND whenever the widget's `density` attribute was never
|
|
108
|
+
* set (same "not applicable" convention as `cutMeters3`; the widget only
|
|
109
|
+
* renders a tonnage row when this is non-null).
|
|
110
|
+
*/
|
|
111
|
+
cutMassKg?: number | null;
|
|
112
|
+
fillMassKg?: number | null;
|
|
113
|
+
/**
|
|
114
|
+
* volume mode only (spec: issue #35 — the real per-cell grid integration).
|
|
115
|
+
* Populated ONLY once the grid integrator has run — i.e. terrain is
|
|
116
|
+
* active, the footprint has closed, and the covering DEM tiles resolved;
|
|
117
|
+
* `null` before that (and forever, on a no-terrain page, where the flat
|
|
118
|
+
* fallback math is all there is). When these are non-null, `cutMeters3`/
|
|
119
|
+
* `fillMeters3` above ARE the integrator's per-cell results — mixed cut
|
|
120
|
+
* AND fill within one footprint on undulating ground, not the flat
|
|
121
|
+
* one-or-the-other approximation — and these fields carry the published
|
|
122
|
+
* error model the numbers were computed under:
|
|
123
|
+
* - `cellSizeM`: the grid cell size actually used (≥ `gsdM` when the cell
|
|
124
|
+
* budget forced coarsening — never silently finer than the data).
|
|
125
|
+
* - `gsdM`: the DEM's native ground-sample distance at the ring's
|
|
126
|
+
* latitude — what the ±error is quoted against.
|
|
127
|
+
* - `cutErrorM3`/`fillErrorM3`: ± bounds, per-cell `cellArea × 1.5 × gsd`
|
|
128
|
+
* summed on whichever side each cell landed (the spec's exact formula).
|
|
129
|
+
* - `nodataFraction`: the share of inside-the-ring cells skipped for
|
|
130
|
+
* missing DEM data — a consumer should distrust the totals when this is
|
|
131
|
+
* material.
|
|
132
|
+
*/
|
|
133
|
+
cellSizeM?: number | null;
|
|
134
|
+
gsdM?: number | null;
|
|
135
|
+
cutErrorM3?: number | null;
|
|
136
|
+
fillErrorM3?: number | null;
|
|
137
|
+
nodataFraction?: number | null;
|
|
138
|
+
/**
|
|
139
|
+
* volume mode only. Which base-surface strategy produced the cut/fill
|
|
140
|
+
* numbers (widget `base-surface` attribute; default `custom` — the
|
|
141
|
+
* gizmo's draggable target plane). Always present in volume mode, even
|
|
142
|
+
* while the integrator hasn't resolved (the flat fallback is a `custom`
|
|
143
|
+
* plane by construction).
|
|
144
|
+
*/
|
|
145
|
+
baseSurface?: BaseSurfaceKind;
|
|
146
|
+
/**
|
|
147
|
+
* volume mode + `base-surface="custom"` only (the gizmo-draggable target
|
|
148
|
+
* plane — the other strategies have no gizmo to toggle). Switches which
|
|
149
|
+
* target SURFACE the footprint is graded to, math and rendering together:
|
|
150
|
+
* `false` (default) — the terrain itself offset by the dragged distance
|
|
151
|
+
* (a slope-parallel prism; uniform depth everywhere, so volume =
|
|
152
|
+
* |offset| × area, pure cut OR pure fill, exactly zero at rest); `true` —
|
|
153
|
+
* one genuinely level plane at centroid elevation + offset (mixed cut AND
|
|
154
|
+
* fill on sloped ground, per-cell integrated). The drawn footprint ring
|
|
155
|
+
* itself always stays on real terrain in both — the toggle only moves the
|
|
156
|
+
* TARGET face. Reported here so the widget's toggle button can reflect
|
|
157
|
+
* the controller's own truth rather than tracking a shadow copy of it.
|
|
158
|
+
*/
|
|
159
|
+
flatTargetPlane?: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* volume mode only (spec item D). True while the currently-dispatched
|
|
162
|
+
* cut/fill/net/total numbers reflect a PRIOR footprint/elevation sample,
|
|
163
|
+
* not the one now in effect — set the instant a footprint commits (before
|
|
164
|
+
* its one-shot terrain fetch resolves) and cleared once that fetch
|
|
165
|
+
* completes and a fresh readout is dispatched. Distinct from
|
|
166
|
+
* `footprintClosed`'s "sampling elevation…" case (no numbers exist yet at
|
|
167
|
+
* all): `stale` covers the narrower "numbers exist, but they're for a
|
|
168
|
+
* footprint/base-elevation that no longer applies" window, e.g. right
|
|
169
|
+
* after committing a SECOND footprint while the first's numbers are still
|
|
170
|
+
* the last thing dispatched.
|
|
171
|
+
*/
|
|
172
|
+
stale?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* volume mode only. Disambiguates the two states that otherwise look
|
|
175
|
+
* identical from the numbers alone (segments >= 3, areaMeters2 set,
|
|
176
|
+
* cutMeters3 still null): still sketching a 3+-vertex footprint that
|
|
177
|
+
* hasn't been double-clicked closed yet, vs. just closed and waiting on
|
|
178
|
+
* the one-shot terrain elevation sample to resolve. The widget uses this
|
|
179
|
+
* to avoid claiming "sampling elevation…" while the user is still
|
|
180
|
+
* dragging the rubber-band preview around.
|
|
181
|
+
*/
|
|
182
|
+
footprintClosed?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* volume mode only, and only when the measure widget's `profile` attribute
|
|
185
|
+
* is set (spec item E — "Elevation profile"; not a mode of its own — see
|
|
186
|
+
* `setProfileEnabled`'s doc comment). `PROFILE_SAMPLE_COUNT` evenly-spaced
|
|
187
|
+
* `{x: distance-from-start meters, y: elevation meters}` points around the
|
|
188
|
+
* CLOSED footprint's own perimeter, sampled once alongside the existing
|
|
189
|
+
* per-vertex elevation fetch when the footprint closes — not resampled on
|
|
190
|
+
* drag, since dragging only changes `heightOffsetM`, not the ground ring.
|
|
191
|
+
* `null` (the same "not applicable yet" convention as `cutMeters3`/
|
|
192
|
+
* `fillMeters3`) before a footprint has closed, or whenever `profile`
|
|
193
|
+
* isn't set; absent entirely (not even `null`) in `distance`/`area` mode,
|
|
194
|
+
* whose own dispatches never set this field at all. Either way, a
|
|
195
|
+
* `dynamic-chart` widget reading it sees "not a real array" and leaves
|
|
196
|
+
* whatever it last rendered alone — that's the whole "freeze" mechanism.
|
|
197
|
+
*/
|
|
198
|
+
profileSeries?: ProfilePoint[] | null;
|
|
16
199
|
}
|
|
17
200
|
export declare class MeasureController {
|
|
18
201
|
private readonly mapEl;
|
|
19
202
|
private readonly core;
|
|
20
203
|
private mode;
|
|
21
204
|
private units;
|
|
205
|
+
private baseElevationM;
|
|
206
|
+
private heightOffsetM;
|
|
207
|
+
private lastCommittedCount;
|
|
208
|
+
private committedRing;
|
|
209
|
+
/**
|
|
210
|
+
* One terrain elevation sample per `committedRing` vertex (same order),
|
|
211
|
+
* used for the VISUAL geometry (the extruded prism's base ring + the cut
|
|
212
|
+
* guide lines) so the footprint's corners hug the real ground even where
|
|
213
|
+
* it differs from `baseElevationM`'s single centroid sample. The cut/fill
|
|
214
|
+
* VOLUME MATH still deliberately uses that one flat `baseElevationM`
|
|
215
|
+
* delta (documented v1 simplification, unaffected) — this only fixes the
|
|
216
|
+
* PICTURE: reported as "certain sides of the base floating above the
|
|
217
|
+
* terrain rather than draping onto it," which is the expected result of
|
|
218
|
+
* rendering a perfectly flat plane under a footprint whose corners sit at
|
|
219
|
+
* genuinely different real elevations away from the one sampled point.
|
|
220
|
+
*/
|
|
221
|
+
private vertexElevationsM;
|
|
222
|
+
/**
|
|
223
|
+
* The committed ring's perimeter DENSIFIED to `GROUND_RING_SAMPLES` points,
|
|
224
|
+
* each with its own terrain sample — the ground-facing edge of everything
|
|
225
|
+
* rendered (extruded base ring, flat-mode wall bottoms). Corner-only rings
|
|
226
|
+
* rendered straight wall bottoms across valleys (visible daylight between
|
|
227
|
+
* the prism and the ground). Sampled once per commit in the SAME
|
|
228
|
+
* heightfield batch as the vertex/centroid/profile points. The footprint
|
|
229
|
+
* INTERIOR ground face is the terrain-draped VOLUME_DRAPE_ID layer, which
|
|
230
|
+
* conforms exactly by construction. Null before terrain resolves — the
|
|
231
|
+
* drawn corners stand in.
|
|
232
|
+
*/
|
|
233
|
+
private densifiedGroundRing;
|
|
234
|
+
/** Drag-invariant visual geometry derived from the current ground ring (`buildVolumeVisual` runs per drag frame; stable `data` identity lets deck skip re-parsing/re-draping the unchanged footprint). Keyed by the `ground` array's identity. */
|
|
235
|
+
private volumeVisualCache;
|
|
236
|
+
/** Whether VOLUME_FLAT_ID currently holds faces — an empty-clear of an already-empty layer still costs a full patch/rebuild, and `buildVolumeVisual` runs per drag frame on the (default) non-flat path. */
|
|
237
|
+
private flatFacesLive;
|
|
238
|
+
private centroidLngLat;
|
|
239
|
+
private gizmoDrag;
|
|
240
|
+
/**
|
|
241
|
+
* "Flat target plane" toggle (a widget button, not an authored attribute
|
|
242
|
+
* — a live per-interaction preference, same category as the clip-box
|
|
243
|
+
* gizmo's own `editing` flag, not something a page author configures
|
|
244
|
+
* upfront). See `MeasureReadout.flatTargetPlane`'s own doc comment: it
|
|
245
|
+
* switches the target-surface semantics — parallel-offset vs level plane
|
|
246
|
+
* — so both the integration and the rendered prism change with it.
|
|
247
|
+
*/
|
|
248
|
+
private flatTargetPlane;
|
|
249
|
+
/**
|
|
250
|
+
* measure-config action, set from the widget's own `profile` attribute
|
|
251
|
+
* (mirrors `draw-config`'s autosave — a widget-level SETTING, not tied to
|
|
252
|
+
* mode-switching). Deliberately NOT a fourth measure mode: there's no
|
|
253
|
+
* separate line to draw — the profile is always the boundary of whatever
|
|
254
|
+
* footprint volume mode's own polygon already is, resampled live as
|
|
255
|
+
* vertices are added while still sketching and once more, authoritatively,
|
|
256
|
+
* on commit (see `recomputeVolume`/`resampleLivePreviewProfile` and
|
|
257
|
+
* `onFootprintCommitted`). Persists across mode switches (like autosave)
|
|
258
|
+
* rather than resetting — it's a standing "when you draw/close a volume
|
|
259
|
+
* footprint, also do this" preference.
|
|
260
|
+
*/
|
|
261
|
+
private profileEnabled;
|
|
262
|
+
private profileSeries;
|
|
263
|
+
/** The vertex count `profileSeries` was last live-resampled at while sketching — gates resampling on a REAL vertex being added (a click), not on hover, since it touches the network. Unrelated to `lastCommittedCount`, which gates the separate final-commit sample. */
|
|
264
|
+
private lastProfileVertexCount;
|
|
265
|
+
/** Generation token — a live-preview resample that resolves AFTER a newer vertex has already superseded it must not overwrite `profileSeries` with stale (shorter-path) data. Two clicks close enough together for their tile fetches to overlap and resolve out of order is the exact scenario this guards. */
|
|
266
|
+
private profileFetchGeneration;
|
|
267
|
+
/** Canonical kg/m³ (converted from the author's t/m³ or lb/yd³ at set-time — see `densityToKgM3`); `null` = tonnage not reported. */
|
|
268
|
+
private densityKgM3;
|
|
269
|
+
/** Bulking multiplier applied to CUT (excavated material occupies more volume loose than in situ). */
|
|
270
|
+
private swell;
|
|
271
|
+
/** Compaction multiplier applied to FILL (placed/compacted material occupies less volume than loose). */
|
|
272
|
+
private shrink;
|
|
273
|
+
/** Cut/fill below this volume (m³, post swell/shrink) reports as zero — filters drag noise/float jitter near h=0 from reading as a nonzero result. */
|
|
274
|
+
private deadbandM3;
|
|
275
|
+
/** True while the dispatched cut/fill/net/total reflect a stale (pre-fetch or prior-footprint) elevation sample — see `MeasureReadout.stale`'s doc comment. */
|
|
276
|
+
private volumeStale;
|
|
277
|
+
/** Base-surface strategy (widget `base-surface` attribute; `custom` = the gizmo's draggable target plane, the v1-compatible default). */
|
|
278
|
+
private baseSurface;
|
|
279
|
+
/** The latest grid-integration result — `null` until the integrator has run for the CURRENT footprint (no terrain, still in flight, or it failed → the flat fallback math stands in). */
|
|
280
|
+
private volResult;
|
|
281
|
+
/** The cached per-cell grid (custom base only) — gizmo drags re-sum it synchronously via `reintegrateCustomBase` instead of re-running the whole pipeline. */
|
|
282
|
+
private volGrid;
|
|
283
|
+
/** Bounds heightfield + boundary samples cached per footprint, so a `base-surface` switch re-integrates without re-fetching tiles. */
|
|
284
|
+
private volHf;
|
|
285
|
+
private volBoundary;
|
|
286
|
+
/** Generation token — an integration resolving AFTER the footprint it was started for is gone (cleared, replaced, mode-switched) must not clobber the new state. */
|
|
287
|
+
private volGeneration;
|
|
22
288
|
constructor(mapEl: Element, core: RuntimeCore);
|
|
289
|
+
/** measure-config action. */
|
|
290
|
+
setProfileEnabled(enabled: boolean): void;
|
|
291
|
+
/**
|
|
292
|
+
* measure-config action (spec item D). Each field left `undefined` keeps
|
|
293
|
+
* its current value — the widget always emits all four together, but a
|
|
294
|
+
* future caller updating just one factor shouldn't have to resend the
|
|
295
|
+
* others. `density` is interpreted in whatever unit system is ACTIVE right
|
|
296
|
+
* now (`this.units`) and canonicalized to kg/m³ immediately, so a later
|
|
297
|
+
* units toggle doesn't reinterpret it (see `densityToKgM3`'s doc comment).
|
|
298
|
+
* Non-finite or non-positive `density`/`swell`/`shrink` are ignored (fall
|
|
299
|
+
* back to "not set"/1×) rather than propagating NaN/0 into every volume
|
|
300
|
+
* number; a non-finite or negative `deadband` falls back to 0 (no deadband).
|
|
301
|
+
*/
|
|
302
|
+
setVolumeFactors(opts: {
|
|
303
|
+
density?: number;
|
|
304
|
+
swell?: number;
|
|
305
|
+
shrink?: number;
|
|
306
|
+
deadband?: number;
|
|
307
|
+
}): void;
|
|
308
|
+
/**
|
|
309
|
+
* measure-config action (spec: issue #35 — "base-surface picker"). A
|
|
310
|
+
* widget-level setting like the factors above. Switching strategies on an
|
|
311
|
+
* ALREADY-COMMITTED footprint re-integrates against the cached heightfield
|
|
312
|
+
* and boundary samples — no tile re-fetch — and flags the readout stale
|
|
313
|
+
* for the (short) in-flight window. `custom` is the gizmo's draggable
|
|
314
|
+
* target plane; every other strategy derives the base from the footprint's
|
|
315
|
+
* own boundary, so the gizmo disappears (there's no plane to drag).
|
|
316
|
+
*/
|
|
317
|
+
setBaseSurface(kind: BaseSurfaceKind): void;
|
|
23
318
|
isActive(): boolean;
|
|
24
|
-
/** measure-mode action. `distance`→line capture, `area`→polygon capture, `null`→off. */
|
|
319
|
+
/** measure-mode action. `distance`→line capture, `area`/`volume`→polygon capture, `null`→off. */
|
|
25
320
|
setMode(mode: MeasureMode | null): void;
|
|
321
|
+
/**
|
|
322
|
+
* Reset volume-tool state (spec: "Cut/fill volume measurement") — on mode
|
|
323
|
+
* change, clear, or teardown. `lastCommittedCount` baselines against the
|
|
324
|
+
* session's ACTUAL current feature count, not 0: distance/area/volume all
|
|
325
|
+
* share one DrawSession (same MEASURE_TARGET), and a mode switch never
|
|
326
|
+
* clears its committed features (only `pending`/`cursor` reset — see
|
|
327
|
+
* draw.ts's own `setMode`). Baselining at 0 made entering volume mode with
|
|
328
|
+
* ANYTHING already committed (a shape drawn in area mode first, or a prior
|
|
329
|
+
* volume attempt) misread as "just committed" on the very next recompute —
|
|
330
|
+
* building the extrusion/gizmo from stale leftover geometry before the
|
|
331
|
+
* user had drawn anything new. `featureCount` reads the session directly
|
|
332
|
+
* (not `getDrawData`'s shared cross-map mirror, which can itself carry a
|
|
333
|
+
* DIFFERENT map's leftover data under the same target id).
|
|
334
|
+
*/
|
|
335
|
+
private resetVolumeState;
|
|
336
|
+
/**
|
|
337
|
+
* measure-flat-target-plane action (the widget's own toggle button — see
|
|
338
|
+
* `flatTargetPlane`'s field doc comment). The toggle changes the target
|
|
339
|
+
* SURFACE the prism is graded to — parallel-to-terrain offset vs one level
|
|
340
|
+
* plane — so it re-integrates the cached grid AND re-renders: two
|
|
341
|
+
* different shapes report two different volumes.
|
|
342
|
+
*/
|
|
343
|
+
setFlatTargetPlane(flat: boolean): void;
|
|
344
|
+
/**
|
|
345
|
+
* Re-sums the cached per-cell grid under the CURRENT target semantics —
|
|
346
|
+
* `flatTargetPlane` picks which surface the terrain is differenced
|
|
347
|
+
* against: one level plane at centroid + offset (`reintegrateCustomBase`),
|
|
348
|
+
* or the terrain itself shifted by the offset (`reintegrateParallelOffset`,
|
|
349
|
+
* the default — matching the default-rendered slope-parallel prism, and
|
|
350
|
+
* yielding exactly zero at h=0 instead of "grade everything to the
|
|
351
|
+
* centroid's elevation"). Synchronous arithmetic over the cached samples —
|
|
352
|
+
* no re-sampling, no worker round-trip — so it's safe on every drag frame
|
|
353
|
+
* and on the toggle itself.
|
|
354
|
+
*/
|
|
355
|
+
private resumCachedGrid;
|
|
26
356
|
/** measure-units action. Re-render labels + readout in the new system (from the committed shape). */
|
|
27
357
|
setUnits(units: UnitSystem): void;
|
|
28
|
-
/**
|
|
358
|
+
/**
|
|
359
|
+
* measure-clear action. Drop the geometry and reset the readout; tool
|
|
360
|
+
* stays selected. `resetVolumeState()` must run AFTER `draw.clear()` (not
|
|
361
|
+
* before) — it reads the draw controller's own feature count as the new
|
|
362
|
+
* `lastCommittedCount` baseline, which is only correct once the clear has
|
|
363
|
+
* actually happened. But `draw.clear()`'s own observer fires FIRST, with
|
|
364
|
+
* `profileSeries` (and the rest of the volume-mode fields) still at their
|
|
365
|
+
* PRE-clear values — previously the only reset was on a MODE SWITCH, so a
|
|
366
|
+
* Clear left the elevation-profile chart showing the just-deleted
|
|
367
|
+
* footprint's stale data forever. The explicit `recompute()` below is a
|
|
368
|
+
* second, corrected dispatch that supersedes that stale one.
|
|
369
|
+
*/
|
|
29
370
|
clear(): void;
|
|
371
|
+
/**
|
|
372
|
+
* Forwarded from om-map's own `onViewportChange` (any camera move, not
|
|
373
|
+
* just gizmo drags/commits) — the gizmo's pixel-locked size needs to
|
|
374
|
+
* re-derive its world-space meters on every zoom change, not only when
|
|
375
|
+
* something ELSE triggers a `buildVolumeVisual` call, or it'd only snap to
|
|
376
|
+
* the correct size the next time the user happens to drag or redraw.
|
|
377
|
+
*/
|
|
378
|
+
onViewportChanged(): void;
|
|
30
379
|
private recompute;
|
|
31
380
|
/** The path/ring currently being measured: the in-progress preview (+ live cursor), else the last committed shape. */
|
|
32
381
|
private activeCoords;
|
|
382
|
+
/** Shared by `distance` and `profile` — both are just a line measured live as vertices are added; profile additionally resamples elevation along it. */
|
|
383
|
+
private lineLabelsAndTotal;
|
|
33
384
|
private recomputeDistance;
|
|
34
385
|
private recomputeArea;
|
|
386
|
+
/**
|
|
387
|
+
* Detects the footprint's close (a feature-count diff against THIS map's
|
|
388
|
+
* own DrawSession — see DrawSession.count's doc comment for why that beats
|
|
389
|
+
* the shared `getDrawData` mirror — rather than a new DrawController event
|
|
390
|
+
* kind, matching how `activeCoords` already duplicates a bit of logic
|
|
391
|
+
* rather than asking DrawController for something new) and kicks off the
|
|
392
|
+
* one-shot elevation sample. While still sketching, area/perimeter update
|
|
393
|
+
* live exactly like area mode; cut/fill/net stay null until closed.
|
|
394
|
+
*/
|
|
395
|
+
private recomputeVolume;
|
|
396
|
+
/**
|
|
397
|
+
* The async half of the live-preview path above — mirrors
|
|
398
|
+
* `onFootprintCommitted`'s own sampling but against the CURRENT (still
|
|
399
|
+
* open) preview PATH, left open (not closed back to the first vertex) —
|
|
400
|
+
* closing only makes sense once there's an actual footprint, which is
|
|
401
|
+
* `onFootprintCommitted`'s job. With 1 vertex and no cursor yet this is a
|
|
402
|
+
* single point at distance 0 (`resamplePath`'s own documented behavior for
|
|
403
|
+
* a 1-point input); with 2+ it's a real, growing line. Re-dispatches off
|
|
404
|
+
* the SAME `coords` snapshot passed in (not a fresh `recompute()`, which
|
|
405
|
+
* needs a live `DrawSession` this method doesn't have) — if the user has
|
|
406
|
+
* moved on by the time this resolves, the vertex-count gate above already
|
|
407
|
+
* queued a fresh call for that, or the shape has since closed and
|
|
408
|
+
* `onFootprintCommitted` owns the readout instead (guarded by the mode
|
|
409
|
+
* check below either way).
|
|
410
|
+
*/
|
|
411
|
+
private resampleLivePreviewProfile;
|
|
412
|
+
private onFootprintCommitted;
|
|
413
|
+
/**
|
|
414
|
+
* Fetch the covering DEM tile set for the committed footprint's bbox,
|
|
415
|
+
* sample the boundary, and run the grid integrator (worker-offloaded via
|
|
416
|
+
* `runVolumetrics`; the pure fallback computes identically). Results land
|
|
417
|
+
* in `volResult`/`volGrid` unless the footprint changed underneath the
|
|
418
|
+
* async work (generation token) — the flat v1 math remains the standing
|
|
419
|
+
* fallback whenever this leaves `volResult` null (no terrain, fetch
|
|
420
|
+
* failure, degenerate ring).
|
|
421
|
+
*/
|
|
422
|
+
private runVolumeIntegration;
|
|
423
|
+
/**
|
|
424
|
+
* Cut/fill/net/total + material-adjusted volumes + tonnage (spec item D —
|
|
425
|
+
* "Reporting, units, factors"). The RAW cut/fill come from the per-cell
|
|
426
|
+
* grid integrator (`volResult` — spec: issue #35) whenever it has resolved
|
|
427
|
+
* for the current footprint: real terrain-relative integration, mixed cut
|
|
428
|
+
* AND fill within one polygon on undulating ground, with the published
|
|
429
|
+
* error model riding along. Before it resolves — or forever, with no
|
|
430
|
+
* terrain — the v1 flat single-elevation math stands in (one centroid
|
|
431
|
+
* sample × area, one side at a time), with the `stale` flag covering the
|
|
432
|
+
* in-flight window. Either way: `cutMeters3`/`fillMeters3`/`netMeters3`/
|
|
433
|
+
* `totalMeters3` are RAW geometric volumes — `deadband` zeroes out either
|
|
434
|
+
* side of a figure too small to be meaningful (drag jitter near h=0), but
|
|
435
|
+
* `swell`/`shrink` never touch them, so these four always answer "does the
|
|
436
|
+
* ground reach target elevation" regardless of whether a density/factor
|
|
437
|
+
* attribute happens to be configured. `cutAdjustedMeters3`/
|
|
438
|
+
* `fillAdjustedMeters3` are the SEPARATE material-adjusted figures (Bank/
|
|
439
|
+
* Loose/Compacted convention — see their own doc comments), derived from
|
|
440
|
+
* the raw (deadband-applied) cut/fill, not from area×height directly, so a
|
|
441
|
+
* zeroed-by-deadband side stays zero in its adjusted form too. Tonnage is
|
|
442
|
+
* likewise computed from the raw volume (mass-conserving — see
|
|
443
|
+
* `cutMassKg`'s doc comment).
|
|
444
|
+
*/
|
|
445
|
+
private recomputeVolumeReadout;
|
|
446
|
+
/**
|
|
447
|
+
* Rebuilds the footprint + gizmo from current baseElevationM/heightOffsetM.
|
|
448
|
+
* Called on commit and on every gizmo-drag frame. At h=0 and while filling
|
|
449
|
+
* (h>0), the DRAPED layer and the extruded prism are mutually exclusive —
|
|
450
|
+
* one flat, one solid, never both populated. While CUTTING (h<0) the
|
|
451
|
+
* drape layer stays populated ALONGSIDE the extruded prism: the prism
|
|
452
|
+
* alone represents the excavation, but its own top face sits at the real
|
|
453
|
+
* terrain surface where it's unreliable to see (near-coplanar with the
|
|
454
|
+
* opaque terrain mesh) — the drape layer is what reliably keeps the
|
|
455
|
+
* original green footprint visible for a cut, matching how it already
|
|
456
|
+
* reads for fill/at-rest.
|
|
457
|
+
*/
|
|
458
|
+
/** Clear the flat-target faces layer only when it actually holds any — see `flatFacesLive`. */
|
|
459
|
+
private clearFlatFaces;
|
|
460
|
+
private buildVolumeVisual;
|
|
461
|
+
/**
|
|
462
|
+
* Gizmo drag lifecycle — from om-map's forwarded RuntimeCore onGizmoDrag*
|
|
463
|
+
* callbacks (raw deck.gl PickingInfo, not a resolved Selection). Converts
|
|
464
|
+
* vertical screen-pixel delta to a world elevation delta by linearizing
|
|
465
|
+
* the viewport's projection once at grab-time (project the anchor at two
|
|
466
|
+
* known elevations 100m apart) — a local approximation valid for the
|
|
467
|
+
* gesture's duration since dragPan is suspended for the same duration, so
|
|
468
|
+
* the camera can't move underneath it.
|
|
469
|
+
*/
|
|
470
|
+
handleGizmoDragStart(info: PickingInfo, event: GizmoDragEvent): void;
|
|
471
|
+
handleGizmoDrag(info: PickingInfo): void;
|
|
472
|
+
handleGizmoDragEnd(): void;
|
|
35
473
|
private emptyReadout;
|
|
36
474
|
private dispatch;
|
|
37
475
|
private ensureLayers;
|
|
476
|
+
/**
|
|
477
|
+
* Volume mode's internal layers (spec: "Cut/fill volume measurement").
|
|
478
|
+
* TWO layers carry the footprint, only one populated at a time, mirroring
|
|
479
|
+
* a real-world reference implementation's own documented fix for the same
|
|
480
|
+
* problem (mutating `terrain`/extensions on a LIVE layer corrupts its
|
|
481
|
+
* shader pipeline — the fix is a distinct layer id per terrain mode, so
|
|
482
|
+
* deck tears down and rebuilds cleanly instead of patching in place):
|
|
483
|
+
* - VOLUME_DRAPE_ID: flat 2D ring, `terrain="drape"` — used exactly while
|
|
484
|
+
* `heightOffsetM === 0`, so the "ready" footprint sits seamlessly on
|
|
485
|
+
* the real terrain surface (same as the in-progress sketch preview),
|
|
486
|
+
* instead of floating above/sinking below it as a lone flat plane
|
|
487
|
+
* would on anything but dead-flat ground.
|
|
488
|
+
* - VOLUME_ID: extruded prism, `terrain="off"`, explicit `[lng,lat,z]` at
|
|
489
|
+
* each vertex's own sampled elevation (`vertexElevationsM` — not one
|
|
490
|
+
* repeated flat value, so the base ring hugs real ground undulation
|
|
491
|
+
* instead of visibly floating on sides away from the centroid sample)
|
|
492
|
+
* — used once the gizmo is dragged away from zero, when the shape
|
|
493
|
+
* needs a real, undistorted prism (a truly draped ring's per-vertex
|
|
494
|
+
* heights would twist the side walls).
|
|
495
|
+
* - VOLUME_CUT_GUIDES_ID: a LineLayer, one vertical strut per footprint
|
|
496
|
+
* vertex, populated only while cutting (heightOffsetM < 0). A cut's
|
|
497
|
+
* entire prism sits below the original ground, so it's fully occluded
|
|
498
|
+
* by the opaque terrain mesh from a normal view — `depthTest: false`
|
|
499
|
+
* (patched once below, not attribute-expressible) keeps these struts
|
|
500
|
+
* visible through it, the only way to SEE how deep a cut goes short of
|
|
501
|
+
* reading the numeric Cut readout.
|
|
502
|
+
* Plus the draggable height gizmo: two SimpleMeshLayers (GIZMO_SHAFT_ID a
|
|
503
|
+
* cylinder, GIZMO_CONE_ID a cone reused for both the top cap and the
|
|
504
|
+
* inverted bottom one) sharing the static unit meshes built once at module
|
|
505
|
+
* load. All start with empty data; `buildVolumeVisual` fills in whichever
|
|
506
|
+
* pair is live on commit/drag.
|
|
507
|
+
*/
|
|
508
|
+
private ensureVolumeLayers;
|
|
509
|
+
/**
|
|
510
|
+
* Numbered vertex badges for the profile, in draw order. Only meaningful
|
|
511
|
+
* with `profile` on (nothing else references vertex order), and only once a
|
|
512
|
+
* ring exists — so it clears itself in every other state rather than
|
|
513
|
+
* lingering on a deleted footprint.
|
|
514
|
+
*/
|
|
515
|
+
private buildProfileVertexMarkers;
|
|
38
516
|
private removeLayers;
|
|
39
517
|
}
|
|
40
518
|
export declare function getMeasureController(mapEl: Element, core: RuntimeCore): MeasureController;
|