gps-plus-slam-app-framework 1.0.0 → 1.0.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/LICENSE +4 -4
- package/README.md +249 -108
- package/dist/accuracy-circles-CsxE_7x1.d.ts +43 -0
- package/dist/{app-selectors-DyMzxCEC.d.ts → app-selectors-BeU0_ab8.d.ts} +14 -15
- package/dist/ar/camera-blit-capture.d.ts +1 -1
- package/dist/ar/capture-failure-tracker.d.ts +1 -1
- package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/depth-sampler.js +1 -1
- package/dist/ar/frame-loop.d.ts +2 -0
- package/dist/ar/frame-loop.js +40 -0
- package/dist/ar/image-capture.d.ts +1 -1
- package/dist/ar/image-capture.js +1 -1
- package/dist/ar/index.d.ts +12 -12
- package/dist/ar/index.js +3 -3
- 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 +151 -56
- package/dist/ar/xr-camera-texture.d.ts +1 -1
- package/dist/ar/xr-error-handler.d.ts +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/create-slam-app-store-CNsZ3rcX.d.ts +371 -0
- package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-xg8vna6c.d.ts} +1 -1
- package/dist/file-system-DTApEv_Q.d.ts +114 -0
- package/dist/frame-loop-QJIxLin0.d.ts +42 -0
- package/dist/frustum-visibility-BHnFrSKQ.d.ts +58 -0
- package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-CB4DwWOG.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +2 -0
- package/dist/{ref-points/h3-ref-point.js → geo/h3-proximity.js} +16 -12
- package/dist/geo/index.d.ts +2 -0
- package/dist/geo/index.js +2 -0
- package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-CTuF23gG.d.ts} +16 -1
- package/dist/{gps-Ddzr2KlX.d.ts → gps-DXoQb9Kf.d.ts} +1 -1
- package/dist/gps-anchor-CnWY61XK.d.ts +48 -0
- package/dist/gps-anchor-DjC_3MoT.js +257 -0
- package/dist/gps-ar-pose-sampler-DsM1Kb2N.d.ts +43 -0
- package/dist/{recording-coordinator-DzflP07_.d.ts → gps-event-coordinator-DBx9I1YX.d.ts} +7 -6
- package/dist/gps-event-markers-BGr-iLvi.d.ts +103 -0
- package/dist/{h3-ref-point-CFhayNSZ.d.ts → h3-proximity-BhfgNbdW.d.ts} +16 -12
- package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-DZLxHDN9.d.ts} +1 -1
- package/dist/index-gzWJMVpo.d.ts +2 -0
- package/dist/index-pOjUCKEP.d.ts +24 -0
- package/dist/index.d.ts +59 -56
- package/dist/index.js +22 -21
- package/dist/{leaflet-map-overlay-CvfQXTye.d.ts → leaflet-map-overlay-Cew7XAoO.d.ts} +32 -17
- package/dist/licensing/index.d.ts +1 -1
- package/dist/licensing/index.js +1 -1
- package/dist/{logger-B81iwxx0.d.ts → logger-C9vugiQb.d.ts} +7 -1
- package/dist/map-data-sih4g64v.d.ts +49 -0
- package/dist/map-overlay-draw-B3R1c9Xp.d.ts +42 -0
- package/dist/{null-storage-backend-CsWvQwN8.d.ts → null-storage-backend-BKOS7JpL.d.ts} +4 -2
- package/dist/{opfs-storage-ChOzF_Wp.d.ts → opfs-storage-DOPkzgeT.d.ts} +54 -51
- package/dist/opfs-storage-backend-Cj_7PWoq.d.ts +13 -0
- package/dist/{permission-checker-jO2ziCWC.d.ts → permission-checker-DPJl_RU6.d.ts} +41 -1
- package/dist/persistence-middleware-BkHIqvaA.d.ts +55 -0
- package/dist/{recording-replayer-oOzPITrd.d.ts → recording-replayer-CmCIIZ02.d.ts} +2 -2
- package/dist/recording-slice-d6OtCrza.d.ts +28 -0
- package/dist/{replay-engine-DuX316ae.d.ts → replay-engine-BGBRQyXs.d.ts} +4 -1
- package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DpAGcAKY.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/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 +2 -0
- package/dist/state/create-slam-app-store.js +75 -0
- package/dist/state/gps-ar-pose-sampler.d.ts +4 -0
- package/dist/state/gps-ar-pose-sampler.js +19 -0
- package/dist/state/{recording-coordinator.d.ts → gps-event-coordinator.d.ts} +1 -1
- package/dist/state/{recording-coordinator.js → gps-event-coordinator.js} +3 -4
- package/dist/state/index.d.ts +17 -17
- package/dist/state/index.js +10 -8
- package/dist/state/persistence-middleware.d.ts +2 -2
- package/dist/state/persistence-middleware.js +39 -11
- package/dist/state/recording-options.d.ts +1 -1
- package/dist/state/recording-replayer.d.ts +3 -2
- package/dist/state/recording-replayer.js +2 -2
- package/dist/state/recording-slice.d.ts +2 -0
- package/dist/state/{recorder-slice.js → recording-slice.js} +7 -15
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/replay-engine.js +3 -3
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/store-subscribers.js +38 -20
- 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/storage/file-system-utils.d.ts +1 -1
- package/dist/storage/file-system.d.ts +2 -2
- package/dist/storage/file-system.js +154 -94
- package/dist/storage/index.d.ts +9 -11
- package/dist/storage/index.js +5 -7
- package/dist/storage/null-storage-backend.d.ts +1 -1
- package/dist/storage/null-storage-backend.js +7 -0
- package/dist/storage/opfs-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage-backend.js +7 -1
- package/dist/storage/opfs-storage.d.ts +2 -2
- package/dist/storage/opfs-storage.js +69 -85
- package/dist/storage/storage-backend.d.ts +2 -2
- package/dist/storage/zip-export.d.ts +2 -2
- package/dist/storage/zip-export.js +55 -62
- package/dist/storage/zip-reader.d.ts +2 -2
- package/dist/storage/zip-reader.js +32 -5
- package/dist/storage-backend-CT0Il_AB.d.ts +28 -0
- package/dist/{store-subscribers-CuoIzB5T.d.ts → store-subscribers-Dce_CoJF.d.ts} +31 -7
- package/dist/{subscribe-to-selector-UTZ0KkAc.d.ts → subscribe-to-selector-DIHm3BRZ.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 +6 -5
- package/dist/tracking-slice-utCNND8t.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/camera-follower.d.ts +1 -1
- package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
- 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 +2 -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/index.d.ts +17 -12
- package/dist/visualization/index.js +6 -2
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +56 -98
- 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-session-COlSrXdL.d.ts → webxr-session-DB5cIZnN.d.ts} +53 -12
- package/dist/{zip-export-Rtpi75JJ.d.ts → zip-export-QO4iLIi7.d.ts} +58 -7
- package/dist/{zip-reader-D7pkpX8K.d.ts → zip-reader-D1105idz.d.ts} +38 -1
- package/package.json +29 -18
- package/dist/ar/tracking-state.d.ts +0 -2
- package/dist/ar/tracking-state.js +0 -164
- package/dist/community-license-key-ChZLCJyO.d.ts +0 -21
- package/dist/file-system-BsQgYCU3.d.ts +0 -128
- package/dist/gps-event-markers-DpEy2qRd.d.ts +0 -67
- package/dist/licensing/community-license-key.d.ts +0 -2
- package/dist/licensing/community-license-key.js +0 -21
- package/dist/opfs-storage-backend-CVcmfJHN.d.ts +0 -11
- package/dist/persistence-middleware-DrptY02i.d.ts +0 -31
- package/dist/recorder-slice-fcwAXrsh.d.ts +0 -35
- package/dist/ref-point-importer-B_wn5ABk.d.ts +0 -62
- package/dist/ref-point-loader-Pt1smgHS.d.ts +0 -117
- package/dist/ref-points/h3-ref-point.d.ts +0 -2
- package/dist/ref-points/index.d.ts +0 -2
- package/dist/ref-points/index.js +0 -2
- package/dist/ref-points-slice-tC-Wd6XJ.d.ts +0 -48
- package/dist/reference-points-CFWy8_wj.d.ts +0 -68
- package/dist/routing-slice-BlHTM8eh.d.ts +0 -23
- package/dist/state/recorder-slice.d.ts +0 -2
- package/dist/state/ref-points-slice.d.ts +0 -2
- package/dist/state/ref-points-slice.js +0 -44
- package/dist/state/routing-slice.d.ts +0 -2
- package/dist/state/routing-slice.js +0 -13
- package/dist/state/store.d.ts +0 -9
- package/dist/state/store.js +0 -71
- package/dist/storage/ref-point-importer.d.ts +0 -2
- package/dist/storage/ref-point-importer.js +0 -163
- package/dist/storage/ref-point-loader.d.ts +0 -2
- package/dist/storage/ref-point-loader.js +0 -248
- package/dist/storage/ref-point-recovery.d.ts +0 -28
- package/dist/storage/ref-point-recovery.js +0 -159
- package/dist/storage-backend-BcEItnFr.d.ts +0 -20
- package/dist/store-C7PKOj8S.d.ts +0 -96
- package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
- package/dist/visualization/reference-points.d.ts +0 -2
- package/dist/visualization/reference-points.js +0 -160
- /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-L1RbWHQ4.d.ts} +0 -0
- /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-Ck8LEq8k.d.ts} +0 -0
- /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-C1V9I1Z6.d.ts} +0 -0
- /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-1k8mnw5J.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-JKb1ST8E.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-CU5zSKNr.d.ts → chromium-camera-access-workaround-NvChXsi8.d.ts} +0 -0
- /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-CMw8pSaG.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-CkjCb2HW.d.ts} +0 -0
- /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-DnHXOIRa.d.ts} +0 -0
- /package/dist/{file-system-utils-5cWO8Xyv.d.ts → file-system-utils-CtKiiw27.d.ts} +0 -0
- /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-BTS5H62-.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-C-6JEnpR.d.ts → gps-compass-cubes-CsLtFv4B.d.ts} +0 -0
- /package/dist/{gps-error-handler-VPuNOuf8.d.ts → gps-error-handler-Dg3ziRVY.d.ts} +0 -0
- /package/dist/{lerp-utils-87NWjUOD.d.ts → lerp-utils-B8MM8zCR.d.ts} +0 -0
- /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CNqi-E2j.d.ts} +0 -0
- /package/dist/{map-overlay-DuiZuCTx.d.ts → map-overlay-DAmreyA3.d.ts} +0 -0
- /package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-Dq6IsKAv.d.ts} +0 -0
- /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-6LBjPmwz.d.ts} +0 -0
- /package/dist/{three-dispose-CCBHjE_a.d.ts → three-dispose-C2drkp6f.d.ts} +0 -0
- /package/dist/{vis-colors-Qi2vlRGt.d.ts → vis-colors-CHQhjal3.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-CDY5QxGb.d.ts} +0 -0
|
@@ -8,26 +8,26 @@ import { formatActionFilename, formatFrameFilename, formatTimestamp } from "./fi
|
|
|
8
8
|
* OPFS works consistently on Desktop Chrome, Android Chrome, and iOS Safari,
|
|
9
9
|
* unlike showDirectoryPicker which has write restrictions on Android.
|
|
10
10
|
*
|
|
11
|
-
* Directory structure:
|
|
12
|
-
* /gps-
|
|
13
|
-
* └──
|
|
14
|
-
* ├──
|
|
15
|
-
* │ ├──
|
|
16
|
-
* │
|
|
17
|
-
* │ │ ├──
|
|
18
|
-
* │ │
|
|
19
|
-
* │
|
|
20
|
-
* │
|
|
21
|
-
* │
|
|
22
|
-
* │ │ └── ...
|
|
23
|
-
* │ └── ...
|
|
11
|
+
* Directory structure (flat sessions — no scenario hierarchy):
|
|
12
|
+
* /gps-plus-slam/
|
|
13
|
+
* └── sessions/
|
|
14
|
+
* ├── recording-YYYY-MM-DD_HH-MM-SSutc/
|
|
15
|
+
* │ ├── session.json
|
|
16
|
+
* │ ├── actions/
|
|
17
|
+
* │ │ ├── 000001.json
|
|
18
|
+
* │ │ └── ...
|
|
19
|
+
* │ └── frames/
|
|
20
|
+
* │ ├── frame-000001.jpg
|
|
21
|
+
* │ └── ...
|
|
24
22
|
* └── ...
|
|
23
|
+
*
|
|
24
|
+
* Scenario grouping (scenarios/{name}/recording-…) is a recorder-only concern,
|
|
25
|
+
* handled by ScenarioWrappingStorageBackend in the recorder app.
|
|
25
26
|
*/
|
|
26
27
|
const log = createLogger("OPFS");
|
|
27
28
|
let opfsRoot = null;
|
|
28
|
-
let
|
|
29
|
-
let
|
|
30
|
-
let currentScenarioHandle = null;
|
|
29
|
+
let gpsPlusSlamDir = null;
|
|
30
|
+
let sessionsDir = null;
|
|
31
31
|
let currentSessionHandle = null;
|
|
32
32
|
let actionsHandle = null;
|
|
33
33
|
let framesHandle = null;
|
|
@@ -37,9 +37,8 @@ let framesHandle = null;
|
|
|
37
37
|
*/
|
|
38
38
|
function resetOpfsStorage() {
|
|
39
39
|
opfsRoot = null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
currentScenarioHandle = null;
|
|
40
|
+
gpsPlusSlamDir = null;
|
|
41
|
+
sessionsDir = null;
|
|
43
42
|
currentSessionHandle = null;
|
|
44
43
|
actionsHandle = null;
|
|
45
44
|
framesHandle = null;
|
|
@@ -47,14 +46,11 @@ function resetOpfsStorage() {
|
|
|
47
46
|
/**
|
|
48
47
|
* Reset session-level handles only.
|
|
49
48
|
*
|
|
50
|
-
* Preserves opfsRoot/
|
|
51
|
-
* but clears current
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* Used during soft reset for new recordings (Issue 4, 2026-02-06 user feedback).
|
|
49
|
+
* Preserves opfsRoot/gpsPlusSlamDir/sessionsDir (OPFS stays initialized)
|
|
50
|
+
* but clears current session/actions/frames handles so a new session can
|
|
51
|
+
* be started fresh.
|
|
55
52
|
*/
|
|
56
53
|
function resetSessionHandles() {
|
|
57
|
-
currentScenarioHandle = null;
|
|
58
54
|
currentSessionHandle = null;
|
|
59
55
|
actionsHandle = null;
|
|
60
56
|
framesHandle = null;
|
|
@@ -63,8 +59,8 @@ function resetSessionHandles() {
|
|
|
63
59
|
* Initialize OPFS storage by creating the required directory structure.
|
|
64
60
|
*
|
|
65
61
|
* Creates:
|
|
66
|
-
* - /gps-
|
|
67
|
-
* - /gps-
|
|
62
|
+
* - /gps-plus-slam/
|
|
63
|
+
* - /gps-plus-slam/sessions/
|
|
68
64
|
*
|
|
69
65
|
* This is idempotent - calling multiple times is safe.
|
|
70
66
|
*
|
|
@@ -74,65 +70,74 @@ async function initOpfsStorage() {
|
|
|
74
70
|
if (typeof navigator === "undefined" || !navigator.storage || typeof navigator.storage.getDirectory !== "function") throw new Error("OPFS (Origin Private File System) is not supported in this browser. Please use Chrome 86+, Safari 15.2+, or Firefox 111+.");
|
|
75
71
|
opfsRoot = await navigator.storage.getDirectory();
|
|
76
72
|
log.info("OPFS root obtained");
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
gpsPlusSlamDir = await opfsRoot.getDirectoryHandle("gps-plus-slam", { create: true });
|
|
74
|
+
sessionsDir = await gpsPlusSlamDir.getDirectoryHandle("sessions", { create: true });
|
|
79
75
|
log.info("OPFS storage initialized");
|
|
80
76
|
}
|
|
81
77
|
/**
|
|
82
|
-
*
|
|
78
|
+
* Get the sessions root directory handle.
|
|
79
|
+
* Used by consumers that need direct OPFS access (e.g. ZIP export).
|
|
80
|
+
*/
|
|
81
|
+
function getSessionsRootHandle() {
|
|
82
|
+
return sessionsDir;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the app root directory handle (gps-plus-slam/).
|
|
86
|
+
* Used by consumers that need to create additional directories alongside sessions/.
|
|
87
|
+
*/
|
|
88
|
+
function getAppRootHandle() {
|
|
89
|
+
return gpsPlusSlamDir;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a new recording session.
|
|
83
93
|
*
|
|
84
94
|
* Creates the directory structure:
|
|
85
|
-
* - /gps-
|
|
86
|
-
* - /gps-
|
|
87
|
-
* - /gps-
|
|
95
|
+
* - /gps-plus-slam/sessions/recording-{timestamp}/
|
|
96
|
+
* - /gps-plus-slam/sessions/recording-{timestamp}/actions/
|
|
97
|
+
* - /gps-plus-slam/sessions/recording-{timestamp}/frames/
|
|
88
98
|
*
|
|
89
|
-
* @param scenarioName - Name of the scenario (creates if doesn't exist)
|
|
90
99
|
* @param timestamp - Session start time (used for folder naming)
|
|
100
|
+
* @param _contextTag - Opaque tag; not used by the flat layout but accepted
|
|
101
|
+
* for interface compatibility with wrapping backends
|
|
91
102
|
* @returns Session information
|
|
92
103
|
* @throws Error if storage not initialized
|
|
93
104
|
*/
|
|
94
|
-
async function createSession(
|
|
95
|
-
if (!
|
|
96
|
-
currentScenarioHandle = await scenariosDir.getDirectoryHandle(scenarioName, { create: true });
|
|
105
|
+
async function createSession(timestamp, _contextTag) {
|
|
106
|
+
if (!sessionsDir) throw new Error("OPFS storage not initialized. Call initOpfsStorage first.");
|
|
97
107
|
const sessionName = `recording-${formatTimestamp(timestamp)}`;
|
|
98
|
-
currentSessionHandle = await
|
|
108
|
+
currentSessionHandle = await sessionsDir.getDirectoryHandle(sessionName, { create: true });
|
|
99
109
|
actionsHandle = await currentSessionHandle.getDirectoryHandle("actions", { create: true });
|
|
100
110
|
framesHandle = await currentSessionHandle.getDirectoryHandle("frames", { create: true });
|
|
101
|
-
log.info("Session created:",
|
|
102
|
-
return {
|
|
103
|
-
scenarioName,
|
|
104
|
-
sessionName
|
|
105
|
-
};
|
|
111
|
+
log.info("Session created:", sessionName);
|
|
112
|
+
return { sessionName };
|
|
106
113
|
}
|
|
107
114
|
/**
|
|
108
115
|
* Get the current session directory handle.
|
|
109
|
-
* Useful for test assertions.
|
|
110
116
|
* @internal
|
|
111
117
|
*/
|
|
112
118
|
function getSessionHandle() {
|
|
113
119
|
return currentSessionHandle;
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*
|
|
123
|
-
*
|
|
122
|
+
* Set the active session handles externally.
|
|
123
|
+
*
|
|
124
|
+
* Used by file-system.ts's scenario-based path to reuse opfs-storage's
|
|
125
|
+
* write infrastructure for sessions created outside the flat layout.
|
|
126
|
+
* This is a temporary bridge until the recorder migrates to
|
|
127
|
+
* ScenarioWrappingStorageBackend.
|
|
128
|
+
*
|
|
129
|
+
* @internal
|
|
124
130
|
*/
|
|
125
|
-
function
|
|
126
|
-
|
|
131
|
+
function setSessionHandles(session, actions, frames) {
|
|
132
|
+
currentSessionHandle = session;
|
|
133
|
+
actionsHandle = actions;
|
|
134
|
+
framesHandle = frames;
|
|
127
135
|
}
|
|
128
136
|
/**
|
|
129
137
|
* Safely write data to a file handle with proper cleanup.
|
|
130
138
|
*
|
|
131
139
|
* Uses try/finally to ensure the writable stream is always cleaned up,
|
|
132
140
|
* calling abort() on errors to prevent resource leaks and file locks.
|
|
133
|
-
*
|
|
134
|
-
* @param fileHandle - The file handle to write to
|
|
135
|
-
* @param data - The data to write (string or Blob)
|
|
136
141
|
*/
|
|
137
142
|
async function safeWriteToFile(fileHandle, data) {
|
|
138
143
|
const writable = await fileHandle.createWritable();
|
|
@@ -188,40 +193,19 @@ async function writeSessionMetadata(metadata) {
|
|
|
188
193
|
log.info("Session metadata written");
|
|
189
194
|
}
|
|
190
195
|
/**
|
|
191
|
-
* List all
|
|
192
|
-
*
|
|
193
|
-
* @returns Array of scenario folder names
|
|
194
|
-
*/
|
|
195
|
-
async function listScenarios() {
|
|
196
|
-
if (!scenariosDir) return [];
|
|
197
|
-
const scenarios = [];
|
|
198
|
-
for await (const entry of scenariosDir.values()) if (entry.kind === "directory") scenarios.push(entry.name);
|
|
199
|
-
return scenarios;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* List all session names within a scenario.
|
|
196
|
+
* List all session names in OPFS storage.
|
|
203
197
|
*
|
|
204
|
-
* @param scenarioName - Name of the scenario
|
|
205
198
|
* @returns Array of session folder names
|
|
206
199
|
*/
|
|
207
|
-
async function listSessions(
|
|
208
|
-
if (!
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for await (const entry of scenarioHandle.values()) if (entry.kind === "directory") sessions.push(entry.name);
|
|
213
|
-
return sessions;
|
|
214
|
-
} catch {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
200
|
+
async function listSessions() {
|
|
201
|
+
if (!sessionsDir) return [];
|
|
202
|
+
const sessions = [];
|
|
203
|
+
for await (const entry of sessionsDir.values()) if (entry.kind === "directory") sessions.push(entry.name);
|
|
204
|
+
return sessions;
|
|
217
205
|
}
|
|
218
206
|
/**
|
|
219
207
|
* Check storage quota.
|
|
220
208
|
*
|
|
221
|
-
* Note: Prefer calling `initOpfsStorage()` before this function to ensure
|
|
222
|
-
* the Storage API is available. If the Storage Manager API is not supported,
|
|
223
|
-
* returns a safe default of `{ available: 0, used: 0 }`.
|
|
224
|
-
*
|
|
225
209
|
* @returns Available and used storage in bytes
|
|
226
210
|
*/
|
|
227
211
|
async function checkStorageQuota() {
|
|
@@ -239,4 +223,4 @@ async function checkStorageQuota() {
|
|
|
239
223
|
};
|
|
240
224
|
}
|
|
241
225
|
//#endregion
|
|
242
|
-
export { checkStorageQuota, createSession,
|
|
226
|
+
export { checkStorageQuota, createSession, getAppRootHandle, getSessionHandle, getSessionsRootHandle, initOpfsStorage, listSessions, resetOpfsStorage, resetSessionHandles, setSessionHandles, writeAction, writeFrame, writeSessionMetadata };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { StorageBackend };
|
|
1
|
+
import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-CT0Il_AB.js";
|
|
2
|
+
export { CreateSessionResult, StorageBackend };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as syncToExternalZip, i as
|
|
2
|
-
export { ZipExportResult, downloadZip, exportAndDownloadSession, exportSessionAsZip, syncToExternalZip };
|
|
1
|
+
import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportAndDownloadSession, r as ZipExportContributor, s as exportSessionAsZip, t as ExportSessionAsZipOptions } from "../zip-export-QO4iLIi7.js";
|
|
2
|
+
export { ExportSessionAsZipOptions, ZipContributorAddFile, ZipExportContributor, ZipExportResult, downloadZip, exportAndDownloadSession, exportSessionAsZip, syncToExternalZip };
|
|
@@ -22,9 +22,16 @@ const log = createLogger("ZipExport");
|
|
|
22
22
|
/**
|
|
23
23
|
* Get the OPFS scenarios directory handle.
|
|
24
24
|
* Re-acquires from navigator.storage to avoid holding stale references.
|
|
25
|
+
* Creates on demand so it works even when initOpfsStorage only creates sessions/.
|
|
25
26
|
*/
|
|
26
27
|
async function getScenariosHandle() {
|
|
27
|
-
return (await (await navigator.storage.getDirectory()).getDirectoryHandle("gps-
|
|
28
|
+
return (await (await navigator.storage.getDirectory()).getDirectoryHandle("gps-plus-slam")).getDirectoryHandle("scenarios", { create: true });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the OPFS sessions directory handle (flat layout).
|
|
32
|
+
*/
|
|
33
|
+
async function getSessionsHandle() {
|
|
34
|
+
return (await (await navigator.storage.getDirectory()).getDirectoryHandle("gps-plus-slam")).getDirectoryHandle("sessions");
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
30
37
|
* Stream all files from a directory recursively into a ZipWriter.
|
|
@@ -49,80 +56,66 @@ async function streamDirectoryToZip(dirHandle, zipWriter, basePath = "") {
|
|
|
49
56
|
return count;
|
|
50
57
|
}
|
|
51
58
|
/**
|
|
52
|
-
* Filter and stream per-session ref point observations into a ZipWriter.
|
|
53
|
-
*
|
|
54
|
-
* Reads each ref point JSON from the scenario-level refPoints/ directory,
|
|
55
|
-
* keeps only observations where `sessionId` matches `sessionName`, and writes
|
|
56
|
-
* the filtered definition into the ZIP under `refPoints/{h3}.json`.
|
|
57
|
-
*
|
|
58
|
-
* Ref points with no observations for the current session are skipped entirely.
|
|
59
|
-
* If the refPoints/ directory doesn't exist yet (new scenario), returns 0.
|
|
60
|
-
*
|
|
61
|
-
* @returns Number of ref point files added
|
|
62
|
-
*/
|
|
63
|
-
async function streamSessionRefPointsToZip(scenarioHandle, sessionName, zipWriter) {
|
|
64
|
-
let count = 0;
|
|
65
|
-
let refPointsHandle;
|
|
66
|
-
try {
|
|
67
|
-
refPointsHandle = await scenarioHandle.getDirectoryHandle("refPoints");
|
|
68
|
-
} catch {
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
for await (const [name, handle] of refPointsHandle.entries()) {
|
|
72
|
-
if (handle.kind !== "file" || !name.endsWith(".json")) continue;
|
|
73
|
-
try {
|
|
74
|
-
const text = await (await handle.getFile()).text();
|
|
75
|
-
const def = JSON.parse(text);
|
|
76
|
-
if (!Array.isArray(def.observations)) continue;
|
|
77
|
-
const sessionObs = def.observations.filter((o) => o.sessionId === sessionName);
|
|
78
|
-
if (sessionObs.length === 0) continue;
|
|
79
|
-
const filtered = {
|
|
80
|
-
...def,
|
|
81
|
-
observations: sessionObs
|
|
82
|
-
};
|
|
83
|
-
const blob = new Blob([JSON.stringify(filtered, null, 2)], { type: "application/json" });
|
|
84
|
-
await zipWriter.add(`refPoints/${name}`, new BlobReader(blob));
|
|
85
|
-
count++;
|
|
86
|
-
} catch (err) {
|
|
87
|
-
log.warn(`Failed to process ref point ${name}:`, err);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return count;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
59
|
* Export a session from OPFS as a ZIP blob.
|
|
94
60
|
*
|
|
95
61
|
* The ZIP structure mirrors the OPFS session structure:
|
|
96
62
|
* - session.json (at root)
|
|
97
63
|
* - actions/000001.json, actions/000002.json, ...
|
|
98
64
|
* - frames/frame-000001.jpg, frames/frame-000002.jpg, ...
|
|
99
|
-
* - refPoints/{h3}.json (only observations from this session)
|
|
65
|
+
* - refPoints/{h3}.json (only observations from this session, scenario layout only)
|
|
100
66
|
*
|
|
101
67
|
* Uses "store" mode (compression level 0) for fast packaging.
|
|
102
68
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
69
|
+
* Supports both flat and scenario-based layouts:
|
|
70
|
+
* - `exportSessionAsZip(sessionName)` — flat layout (sessions/)
|
|
71
|
+
* - `exportSessionAsZip(scenarioName, sessionName)` — scenario layout (scenarios/{name}/)
|
|
72
|
+
*
|
|
105
73
|
* @returns ZIP export result with blob and file count
|
|
106
|
-
* @throws Error if
|
|
74
|
+
* @throws Error if session not found
|
|
107
75
|
*/
|
|
108
|
-
async function exportSessionAsZip(
|
|
109
|
-
log.info(`Exporting session: ${scenarioName}/${sessionName}`);
|
|
110
|
-
const scenariosDir = await getScenariosHandle();
|
|
111
|
-
let scenarioHandle;
|
|
112
|
-
try {
|
|
113
|
-
scenarioHandle = await scenariosDir.getDirectoryHandle(scenarioName);
|
|
114
|
-
} catch {
|
|
115
|
-
throw new Error(`Scenario "${scenarioName}" not found in OPFS storage`);
|
|
116
|
-
}
|
|
76
|
+
async function exportSessionAsZip(scenarioNameOrSessionName, sessionName, options) {
|
|
117
77
|
let sessionHandle;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
78
|
+
if (sessionName) {
|
|
79
|
+
const scenarioName = scenarioNameOrSessionName;
|
|
80
|
+
log.info(`Exporting session: ${scenarioName}/${sessionName}`);
|
|
81
|
+
const scenariosDir = await getScenariosHandle();
|
|
82
|
+
let scenarioHandle;
|
|
83
|
+
try {
|
|
84
|
+
scenarioHandle = await scenariosDir.getDirectoryHandle(scenarioName);
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error(`Scenario "${scenarioName}" not found in OPFS storage`);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
sessionHandle = await scenarioHandle.getDirectoryHandle(sessionName);
|
|
90
|
+
} catch {
|
|
91
|
+
throw new Error(`Session "${sessionName}" not found in scenario "${scenarioName}"`);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
log.info(`Exporting session: ${scenarioNameOrSessionName}`);
|
|
95
|
+
const sessionsDir = await getSessionsHandle();
|
|
96
|
+
try {
|
|
97
|
+
sessionHandle = await sessionsDir.getDirectoryHandle(scenarioNameOrSessionName);
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error(`Session "${scenarioNameOrSessionName}" not found in OPFS storage`);
|
|
100
|
+
}
|
|
122
101
|
}
|
|
123
102
|
const zipWriter = new ZipWriter(new BlobWriter("application/zip"), { level: 0 });
|
|
124
103
|
let fileCount = await streamDirectoryToZip(sessionHandle, zipWriter);
|
|
125
|
-
|
|
104
|
+
const contributors = options?.contributors ?? [];
|
|
105
|
+
const seenSubdirs = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const contributor of contributors) {
|
|
107
|
+
const subdir = contributor.subdir;
|
|
108
|
+
if (!subdir || subdir.includes("/") || subdir.startsWith(".")) throw new Error(`ZipExportContributor.subdir must be a non-empty single path segment, got: ${JSON.stringify(subdir)}`);
|
|
109
|
+
if (seenSubdirs.has(subdir)) throw new Error(`Duplicate ZipExportContributor.subdir registered: ${subdir}`);
|
|
110
|
+
seenSubdirs.add(subdir);
|
|
111
|
+
const addFile = async (relativePath, blob) => {
|
|
112
|
+
if (relativePath.startsWith("/")) throw new Error(`ZipExportContributor relative path must not start with '/' (got ${relativePath})`);
|
|
113
|
+
if (relativePath.includes("\\")) throw new Error(`ZipExportContributor relative path must not contain '\\' (got ${relativePath})`);
|
|
114
|
+
if (relativePath.split("/").some((s) => s === ".." || s === ".")) throw new Error(`ZipExportContributor relative path must not contain '.' or '..' segments (got ${relativePath})`);
|
|
115
|
+
await zipWriter.add(`${subdir}/${relativePath}`, new BlobReader(blob));
|
|
116
|
+
};
|
|
117
|
+
fileCount += await contributor.contribute(addFile);
|
|
118
|
+
}
|
|
126
119
|
const blob = await zipWriter.close();
|
|
127
120
|
log.info(`ZIP created: ${blob.size} bytes, ${fileCount} files`);
|
|
128
121
|
return {
|
|
@@ -148,9 +141,9 @@ async function exportSessionAsZip(scenarioName, sessionName) {
|
|
|
148
141
|
* @returns ZIP export result with blob and file count
|
|
149
142
|
* @throws Error if scenario or session not found
|
|
150
143
|
*/
|
|
151
|
-
async function syncToExternalZip(fileHandle, scenarioName, sessionName) {
|
|
144
|
+
async function syncToExternalZip(fileHandle, scenarioName, sessionName, options) {
|
|
152
145
|
log.info(`Syncing to external ZIP: ${scenarioName}/${sessionName}`);
|
|
153
|
-
const result = await exportSessionAsZip(scenarioName, sessionName);
|
|
146
|
+
const result = await exportSessionAsZip(scenarioName, sessionName, options);
|
|
154
147
|
const writable = await fileHandle.createWritable();
|
|
155
148
|
await writable.write(result.blob);
|
|
156
149
|
await writable.close();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ZipActionEntry, c as
|
|
2
|
-
export { Entry, GpsPathCoord, MAX_ACTION_FILE_SIZE, RecordedAction, ZipActionEntry, loadActionsFromZip, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
|
|
1
|
+
import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-D1105idz.js";
|
|
2
|
+
export { Entry, GpsPathCoord, MAX_ACTION_FILE_SIZE, RecordedAction, ZipActionEntry, ZipSubdirEntry, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
|
|
@@ -172,10 +172,14 @@ async function loadGpsPathFromBlob(blob, maxFileSize = MAX_ACTION_FILE_SIZE) {
|
|
|
172
172
|
continue;
|
|
173
173
|
}
|
|
174
174
|
const gps = action.payload?.rawGpsPoint ?? action.payload?.gpsPoint;
|
|
175
|
-
if (action.type === "gpsData/recordGpsEvent" && gps && typeof gps.latitude === "number" && typeof gps.longitude === "number")
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
if (action.type === "gpsData/recordGpsEvent" && gps && typeof gps.latitude === "number" && typeof gps.longitude === "number") {
|
|
176
|
+
const accuracy = typeof gps.latLongAccuracy === "number" && gps.latLongAccuracy > 0 ? gps.latLongAccuracy : void 0;
|
|
177
|
+
coords.push({
|
|
178
|
+
lat: gps.latitude,
|
|
179
|
+
lng: gps.longitude,
|
|
180
|
+
...accuracy !== void 0 ? { accuracy } : {}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
179
183
|
}
|
|
180
184
|
return coords;
|
|
181
185
|
} catch {
|
|
@@ -186,5 +190,28 @@ async function loadGpsPathFromBlob(blob, maxFileSize = MAX_ACTION_FILE_SIZE) {
|
|
|
186
190
|
} catch {}
|
|
187
191
|
}
|
|
188
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Enumerate every file under a single top-level subdirectory of a recording
|
|
195
|
+
* zip. Mirrors the writer-side {@link ZipExportContributor} seam so consumers
|
|
196
|
+
* (typically the recorder) can read back what they wrote without
|
|
197
|
+
* re-implementing zip enumeration.
|
|
198
|
+
*
|
|
199
|
+
* Skips directory entries and files outside `subdir`. Sorts entries by
|
|
200
|
+
* `relativePath` for deterministic iteration. Returns an empty array when
|
|
201
|
+
* the subdir is absent (graceful degradation for older zips).
|
|
202
|
+
*
|
|
203
|
+
* @param data - The zip file content as a Uint8Array
|
|
204
|
+
* @param subdir - Top-level subdir to scan (no leading or trailing `/`)
|
|
205
|
+
*/
|
|
206
|
+
async function loadEntriesFromSubdir(data, subdir) {
|
|
207
|
+
if (!subdir || subdir.includes("/") || subdir.startsWith(".")) throw new Error(`subdir must be a non-empty single path segment, got: ${JSON.stringify(subdir)}`);
|
|
208
|
+
const prefix = `${subdir}/`;
|
|
209
|
+
return (await readZipEntries(data)).filter((e) => !e.directory && e.filename.startsWith(prefix) && e.filename !== prefix).sort((a, b) => a.filename.localeCompare(b.filename)).map((entry) => ({
|
|
210
|
+
relativePath: entry.filename.slice(prefix.length),
|
|
211
|
+
fullPath: entry.filename,
|
|
212
|
+
uncompressedSize: entry.uncompressedSize,
|
|
213
|
+
getText: () => entry.getData(new TextWriter())
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
189
216
|
//#endregion
|
|
190
|
-
export { MAX_ACTION_FILE_SIZE, loadActionsFromZip, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
|
|
217
|
+
export { MAX_ACTION_FILE_SIZE, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { n as SessionMetadata } from "./opfs-storage-DOPkzgeT.js";
|
|
2
|
+
|
|
3
|
+
//#region ../src/storage/storage-backend.d.ts
|
|
4
|
+
interface CreateSessionResult {
|
|
5
|
+
sessionName: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Contract for session lifecycle and recording persistence.
|
|
9
|
+
*
|
|
10
|
+
* Implementations:
|
|
11
|
+
* - OpfsStorageBackend: production — flat sessions under OPFS
|
|
12
|
+
* - NullStorageBackend: tests/replay — silent no-ops
|
|
13
|
+
* - ScenarioWrappingStorageBackend (recorder): layers scenario hierarchy on top
|
|
14
|
+
*/
|
|
15
|
+
interface StorageBackend {
|
|
16
|
+
/** Create a new recording session. Returns the session name for reference. */
|
|
17
|
+
createSession(timestamp: Date, contextTag?: string): Promise<CreateSessionResult>;
|
|
18
|
+
/** List all session names managed by this backend. */
|
|
19
|
+
listSessions(): Promise<string[]>;
|
|
20
|
+
/** Persist a Redux action as a numbered JSON file. */
|
|
21
|
+
writeAction(action: unknown, index: number): Promise<void>;
|
|
22
|
+
/** Persist a captured camera frame as a numbered JPEG. */
|
|
23
|
+
writeFrame(blob: Blob, index: number): Promise<void>;
|
|
24
|
+
/** Persist session metadata (session.json). */
|
|
25
|
+
writeSessionMetadata(metadata: SessionMetadata): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { StorageBackend as n, CreateSessionResult as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { t as MapData } from "./map-data-sih4g64v.js";
|
|
2
|
+
import { t as SubscribableStore } from "./subscribe-to-selector-DIHm3BRZ.js";
|
|
2
3
|
import { LatLong, Matrix4, Quaternion, Vector3 } from "gps-plus-slam-js";
|
|
3
4
|
|
|
4
5
|
//#region ../src/state/store-subscribers.d.ts
|
|
@@ -13,16 +14,28 @@ interface StoreSubscriberDeps {
|
|
|
13
14
|
gpsEventVisualizer: {
|
|
14
15
|
getZeroRef: () => LatLong | null;
|
|
15
16
|
setZeroRef: (zero: LatLong) => void;
|
|
16
|
-
addGpsEvent: (gpsCoords: Vector3, odomPosition: Vector3
|
|
17
|
+
addGpsEvent: (gpsCoords: Vector3, odomPosition: Vector3, accuracy?: {
|
|
18
|
+
horizontal?: number;
|
|
19
|
+
vertical?: number;
|
|
20
|
+
}) => void;
|
|
17
21
|
addAlignmentSnapshot: (nuePosition: Vector3) => void;
|
|
18
22
|
};
|
|
19
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Map overlay — renders the shared trajectory snapshot. Nullable (not
|
|
25
|
+
* present in all modes).
|
|
26
|
+
*
|
|
27
|
+
* `setGpsPosition` centers the map on the latest fix; `render` draws the
|
|
28
|
+
* full {@link MapData} snapshot (raw GPS + accuracy circles, fused path,
|
|
29
|
+
* alignment snapshots, user dot) via the shared `drawMapData` routine — the
|
|
30
|
+
* SAME one the 2D session-summary map uses, so the live and summary maps
|
|
31
|
+
* stay identical (unified-trajectory-map Phase 3). The previous incremental
|
|
32
|
+
* `addRawGpsPoint` / `addFusedPoint` / `addAlignmentSnapshot` API is gone:
|
|
33
|
+
* the fused path now recomputes from the latest matrix on every rebuild
|
|
34
|
+
* (D2), so the live polyline "snaps" as alignment improves.
|
|
35
|
+
*/
|
|
20
36
|
mapOverlay?: {
|
|
21
37
|
setGpsPosition: (lat: number, lon: number) => void;
|
|
22
|
-
|
|
23
|
-
addFusedPoint?: (lat: number, lon: number) => void;
|
|
24
|
-
addAlignmentSnapshot?: (lat: number, lon: number) => void;
|
|
25
|
-
addRefPoint?: (lat: number, lon: number, name: string) => void;
|
|
38
|
+
render?: (data: MapData) => void;
|
|
26
39
|
} | null;
|
|
27
40
|
/**
|
|
28
41
|
* Optional callback invoked with the GPS world-space coordinates of each
|
|
@@ -52,6 +65,17 @@ interface StoreSubscriberDeps {
|
|
|
52
65
|
* for dynamic button label updates.
|
|
53
66
|
*/
|
|
54
67
|
onNewGpsLatLng?: (lat: number, lng: number) => void;
|
|
68
|
+
/**
|
|
69
|
+
* When `true`, the recorded GPS 1σ accuracies (`latLongAccuracy`,
|
|
70
|
+
* `altitudeAccuracy`) on each `GpsPoint` are forwarded to `addGpsEvent`
|
|
71
|
+
* so the raw-GPS marker renders as a non-uniform-scaled ellipsoid (see
|
|
72
|
+
* `gps-event-markers.ts` §3). When `false` (the default) the accuracies
|
|
73
|
+
* are dropped and the legacy fixed 8 cm sphere is used.
|
|
74
|
+
*
|
|
75
|
+
* Live recording leaves this `false` (large ellipsoids would distract the
|
|
76
|
+
* operator). Replay mode sets it to `true` so the diagnostic is visible.
|
|
77
|
+
*/
|
|
78
|
+
showAccuracySpheres?: boolean;
|
|
55
79
|
}
|
|
56
80
|
/**
|
|
57
81
|
* Wire store state-change subscriptions to visualization dependencies.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as RecordedAction } from "../zip-reader-
|
|
1
|
+
import { i as RecordedAction } from "../zip-reader-D1105idz.js";
|
|
2
2
|
import { LatLong } from "gps-plus-slam-js";
|
|
3
3
|
|
|
4
4
|
//#region ../src/test-utils/zip-round-trip-helpers.d.ts
|
|
@@ -12,6 +12,12 @@ interface ProduceTestZipOptions {
|
|
|
12
12
|
frameCount: number;
|
|
13
13
|
deviceInfo: string;
|
|
14
14
|
zeroPos: LatLong;
|
|
15
|
+
/**
|
|
16
|
+
* Optional horizontal accuracy (meters) stamped onto every produced
|
|
17
|
+
* `recordGpsEvent` action's `rawGpsPoint.latLongAccuracy`. When omitted,
|
|
18
|
+
* the field is left off — matching pre-accuracy recordings.
|
|
19
|
+
*/
|
|
20
|
+
gpsAccuracy?: number;
|
|
15
21
|
}
|
|
16
22
|
/** Result of producing a test zip, with all metadata needed for assertions. */
|
|
17
23
|
interface TestZipResult {
|
|
@@ -65,7 +65,7 @@ async function produceTestZip(opts) {
|
|
|
65
65
|
const { cleanup } = installOPFSMocks();
|
|
66
66
|
try {
|
|
67
67
|
await initOpfsStorage();
|
|
68
|
-
const {
|
|
68
|
+
const { sessionName } = await createSession(options.sessionTimestamp, options.scenarioName);
|
|
69
69
|
const allActions = [];
|
|
70
70
|
let actionIndex = 0;
|
|
71
71
|
/** Increment index, persist to OPFS, and record for later assertions. */
|
|
@@ -75,7 +75,7 @@ async function produceTestZip(opts) {
|
|
|
75
75
|
allActions.push(action);
|
|
76
76
|
};
|
|
77
77
|
await writeAndRecordAction({
|
|
78
|
-
type: "
|
|
78
|
+
type: "recording/startSession",
|
|
79
79
|
payload: {
|
|
80
80
|
scenarioName: options.scenarioName,
|
|
81
81
|
sessionName,
|
|
@@ -131,7 +131,8 @@ async function produceTestZip(opts) {
|
|
|
131
131
|
id: `gps-event-${i + 1}`,
|
|
132
132
|
latitude: options.zeroPos.lat + (i + 1) * 1e-4,
|
|
133
133
|
longitude: options.zeroPos.lon + (i + 1) * 1e-4,
|
|
134
|
-
timestamp: t
|
|
134
|
+
timestamp: t,
|
|
135
|
+
...typeof options.gpsAccuracy === "number" ? { latLongAccuracy: options.gpsAccuracy } : {}
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
});
|
|
@@ -174,12 +175,12 @@ async function produceTestZip(opts) {
|
|
|
174
175
|
version: 1,
|
|
175
176
|
startedAt: options.sessionTimestamp.toISOString(),
|
|
176
177
|
endedAt: new Date(options.sessionTimestamp.getTime() + actionIndex * 1e3 + 5e3).toISOString(),
|
|
177
|
-
|
|
178
|
+
contextTag: options.scenarioName,
|
|
178
179
|
actionCount: actionIndex,
|
|
179
180
|
frameCount: options.frameCount,
|
|
180
181
|
userAgent: options.deviceInfo
|
|
181
182
|
});
|
|
182
|
-
const { blob } = await exportSessionAsZip(
|
|
183
|
+
const { blob } = await exportSessionAsZip(sessionName);
|
|
183
184
|
return {
|
|
184
185
|
zipData: new Uint8Array(await blob.arrayBuffer()),
|
|
185
186
|
totalActionCount: actionIndex,
|