diff --git a/frontend/src/components/korea/AnalysisStatsPanel.tsx b/frontend/src/components/korea/AnalysisStatsPanel.tsx index 63b1508..dd8b27f 100644 --- a/frontend/src/components/korea/AnalysisStatsPanel.tsx +++ b/frontend/src/components/korea/AnalysisStatsPanel.tsx @@ -82,8 +82,11 @@ export function AnalysisStatsPanel({ stats, lastUpdated, isLoading, analysisMap, const gearStats = useMemo(() => { const source = allShips ?? ships; const gearPattern = /^(.+?)_\d+_\d+_?$/; + const STALE_MS = 60 * 60_000; // 60분 이내만 + const now = Date.now(); const parentMap = new Map(); for (const s of source) { + if (now - s.lastSeen > STALE_MS) continue; const m = (s.name || '').match(gearPattern); if (m) { const parent = m[1].trim(); diff --git a/frontend/src/components/korea/FleetClusterLayer.tsx b/frontend/src/components/korea/FleetClusterLayer.tsx index 2365f95..57fa7c2 100644 --- a/frontend/src/components/korea/FleetClusterLayer.tsx +++ b/frontend/src/components/korea/FleetClusterLayer.tsx @@ -127,6 +127,10 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect, // 비허가 어구 클러스터: parentName → { parent: Ship | null, gears: Ship[] } const gearGroupMap = useMemo(() => { const gearPattern = /^(.+?)_\d+_\d+_?$/; + const MAX_DIST_DEG = 0.15; // ~10NM — 모선과 어구 간 최대 거리 + const STALE_MS = 60 * 60_000; // 60분 이내 수신 신호만 + const now = Date.now(); + const nameToShip = new Map(); for (const s of ships) { const nm = (s.name || '').trim(); @@ -134,12 +138,25 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect, nameToShip.set(nm, s); } } + const map = new Map(); for (const s of ships) { + // 60분 이내 수신 신호만 + if (now - s.lastSeen > STALE_MS) continue; + const m = (s.name || '').match(gearPattern); if (!m) continue; const parentName = m[1].trim(); - const entry = map.get(parentName) ?? { parent: nameToShip.get(parentName) ?? null, gears: [] }; + const parent = nameToShip.get(parentName) ?? null; + + // 모선이 있으면 거리 제한 적용 + if (parent) { + const dlat = Math.abs(s.lat - parent.lat); + const dlng = Math.abs(s.lng - parent.lng); + if (dlat > MAX_DIST_DEG || dlng > MAX_DIST_DEG) continue; + } + + const entry = map.get(parentName) ?? { parent, gears: [] }; entry.gears.push(s); map.set(parentName, entry); }