import { fetchVesselTrack } from '../../../entities/vesselTrack/api/fetchTrack'; import { convertLegacyTrackPointsToProcessedTrack } from '../lib/adapters'; import type { ProcessedTrack } from '../model/track.types'; type QueryTrackByMmsiParams = { mmsi: number; minutes: number; shipNameHint?: string; shipKindCodeHint?: string; nationalCodeHint?: string; }; type V2TrackResponse = { vesselId?: string; targetId?: string; sigSrcCd?: string; shipName?: string; shipKindCode?: string; nationalCode?: string; geometry?: [number, number][]; timestamps?: Array; speeds?: number[]; totalDistance?: number; avgSpeed?: number; maxSpeed?: number; pointCount?: number; }; function normalizeTimestampMs(value: string | number): number { if (typeof value === 'number') return value < 1e12 ? value * 1000 : value; if (/^\d{10,}$/.test(value)) { const asNum = Number(value); return asNum < 1e12 ? asNum * 1000 : asNum; } const parsed = new Date(value).getTime(); return Number.isFinite(parsed) ? parsed : 0; } function toFiniteNumber(value: unknown): number | null { if (typeof value === 'number') return Number.isFinite(value) ? value : null; if (typeof value === 'string') { const parsed = Number(value.trim()); return Number.isFinite(parsed) ? parsed : null; } return null; } function convertV2Tracks(rows: V2TrackResponse[]): ProcessedTrack[] { const out: ProcessedTrack[] = []; for (const row of rows) { if (!row.geometry || row.geometry.length === 0) continue; const timestamps = Array.isArray(row.timestamps) ? row.timestamps : []; const timestampsMs = timestamps.map((ts) => normalizeTimestampMs(ts)); const sortedIndices = timestampsMs .map((_, idx) => idx) .sort((a, b) => timestampsMs[a] - timestampsMs[b]); const geometry: [number, number][] = []; const sortedTimes: number[] = []; const speeds: number[] = []; for (const idx of sortedIndices) { const coord = row.geometry?.[idx]; if (!Array.isArray(coord) || coord.length !== 2) continue; const nLon = toFiniteNumber(coord[0]); const nLat = toFiniteNumber(coord[1]); if (nLon == null || nLat == null) continue; geometry.push([nLon, nLat]); sortedTimes.push(timestampsMs[idx]); speeds.push(toFiniteNumber(row.speeds?.[idx]) ?? 0); } if (geometry.length === 0) continue; const targetId = row.targetId || row.vesselId || ''; const sigSrcCd = row.sigSrcCd || '000001'; out.push({ vesselId: row.vesselId || `${sigSrcCd}_${targetId}`, targetId, sigSrcCd, shipName: (row.shipName || '').trim() || targetId, shipKindCode: row.shipKindCode || '000027', nationalCode: row.nationalCode || '', geometry, timestampsMs: sortedTimes, speeds, stats: { totalDistanceNm: row.totalDistance || 0, avgSpeed: row.avgSpeed || 0, maxSpeed: row.maxSpeed || 0, pointCount: row.pointCount || geometry.length, }, }); } return out; } async function queryLegacyTrack(params: QueryTrackByMmsiParams): Promise { const response = await fetchVesselTrack(params.mmsi, params.minutes); if (!response.success || response.data.length === 0) return []; const converted = convertLegacyTrackPointsToProcessedTrack(params.mmsi, response.data, { shipName: params.shipNameHint, shipKindCode: params.shipKindCodeHint, nationalCode: params.nationalCodeHint, }); return converted ? [converted] : []; } async function queryV2Track(params: QueryTrackByMmsiParams): Promise { const base = (import.meta.env.VITE_TRACK_V2_BASE_URL || '').trim(); if (!base) { return queryLegacyTrack(params); } const end = new Date(); const start = new Date(end.getTime() - params.minutes * 60_000); const requestBody = { startTime: start.toISOString().slice(0, 19), endTime: end.toISOString().slice(0, 19), vessels: [{ sigSrcCd: '000001', targetId: String(params.mmsi) }], isIntegration: '0', }; const endpoint = `${base.replace(/\/$/, '')}/api/v2/tracks/vessels`; const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', accept: 'application/json' }, body: JSON.stringify(requestBody), }); if (!res.ok) { return queryLegacyTrack(params); } const json = (await res.json()) as unknown; const rows = Array.isArray(json) ? (json as V2TrackResponse[]) : Array.isArray((json as { data?: unknown }).data) ? ((json as { data: V2TrackResponse[] }).data) : []; const converted = convertV2Tracks(rows); if (converted.length > 0) return converted; return queryLegacyTrack(params); } export async function queryTrackByMmsi(params: QueryTrackByMmsiParams): Promise { const mode = String(import.meta.env.VITE_TRACK_SOURCE_MODE || 'legacy').toLowerCase(); if (mode === 'v2') { return queryV2Track(params); } return queryLegacyTrack(params); }