Separate vessel records and acquisition from scene resources

This commit is contained in:
Sameh Khamis committed 2026-09-14 16:37:41 -07:00
1 parent 852008df4d
commit bdc026e50a
26 files changed
+877 -508

No files matched your search

+43 -31
View File
@@ -27,8 +27,8 @@ export function createQueries({
function aisRetryInSec() {
// A rejected key is terminal until someone changes it; an hour-long
// countdown would imply waiting is the fix.
if (state.transportStatus === 'auth-failed') return 0;
const at = Number(state.nextAttemptAt);
if (state.feed.transportStatus === 'auth-failed') return 0;
const at = Number(state.feed.nextAttemptAt);
if (!Number.isFinite(at) || at <= 0) return 0;
return Math.max(0, Math.ceil((at - vesselState._aisRuntime.now()) / 1000));
}
@@ -142,7 +142,7 @@ export function createQueries({
* fields are null, never NaN/undefined. navStatus is always null: the
* /api/ais-live proxy does not surface AIS NavigationalStatus, so it
* cannot be derived client-side.
* @param {Object|null|undefined} record - `state.vesselMap`/`state.vesselRecords` entry.
* @param {Object|null|undefined} record - `state.records.byMmsi`/`state.records.all` entry.
* @returns {{id: string|null, mmsi: string|null, name: string|null,
* lat: number|null, lon: number|null, speedKts: number|null,
* courseDeg: number|null, shipType: string|null, destination: string|null,
@@ -248,14 +248,14 @@ export function createQueries({
*/
findByQuery(query) {
if (query === null || query === undefined) return null;
const records = state.vesselRecords;
const records = state.records.all;
if (!Array.isArray(records) || !records.length) return null;
const q = String(query).trim();
if (!q) return null;
let record = null;
if (/^\d+$/.test(q)) {
record = state.vesselMap.get(q) || null;
record = state.records.byMmsi.get(q) || null;
}
if (!record) {
const lower = q.toLowerCase();
@@ -268,7 +268,9 @@ export function createQueries({
}
if (!record) return null;
const position = record.billboard?.position || record.position;
const position =
components.rendering.getVisual(record).billboard?.position ||
components.rendering.getVisual(record).position;
if (!position) return null;
return {
mmsi: record.mmsi,
@@ -290,7 +292,7 @@ export function createQueries({
* @returns {Array<{ mmsi: string, name: string, position: Cesium.Cartesian3, distanceM: number }>}
*/
getNearby(centerCartesian, rangeM, maxCount = 25) {
const records = state.vesselRecords;
const records = state.records.all;
if (!centerCartesian || !Array.isArray(records) || !records.length)
return [];
const range = Number.isFinite(rangeM) && rangeM > 0 ? rangeM : Infinity;
@@ -299,9 +301,10 @@ export function createQueries({
const entries = [];
for (const record of records) {
const visual = components.rendering.getVisual(record);
if (!Number.isFinite(record.lat) || !Number.isFinite(record.lon))
continue;
const position = record.billboard?.position || record.position;
const position = visual.billboard?.position || visual.position;
if (!position) continue;
const distanceM = Cesium.Cartesian3.distance(centerCartesian, position);
if (!Number.isFinite(distanceM) || distanceM > range) continue;
@@ -333,22 +336,27 @@ export function createQueries({
* holds no data and therefore cannot answer.
*/
hasContact(mmsi) {
if (!state.enabled || !state.vesselMap || state.vesselMap.size === 0)
if (
!state.feed.enabled ||
!state.records.byMmsi ||
state.records.byMmsi.size === 0
)
return null;
if (!mmsi) return false;
return state.vesselMap.has(String(mmsi).trim());
return state.records.byMmsi.has(String(mmsi).trim());
},
getAllPositions(maxCount = 800) {
const result = [];
const records = state.vesselRecords;
const records = state.records.all;
if (!Array.isArray(records)) return result;
const cap =
Number.isFinite(maxCount) && maxCount > 0 ? Math.floor(maxCount) : 800;
for (const record of records) {
const visual = components.rendering.getVisual(record);
if (result.length >= cap) break;
const position = record.billboard?.position || record.position;
const position = visual.billboard?.position || visual.position;
if (!position) continue;
result.push({
id: record.mmsi,
@@ -370,8 +378,8 @@ export function createQueries({
* @returns {Array<Object>} See mapAnalystRecord for the record shape.
*/
getAnalystRecords(maxCount = 2000) {
if (!state.enabled) return [];
const records = state.vesselRecords;
if (!state.feed.enabled) return [];
const records = state.records.all;
if (!Array.isArray(records) || !records.length) return [];
const limit = Number.isFinite(maxCount)
? Math.max(1, Math.floor(maxCount))
@@ -394,7 +402,7 @@ export function createQueries({
if (mmsi === null || mmsi === undefined) return false;
const target = String(mmsi).trim();
if (!target) return false;
const record = state.vesselMap.get(target);
const record = state.records.byMmsi.get(target);
if (!record) return false;
components.selection.selectVessel(record);
return true;
@@ -438,12 +446,12 @@ export function createQueries({
*/
getDetectableObjects(options = {}) {
if (
!state.enabled ||
!state.feed.enabled ||
!state.billboardCollection ||
!state.billboardCollection.show
)
return [];
const records = state.vesselRecords;
const records = state.records.all;
if (!Array.isArray(records) || !records.length) return [];
const maxCount = Number.isFinite(options.maxCount)
@@ -459,8 +467,9 @@ export function createQueries({
for (let idx = 0; idx < records.length; idx += 1) {
if ((idx - start) % stride !== 0) continue;
const record = records[idx];
if (record.billboard && !record.billboard.show) continue;
const position = record.billboard?.position || record.position;
const visual = components.rendering.getVisual(record);
if (visual.billboard && !visual.billboard.show) continue;
const position = visual.billboard?.position || visual.position;
if (!position) continue;
result.push({
position,
@@ -488,22 +497,25 @@ export function createQueries({
: {}),
getStats() {
const waitingForFirstPosition = state.firstConnectPhase === 'loading';
const waitingForFirstPosition =
state.feed.firstConnectPhase === 'loading';
return {
count: state.count,
lastUpdate: state.lastUpdate,
loading: state.loading || waitingForFirstPosition,
count: state.feed.count,
lastUpdate: state.feed.lastUpdate,
loading: state.feed.loading || waitingForFirstPosition,
loadingLabel: waitingForFirstPosition
? AIS_FIRST_CONNECT_LABEL
: state.loadingLabel,
error: state.error,
stale: state.stale,
: state.feed.loadingLabel,
error: state.feed.error,
stale: state.feed.stale,
status:
state.firstConnectPhase === 'unavailable' ? 'unavailable' : undefined,
transportStatus: state.transportStatus,
lastMessageAt: state.lastMessageAt,
rawRowCount: state.rawRowCount,
acceptedRowCount: state.acceptedRowCount,
state.feed.firstConnectPhase === 'unavailable'
? 'unavailable'
: undefined,
transportStatus: state.feed.transportStatus,
lastMessageAt: state.feed.lastMessageAt,
rawRowCount: state.feed.rawRowCount,
acceptedRowCount: state.feed.acceptedRowCount,
// Same chip affordance the flights layer uses: when the server is
// backing off, say how long until the next attempt instead of leaving
// the user to guess whether anything is still happening.