gps-plus-slam-app-framework 1.0.6 → 1.1.0
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/LICENSE +4 -4
- package/README.md +310 -115
- package/dist/accuracy-circles-DEKr0Hoh.d.ts +43 -0
- package/dist/{app-selectors-CHt7DLuq.d.ts → app-selectors-CR9OWodf.d.ts} +8 -10
- package/dist/ar/camera-blit-capture.d.ts +1 -1
- package/dist/ar/capability-checker.d.ts +2 -0
- package/dist/ar/capability-checker.js +25 -0
- package/dist/ar/capture-failure-tracker.d.ts +1 -1
- package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
- package/dist/ar/chromium-camera-access-workaround.js +197 -8
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/enable-gps-ar.d.ts +2 -0
- package/dist/ar/enable-gps-ar.js +137 -0
- package/dist/ar/frame-loop.d.ts +2 -0
- package/dist/ar/frame-loop.js +63 -0
- package/dist/ar/image-capture.d.ts +1 -1
- package/dist/ar/image-capture.js +1 -1
- package/dist/ar/index.d.ts +16 -12
- package/dist/ar/index.js +8 -4
- package/dist/ar/replay-scene.d.ts +1 -1
- package/dist/ar/scene-node-names.d.ts +1 -1
- package/dist/ar/webxr-session.d.ts +4 -4
- package/dist/ar/webxr-session.js +173 -93
- package/dist/ar/xr-camera-texture.d.ts +1 -1
- package/dist/ar/xr-error-handler.d.ts +1 -1
- package/dist/ar/xr-frame-loop.d.ts +2 -0
- package/dist/ar/xr-frame-loop.js +69 -0
- package/dist/ar-world-group-alignment-Dn4rQk_c.d.ts +43 -0
- package/dist/capability-checker-BTCmZRL4.d.ts +43 -0
- package/dist/chromium-camera-access-workaround-MifIwK9x.d.ts +137 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/create-slam-app-store-B76AGaI0.d.ts +371 -0
- package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-CqrkRr2N.d.ts} +1 -1
- package/dist/enable-gps-ar-DNbO0zbg.d.ts +95 -0
- package/dist/{file-system-DD5TczSO.d.ts → file-system-TIsDfamK.d.ts} +32 -2
- package/dist/frame-conversions-D2EYjeNa.d.ts +54 -0
- package/dist/frame-loop-BTeRpDm4.d.ts +47 -0
- package/dist/frustum-visibility-DGnJqls0.d.ts +58 -0
- package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-BdVHmDW9.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +1 -1
- package/dist/geo/index.d.ts +1 -1
- package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-clgzl8b5.d.ts} +16 -1
- package/dist/{gps-GKc6_Cly.d.ts → gps-B7AlMPz5.d.ts} +1 -1
- package/dist/gps-anchor-CISLPQIb.d.ts +59 -0
- package/dist/{gps-ar-pose-sampler-D_Zso40V.d.ts → gps-ar-pose-sampler-BypXZUqC.d.ts} +1 -1
- package/dist/{gps-event-coordinator-fwvmVfku.d.ts → gps-event-coordinator-BKVZbS1M.d.ts} +3 -3
- package/dist/gps-event-markers-BlHq3jYV.d.ts +103 -0
- package/dist/hit-test-reticle-BGXOxrUh.d.ts +45 -0
- package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-BXZUmj7j.d.ts} +1 -1
- package/dist/index--ldLph4V.d.ts +2 -0
- package/dist/index-22v8MXJX.d.ts +23 -0
- package/dist/index-BB2KEWbN2.d.ts +37 -0
- package/dist/index.d.ts +64 -52
- package/dist/index.js +21 -8
- package/dist/{leaflet-map-overlay-CQZcnxbc.d.ts → leaflet-map-overlay-jNt-kcUr.d.ts} +22 -11
- package/dist/{logger-B81iwxx0.d.ts → logger-BZ44Dhd3.d.ts} +7 -1
- package/dist/map-data-BGIBM5rv.d.ts +49 -0
- package/dist/map-overlay-draw-DcGs9UV3.d.ts +42 -0
- package/dist/{null-storage-backend-C0TiwwzQ.d.ts → null-storage-backend-BPQJSaMw.d.ts} +2 -2
- package/dist/{opfs-storage-backend-Gt3rd76D.d.ts → opfs-storage-backend-u4QKvgvP.d.ts} +2 -2
- package/dist/{permission-checker-Cu3nbH1S.d.ts → permission-checker-CfV7INCa.d.ts} +41 -1
- package/dist/persistence-middleware-xB5sTllJ.d.ts +55 -0
- package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-BSFpBSZH.d.ts} +10 -4
- package/dist/{recording-replayer-BFhFs7Mc.d.ts → recording-replayer-CTL2cUE0.d.ts} +2 -2
- package/dist/{recording-slice-BiktUIWF.d.ts → recording-slice-Cb888P6d.d.ts} +2 -2
- package/dist/{replay-engine-B88Va62x.d.ts → replay-engine-B-5GIZtc.d.ts} +1 -1
- package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DkNfldPh.d.ts} +3 -3
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/gps.js +2 -2
- package/dist/sensors/index.d.ts +3 -3
- package/dist/sensors/index.js +1 -1
- package/dist/sensors/permission-checker.d.ts +2 -2
- package/dist/sensors/permission-checker.js +79 -1
- package/dist/session-disposers-M-oashRH.js +57 -0
- package/dist/state/app-selectors.d.ts +2 -2
- package/dist/state/app-selectors.js +33 -3
- package/dist/state/combined-root-state.d.ts +2 -0
- package/dist/state/combined-root-state.js +1 -0
- package/dist/state/create-slam-app-store.d.ts +1 -1
- package/dist/state/create-slam-app-store.js +27 -13
- package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +1 -1
- package/dist/state/index.d.ts +17 -16
- package/dist/state/index.js +6 -4
- package/dist/state/persistence-middleware.d.ts +2 -2
- package/dist/state/persistence-middleware.js +43 -9
- package/dist/state/recording-options.d.ts +1 -1
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-slice.d.ts +1 -1
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/store-subscribers.js +38 -12
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +2 -0
- package/dist/state/tracking-quality.js +723 -0
- package/dist/state/tracking-slice.d.ts +2 -0
- package/dist/state/tracking-slice.js +147 -0
- package/dist/state-BkIfpiyA.js +87 -0
- package/dist/storage/file-system-utils.d.ts +1 -1
- package/dist/storage/file-system.d.ts +2 -2
- package/dist/storage/file-system.js +52 -1
- package/dist/storage/index.d.ts +8 -8
- package/dist/storage/null-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage.d.ts +1 -1
- package/dist/storage/storage-backend.d.ts +1 -1
- package/dist/storage/zip-export.d.ts +1 -1
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/storage/zip-reader.js +8 -4
- package/dist/{storage-backend-yVnuWGTr.d.ts → storage-backend-yDSKafAQ.d.ts} +1 -1
- package/dist/{store-subscribers-DKKHld49.d.ts → store-subscribers-swlbtg_z.d.ts} +31 -6
- package/dist/{subscribe-to-selector-ghDKYtap.d.ts → subscribe-to-selector--KQpgLXG.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +1 -1
- package/dist/test-utils/zip-round-trip-helpers.d.ts +7 -1
- package/dist/test-utils/zip-round-trip-helpers.js +2 -1
- package/dist/tracking-slice-CDREeoZ_.d.ts +86 -0
- package/dist/types/ar-types.d.ts +1 -1
- package/dist/types/geo-types.d.ts +2 -2
- package/dist/types/index.d.ts +3 -3
- package/dist/utils/concurrency.d.ts +1 -1
- package/dist/utils/failure-tracker.d.ts +1 -1
- package/dist/utils/format-file-size.d.ts +1 -1
- package/dist/utils/fused-path.d.ts +1 -1
- package/dist/utils/index.d.ts +6 -6
- package/dist/utils/list-formatter.d.ts +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +68 -8
- package/dist/visualization/accuracy-circles.d.ts +2 -0
- package/dist/visualization/accuracy-circles.js +57 -0
- package/dist/visualization/alignment-lerper.d.ts +1 -1
- package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
- package/dist/visualization/ar-world-group-alignment.js +51 -0
- package/dist/visualization/camera-follower.d.ts +1 -1
- package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
- package/dist/visualization/frame-conversions.d.ts +2 -0
- package/dist/visualization/frame-conversions.js +88 -0
- package/dist/visualization/frustum-visibility.d.ts +2 -0
- package/dist/visualization/frustum-visibility.js +139 -0
- package/dist/visualization/gps-anchor.d.ts +2 -0
- package/dist/visualization/gps-anchor.js +217 -0
- package/dist/visualization/gps-compass-cubes.d.ts +1 -1
- package/dist/visualization/gps-event-markers.d.ts +2 -2
- package/dist/visualization/gps-event-markers.js +69 -2
- package/dist/visualization/hit-test-reticle.d.ts +2 -0
- package/dist/visualization/hit-test-reticle.js +81 -0
- package/dist/visualization/index.d.ts +19 -12
- package/dist/visualization/index.js +9 -2
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +33 -79
- package/dist/visualization/lerp-utils.d.ts +1 -1
- package/dist/visualization/map-data.d.ts +2 -0
- package/dist/visualization/map-data.js +33 -0
- package/dist/visualization/map-overlay-draw.d.ts +2 -0
- package/dist/visualization/map-overlay-draw.js +107 -0
- package/dist/visualization/map-overlay.d.ts +1 -1
- package/dist/visualization/three-dispose.d.ts +1 -1
- package/dist/visualization/vis-colors.d.ts +1 -1
- package/dist/webxr-nue-basis-BbdqhqY6.js +54 -0
- package/dist/{webxr-session-COlSrXdL.d.ts → webxr-session-BnArFCew.d.ts} +76 -14
- package/dist/xr-frame-loop-BKckC7xC.d.ts +70 -0
- package/dist/{zip-reader-KgJEXbca.d.ts → zip-reader-B2lzN8F5.d.ts} +5 -0
- package/package.json +16 -16
- package/dist/ar/tracking-state.d.ts +0 -2
- package/dist/ar/tracking-state.js +0 -164
- package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +0 -61
- package/dist/combined-root-state-B3SvPNjU.d.ts +0 -6
- package/dist/create-slam-app-store-DBm7g2Zd.d.ts +0 -85
- package/dist/gps-anchored-mesh-manager-CovAFK4g.d.ts +0 -56
- package/dist/gps-event-markers-sVyO9utS.d.ts +0 -67
- package/dist/persistence-middleware-z-75iisb.d.ts +0 -31
- package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
- package/dist/visualization/gps-anchored-mesh-manager.d.ts +0 -2
- package/dist/visualization/gps-anchored-mesh-manager.js +0 -128
- /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-CCZJf-Xb.d.ts} +0 -0
- /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-isPsQptb.d.ts} +0 -0
- /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-75WWa9Xb.d.ts} +0 -0
- /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-B-nS1sr6.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-D4HYJNOj.d.ts} +0 -0
- /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-Bsmv53qw.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-EDED3nvg.d.ts} +0 -0
- /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-D7ELffm0.d.ts} +0 -0
- /package/dist/{file-system-utils-BH8FdyIu.d.ts → file-system-utils-BH6uIs2i.d.ts} +0 -0
- /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-B7gJb3Md.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-jRIJMOuh.d.ts → gps-compass-cubes-Ys6Hf1nc.d.ts} +0 -0
- /package/dist/{gps-error-handler-CH1S528z.d.ts → gps-error-handler-BLDhUlw5.d.ts} +0 -0
- /package/dist/{h3-proximity-GwesPTFT.d.ts → h3-proximity-DJnghtir.d.ts} +0 -0
- /package/dist/{lerp-utils-CZgQlrmu.d.ts → lerp-utils-DNcSmKVX.d.ts} +0 -0
- /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CsqcU4v5.d.ts} +0 -0
- /package/dist/{map-overlay-C9ifl_4-.d.ts → map-overlay-yrFsUtGv.d.ts} +0 -0
- /package/dist/{opfs-storage-Dlq1I_Gb.d.ts → opfs-storage-LsLY6VZV.d.ts} +0 -0
- /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-IHW7HR4S.d.ts} +0 -0
- /package/dist/{three-dispose-qt_r5wR1.d.ts → three-dispose-WTBAfaWU.d.ts} +0 -0
- /package/dist/{vis-colors-BKkdTIKb.d.ts → vis-colors-Djnyg_UH.d.ts} +0 -0
- /package/dist/{xr-camera-texture-BbukNE79.d.ts → xr-camera-texture-BU5YvKAM.d.ts} +0 -0
- /package/dist/{xr-error-handler-DTHfmvrl.d.ts → xr-error-handler-DcUFshNF.d.ts} +0 -0
- /package/dist/{zip-export-BjrQPv0X.d.ts → zip-export-C0_ErAqT.d.ts} +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { registerFrameUpdate } from "../ar/frame-loop.js";
|
|
2
|
+
import { isObjectInCameraFrustum } from "./frustum-visibility.js";
|
|
3
|
+
import { calcRelativeCoordsInMeters } from "../core/index.js";
|
|
4
|
+
import { nueToArLocal } from "./frame-conversions.js";
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
//#region ../src/visualization/gps-anchor.ts
|
|
7
|
+
/**
|
|
8
|
+
* `createGpsAnchor` — GPS-anchored placement of a single `THREE.Object3D`.
|
|
9
|
+
*
|
|
10
|
+
* See the colocated sidecar (`gps-anchor.ts.md`) and the port plan at
|
|
11
|
+
* `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-13-gps-anchor-port-plan.md`
|
|
12
|
+
* for the full design, state machine, and test matrix.
|
|
13
|
+
*
|
|
14
|
+
* This file implements sub-steps 2 (bootstrap phase), 3 (steady-state
|
|
15
|
+
* `'snap-every-tick'` + distance-scaled threshold gate), and 4
|
|
16
|
+
* (`'snap-when-offscreen'` mode gate, with a one-time initial-placement
|
|
17
|
+
* exemption for `skipBootstrap` anchors). Floor-Y correction is sub-step
|
|
18
|
+
* 6 and remains deferred.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Module-level registry of objects currently owned by a `GpsAnchor`.
|
|
22
|
+
* Used to detect nested anchors (parent + child both anchored) which
|
|
23
|
+
* we explicitly forbid; mirrors the C# invariant.
|
|
24
|
+
*/
|
|
25
|
+
const anchoredObjects = /* @__PURE__ */ new WeakSet();
|
|
26
|
+
function isObjectInAnchoredChain(object) {
|
|
27
|
+
let cursor = object;
|
|
28
|
+
while (cursor) {
|
|
29
|
+
if (anchoredObjects.has(cursor)) return true;
|
|
30
|
+
cursor = cursor.parent;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns true iff `object` is `arWorldGroup` itself or a descendant of it.
|
|
36
|
+
* The anchor's `object3D` MUST satisfy this: only then does it ride the
|
|
37
|
+
* alignment applied to `arWorldGroup.matrix` (the node the camera also lives
|
|
38
|
+
* under) via scene-graph propagation. An anchor parented to the scene root
|
|
39
|
+
* instead never receives the alignment, so each steady-state re-registration
|
|
40
|
+
* snaps the full alignment delta and the object visibly slides as the user
|
|
41
|
+
* moves — the exact instability the anchor exists to prevent.
|
|
42
|
+
*/
|
|
43
|
+
function isDescendantOf(object, arWorldGroup) {
|
|
44
|
+
let cursor = object;
|
|
45
|
+
while (cursor) {
|
|
46
|
+
if (cursor === arWorldGroup) return true;
|
|
47
|
+
cursor = cursor.parent;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function median(values) {
|
|
52
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
53
|
+
const mid = Math.floor(sorted.length / 2);
|
|
54
|
+
if (sorted.length % 2 === 0) return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
55
|
+
return sorted[mid];
|
|
56
|
+
}
|
|
57
|
+
function medianPoint(samples) {
|
|
58
|
+
const lat = median(samples.map((s) => s.lat));
|
|
59
|
+
const lon = median(samples.map((s) => s.lon));
|
|
60
|
+
const alts = samples.map((s) => "altitude" in s ? s.altitude : void 0).filter((a) => typeof a === "number");
|
|
61
|
+
if (alts.length > 0) return {
|
|
62
|
+
lat,
|
|
63
|
+
lon,
|
|
64
|
+
altitude: median(alts)
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
lat,
|
|
68
|
+
lon
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function createGpsAnchor(options) {
|
|
72
|
+
if (isObjectInAnchoredChain(options.object3D)) throw new Error("createGpsAnchor: nested GpsAnchors are not supported — the supplied object3D is already inside an anchored parent chain.");
|
|
73
|
+
if (!isDescendantOf(options.object3D, options.arWorldGroup)) throw new Error("createGpsAnchor: object3D must be a descendant of arWorldGroup (the alignment-bearing node the camera lives under); parenting an anchor to the scene root defeats AR stability.");
|
|
74
|
+
anchoredObjects.add(options.object3D);
|
|
75
|
+
const sampleCount = options.secondsToAccumulateGpsPose ?? 7;
|
|
76
|
+
if (sampleCount < 1) {
|
|
77
|
+
anchoredObjects.delete(options.object3D);
|
|
78
|
+
throw new Error("createGpsAnchor: secondsToAccumulateGpsPose must be >= 1 — use skipBootstrap:true to bypass the accumulation phase.");
|
|
79
|
+
}
|
|
80
|
+
const settlingSeconds = options.settlingSeconds ?? 0;
|
|
81
|
+
const distanceThreshold = options.distanceThreshold ?? 2;
|
|
82
|
+
options.angleThresholdInDegrees;
|
|
83
|
+
const mode = options.mode ?? "snap-when-offscreen";
|
|
84
|
+
const scratchTarget = new THREE.Vector3();
|
|
85
|
+
const scratchCamWorld = new THREE.Vector3();
|
|
86
|
+
const scratchObjWorld = new THREE.Vector3();
|
|
87
|
+
let phase = options.skipBootstrap === true ? "anchored" : "bootstrap";
|
|
88
|
+
let isFullyAnchored = phase === "anchored";
|
|
89
|
+
let gpsPoint = options.gpsPoint;
|
|
90
|
+
let phaseEnteredAtElapsed = null;
|
|
91
|
+
let lastSampleAtElapsed = null;
|
|
92
|
+
const samples = [];
|
|
93
|
+
/**
|
|
94
|
+
* The one-time "initial placement" exemption from the `snap-when-offscreen`
|
|
95
|
+
* frustum gate. A `skipBootstrap` anchor is `anchored` from frame one but its
|
|
96
|
+
* `object3D` still sits at the AR origin (local 0,0,0 — "inside the user")
|
|
97
|
+
* until its first steady-state commit. That first commit MUST land even while
|
|
98
|
+
* the object is on-screen, otherwise the marker stays stuck at the origin
|
|
99
|
+
* until the user happens to look away. A fresh appearance is not a "jump", so
|
|
100
|
+
* it is exempt. Consumed (set false) by the first committed correction; never
|
|
101
|
+
* re-armed (a re-bootstrap moves an already-placed object, so its later
|
|
102
|
+
* corrections are normal gated snaps). Anchors that bootstrap are placed at
|
|
103
|
+
* their seed pose by the host, so they never need this exemption — hence it is
|
|
104
|
+
* armed only for `skipBootstrap`.
|
|
105
|
+
*/
|
|
106
|
+
let firstCommitPending = options.skipBootstrap === true;
|
|
107
|
+
const enterBootstrap = () => {
|
|
108
|
+
phase = "bootstrap";
|
|
109
|
+
isFullyAnchored = false;
|
|
110
|
+
phaseEnteredAtElapsed = null;
|
|
111
|
+
lastSampleAtElapsed = null;
|
|
112
|
+
samples.length = 0;
|
|
113
|
+
};
|
|
114
|
+
const commitMedian = () => {
|
|
115
|
+
gpsPoint = medianPoint(samples);
|
|
116
|
+
phase = "anchored";
|
|
117
|
+
isFullyAnchored = true;
|
|
118
|
+
samples.length = 0;
|
|
119
|
+
options.onBootstrapComplete?.(gpsPoint);
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Steady-state: compute the GPS-world NUE target from the stored
|
|
123
|
+
* `gpsPoint` and the current `zeroRef`, map it into `arWorldGroup`'s
|
|
124
|
+
* AR-local frame via the inverse alignment matrix, and commit it to
|
|
125
|
+
* `object3D.position` iff the (AR-local) position delta exceeds the
|
|
126
|
+
* distance-scaled threshold AND the mode gate allows it.
|
|
127
|
+
*
|
|
128
|
+
* Mode gate: in `'snap-when-offscreen'` a correction is suppressed while the
|
|
129
|
+
* object is inside the camera frustum — corrections only land when the user
|
|
130
|
+
* is not looking, so the anchor never visibly jumps. The whole AR frame
|
|
131
|
+
* (camera + every anchor) rides one lerped `arWorldGroup.matrix`
|
|
132
|
+
* (`enableArWorldGroupAlignment`), so an on-screen alignment change is
|
|
133
|
+
* absorbed smoothly for the entire view and never needs a per-anchor
|
|
134
|
+
* on-screen snap. The one exception is the first placement of a `skipBootstrap`
|
|
135
|
+
* anchor (see `firstCommitPending`), which must escape the origin even while
|
|
136
|
+
* on-screen. See
|
|
137
|
+
* `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-06-06-anchor-starter-cachehit-jump-investigation.md`.
|
|
138
|
+
*
|
|
139
|
+
* Frame note: `arWorldGroup.matrix` IS the alignment matrix, which maps
|
|
140
|
+
* **AR-odometry NUE → GPS-world NUE**. `object3D.position` is a *local*
|
|
141
|
+
* transform in that AR-odometry frame. To make the object's *world*
|
|
142
|
+
* position equal the GPS-world `nue`, the local target must be
|
|
143
|
+
* `alignment⁻¹ · nue` — writing raw `nue` would double-apply the
|
|
144
|
+
* alignment (world = `alignment · nue`). See
|
|
145
|
+
* `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md`.
|
|
146
|
+
*
|
|
147
|
+
* Returns silently on missing inputs (no `zeroRef`, or no alignment
|
|
148
|
+
* matrix yet — an AR-local object cannot be placed without knowing the
|
|
149
|
+
* AR↔NUE transform).
|
|
150
|
+
*/
|
|
151
|
+
const maybeCommitSteadyState = () => {
|
|
152
|
+
const zero = options.getGpsZeroRef();
|
|
153
|
+
if (zero === null || zero === void 0) return;
|
|
154
|
+
const currentAlignment = options.getAlignmentMatrix();
|
|
155
|
+
if (currentAlignment === null || currentAlignment === void 0) return;
|
|
156
|
+
const targetAlt = "altitude" in gpsPoint && typeof gpsPoint.altitude === "number" ? gpsPoint.altitude : 0;
|
|
157
|
+
const nue = calcRelativeCoordsInMeters(zero, gpsPoint, targetAlt, 0);
|
|
158
|
+
nueToArLocal(currentAlignment, [
|
|
159
|
+
nue[0],
|
|
160
|
+
nue[1],
|
|
161
|
+
nue[2]
|
|
162
|
+
], scratchTarget);
|
|
163
|
+
options.camera.getWorldPosition(scratchCamWorld);
|
|
164
|
+
options.object3D.getWorldPosition(scratchObjWorld);
|
|
165
|
+
const scale = 1 + 10 * scratchCamWorld.distanceTo(scratchObjWorld) / 100;
|
|
166
|
+
if (options.object3D.position.distanceTo(scratchTarget) >= distanceThreshold * scale) {
|
|
167
|
+
if (!(mode === "snap-when-offscreen" && !firstCommitPending && isObjectInCameraFrustum(options.camera, options.object3D))) {
|
|
168
|
+
options.object3D.position.copy(scratchTarget);
|
|
169
|
+
firstCommitPending = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const tick = (_dt, elapsed) => {
|
|
174
|
+
if (phase === "anchored") {
|
|
175
|
+
maybeCommitSteadyState();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (phaseEnteredAtElapsed === null) {
|
|
179
|
+
phaseEnteredAtElapsed = elapsed;
|
|
180
|
+
lastSampleAtElapsed = elapsed - 1;
|
|
181
|
+
}
|
|
182
|
+
if (elapsed - phaseEnteredAtElapsed < settlingSeconds) return;
|
|
183
|
+
if (lastSampleAtElapsed !== null && elapsed - lastSampleAtElapsed < 1) return;
|
|
184
|
+
const sample = options.getCurrentGpsPoint();
|
|
185
|
+
if (sample === null || sample === void 0) return;
|
|
186
|
+
samples.push(sample);
|
|
187
|
+
lastSampleAtElapsed = elapsed;
|
|
188
|
+
if (samples.length >= sampleCount) commitMedian();
|
|
189
|
+
};
|
|
190
|
+
const unregister = registerFrameUpdate(tick);
|
|
191
|
+
return {
|
|
192
|
+
get phase() {
|
|
193
|
+
return phase;
|
|
194
|
+
},
|
|
195
|
+
get isFullyAnchored() {
|
|
196
|
+
return isFullyAnchored;
|
|
197
|
+
},
|
|
198
|
+
get gpsPoint() {
|
|
199
|
+
return gpsPoint;
|
|
200
|
+
},
|
|
201
|
+
markMovedExternally() {
|
|
202
|
+
enterBootstrap();
|
|
203
|
+
},
|
|
204
|
+
setGpsPoint(point) {
|
|
205
|
+
gpsPoint = point;
|
|
206
|
+
},
|
|
207
|
+
dispose() {
|
|
208
|
+
unregister();
|
|
209
|
+
anchoredObjects.delete(options.object3D);
|
|
210
|
+
},
|
|
211
|
+
__tickForTests(dt, elapsed) {
|
|
212
|
+
tick(dt, elapsed);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { createGpsAnchor };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-
|
|
1
|
+
import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-Ys6Hf1nc.js";
|
|
2
2
|
export { COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, GpsCompassCubes, createGpsCompassCubes };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as gpsEventVisualizer, t as
|
|
2
|
-
export { GpsEventVisualizer, gpsEventVisualizer };
|
|
1
|
+
import { n as GpsEventVisualizer, r as gpsEventVisualizer, t as GpsEventAccuracy } from "../gps-event-markers-BlHq3jYV.js";
|
|
2
|
+
export { GpsEventAccuracy, GpsEventVisualizer, gpsEventVisualizer };
|
|
@@ -25,6 +25,38 @@ const GPS_MARKER_RADIUS = .08;
|
|
|
25
25
|
/** Slightly larger radius for alignment snapshots to stand out */
|
|
26
26
|
const SNAPSHOT_MARKER_RADIUS = .1;
|
|
27
27
|
/**
|
|
28
|
+
* Default opacity for the yellow raw-GPS sphere when it is rendered at the
|
|
29
|
+
* legacy fixed 8 cm radius (no accuracy passed). Matches the original 30 %
|
|
30
|
+
* value so live recording sessions look unchanged.
|
|
31
|
+
*/
|
|
32
|
+
const RAW_GPS_FIXED_OPACITY = .3;
|
|
33
|
+
/**
|
|
34
|
+
* Reduced opacity for the yellow raw-GPS sphere when it is scaled by GPS
|
|
35
|
+
* accuracy (replay mode). At metre-scale ellipsoids the sphere can land on
|
|
36
|
+
* top of the cyan fused / red snapshot markers; a lower alpha keeps those
|
|
37
|
+
* smaller markers visible inside the ellipsoid.
|
|
38
|
+
*/
|
|
39
|
+
const RAW_GPS_ACCURACY_OPACITY = .13;
|
|
40
|
+
/**
|
|
41
|
+
* Validate a {@link GpsEventAccuracy} hint and return the concrete
|
|
42
|
+
* {horizontal, vertical} scale when both axes are usable, or `null` to signal
|
|
43
|
+
* "fall back to the legacy fixed 8 cm sphere". A half-populated, non-positive,
|
|
44
|
+
* or non-finite (`NaN`/`Infinity`) accuracy must NOT produce an ellipsoid:
|
|
45
|
+
* a degenerate or infinite axis would corrupt the mesh transform and can
|
|
46
|
+
* crash Three.js rendering.
|
|
47
|
+
*/
|
|
48
|
+
function resolveEllipsoidScale(accuracy) {
|
|
49
|
+
if (accuracy == null) return null;
|
|
50
|
+
const { horizontal, vertical } = accuracy;
|
|
51
|
+
if (typeof horizontal !== "number" || typeof vertical !== "number") return null;
|
|
52
|
+
if (!Number.isFinite(horizontal) || !Number.isFinite(vertical)) return null;
|
|
53
|
+
if (horizontal <= 0 || vertical <= 0) return null;
|
|
54
|
+
return {
|
|
55
|
+
horizontal,
|
|
56
|
+
vertical
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
28
60
|
* Manager for GPS event visualization
|
|
29
61
|
*/
|
|
30
62
|
var GpsEventVisualizer = class {
|
|
@@ -55,8 +87,14 @@ var GpsEventVisualizer = class {
|
|
|
55
87
|
*
|
|
56
88
|
* @param gpsCoords - GPS position as [x, y, z] meters from zero reference
|
|
57
89
|
* @param odomPosition - AR odometry position at the time of GPS reading
|
|
90
|
+
* @param accuracy - Optional 1σ GPS accuracy. When both fields are positive,
|
|
91
|
+
* the raw-GPS marker is rendered as a non-uniform-scaled ellipsoid:
|
|
92
|
+
* `mesh.scale.set(horizontal, vertical, horizontal)` over a unit sphere.
|
|
93
|
+
* When missing, zero, or negative, the marker falls back to the legacy
|
|
94
|
+
* fixed 8 cm sphere (same defensive policy as `preview-map.ts`).
|
|
95
|
+
* Cyan fused and red snapshot markers are never affected by this argument.
|
|
58
96
|
*/
|
|
59
|
-
addGpsEvent(gpsCoords, odomPosition) {
|
|
97
|
+
addGpsEvent(gpsCoords, odomPosition, accuracy) {
|
|
60
98
|
if (!this.zeroRef) {
|
|
61
99
|
log.warn("No zero reference set");
|
|
62
100
|
return;
|
|
@@ -67,8 +105,13 @@ var GpsEventVisualizer = class {
|
|
|
67
105
|
return;
|
|
68
106
|
}
|
|
69
107
|
const eventId = this.eventCounter++;
|
|
70
|
-
const
|
|
108
|
+
const ellipsoidScale = resolveEllipsoidScale(accuracy);
|
|
109
|
+
const rawMesh = this.createMarkerMesh(VIS_COLORS.RAW_GPS.hex, "raw-gps", eventId, ellipsoidScale ? 1 : GPS_MARKER_RADIUS, ellipsoidScale ? RAW_GPS_ACCURACY_OPACITY : RAW_GPS_FIXED_OPACITY);
|
|
71
110
|
rawMesh.position.set(gpsCoords[0], gpsCoords[1], gpsCoords[2]);
|
|
111
|
+
if (ellipsoidScale) {
|
|
112
|
+
rawMesh.scale.set(ellipsoidScale.horizontal, ellipsoidScale.vertical, ellipsoidScale.horizontal);
|
|
113
|
+
rawMesh.renderOrder = -1;
|
|
114
|
+
}
|
|
72
115
|
scene.add(rawMesh);
|
|
73
116
|
this.rawGpsMarkers.push(rawMesh);
|
|
74
117
|
const arWorldGroup = getArWorldGroup();
|
|
@@ -149,6 +192,30 @@ var GpsEventVisualizer = class {
|
|
|
149
192
|
snapshots: this.snapshotMarkers.length
|
|
150
193
|
};
|
|
151
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Diagnostic accessor: returns the world-space size (`THREE.Box3.setFromObject`)
|
|
197
|
+
* of each raw-GPS marker, in insertion order.
|
|
198
|
+
*
|
|
199
|
+
* Used by the rec31 accuracy-ellipsoid Playwright spec (§3c) to verify the
|
|
200
|
+
* relative scaling of two events with different `latLongAccuracy`. This is
|
|
201
|
+
* the size of the rendered ellipsoid in scene units (= metres in replay
|
|
202
|
+
* mode), not the underlying geometry radius — so it correctly reflects the
|
|
203
|
+
* `mesh.scale.set(h, v, h)` applied for accuracy-aware markers.
|
|
204
|
+
*
|
|
205
|
+
* @returns array of `{ x, y, z }` sizes — empty array if there are no markers.
|
|
206
|
+
*/
|
|
207
|
+
getRawMarkerWorldSizes() {
|
|
208
|
+
const tmpBox = new THREE.Box3();
|
|
209
|
+
return this.rawGpsMarkers.map((mesh) => {
|
|
210
|
+
tmpBox.setFromObject(mesh);
|
|
211
|
+
const size = tmpBox.getSize(new THREE.Vector3());
|
|
212
|
+
return {
|
|
213
|
+
x: size.x,
|
|
214
|
+
y: size.y,
|
|
215
|
+
z: size.z
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
}
|
|
152
219
|
};
|
|
153
220
|
/**
|
|
154
221
|
* Singleton instance for global use.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { t as WEBXR_TO_NUE } from "../webxr-nue-basis-BbdqhqY6.js";
|
|
2
|
+
import { Matrix4, Mesh, MeshBasicMaterial, RingGeometry } from "three";
|
|
3
|
+
//#region ../src/visualization/hit-test-reticle.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hit-test reticle — the small, deterministic "reticle view-model" shared by
|
|
6
|
+
* the framework's example/starter apps.
|
|
7
|
+
*
|
|
8
|
+
* A faithful port of the reticle from the stock three.js `webxr_ar_hittest`
|
|
9
|
+
* example: a flat ring laid on whatever real-world surface the WebXR
|
|
10
|
+
* `hit-test` API reports under the screen centre. The framework delta is
|
|
11
|
+
* *where* the mesh is parented and *which frame the hit pose is in*: apps add
|
|
12
|
+
* the mesh under `getArWorldGroup()` (NUE local space) rather than the
|
|
13
|
+
* GPS-aligned scene root, so the reticle rides the same lerped `arWorldGroup`
|
|
14
|
+
* alignment as the camera — but the WebXR hit pose arrives in the WebXR
|
|
15
|
+
* reference space, so `updateReticle` applies the `WEBXR_TO_NUE` basis change
|
|
16
|
+
* to it (see that function for the full rationale).
|
|
17
|
+
*
|
|
18
|
+
* The per-frame XR plumbing (requesting the hit-test source, reading
|
|
19
|
+
* `frame.getHitTestResults(...)`) stays in each app's WebXR glue and is
|
|
20
|
+
* verified manually on-device. The two functions here are the unit-tested core:
|
|
21
|
+
* given the latest hit pose (a column-major 4x4 transform matrix) or `null`,
|
|
22
|
+
* drive the mesh's visibility + transform. They are unit tested because that is
|
|
23
|
+
* the logic a porting developer is most likely to get subtly wrong (e.g.
|
|
24
|
+
* forgetting the basis change so the reticle drifts off-centre, forgetting to
|
|
25
|
+
* hide the reticle when no surface is found, or letting Three.js overwrite the
|
|
26
|
+
* matrix).
|
|
27
|
+
*/
|
|
28
|
+
const hitPoseMatrix = /* @__PURE__ */ new Matrix4();
|
|
29
|
+
/**
|
|
30
|
+
* Build the reticle mesh: a thin ring oriented flat (rotated to lie in the
|
|
31
|
+
* XZ plane) so it reads as a marker on the ground/wall.
|
|
32
|
+
*
|
|
33
|
+
* `matrixAutoUpdate` is disabled because the reticle's world transform is
|
|
34
|
+
* written wholesale from the hit-test pose every frame; letting Three.js
|
|
35
|
+
* recompose it from position/quaternion/scale would discard that pose.
|
|
36
|
+
*
|
|
37
|
+
* The mesh starts hidden (`visible = false`) — there is no surface yet.
|
|
38
|
+
*/
|
|
39
|
+
function createReticleMesh() {
|
|
40
|
+
const reticle = new Mesh(new RingGeometry(.07, .09, 32).rotateX(-Math.PI / 2), new MeshBasicMaterial({ color: 5213439 }));
|
|
41
|
+
reticle.matrixAutoUpdate = false;
|
|
42
|
+
reticle.visible = false;
|
|
43
|
+
return reticle;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply the latest hit-test pose to the reticle.
|
|
47
|
+
*
|
|
48
|
+
* - When `matrix` is a 16-element transform, the reticle adopts it (after the
|
|
49
|
+
* WebXR→NUE basis change, see below) and becomes visible.
|
|
50
|
+
* - When `matrix` is `null` (no surface under the screen centre, or the
|
|
51
|
+
* hit-test source is not ready yet), the reticle is hidden.
|
|
52
|
+
*
|
|
53
|
+
* Frame handling — the part a porting developer must not get wrong. The
|
|
54
|
+
* hit-test pose is expressed in the **WebXR reference space** (`X=East, Y=Up,
|
|
55
|
+
* Z=South`), the same frame as the live camera pose. The reticle, however, is
|
|
56
|
+
* parented under `getArWorldGroup()`, whose local space is **NUE** (`X=North,
|
|
57
|
+
* Y=Up, Z=East`) — the camera reaches that frame through the static
|
|
58
|
+
* `basisChangeNode` that holds `WEBXR_TO_NUE`. The reticle has no such
|
|
59
|
+
* intermediate node, so we apply the same basis change here:
|
|
60
|
+
* `reticle.matrix = WEBXR_TO_NUE · hitPose`. The reticle's resulting world pose
|
|
61
|
+
* is then `arWorldGroup.matrix · WEBXR_TO_NUE · hitPose` — identical to the
|
|
62
|
+
* transform chain the camera rides, so it stays pinned under the screen centre.
|
|
63
|
+
*
|
|
64
|
+
* Without the basis change the WebXR coordinates were interpreted as NUE: the
|
|
65
|
+
* Up axis matched (both are Y) but East/North were swapped, so the reticle
|
|
66
|
+
* drifted sideways instead of tracking the screen centre.
|
|
67
|
+
*
|
|
68
|
+
* Works on any `Object3D` (not just the mesh from `createReticleMesh`) so it can
|
|
69
|
+
* be unit tested without a WebGL context.
|
|
70
|
+
*/
|
|
71
|
+
function updateReticle(reticle, matrix) {
|
|
72
|
+
if (matrix === null) {
|
|
73
|
+
reticle.visible = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
hitPoseMatrix.fromArray(matrix);
|
|
77
|
+
reticle.matrix.multiplyMatrices(WEBXR_TO_NUE, hitPoseMatrix);
|
|
78
|
+
reticle.visible = true;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { createReticleMesh, updateReticle };
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import { n as
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import { n as
|
|
6
|
-
import {
|
|
7
|
-
import { n as
|
|
8
|
-
import {
|
|
9
|
-
import { n as
|
|
10
|
-
import { n as
|
|
11
|
-
import {
|
|
12
|
-
|
|
1
|
+
import { n as createCss3dRendererManager, t as Css3dRendererManager } from "../css3d-renderer-manager-EDED3nvg.js";
|
|
2
|
+
import { n as createCameraFollower, t as CameraFollower } from "../camera-follower-B-nS1sr6.js";
|
|
3
|
+
import { n as createAlignmentLerper, t as AlignmentLerper } from "../alignment-lerper-CCZJf-Xb.js";
|
|
4
|
+
import { n as MapDataInput, r as buildMapData, t as MapData } from "../map-data-BGIBM5rv.js";
|
|
5
|
+
import { n as ArWorldGroupAlignmentOptions, r as enableArWorldGroupAlignment, t as ArWorldGroupAlignmentHandle } from "../ar-world-group-alignment-Dn4rQk_c.js";
|
|
6
|
+
import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-Ys6Hf1nc.js";
|
|
7
|
+
import { i as isSphereInCameraFrustum, n as isObjectInCameraFrustum, r as isPointInCameraFrustum, t as buildCameraFrustum } from "../frustum-visibility-DGnJqls0.js";
|
|
8
|
+
import { n as worldNueToGps, t as nueToArLocal } from "../frame-conversions-D2EYjeNa.js";
|
|
9
|
+
import { n as createReticleMesh, r as updateReticle, t as HitMatrix } from "../hit-test-reticle-BGXOxrUh.js";
|
|
10
|
+
import { a as GpsAnchorSamplePoint, i as GpsAnchorPhase, n as GpsAnchorMode, o as createGpsAnchor, r as GpsAnchorOptions, t as GpsAnchor } from "../gps-anchor-CISLPQIb.js";
|
|
11
|
+
import { n as GpsEventVisualizer, r as gpsEventVisualizer } from "../gps-event-markers-BlHq3jYV.js";
|
|
12
|
+
import { a as DEFAULT_Z_OFFSET, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions } from "../leaflet-map-overlay-jNt-kcUr.js";
|
|
13
|
+
import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-DNcSmKVX.js";
|
|
14
|
+
import { a as addAccuracyCircles, i as AccuracyCircleSample, n as ACCURACY_CIRCLE_STROKE_OPACITY, r as ACCURACY_CIRCLE_WEIGHT, t as ACCURACY_CIRCLE_FILL_OPACITY } from "../accuracy-circles-DEKr0Hoh.js";
|
|
15
|
+
import { a as MAP_PATH_POLYLINE_OPACITY, c as USER_POSITION_COLOR, i as FUSED_PATH_COLOR, l as drawMapData, n as DrawMapDataOptions, o as MAP_PATH_POLYLINE_WEIGHT, r as DrawnMapData, s as RAW_GPS_COLOR, t as ALIGNMENT_SNAPSHOT_COLOR } from "../map-overlay-draw-DcGs9UV3.js";
|
|
16
|
+
import { a as MapOverlayOptions, c as tileXYToLatLon, i as MapOverlay, n as DEFAULT_MAP_SIZE, o as TextureLoaderInterface, r as DEFAULT_ZOOM, s as latLonToTileXY, t as DEFAULT_HEIGHT_OFFSET } from "../map-overlay-yrFsUtGv.js";
|
|
17
|
+
import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-WTBAfaWU.js";
|
|
18
|
+
import { t as VIS_COLORS } from "../vis-colors-Djnyg_UH.js";
|
|
19
|
+
export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, type AccuracyCircleSample, type AlignmentLerper, type ArWorldGroupAlignmentHandle, type ArWorldGroupAlignmentOptions, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, type CameraFollower, type Css3dRendererManager, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, type DisposeOptions, type DrawMapDataOptions, type DrawnMapData, FUSED_PATH_COLOR, type GpsAnchor, type GpsAnchorMode, type GpsAnchorOptions, type GpsAnchorPhase, type GpsAnchorSamplePoint, type GpsCompassCubes, GpsEventVisualizer, type HitMatrix, LeafletMapOverlay, type LeafletMapOverlayOptions, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, type MapData, type MapDataInput, MapOverlay, type MapOverlayOptions, RAW_GPS_COLOR, type TextureLoaderInterface, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, createReticleMesh, disposeMeshArray, disposeObject3D, drawMapData, enableArWorldGroupAlignment, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon, updateReticle, worldNueToGps };
|
|
@@ -5,8 +5,15 @@ import { createCameraFollower } from "./camera-follower.js";
|
|
|
5
5
|
import { createAlignmentLerper } from "./alignment-lerper.js";
|
|
6
6
|
import { VIS_COLORS } from "./vis-colors.js";
|
|
7
7
|
import { COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, createGpsCompassCubes } from "./gps-compass-cubes.js";
|
|
8
|
+
import { buildMapData } from "./map-data.js";
|
|
9
|
+
import { enableArWorldGroupAlignment } from "./ar-world-group-alignment.js";
|
|
10
|
+
import { buildCameraFrustum, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum } from "./frustum-visibility.js";
|
|
11
|
+
import { nueToArLocal, worldNueToGps } from "./frame-conversions.js";
|
|
12
|
+
import { createReticleMesh, updateReticle } from "./hit-test-reticle.js";
|
|
13
|
+
import { createGpsAnchor } from "./gps-anchor.js";
|
|
8
14
|
import { GpsEventVisualizer, gpsEventVisualizer } from "./gps-event-markers.js";
|
|
15
|
+
import { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, addAccuracyCircles } from "./accuracy-circles.js";
|
|
16
|
+
import { ALIGNMENT_SNAPSHOT_COLOR, FUSED_PATH_COLOR, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, RAW_GPS_COLOR, USER_POSITION_COLOR, drawMapData } from "./map-overlay-draw.js";
|
|
9
17
|
import { DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_WORLD_SIZE, DEFAULT_Z_OFFSET, LeafletMapOverlay } from "./leaflet-map-overlay.js";
|
|
10
18
|
import { DEFAULT_HEIGHT_OFFSET, DEFAULT_MAP_SIZE, DEFAULT_ZOOM, MapOverlay, latLonToTileXY, tileXYToLatLon } from "./map-overlay.js";
|
|
11
|
-
|
|
12
|
-
export { COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, GpsAnchoredMeshManager, GpsEventVisualizer, LeafletMapOverlay, MapOverlay, VIS_COLORS, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsCompassCubes, disposeMeshArray, disposeObject3D, gpsEventVisualizer, latLonToTileXY, tileXYToLatLon };
|
|
19
|
+
export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, FUSED_PATH_COLOR, GpsEventVisualizer, LeafletMapOverlay, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, MapOverlay, RAW_GPS_COLOR, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, createReticleMesh, disposeMeshArray, disposeObject3D, drawMapData, enableArWorldGroupAlignment, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon, updateReticle, worldNueToGps };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as DEFAULT_Z_OFFSET, i as DEFAULT_ZOOM, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions, t as DEFAULT_HEIGHT_OFFSET } from "../leaflet-map-overlay-
|
|
1
|
+
import { a as DEFAULT_Z_OFFSET, i as DEFAULT_ZOOM, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions, t as DEFAULT_HEIGHT_OFFSET } from "../leaflet-map-overlay-jNt-kcUr.js";
|
|
2
2
|
export { DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, LeafletMapOverlay, LeafletMapOverlayOptions };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger.js";
|
|
2
2
|
import { VIS_COLORS } from "./vis-colors.js";
|
|
3
|
+
import { drawMapData } from "./map-overlay-draw.js";
|
|
3
4
|
import { CSS3DObject } from "three/addons/renderers/CSS3DRenderer.js";
|
|
4
5
|
import L from "leaflet";
|
|
5
6
|
//#region ../src/visualization/leaflet-map-overlay.ts
|
|
@@ -35,14 +36,9 @@ var LeafletMapOverlay = class {
|
|
|
35
36
|
leafletMap = null;
|
|
36
37
|
tileLayer = null;
|
|
37
38
|
cssObject = null;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
snapshotPoints = [];
|
|
39
|
+
latestMapData = null;
|
|
40
|
+
trajectoryLayers = [];
|
|
41
41
|
namedMarkers = [];
|
|
42
|
-
userMarker = null;
|
|
43
|
-
rawGpsPolyline = null;
|
|
44
|
-
fusedPolyline = null;
|
|
45
|
-
snapshotPolyline = null;
|
|
46
42
|
constructor(scene, camera, options = {}) {
|
|
47
43
|
this.scene = scene;
|
|
48
44
|
this.camera = camera;
|
|
@@ -60,10 +56,7 @@ var LeafletMapOverlay = class {
|
|
|
60
56
|
lat,
|
|
61
57
|
lon
|
|
62
58
|
};
|
|
63
|
-
if (this.leafletMap)
|
|
64
|
-
this.leafletMap.setView([lat, lon], this.zoomLevel);
|
|
65
|
-
this.updateUserMarker();
|
|
66
|
-
}
|
|
59
|
+
if (this.leafletMap) this.leafletMap.setView([lat, lon], this.zoomLevel);
|
|
67
60
|
}
|
|
68
61
|
getGpsPosition() {
|
|
69
62
|
return this.gpsPosition;
|
|
@@ -93,38 +86,22 @@ var LeafletMapOverlay = class {
|
|
|
93
86
|
isVisible() {
|
|
94
87
|
return this.visible;
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
weight: 3,
|
|
113
|
-
opacity: .8
|
|
114
|
-
}).addTo(this.leafletMap);
|
|
115
|
-
this.fusedPolyline.addLatLng([lat, lon]);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
addAlignmentSnapshot(lat, lon) {
|
|
119
|
-
this.snapshotPoints.push([lat, lon]);
|
|
120
|
-
if (this.leafletMap) {
|
|
121
|
-
if (!this.snapshotPolyline) this.snapshotPolyline = L.polyline([], {
|
|
122
|
-
color: VIS_COLORS.ALIGNMENT_SNAPSHOT.css,
|
|
123
|
-
weight: 3,
|
|
124
|
-
opacity: .8
|
|
125
|
-
}).addTo(this.leafletMap);
|
|
126
|
-
this.snapshotPolyline.addLatLng([lat, lon]);
|
|
127
|
-
}
|
|
89
|
+
/**
|
|
90
|
+
* Render a full trajectory snapshot.
|
|
91
|
+
*
|
|
92
|
+
* Replaces the previous incremental `addRawGpsPoint` / `addFusedPoint` /
|
|
93
|
+
* `addAlignmentSnapshot` API with a single full-snapshot path: the caller
|
|
94
|
+
* builds a {@link MapData} (via the shared `buildMapData`) from the latest
|
|
95
|
+
* store slices and hands it here. The trajectory layers are redrawn
|
|
96
|
+
* wholesale through the shared {@link drawMapData} routine, so the live map
|
|
97
|
+
* stays pixel-identical to the 2D session-summary map and the fused path
|
|
98
|
+
* recomputes (D2) as the alignment matrix improves.
|
|
99
|
+
*
|
|
100
|
+
* Buffered when the map is not yet shown; applied on `show()`.
|
|
101
|
+
*/
|
|
102
|
+
render(data) {
|
|
103
|
+
this.latestMapData = data;
|
|
104
|
+
if (this.leafletMap) this.drawTrajectory();
|
|
128
105
|
}
|
|
129
106
|
/** Add a "current" named marker (red). Buffered if map is not yet shown. */
|
|
130
107
|
addCurrentMarker(lat, lon, name) {
|
|
@@ -199,9 +176,8 @@ var LeafletMapOverlay = class {
|
|
|
199
176
|
dispose() {
|
|
200
177
|
this.hide();
|
|
201
178
|
this.destroyLeafletMap();
|
|
202
|
-
this.
|
|
203
|
-
this.
|
|
204
|
-
this.snapshotPoints = [];
|
|
179
|
+
this.latestMapData = null;
|
|
180
|
+
this.trajectoryLayers = [];
|
|
205
181
|
this.namedMarkers = [];
|
|
206
182
|
this.cssObject = null;
|
|
207
183
|
log.info("Leaflet map overlay disposed");
|
|
@@ -232,14 +208,8 @@ var LeafletMapOverlay = class {
|
|
|
232
208
|
this.tileLayer.addTo(this.leafletMap);
|
|
233
209
|
}
|
|
234
210
|
destroyLeafletMap() {
|
|
235
|
-
this.
|
|
236
|
-
this.
|
|
237
|
-
this.rawGpsPolyline?.remove();
|
|
238
|
-
this.rawGpsPolyline = null;
|
|
239
|
-
this.fusedPolyline?.remove();
|
|
240
|
-
this.fusedPolyline = null;
|
|
241
|
-
this.snapshotPolyline?.remove();
|
|
242
|
-
this.snapshotPolyline = null;
|
|
211
|
+
for (const layer of this.trajectoryLayers) layer.remove();
|
|
212
|
+
this.trajectoryLayers = [];
|
|
243
213
|
for (const m of this.namedMarkers) {
|
|
244
214
|
m.marker?.remove();
|
|
245
215
|
m.marker = null;
|
|
@@ -273,33 +243,17 @@ var LeafletMapOverlay = class {
|
|
|
273
243
|
}
|
|
274
244
|
applyBufferedOverlays() {
|
|
275
245
|
if (!this.leafletMap) return;
|
|
276
|
-
this.
|
|
277
|
-
if (this.rawGpsPoints.length > 0) this.rawGpsPolyline = L.polyline(this.rawGpsPoints, {
|
|
278
|
-
color: VIS_COLORS.RAW_GPS.css,
|
|
279
|
-
weight: 3,
|
|
280
|
-
opacity: .8
|
|
281
|
-
}).addTo(this.leafletMap);
|
|
282
|
-
if (this.fusedPoints.length > 0) this.fusedPolyline = L.polyline(this.fusedPoints, {
|
|
283
|
-
color: VIS_COLORS.FUSED_VIO.css,
|
|
284
|
-
weight: 3,
|
|
285
|
-
opacity: .8
|
|
286
|
-
}).addTo(this.leafletMap);
|
|
287
|
-
if (this.snapshotPoints.length > 0) this.snapshotPolyline = L.polyline(this.snapshotPoints, {
|
|
288
|
-
color: VIS_COLORS.ALIGNMENT_SNAPSHOT.css,
|
|
289
|
-
weight: 3,
|
|
290
|
-
opacity: .8
|
|
291
|
-
}).addTo(this.leafletMap);
|
|
246
|
+
this.drawTrajectory();
|
|
292
247
|
for (const m of this.namedMarkers) if (!m.marker) m.marker = this.createNamedMarker(m.lat, m.lng, m.name, m.isPrior);
|
|
293
248
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}) }).addTo(this.leafletMap);
|
|
249
|
+
/**
|
|
250
|
+
* Redraw the trajectory layers from {@link latestMapData} via the shared
|
|
251
|
+
* {@link drawMapData} routine, removing any layers from the previous draw.
|
|
252
|
+
*/
|
|
253
|
+
drawTrajectory() {
|
|
254
|
+
if (!this.leafletMap || !this.latestMapData) return;
|
|
255
|
+
for (const layer of this.trajectoryLayers) layer.remove();
|
|
256
|
+
this.trajectoryLayers = drawMapData(this.leafletMap, this.latestMapData, { showUserPosition: true }).layers;
|
|
303
257
|
}
|
|
304
258
|
createNamedMarker(lat, lon, name, isPrior) {
|
|
305
259
|
const color = isPrior ? VIS_COLORS.PRIOR_REF_POINT.css : VIS_COLORS.CURRENT_REF_POINT.css;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-
|
|
1
|
+
import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-DNcSmKVX.js";
|
|
2
2
|
export { DEFAULT_LERP_RATE, clampedAlpha };
|