@aeroswarm/client 0.1.12 → 0.1.13
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 +35 -1
- package/dist/index-BXiTmKdS.d.ts +701 -0
- package/dist/index.d.ts +71 -688
- package/dist/index.js +317 -11
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +24 -2
- package/dist/react/index.js +355 -30
- package/dist/react/index.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -2
- package/dist/sdk/index.js +7 -6
- package/dist/sdk/index.js.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -144,12 +144,13 @@ var AeroWsClient = class {
|
|
|
144
144
|
this._requestSnapshot();
|
|
145
145
|
}
|
|
146
146
|
this.lastSeq = msg.seq;
|
|
147
|
+
const meta = { serverTime: msg.serverTime, seq: msg.seq };
|
|
147
148
|
switch (msg.type) {
|
|
148
149
|
case "snapshot":
|
|
149
|
-
this.callbacks.onSnapshot?.(msg.payload.tracks);
|
|
150
|
+
this.callbacks.onSnapshot?.(msg.payload.tracks, meta);
|
|
150
151
|
break;
|
|
151
152
|
case "track.updated":
|
|
152
|
-
this.callbacks.onTrackUpdate?.(msg.payload.track);
|
|
153
|
+
this.callbacks.onTrackUpdate?.(msg.payload.track, meta);
|
|
153
154
|
break;
|
|
154
155
|
case "track.removed":
|
|
155
156
|
this.callbacks.onTrackRemoved?.(msg.payload);
|
|
@@ -571,15 +572,15 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
571
572
|
this._status = s;
|
|
572
573
|
callbacks.onStatusChange?.(s);
|
|
573
574
|
},
|
|
574
|
-
onSnapshot: (tracks) => {
|
|
575
|
+
onSnapshot: (tracks, meta) => {
|
|
575
576
|
const labeled = this.cache.applySnapshot(tracks);
|
|
576
577
|
this._lastUpdateAt = Date.now();
|
|
577
|
-
callbacks.onSnapshot?.(labeled);
|
|
578
|
+
callbacks.onSnapshot?.(labeled, meta);
|
|
578
579
|
},
|
|
579
|
-
onTrackUpdate: (track) => {
|
|
580
|
+
onTrackUpdate: (track, meta) => {
|
|
580
581
|
const labeled = this.cache.applyUpdate(track);
|
|
581
582
|
this._lastUpdateAt = Date.now();
|
|
582
|
-
callbacks.onTrackUpdate?.(labeled);
|
|
583
|
+
callbacks.onTrackUpdate?.(labeled, meta);
|
|
583
584
|
},
|
|
584
585
|
onTrackRemoved: (ev) => {
|
|
585
586
|
this.cache.applyRemoved(ev);
|
|
@@ -604,7 +605,7 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
604
605
|
reconnect: () => this.wsClient.reconnect(),
|
|
605
606
|
stream: (opts) => {
|
|
606
607
|
const queue = [];
|
|
607
|
-
let
|
|
608
|
+
let resolve2 = null;
|
|
608
609
|
let done = false;
|
|
609
610
|
this.wsClient.connect({ encoding: "json", ...opts }, {
|
|
610
611
|
onSnapshot: (tracks) => {
|
|
@@ -612,9 +613,9 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
612
613
|
},
|
|
613
614
|
onTrackUpdate: (raw) => {
|
|
614
615
|
const t = this.cache.applyUpdate(raw);
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
616
|
+
if (resolve2) {
|
|
617
|
+
resolve2({ value: t, done: false });
|
|
618
|
+
resolve2 = null;
|
|
618
619
|
} else queue.push(t);
|
|
619
620
|
},
|
|
620
621
|
onStatusChange: (s) => {
|
|
@@ -627,7 +628,7 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
627
628
|
if (queue.length) return Promise.resolve({ value: queue.shift(), done: false });
|
|
628
629
|
if (done) return Promise.resolve({ value: void 0, done: true });
|
|
629
630
|
return new Promise((r) => {
|
|
630
|
-
|
|
631
|
+
resolve2 = r;
|
|
631
632
|
});
|
|
632
633
|
},
|
|
633
634
|
return: async () => {
|
|
@@ -820,8 +821,331 @@ function AeroSwarmProvider({
|
|
|
820
821
|
return /* @__PURE__ */ jsx(QueryClientProvider, { client: qc, children: /* @__PURE__ */ jsx(AeroSwarmContext.Provider, { value: { client }, children }) });
|
|
821
822
|
}
|
|
822
823
|
|
|
824
|
+
// src/react/hooks/use-track-motion.ts
|
|
825
|
+
import { useEffect, useMemo as useMemo2, useRef } from "react";
|
|
826
|
+
|
|
827
|
+
// src/shared/motion/index.ts
|
|
828
|
+
var KT_TO_MS = 0.514444;
|
|
829
|
+
var M_PER_DEG = 111320;
|
|
830
|
+
var MAX_PLAUSIBLE_KT = 1500;
|
|
831
|
+
var OFFSET_WINDOW = 20;
|
|
832
|
+
var ALT_EPSILON_FT = 10;
|
|
833
|
+
var HEADING_EPSILON_DEG = 0.5;
|
|
834
|
+
var SURFACE_ALT_FT = 150;
|
|
835
|
+
var clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
|
|
836
|
+
var smoothstep = (x) => {
|
|
837
|
+
const c = clamp01(x);
|
|
838
|
+
return c * c * (3 - 2 * c);
|
|
839
|
+
};
|
|
840
|
+
var wrapDeg = (d) => ((d + 180) % 360 + 360) % 360 - 180;
|
|
841
|
+
var norm360 = (d) => (d % 360 + 360) % 360;
|
|
842
|
+
var cosLat = (lat) => Math.max(0.01, Math.cos(lat * Math.PI / 180));
|
|
843
|
+
function validHeading(h) {
|
|
844
|
+
return h != null && Number.isFinite(h) && h >= 0 && h <= 360 ? h % 360 : null;
|
|
845
|
+
}
|
|
846
|
+
function move(lat, lon, bearingDeg, distM) {
|
|
847
|
+
const rad = bearingDeg * Math.PI / 180;
|
|
848
|
+
const dLat = distM * Math.cos(rad) / M_PER_DEG;
|
|
849
|
+
const dLon = distM * Math.sin(rad) / (M_PER_DEG * cosLat(lat + dLat / 2));
|
|
850
|
+
return { lat: lat + dLat, lon: lon + dLon };
|
|
851
|
+
}
|
|
852
|
+
function distanceM(aLat, aLon, bLat, bLon) {
|
|
853
|
+
const dy = (bLat - aLat) * M_PER_DEG;
|
|
854
|
+
const dx = (bLon - aLon) * M_PER_DEG * cosLat((aLat + bLat) / 2);
|
|
855
|
+
return Math.hypot(dx, dy);
|
|
856
|
+
}
|
|
857
|
+
var headingChanged = (a, b) => a === null || b === null ? a !== b : Math.abs(wrapDeg(a - b)) > HEADING_EPSILON_DEG;
|
|
858
|
+
var altitudeChanged = (a, b) => a === null || b === null ? a !== b : Math.abs(a - b) >= ALT_EPSILON_FT;
|
|
859
|
+
var defaultIsSurface = (t) => t.targetType === "vehicle" || t.altitudeFt != null && t.altitudeFt <= SURFACE_ALT_FT;
|
|
860
|
+
function resolve(o) {
|
|
861
|
+
return {
|
|
862
|
+
blendMs: o.blendMs ?? 1500,
|
|
863
|
+
headingSmoothMs: o.headingSmoothMs ?? 800,
|
|
864
|
+
maxExtrapolateMs: o.maxExtrapolateMs ?? { air: 2e4, surface: 8e3 },
|
|
865
|
+
minMovingKt: o.minMovingKt ?? 5,
|
|
866
|
+
snapDistanceM: o.snapDistanceM ?? 5e3,
|
|
867
|
+
frameHz: o.frameHz ?? 30,
|
|
868
|
+
epsilonM: o.epsilonM ?? 0.5,
|
|
869
|
+
extrapolate: o.extrapolate ?? true,
|
|
870
|
+
isSurface: o.isSurface ?? defaultIsSurface,
|
|
871
|
+
clock: o.clock ?? Date.now,
|
|
872
|
+
scheduler: o.scheduler
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function defaultScheduler() {
|
|
876
|
+
const g = globalThis;
|
|
877
|
+
if (typeof g.requestAnimationFrame === "function" && typeof g.cancelAnimationFrame === "function") {
|
|
878
|
+
return {
|
|
879
|
+
request: (cb) => g.requestAnimationFrame(cb),
|
|
880
|
+
cancel: (h) => g.cancelAnimationFrame(h)
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
request: (cb) => setTimeout(() => cb(Date.now()), 16),
|
|
885
|
+
cancel: (h) => clearTimeout(h)
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
var Motion = class {
|
|
889
|
+
o;
|
|
890
|
+
entries = /* @__PURE__ */ new Map();
|
|
891
|
+
/** Tracks whose picture can still change. Everything else costs nothing per frame. */
|
|
892
|
+
active = /* @__PURE__ */ new Set();
|
|
893
|
+
listeners = /* @__PURE__ */ new Set();
|
|
894
|
+
offsets = [];
|
|
895
|
+
offset = 0;
|
|
896
|
+
handle = null;
|
|
897
|
+
lastFrameAt = Number.NEGATIVE_INFINITY;
|
|
898
|
+
constructor(options) {
|
|
899
|
+
this.o = resolve(options);
|
|
900
|
+
}
|
|
901
|
+
get size() {
|
|
902
|
+
return this.entries.size;
|
|
903
|
+
}
|
|
904
|
+
offsetMs() {
|
|
905
|
+
return this.offset;
|
|
906
|
+
}
|
|
907
|
+
// ── time ────────────────────────────────────────────────────────────────
|
|
908
|
+
/** Browser clock + learned offset = the server's clock. */
|
|
909
|
+
serverNow(at) {
|
|
910
|
+
return (at ?? this.o.clock()) + this.offset;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* `serverTime - browserNow` is the true offset MINUS the network delay, so the
|
|
914
|
+
* largest recent value is the one with the least delay, i.e. the best estimate.
|
|
915
|
+
*/
|
|
916
|
+
observe(serverTime) {
|
|
917
|
+
if (!Number.isFinite(serverTime)) return;
|
|
918
|
+
this.offsets.push(serverTime - this.o.clock());
|
|
919
|
+
if (this.offsets.length > OFFSET_WINDOW) this.offsets.shift();
|
|
920
|
+
this.offset = Math.max(...this.offsets);
|
|
921
|
+
}
|
|
922
|
+
cap(e) {
|
|
923
|
+
const m = this.o.maxExtrapolateMs;
|
|
924
|
+
return e.surface ? m.surface : m.air;
|
|
925
|
+
}
|
|
926
|
+
// ── model ───────────────────────────────────────────────────────────────
|
|
927
|
+
posAt(e, s) {
|
|
928
|
+
const cap = this.cap(e);
|
|
929
|
+
let elapsed = Math.min(Math.max(0, s - e.fixAt), cap);
|
|
930
|
+
if (e.frozen !== null) elapsed = Math.min(elapsed, e.frozen);
|
|
931
|
+
if (!this.o.extrapolate || e.course === null || e.speedMs <= 0) {
|
|
932
|
+
return { lat: e.fixLat, lon: e.fixLon, extrapolating: false };
|
|
933
|
+
}
|
|
934
|
+
const p = move(e.fixLat, e.fixLon, e.course, e.speedMs * elapsed / 1e3);
|
|
935
|
+
return { ...p, extrapolating: e.frozen === null && elapsed < cap };
|
|
936
|
+
}
|
|
937
|
+
sampleAt(e, s) {
|
|
938
|
+
const p = this.posAt(e, s);
|
|
939
|
+
const rest = 1 - smoothstep((s - e.blendStart) / this.o.blendMs);
|
|
940
|
+
let headingDeg = null;
|
|
941
|
+
if (e.hdgTo !== null) {
|
|
942
|
+
const from = e.hdgFrom ?? e.hdgTo;
|
|
943
|
+
const left = 1 - smoothstep((s - e.hdgStart) / this.o.headingSmoothMs);
|
|
944
|
+
headingDeg = norm360(e.hdgTo + wrapDeg(from - e.hdgTo) * left);
|
|
945
|
+
}
|
|
946
|
+
let altitudeFt = null;
|
|
947
|
+
if (e.altTo !== null) {
|
|
948
|
+
const k = smoothstep((s - e.altStart) / this.o.blendMs);
|
|
949
|
+
altitudeFt = e.altFrom === null ? e.altTo : e.altFrom + (e.altTo - e.altFrom) * k;
|
|
950
|
+
}
|
|
951
|
+
return {
|
|
952
|
+
lat: p.lat + e.errLat * rest,
|
|
953
|
+
lon: p.lon + e.errLon * rest,
|
|
954
|
+
headingDeg,
|
|
955
|
+
altitudeFt,
|
|
956
|
+
moving: e.speedMs > 0,
|
|
957
|
+
extrapolating: p.extrapolating
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
// ── input ───────────────────────────────────────────────────────────────
|
|
961
|
+
update(t, meta) {
|
|
962
|
+
if (meta?.serverTime !== void 0) this.observe(meta.serverTime);
|
|
963
|
+
if (t.lat == null || t.lon == null || !Number.isFinite(t.lat) || !Number.isFinite(t.lon)) {
|
|
964
|
+
this.remove(t.trackId);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const s = this.serverNow();
|
|
968
|
+
const e = this.entries.get(t.trackId);
|
|
969
|
+
if (!e) {
|
|
970
|
+
this.create(t, s);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
this.refresh(e, t, s);
|
|
974
|
+
}
|
|
975
|
+
moving(t, course) {
|
|
976
|
+
const kt = t.groundSpeedKt;
|
|
977
|
+
return course !== null && kt != null && Number.isFinite(kt) && kt >= this.o.minMovingKt && kt <= MAX_PLAUSIBLE_KT;
|
|
978
|
+
}
|
|
979
|
+
create(t, s) {
|
|
980
|
+
const course = validHeading(t.headingDeg);
|
|
981
|
+
const moving = this.moving(t, course);
|
|
982
|
+
const e = {
|
|
983
|
+
id: t.trackId,
|
|
984
|
+
fixLat: t.lat,
|
|
985
|
+
fixLon: t.lon,
|
|
986
|
+
fixAt: t.lastSeenAt,
|
|
987
|
+
speedMs: moving ? t.groundSpeedKt * KT_TO_MS : 0,
|
|
988
|
+
course,
|
|
989
|
+
surface: this.o.isSurface(t),
|
|
990
|
+
frozen: null,
|
|
991
|
+
errLat: 0,
|
|
992
|
+
errLon: 0,
|
|
993
|
+
blendStart: s,
|
|
994
|
+
hdgFrom: course,
|
|
995
|
+
hdgTo: course,
|
|
996
|
+
hdgStart: s,
|
|
997
|
+
altFrom: null,
|
|
998
|
+
altTo: t.altitudeFt,
|
|
999
|
+
altStart: s,
|
|
1000
|
+
settleAt: 0,
|
|
1001
|
+
emitted: null
|
|
1002
|
+
};
|
|
1003
|
+
if (t.status === "lost") e.frozen = this.frozenAt(e, s);
|
|
1004
|
+
this.entries.set(e.id, e);
|
|
1005
|
+
this.settle(e);
|
|
1006
|
+
this.active.add(e.id);
|
|
1007
|
+
this.ensureLoop();
|
|
1008
|
+
}
|
|
1009
|
+
frozenAt(e, s) {
|
|
1010
|
+
return Math.min(Math.max(0, s - e.fixAt), this.cap(e));
|
|
1011
|
+
}
|
|
1012
|
+
refresh(e, t, s) {
|
|
1013
|
+
e.surface = this.o.isSurface(t);
|
|
1014
|
+
if (t.status === "lost") e.frozen ??= this.frozenAt(e, s);
|
|
1015
|
+
else e.frozen = null;
|
|
1016
|
+
const lat = t.lat;
|
|
1017
|
+
const lon = t.lon;
|
|
1018
|
+
const older = t.lastSeenAt < e.fixAt;
|
|
1019
|
+
const same = t.lastSeenAt === e.fixAt && lat === e.fixLat && lon === e.fixLon;
|
|
1020
|
+
if (older || same) {
|
|
1021
|
+
this.settle(e);
|
|
1022
|
+
this.touch(e);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
const shown = this.sampleAt(e, s);
|
|
1026
|
+
const course = validHeading(t.headingDeg) ?? e.course;
|
|
1027
|
+
const moving = this.moving(t, course);
|
|
1028
|
+
e.fixLat = lat;
|
|
1029
|
+
e.fixLon = lon;
|
|
1030
|
+
e.fixAt = t.lastSeenAt;
|
|
1031
|
+
e.speedMs = moving ? t.groundSpeedKt * KT_TO_MS : 0;
|
|
1032
|
+
e.course = course;
|
|
1033
|
+
const predicted = this.posAt(e, s);
|
|
1034
|
+
const jump = distanceM(shown.lat, shown.lon, predicted.lat, predicted.lon);
|
|
1035
|
+
const snap = jump > this.o.snapDistanceM;
|
|
1036
|
+
e.errLat = snap ? 0 : shown.lat - predicted.lat;
|
|
1037
|
+
e.errLon = snap ? 0 : shown.lon - predicted.lon;
|
|
1038
|
+
e.blendStart = s;
|
|
1039
|
+
e.hdgFrom = snap ? course : shown.headingDeg ?? course;
|
|
1040
|
+
e.hdgTo = course;
|
|
1041
|
+
e.hdgStart = s;
|
|
1042
|
+
e.altFrom = snap ? t.altitudeFt : shown.altitudeFt;
|
|
1043
|
+
e.altTo = t.altitudeFt;
|
|
1044
|
+
e.altStart = s;
|
|
1045
|
+
this.settle(e);
|
|
1046
|
+
this.touch(e);
|
|
1047
|
+
}
|
|
1048
|
+
/** The server time after which nothing about this track changes on its own. */
|
|
1049
|
+
settle(e) {
|
|
1050
|
+
const ends = [e.blendStart + this.o.blendMs, e.hdgStart + this.o.headingSmoothMs, e.altStart + this.o.blendMs];
|
|
1051
|
+
if (this.o.extrapolate && e.speedMs > 0 && e.frozen === null) ends.push(e.fixAt + this.cap(e));
|
|
1052
|
+
e.settleAt = Math.max(...ends);
|
|
1053
|
+
}
|
|
1054
|
+
touch(e) {
|
|
1055
|
+
this.active.add(e.id);
|
|
1056
|
+
this.ensureLoop();
|
|
1057
|
+
}
|
|
1058
|
+
remove(trackId) {
|
|
1059
|
+
this.entries.delete(trackId);
|
|
1060
|
+
this.active.delete(trackId);
|
|
1061
|
+
}
|
|
1062
|
+
clear() {
|
|
1063
|
+
this.entries.clear();
|
|
1064
|
+
this.active.clear();
|
|
1065
|
+
}
|
|
1066
|
+
// ── output ──────────────────────────────────────────────────────────────
|
|
1067
|
+
sample(trackId, at) {
|
|
1068
|
+
const e = this.entries.get(trackId);
|
|
1069
|
+
return e ? this.sampleAt(e, this.serverNow(at)) : void 0;
|
|
1070
|
+
}
|
|
1071
|
+
differs(a, b) {
|
|
1072
|
+
if (a === null || a.moving !== b.moving || a.extrapolating !== b.extrapolating) return true;
|
|
1073
|
+
if (distanceM(a.lat, a.lon, b.lat, b.lon) > this.o.epsilonM) return true;
|
|
1074
|
+
return headingChanged(a.headingDeg, b.headingDeg) || altitudeChanged(a.altitudeFt, b.altitudeFt);
|
|
1075
|
+
}
|
|
1076
|
+
tick(at) {
|
|
1077
|
+
const s = this.serverNow(at);
|
|
1078
|
+
const changed = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const id of [...this.active]) {
|
|
1080
|
+
const e = this.entries.get(id);
|
|
1081
|
+
if (!e) {
|
|
1082
|
+
this.active.delete(id);
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const sample = this.sampleAt(e, s);
|
|
1086
|
+
if (this.differs(e.emitted, sample)) {
|
|
1087
|
+
e.emitted = sample;
|
|
1088
|
+
changed.set(id, sample);
|
|
1089
|
+
}
|
|
1090
|
+
if (s >= e.settleAt) this.active.delete(id);
|
|
1091
|
+
}
|
|
1092
|
+
return changed;
|
|
1093
|
+
}
|
|
1094
|
+
// ── frame loop ──────────────────────────────────────────────────────────
|
|
1095
|
+
start(onFrame) {
|
|
1096
|
+
this.listeners.add(onFrame);
|
|
1097
|
+
this.ensureLoop();
|
|
1098
|
+
return () => {
|
|
1099
|
+
this.listeners.delete(onFrame);
|
|
1100
|
+
if (this.listeners.size === 0) this.cancelLoop();
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
ensureLoop() {
|
|
1104
|
+
if (this.handle !== null || this.listeners.size === 0 || this.active.size === 0) return;
|
|
1105
|
+
const scheduler = this.o.scheduler ?? (this.o.scheduler = defaultScheduler());
|
|
1106
|
+
this.handle = scheduler.request((ts) => this.frame(ts));
|
|
1107
|
+
}
|
|
1108
|
+
cancelLoop() {
|
|
1109
|
+
if (this.handle === null) return;
|
|
1110
|
+
(this.o.scheduler ?? defaultScheduler()).cancel(this.handle);
|
|
1111
|
+
this.handle = null;
|
|
1112
|
+
}
|
|
1113
|
+
frame(ts) {
|
|
1114
|
+
this.handle = null;
|
|
1115
|
+
if (this.listeners.size === 0) return;
|
|
1116
|
+
if (ts - this.lastFrameAt >= 1e3 / this.o.frameHz - 1) {
|
|
1117
|
+
this.lastFrameAt = ts;
|
|
1118
|
+
const changed = this.tick();
|
|
1119
|
+
if (changed.size > 0) {
|
|
1120
|
+
const frame = { at: this.serverNow(), changed };
|
|
1121
|
+
for (const fn of [...this.listeners]) fn(frame);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
this.ensureLoop();
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
function createTrackMotion(options = {}) {
|
|
1128
|
+
return new Motion(options);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/react/hooks/use-track-motion.ts
|
|
1132
|
+
function useTrackMotion(options) {
|
|
1133
|
+
const { onFrame, ...motionOptions } = options;
|
|
1134
|
+
const onFrameRef = useRef(onFrame);
|
|
1135
|
+
onFrameRef.current = onFrame;
|
|
1136
|
+
const motion = useMemo2(() => createTrackMotion(motionOptions), []);
|
|
1137
|
+
useEffect(() => {
|
|
1138
|
+
const stop = motion.start((frame) => onFrameRef.current(frame));
|
|
1139
|
+
return () => {
|
|
1140
|
+
stop();
|
|
1141
|
+
motion.clear();
|
|
1142
|
+
};
|
|
1143
|
+
}, [motion]);
|
|
1144
|
+
return motion;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
823
1147
|
// src/react/hooks/use-track-history.ts
|
|
824
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
1148
|
+
import { useCallback, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
825
1149
|
var DR_MIN_SPEED_KT = 10;
|
|
826
1150
|
var MAX_SPEED_KT = 1200;
|
|
827
1151
|
var GAP_RESET_MS = 3e4;
|
|
@@ -841,22 +1165,22 @@ function useTrackHistory(options) {
|
|
|
841
1165
|
maxJumpDeg = 5,
|
|
842
1166
|
onFrame
|
|
843
1167
|
} = options;
|
|
844
|
-
const currentPositions =
|
|
845
|
-
const lastActualPos =
|
|
846
|
-
const history =
|
|
847
|
-
const lastSeen =
|
|
848
|
-
const lastUpdPerf =
|
|
849
|
-
const interp =
|
|
850
|
-
const velocity =
|
|
851
|
-
const drState =
|
|
852
|
-
const rafRef =
|
|
853
|
-
const onFrameRef =
|
|
1168
|
+
const currentPositions = useRef2(/* @__PURE__ */ new Map());
|
|
1169
|
+
const lastActualPos = useRef2(/* @__PURE__ */ new Map());
|
|
1170
|
+
const history = useRef2(/* @__PURE__ */ new Map());
|
|
1171
|
+
const lastSeen = useRef2(/* @__PURE__ */ new Map());
|
|
1172
|
+
const lastUpdPerf = useRef2(/* @__PURE__ */ new Map());
|
|
1173
|
+
const interp = useRef2(/* @__PURE__ */ new Map());
|
|
1174
|
+
const velocity = useRef2(/* @__PURE__ */ new Map());
|
|
1175
|
+
const drState = useRef2(/* @__PURE__ */ new Map());
|
|
1176
|
+
const rafRef = useRef2(null);
|
|
1177
|
+
const onFrameRef = useRef2(onFrame);
|
|
854
1178
|
onFrameRef.current = onFrame;
|
|
855
|
-
const maxHistoryPointsRef =
|
|
1179
|
+
const maxHistoryPointsRef = useRef2(maxHistoryPoints);
|
|
856
1180
|
maxHistoryPointsRef.current = maxHistoryPoints;
|
|
857
|
-
const minDeltaDegRef =
|
|
1181
|
+
const minDeltaDegRef = useRef2(minDeltaDeg);
|
|
858
1182
|
minDeltaDegRef.current = minDeltaDeg;
|
|
859
|
-
const maxJumpDegRef =
|
|
1183
|
+
const maxJumpDegRef = useRef2(maxJumpDeg);
|
|
860
1184
|
maxJumpDegRef.current = maxJumpDeg;
|
|
861
1185
|
function emitFrame() {
|
|
862
1186
|
onFrameRef.current({
|
|
@@ -1019,7 +1343,7 @@ function useTrackHistory(options) {
|
|
|
1019
1343
|
drState.current.delete(trackId);
|
|
1020
1344
|
emitFrame();
|
|
1021
1345
|
}, []);
|
|
1022
|
-
|
|
1346
|
+
useEffect2(() => {
|
|
1023
1347
|
return () => {
|
|
1024
1348
|
if (rafRef.current !== null) {
|
|
1025
1349
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -1034,7 +1358,7 @@ function useTrackHistory(options) {
|
|
|
1034
1358
|
}
|
|
1035
1359
|
|
|
1036
1360
|
// src/react/hooks/index.ts
|
|
1037
|
-
import { useCallback as useCallback2, useEffect as
|
|
1361
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
1038
1362
|
import { useQuery } from "@tanstack/react-query";
|
|
1039
1363
|
import { useShallow } from "zustand/react/shallow";
|
|
1040
1364
|
|
|
@@ -1134,7 +1458,7 @@ function useAeroTracks(opts = {}) {
|
|
|
1134
1458
|
const isDegraded = useTrackStore(selectIsDegraded);
|
|
1135
1459
|
const lastUpdateAt = useTrackStore(selectLastUpdateAt);
|
|
1136
1460
|
const error = useTrackStore(selectError);
|
|
1137
|
-
const unsubRef =
|
|
1461
|
+
const unsubRef = useRef3(null);
|
|
1138
1462
|
const { isLoading } = useQuery({
|
|
1139
1463
|
queryKey: ["aero-snapshot", opts.bbox?.join(","), opts.source, opts.minConfidence],
|
|
1140
1464
|
enabled: opts.enabled !== false,
|
|
@@ -1148,7 +1472,7 @@ function useAeroTracks(opts = {}) {
|
|
|
1148
1472
|
return result;
|
|
1149
1473
|
}
|
|
1150
1474
|
});
|
|
1151
|
-
|
|
1475
|
+
useEffect3(() => {
|
|
1152
1476
|
if (opts.enabled === false) return;
|
|
1153
1477
|
const actions = useTrackStore.getState();
|
|
1154
1478
|
unsubRef.current = client.tracks.subscribe(
|
|
@@ -1190,7 +1514,7 @@ function useAeroTracks(opts = {}) {
|
|
|
1190
1514
|
}
|
|
1191
1515
|
var useTracks = useAeroTracks;
|
|
1192
1516
|
function useAeroTrack(trackId) {
|
|
1193
|
-
const track = useTrackStore(
|
|
1517
|
+
const track = useTrackStore(useMemo3(() => selectTrack(trackId), [trackId]));
|
|
1194
1518
|
return { track };
|
|
1195
1519
|
}
|
|
1196
1520
|
function useAeroSnapshot(opts = {}) {
|
|
@@ -1283,6 +1607,7 @@ export {
|
|
|
1283
1607
|
useSourceHealth,
|
|
1284
1608
|
useTrackCount,
|
|
1285
1609
|
useTrackHistory,
|
|
1610
|
+
useTrackMotion,
|
|
1286
1611
|
useTrackStore,
|
|
1287
1612
|
useTracks,
|
|
1288
1613
|
useWebhooks
|