- 대상선박 우클릭 컨텍스트 메뉴로 항적 조회 (6h~5d) - Mercator: PathLayer(고정) + TripsLayer(애니메이션) + ScatterplotLayer(포인트) - Globe: MapLibre 네이티브 line + arrow + circle 레이어 - rAF 직접 overlay 조작으로 React 재렌더링 방지 - SVG 아이콘 data URL 캐시로 네트워크 재요청 방지 - 항적 조회 시 자동 fitBounds (전체 항적 뷰포트 맞춤) - API 프록시 /api/ais-target/:mmsi/track 엔드포인트 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
33 lines
912 B
TypeScript
33 lines
912 B
TypeScript
import type { TrackResponse } from '../model/types';
|
|
|
|
const API_BASE = (import.meta.env.VITE_API_URL || '/snp-api').replace(/\/$/, '');
|
|
|
|
export async function fetchVesselTrack(
|
|
mmsi: number,
|
|
minutes: number,
|
|
signal?: AbortSignal,
|
|
): Promise<TrackResponse> {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
|
|
const combinedSignal = signal ?? controller.signal;
|
|
|
|
try {
|
|
const url = `${API_BASE}/api/ais-target/${mmsi}/track?minutes=${minutes}`;
|
|
const res = await fetch(url, {
|
|
signal: combinedSignal,
|
|
headers: { accept: 'application/json' },
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`Track API error ${res.status}: ${text.slice(0, 200)}`);
|
|
}
|
|
|
|
const json = (await res.json()) as TrackResponse;
|
|
return json;
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|