@faclon-labs/app-sdk 0.1.0 → 0.2.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/README.md +34 -1
- package/dist/SignIn.js +2 -2
- package/dist/config.d.ts +5 -0
- package/dist/config.js +1 -0
- package/dist/map/globe.d.ts +5 -0
- package/dist/map/globe.js +5 -0
- package/dist/map/index.d.ts +21 -0
- package/dist/map/index.js +21 -0
- package/dist/map/source.d.ts +46 -0
- package/dist/map/source.js +54 -0
- package/dist/map/testing.d.ts +5 -0
- package/dist/map/testing.js +5 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +1 -1
- package/package.json +55 -5
- package/vendor/SOURCE.txt +1 -0
- package/vendor/map-engine/cesium/camera.d.ts +10 -0
- package/vendor/map-engine/cesium/camera.js +25 -0
- package/vendor/map-engine/cesium/index.d.ts +2 -0
- package/vendor/map-engine/cesium/index.js +2 -0
- package/vendor/map-engine/cesium/renderer.d.ts +149 -0
- package/vendor/map-engine/cesium/renderer.js +837 -0
- package/vendor/map-engine/commands/index.d.ts +2 -0
- package/vendor/map-engine/commands/index.js +2 -0
- package/vendor/map-engine/commands/post-message.d.ts +32 -0
- package/vendor/map-engine/commands/post-message.js +77 -0
- package/vendor/map-engine/commands/webmcp.d.ts +17 -0
- package/vendor/map-engine/commands/webmcp.js +59 -0
- package/vendor/map-engine/core/commands.d.ts +82 -0
- package/vendor/map-engine/core/commands.js +46 -0
- package/vendor/map-engine/core/emitter.d.ts +9 -0
- package/vendor/map-engine/core/emitter.js +38 -0
- package/vendor/map-engine/core/equal.d.ts +2 -0
- package/vendor/map-engine/core/equal.js +28 -0
- package/vendor/map-engine/core/errors.d.ts +20 -0
- package/vendor/map-engine/core/errors.js +34 -0
- package/vendor/map-engine/core/geometry-family.d.ts +3 -0
- package/vendor/map-engine/core/geometry-family.js +13 -0
- package/vendor/map-engine/core/index.d.ts +4 -0
- package/vendor/map-engine/core/index.js +3 -0
- package/vendor/map-engine/core/types.d.ts +709 -0
- package/vendor/map-engine/core/types.js +7 -0
- package/vendor/map-engine/data/annotations.d.ts +3 -0
- package/vendor/map-engine/data/annotations.js +63 -0
- package/vendor/map-engine/data/contracts.d.ts +172 -0
- package/vendor/map-engine/data/contracts.js +12 -0
- package/vendor/map-engine/data/index.d.ts +4 -0
- package/vendor/map-engine/data/index.js +3 -0
- package/vendor/map-engine/data/schema.d.ts +7 -0
- package/vendor/map-engine/data/schema.js +40 -0
- package/vendor/map-engine/geofence/evaluator.d.ts +35 -0
- package/vendor/map-engine/geofence/evaluator.js +172 -0
- package/vendor/map-engine/geofence/history.d.ts +9 -0
- package/vendor/map-engine/geofence/history.js +19 -0
- package/vendor/map-engine/geofence/index.d.ts +2 -0
- package/vendor/map-engine/geofence/index.js +2 -0
- package/vendor/map-engine/index.d.ts +27 -0
- package/vendor/map-engine/index.js +14 -0
- package/vendor/map-engine/layers/index.d.ts +1 -0
- package/vendor/map-engine/layers/index.js +1 -0
- package/vendor/map-engine/layers/layers.d.ts +49 -0
- package/vendor/map-engine/layers/layers.js +316 -0
- package/vendor/map-engine/maplibre/deck-host.d.ts +105 -0
- package/vendor/map-engine/maplibre/deck-host.js +401 -0
- package/vendor/map-engine/maplibre/icons.d.ts +17 -0
- package/vendor/map-engine/maplibre/icons.js +65 -0
- package/vendor/map-engine/maplibre/index.d.ts +3 -0
- package/vendor/map-engine/maplibre/index.js +2 -0
- package/vendor/map-engine/maplibre/map-like.d.ts +56 -0
- package/vendor/map-engine/maplibre/map-like.js +1 -0
- package/vendor/map-engine/maplibre/renderer.d.ts +173 -0
- package/vendor/map-engine/maplibre/renderer.js +889 -0
- package/vendor/map-engine/maplibre/style.d.ts +84 -0
- package/vendor/map-engine/maplibre/style.js +493 -0
- package/vendor/map-engine/present/decorations.d.ts +25 -0
- package/vendor/map-engine/present/decorations.js +115 -0
- package/vendor/map-engine/present/index.d.ts +2 -0
- package/vendor/map-engine/present/index.js +2 -0
- package/vendor/map-engine/present/present.d.ts +47 -0
- package/vendor/map-engine/present/present.js +149 -0
- package/vendor/map-engine/query/index.d.ts +1 -0
- package/vendor/map-engine/query/index.js +1 -0
- package/vendor/map-engine/query/planner.d.ts +26 -0
- package/vendor/map-engine/query/planner.js +47 -0
- package/vendor/map-engine/react/MapView.d.ts +87 -0
- package/vendor/map-engine/react/MapView.js +270 -0
- package/vendor/map-engine/react/context.d.ts +39 -0
- package/vendor/map-engine/react/context.js +94 -0
- package/vendor/map-engine/react/index.d.ts +2 -0
- package/vendor/map-engine/react/index.js +2 -0
- package/vendor/map-engine/react/store.d.ts +19 -0
- package/vendor/map-engine/react/store.js +18 -0
- package/vendor/map-engine/realtime/apply.d.ts +74 -0
- package/vendor/map-engine/realtime/apply.js +155 -0
- package/vendor/map-engine/realtime/index.d.ts +1 -0
- package/vendor/map-engine/realtime/index.js +1 -0
- package/vendor/map-engine/render/index.d.ts +1 -0
- package/vendor/map-engine/render/index.js +1 -0
- package/vendor/map-engine/render/offline.d.ts +8 -0
- package/vendor/map-engine/render/offline.js +47 -0
- package/vendor/map-engine/render/renderer.d.ts +74 -0
- package/vendor/map-engine/render/renderer.js +1 -0
- package/vendor/map-engine/render/terrarium.d.ts +48 -0
- package/vendor/map-engine/render/terrarium.js +110 -0
- package/vendor/map-engine/runtime/drawing.d.ts +28 -0
- package/vendor/map-engine/runtime/drawing.js +58 -0
- package/vendor/map-engine/runtime/index.d.ts +5 -0
- package/vendor/map-engine/runtime/index.js +2 -0
- package/vendor/map-engine/runtime/offline.d.ts +24 -0
- package/vendor/map-engine/runtime/offline.js +37 -0
- package/vendor/map-engine/runtime/overlays.d.ts +25 -0
- package/vendor/map-engine/runtime/overlays.js +65 -0
- package/vendor/map-engine/runtime/runtime.d.ts +4 -0
- package/vendor/map-engine/runtime/runtime.js +2105 -0
- package/vendor/map-engine/runtime/types.d.ts +407 -0
- package/vendor/map-engine/runtime/types.js +1 -0
- package/vendor/map-engine/scene/index.d.ts +1 -0
- package/vendor/map-engine/scene/index.js +1 -0
- package/vendor/map-engine/scene/scene.d.ts +31 -0
- package/vendor/map-engine/scene/scene.js +216 -0
- package/vendor/map-engine/selection/index.d.ts +1 -0
- package/vendor/map-engine/selection/index.js +1 -0
- package/vendor/map-engine/selection/selection.d.ts +12 -0
- package/vendor/map-engine/selection/selection.js +28 -0
- package/vendor/map-engine/spatial/geojson.d.ts +15 -0
- package/vendor/map-engine/spatial/geojson.js +83 -0
- package/vendor/map-engine/spatial/geometry.d.ts +13 -0
- package/vendor/map-engine/spatial/geometry.js +206 -0
- package/vendor/map-engine/spatial/import.d.ts +27 -0
- package/vendor/map-engine/spatial/import.js +96 -0
- package/vendor/map-engine/spatial/index.d.ts +38 -0
- package/vendor/map-engine/spatial/index.js +37 -0
- package/vendor/map-engine/spatial/line-of-sight.d.ts +35 -0
- package/vendor/map-engine/spatial/line-of-sight.js +43 -0
- package/vendor/map-engine/spatial/measure.d.ts +28 -0
- package/vendor/map-engine/spatial/measure.js +90 -0
- package/vendor/map-engine/spatial/operations.d.ts +21 -0
- package/vendor/map-engine/spatial/operations.js +171 -0
- package/vendor/map-engine/spatial/query.d.ts +17 -0
- package/vendor/map-engine/spatial/query.js +88 -0
- package/vendor/map-engine/spatial/spatial-index.d.ts +24 -0
- package/vendor/map-engine/spatial/spatial-index.js +36 -0
- package/vendor/map-engine/spatial/volume-mesh.d.ts +19 -0
- package/vendor/map-engine/spatial/volume-mesh.js +56 -0
- package/vendor/map-engine/temporal/index.d.ts +3 -0
- package/vendor/map-engine/temporal/index.js +3 -0
- package/vendor/map-engine/temporal/journal.d.ts +20 -0
- package/vendor/map-engine/temporal/journal.js +34 -0
- package/vendor/map-engine/temporal/playback.d.ts +41 -0
- package/vendor/map-engine/temporal/playback.js +80 -0
- package/vendor/map-engine/temporal/tracks.d.ts +33 -0
- package/vendor/map-engine/temporal/tracks.js +119 -0
- package/vendor/map-engine/testing/annotation-store.d.ts +24 -0
- package/vendor/map-engine/testing/annotation-store.js +43 -0
- package/vendor/map-engine/testing/contracts.d.ts +41 -0
- package/vendor/map-engine/testing/contracts.js +328 -0
- package/vendor/map-engine/testing/fixtures-monitoring.d.ts +22 -0
- package/vendor/map-engine/testing/fixtures-monitoring.js +103 -0
- package/vendor/map-engine/testing/fixtures.d.ts +18 -0
- package/vendor/map-engine/testing/fixtures.js +28 -0
- package/vendor/map-engine/testing/index.d.ts +7 -0
- package/vendor/map-engine/testing/index.js +7 -0
- package/vendor/map-engine/testing/mock-ontology.d.ts +13 -0
- package/vendor/map-engine/testing/mock-ontology.js +24 -0
- package/vendor/map-engine/testing/mock-provider.d.ts +97 -0
- package/vendor/map-engine/testing/mock-provider.js +369 -0
- package/vendor/map-engine/testing/test-renderer.d.ts +72 -0
- package/vendor/map-engine/testing/test-renderer.js +153 -0
- package/vendor/map-engine/viewport/index.d.ts +1 -0
- package/vendor/map-engine/viewport/index.js +1 -0
- package/vendor/map-engine/viewport/viewport.d.ts +19 -0
- package/vendor/map-engine/viewport/viewport.js +102 -0
- package/vendor/map-provider-uns/client.d.ts +75 -0
- package/vendor/map-provider-uns/client.js +111 -0
- package/vendor/map-provider-uns/index.d.ts +2 -0
- package/vendor/map-provider-uns/index.js +2 -0
- package/vendor/map-provider-uns/provider.d.ts +131 -0
- package/vendor/map-provider-uns/provider.js +561 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { toIso, toMs } from './tracks.js';
|
|
2
|
+
export class Playback {
|
|
3
|
+
options;
|
|
4
|
+
atMs;
|
|
5
|
+
startMs;
|
|
6
|
+
endMs;
|
|
7
|
+
speed;
|
|
8
|
+
playing = false;
|
|
9
|
+
lastReal = 0;
|
|
10
|
+
generation = 0;
|
|
11
|
+
frame;
|
|
12
|
+
now;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.startMs = toMs(options.range.start);
|
|
16
|
+
this.endMs = toMs(options.range.end);
|
|
17
|
+
if (!(this.endMs > this.startMs))
|
|
18
|
+
throw new Error('playback range end must be after start');
|
|
19
|
+
this.atMs = clamp(options.at ? toMs(options.at) : this.startMs, this.startMs, this.endMs);
|
|
20
|
+
this.speed = options.speed ?? 1;
|
|
21
|
+
this.now = options.now ?? (() => (typeof performance !== 'undefined' ? performance.now() : Date.now()));
|
|
22
|
+
this.frame =
|
|
23
|
+
options.frame ??
|
|
24
|
+
(typeof requestAnimationFrame === 'function' ? (cb) => void requestAnimationFrame(cb) : (cb) => void setTimeout(() => cb(this.now()), 50));
|
|
25
|
+
}
|
|
26
|
+
get time() {
|
|
27
|
+
return toIso(this.atMs);
|
|
28
|
+
}
|
|
29
|
+
get isPlaying() {
|
|
30
|
+
return this.playing;
|
|
31
|
+
}
|
|
32
|
+
get rate() {
|
|
33
|
+
return this.speed;
|
|
34
|
+
}
|
|
35
|
+
get range() {
|
|
36
|
+
return { start: toIso(this.startMs), end: toIso(this.endMs) };
|
|
37
|
+
}
|
|
38
|
+
play() {
|
|
39
|
+
if (this.playing)
|
|
40
|
+
return;
|
|
41
|
+
if (this.atMs >= this.endMs)
|
|
42
|
+
this.atMs = this.startMs; // play again from the start
|
|
43
|
+
this.playing = true;
|
|
44
|
+
this.lastReal = this.now();
|
|
45
|
+
const gen = ++this.generation;
|
|
46
|
+
const tick = (nowMs) => {
|
|
47
|
+
if (!this.playing || gen !== this.generation)
|
|
48
|
+
return;
|
|
49
|
+
const elapsed = Math.max(0, nowMs - this.lastReal);
|
|
50
|
+
this.lastReal = nowMs;
|
|
51
|
+
this.atMs = Math.min(this.endMs, this.atMs + elapsed * this.speed);
|
|
52
|
+
this.options.onTime(this.time);
|
|
53
|
+
if (this.atMs >= this.endMs) {
|
|
54
|
+
this.playing = false;
|
|
55
|
+
this.options.onEnd?.();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.frame(tick);
|
|
59
|
+
};
|
|
60
|
+
this.frame(tick);
|
|
61
|
+
}
|
|
62
|
+
pause() {
|
|
63
|
+
this.playing = false;
|
|
64
|
+
this.generation++;
|
|
65
|
+
}
|
|
66
|
+
seek(at) {
|
|
67
|
+
this.atMs = clamp(toMs(at), this.startMs, this.endMs);
|
|
68
|
+
this.lastReal = this.now();
|
|
69
|
+
this.options.onTime(this.time);
|
|
70
|
+
}
|
|
71
|
+
setSpeed(speed) {
|
|
72
|
+
if (!(speed > 0))
|
|
73
|
+
throw new Error('speed must be > 0');
|
|
74
|
+
this.speed = speed;
|
|
75
|
+
}
|
|
76
|
+
dispose() {
|
|
77
|
+
this.pause();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal model (doc 07): state of a moving object at time T, and trails over a window.
|
|
3
|
+
* Pure functions over Track data — no data access, no rendering.
|
|
4
|
+
*/
|
|
5
|
+
import type { Position, Track, TrackPoint, ValidationResult } from '../core/types.js';
|
|
6
|
+
export declare const toMs: (iso: string) => number;
|
|
7
|
+
export declare const toIso: (ms: number) => string;
|
|
8
|
+
/** Validates untrusted track data; points must have valid timestamps and positions. Never throws. */
|
|
9
|
+
export declare function validateTrack(value: unknown): ValidationResult;
|
|
10
|
+
/** Sorted by time, duplicates (same timestamp) collapsed to the last one. */
|
|
11
|
+
export declare function normalizeTrack(track: Track): Track;
|
|
12
|
+
/** Bearing in degrees clockwise from north, from `a` to `b`. */
|
|
13
|
+
export declare function bearing(a: Position, b: Position): number;
|
|
14
|
+
export interface StateAt {
|
|
15
|
+
position: Position;
|
|
16
|
+
heading?: number;
|
|
17
|
+
/** m/s, from the data or derived from neighbouring points. */
|
|
18
|
+
speed?: number;
|
|
19
|
+
/** True after the last observation: the last known position is held (a parked object stays where it was). */
|
|
20
|
+
held?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Where the object was at `ms`: linear interpolation between the surrounding points. After the last observation
|
|
24
|
+
* the last known position is held (`held: true`). Null before the first observation: it was not there yet.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stateAt(track: Track, ms: number): StateAt | null;
|
|
27
|
+
/**
|
|
28
|
+
* Positions between `fromMs` and `toMs` (inclusive), with interpolated end points so a trail ends exactly
|
|
29
|
+
* where the object is at `toMs`. Empty when the object has no observations in or around the window.
|
|
30
|
+
*/
|
|
31
|
+
export declare function trailBetween(track: Track, fromMs: number, toMsValue: number): Position[];
|
|
32
|
+
/** Appends a newer point (live trail growth) and drops points older than `keepFromMs`. Older points are ignored. */
|
|
33
|
+
export declare function appendPoint(track: Track, point: TrackPoint, keepFromMs: number): Track;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { validatePosition } from '../spatial/geometry.js';
|
|
2
|
+
import { distanceMeters } from '../spatial/operations.js';
|
|
3
|
+
export const toMs = (iso) => Date.parse(iso);
|
|
4
|
+
export const toIso = (ms) => new Date(ms).toISOString();
|
|
5
|
+
/** Validates untrusted track data; points must have valid timestamps and positions. Never throws. */
|
|
6
|
+
export function validateTrack(value) {
|
|
7
|
+
if (typeof value !== 'object' || value === null)
|
|
8
|
+
return { valid: false, reason: 'track is not an object' };
|
|
9
|
+
const t = value;
|
|
10
|
+
if (typeof t.objectId !== 'string' || !t.objectId)
|
|
11
|
+
return { valid: false, reason: 'track.objectId is required' };
|
|
12
|
+
if (!Array.isArray(t.points))
|
|
13
|
+
return { valid: false, reason: `track ${t.objectId}: points is not an array` };
|
|
14
|
+
for (const [i, p] of t.points.entries()) {
|
|
15
|
+
if (!p || !Number.isFinite(toMs(p.timestamp)))
|
|
16
|
+
return { valid: false, reason: `track ${t.objectId}: point ${i} has no valid timestamp` };
|
|
17
|
+
const v = validatePosition(p, `track ${t.objectId} point ${i}`);
|
|
18
|
+
if (!v.valid)
|
|
19
|
+
return v;
|
|
20
|
+
}
|
|
21
|
+
return { valid: true };
|
|
22
|
+
}
|
|
23
|
+
/** Sorted by time, duplicates (same timestamp) collapsed to the last one. */
|
|
24
|
+
export function normalizeTrack(track) {
|
|
25
|
+
const byTime = new Map();
|
|
26
|
+
for (const p of track.points)
|
|
27
|
+
byTime.set(toMs(p.timestamp), p);
|
|
28
|
+
return { ...track, points: [...byTime.entries()].sort((a, b) => a[0] - b[0]).map(([, p]) => p) };
|
|
29
|
+
}
|
|
30
|
+
/** Bearing in degrees clockwise from north, from `a` to `b`. */
|
|
31
|
+
export function bearing(a, b) {
|
|
32
|
+
const φ1 = (a.latitude * Math.PI) / 180;
|
|
33
|
+
const φ2 = (b.latitude * Math.PI) / 180;
|
|
34
|
+
const Δλ = ((b.longitude - a.longitude) * Math.PI) / 180;
|
|
35
|
+
const y = Math.sin(Δλ) * Math.cos(φ2);
|
|
36
|
+
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
|
|
37
|
+
return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;
|
|
38
|
+
}
|
|
39
|
+
/** Index of the last point at or before `ms` (binary search); -1 when `ms` is before the track. */
|
|
40
|
+
function indexAtOrBefore(points, ms) {
|
|
41
|
+
let lo = 0;
|
|
42
|
+
let hi = points.length - 1;
|
|
43
|
+
let ans = -1;
|
|
44
|
+
while (lo <= hi) {
|
|
45
|
+
const mid = (lo + hi) >> 1;
|
|
46
|
+
if (toMs(points[mid].timestamp) <= ms) {
|
|
47
|
+
ans = mid;
|
|
48
|
+
lo = mid + 1;
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
hi = mid - 1;
|
|
52
|
+
}
|
|
53
|
+
return ans;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Where the object was at `ms`: linear interpolation between the surrounding points. After the last observation
|
|
57
|
+
* the last known position is held (`held: true`). Null before the first observation: it was not there yet.
|
|
58
|
+
*/
|
|
59
|
+
export function stateAt(track, ms) {
|
|
60
|
+
const pts = track.points;
|
|
61
|
+
if (!pts.length)
|
|
62
|
+
return null;
|
|
63
|
+
const i = indexAtOrBefore(pts, ms);
|
|
64
|
+
if (i < 0)
|
|
65
|
+
return null;
|
|
66
|
+
const a = pts[i];
|
|
67
|
+
const ta = toMs(a.timestamp);
|
|
68
|
+
if (i === pts.length - 1) {
|
|
69
|
+
const s = withMotion({ latitude: a.latitude, longitude: a.longitude }, a, pts[i - 1], undefined);
|
|
70
|
+
if (ms === ta)
|
|
71
|
+
return s;
|
|
72
|
+
const { speed: _speed, ...rest } = s; // not moving while held
|
|
73
|
+
return { ...rest, held: true };
|
|
74
|
+
}
|
|
75
|
+
const b = pts[i + 1];
|
|
76
|
+
const tb = toMs(b.timestamp);
|
|
77
|
+
const f = tb > ta ? (ms - ta) / (tb - ta) : 0;
|
|
78
|
+
const position = { latitude: a.latitude + (b.latitude - a.latitude) * f, longitude: a.longitude + (b.longitude - a.longitude) * f };
|
|
79
|
+
const heading = a.heading !== undefined && b.heading !== undefined ? interpolateAngle(a.heading, b.heading, f) : bearingOrUndefined(a, b);
|
|
80
|
+
const speed = a.speed !== undefined && b.speed !== undefined ? a.speed + (b.speed - a.speed) * f : tb > ta ? distanceMeters(a, b) / ((tb - ta) / 1000) : undefined;
|
|
81
|
+
return { position, ...(heading !== undefined ? { heading } : {}), ...(speed !== undefined ? { speed } : {}) };
|
|
82
|
+
}
|
|
83
|
+
function withMotion(position, p, prev, next) {
|
|
84
|
+
const heading = p.heading ?? (prev ? bearingOrUndefined(prev, p) : next ? bearingOrUndefined(p, next) : undefined);
|
|
85
|
+
return { position, ...(heading !== undefined ? { heading } : {}), ...(p.speed !== undefined ? { speed: p.speed } : {}) };
|
|
86
|
+
}
|
|
87
|
+
function bearingOrUndefined(a, b) {
|
|
88
|
+
return a.latitude === b.latitude && a.longitude === b.longitude ? undefined : bearing(a, b);
|
|
89
|
+
}
|
|
90
|
+
function interpolateAngle(a, b, f) {
|
|
91
|
+
const d = ((b - a + 540) % 360) - 180;
|
|
92
|
+
return (a + d * f + 360) % 360;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Positions between `fromMs` and `toMs` (inclusive), with interpolated end points so a trail ends exactly
|
|
96
|
+
* where the object is at `toMs`. Empty when the object has no observations in or around the window.
|
|
97
|
+
*/
|
|
98
|
+
export function trailBetween(track, fromMs, toMsValue) {
|
|
99
|
+
const out = [];
|
|
100
|
+
const start = stateAt(track, fromMs);
|
|
101
|
+
if (start)
|
|
102
|
+
out.push(start.position);
|
|
103
|
+
for (const p of track.points) {
|
|
104
|
+
const t = toMs(p.timestamp);
|
|
105
|
+
if (t > fromMs && t < toMsValue)
|
|
106
|
+
out.push({ latitude: p.latitude, longitude: p.longitude });
|
|
107
|
+
}
|
|
108
|
+
const end = stateAt(track, toMsValue);
|
|
109
|
+
if (end)
|
|
110
|
+
out.push(end.position);
|
|
111
|
+
// Collapse repeated positions (held or stationary), so a parked object has no zero-length trail.
|
|
112
|
+
return out.filter((p, i) => i === 0 || p.latitude !== out[i - 1].latitude || p.longitude !== out[i - 1].longitude);
|
|
113
|
+
}
|
|
114
|
+
/** Appends a newer point (live trail growth) and drops points older than `keepFromMs`. Older points are ignored. */
|
|
115
|
+
export function appendPoint(track, point, keepFromMs) {
|
|
116
|
+
const last = track.points[track.points.length - 1];
|
|
117
|
+
const points = last && toMs(point.timestamp) <= toMs(last.timestamp) ? track.points : [...track.points, point];
|
|
118
|
+
return { ...track, points: points.filter((p) => toMs(p.timestamp) >= keepFromMs) };
|
|
119
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Annotation, Unsubscribe } from '../core/types.js';
|
|
2
|
+
import type { AnnotationProvider, AuthAware, AuthContext } from '../data/contracts.js';
|
|
3
|
+
/** In-memory annotation store for tests and demos; `others()` plays a second user making changes. */
|
|
4
|
+
export declare class MemoryAnnotationStore implements AnnotationProvider, AuthAware {
|
|
5
|
+
private readonly items;
|
|
6
|
+
private readonly listeners;
|
|
7
|
+
readonly saved: Annotation[];
|
|
8
|
+
auth: AuthContext | null;
|
|
9
|
+
failNextSave: Error | null;
|
|
10
|
+
constructor(initial?: Annotation[]);
|
|
11
|
+
setAuth(auth: AuthContext | null): void;
|
|
12
|
+
list(): Promise<Annotation[]>;
|
|
13
|
+
save(a: Annotation): Promise<void>;
|
|
14
|
+
remove(id: string): Promise<void>;
|
|
15
|
+
subscribe(onChange: (c: {
|
|
16
|
+
upserted?: Annotation[];
|
|
17
|
+
removed?: string[];
|
|
18
|
+
}) => void): Unsubscribe;
|
|
19
|
+
/** Another user's change arriving through the stream. */
|
|
20
|
+
others(change: {
|
|
21
|
+
upserted?: Annotation[];
|
|
22
|
+
removed?: string[];
|
|
23
|
+
}): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** In-memory annotation store for tests and demos; `others()` plays a second user making changes. */
|
|
2
|
+
export class MemoryAnnotationStore {
|
|
3
|
+
items = new Map();
|
|
4
|
+
listeners = new Set();
|
|
5
|
+
saved = [];
|
|
6
|
+
auth = null;
|
|
7
|
+
failNextSave = null;
|
|
8
|
+
constructor(initial = []) {
|
|
9
|
+
for (const a of initial)
|
|
10
|
+
this.items.set(a.id, structuredClone(a));
|
|
11
|
+
}
|
|
12
|
+
setAuth(auth) {
|
|
13
|
+
this.auth = auth;
|
|
14
|
+
}
|
|
15
|
+
async list() {
|
|
16
|
+
return [...this.items.values()].map((a) => structuredClone(a));
|
|
17
|
+
}
|
|
18
|
+
async save(a) {
|
|
19
|
+
if (this.failNextSave) {
|
|
20
|
+
const e = this.failNextSave;
|
|
21
|
+
this.failNextSave = null;
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
this.items.set(a.id, structuredClone(a));
|
|
25
|
+
this.saved.push(structuredClone(a));
|
|
26
|
+
}
|
|
27
|
+
async remove(id) {
|
|
28
|
+
this.items.delete(id);
|
|
29
|
+
}
|
|
30
|
+
subscribe(onChange) {
|
|
31
|
+
this.listeners.add(onChange);
|
|
32
|
+
return () => this.listeners.delete(onChange);
|
|
33
|
+
}
|
|
34
|
+
/** Another user's change arriving through the stream. */
|
|
35
|
+
others(change) {
|
|
36
|
+
for (const a of change.upserted ?? [])
|
|
37
|
+
this.items.set(a.id, structuredClone(a));
|
|
38
|
+
for (const id of change.removed ?? [])
|
|
39
|
+
this.items.delete(id);
|
|
40
|
+
for (const l of this.listeners)
|
|
41
|
+
l(structuredClone(change));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BoundingBox, TimeRange } from '../core/types.js';
|
|
2
|
+
import { type SpatialDataProvider } from '../data/contracts.js';
|
|
3
|
+
import type { Renderer } from '../render/renderer.js';
|
|
4
|
+
import type { SpatialIndex } from '../spatial/spatial-index.js';
|
|
5
|
+
export interface ContractCheck {
|
|
6
|
+
name: string;
|
|
7
|
+
run: () => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare class ContractViolation extends Error {
|
|
10
|
+
readonly name = "ContractViolation";
|
|
11
|
+
}
|
|
12
|
+
export interface ProviderContractOptions {
|
|
13
|
+
/** A fresh provider per check. It may also implement RealtimeProvider, TrackProvider and DatasetCatalog. */
|
|
14
|
+
create: () => SpatialDataProvider;
|
|
15
|
+
/** One object the provider is known to hold, and (optionally) its type and one of its property values. */
|
|
16
|
+
sample: {
|
|
17
|
+
objectId: string;
|
|
18
|
+
ontologyType?: string;
|
|
19
|
+
property?: {
|
|
20
|
+
name: string;
|
|
21
|
+
value: unknown;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/** Bounds that do NOT contain the sample — checked only for providers that claim spatial query support. */
|
|
25
|
+
outside?: BoundingBox;
|
|
26
|
+
/** A time range during which the sample has history — checked only for providers with getTracks. */
|
|
27
|
+
history?: TimeRange;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The SpatialDataProvider contract (doc §26, ADR-005). Providers may return MORE than a query asks for (the runtime
|
|
31
|
+
* re-checks every object), but never less: a matching object must not be left out.
|
|
32
|
+
*/
|
|
33
|
+
export declare function providerContract(o: ProviderContractOptions): ContractCheck[];
|
|
34
|
+
export interface RendererContractOptions {
|
|
35
|
+
create: () => Renderer;
|
|
36
|
+
container: () => HTMLElement;
|
|
37
|
+
}
|
|
38
|
+
/** The Renderer contract (doc §22, §65): bidirectional, incremental, and fully released by destroy(). */
|
|
39
|
+
export declare function rendererContract(o: RendererContractOptions): ContractCheck[];
|
|
40
|
+
/** The SpatialIndex contract (doc §11): any index (RBush today, others later) answers like a full scan. */
|
|
41
|
+
export declare function spatialIndexContract(create: () => SpatialIndex): ContractCheck[];
|