diff --git a/frontend/src/components/korea/ChineseFishingOverlay.tsx b/frontend/src/components/korea/ChineseFishingOverlay.tsx index 6131f3b..64eb937 100644 --- a/frontend/src/components/korea/ChineseFishingOverlay.tsx +++ b/frontend/src/components/korea/ChineseFishingOverlay.tsx @@ -100,47 +100,44 @@ export function ChineseFishingOverlay({ ships }: Props) { })); }, [chineseFishing]); - // 조업 중인 선박만 (어구 아이콘 표시용) - const operating = useMemo(() => analyzed.filter(a => a.analysis.isOperating), [analyzed]); + // 조업 중인 선박만 (어구 아이콘 표시용, 최대 100척) + const operating = useMemo(() => analyzed.filter(a => a.analysis.isOperating).slice(0, 100), [analyzed]); - // 어구/어망 → 모선 연결 탐지 + // 어구/어망 → 모선 연결 탐지 (거리 제한 + 정확 매칭 우선) const gearLinks: GearToParentLink[] = useMemo(() => { - // 어구/어망 선박 (이름_숫자_ 또는 이름% 패턴) const gearPattern = /^.+_\d+_\d*$|%$/; const gearShips = ships.filter(s => gearPattern.test(s.name)); if (gearShips.length === 0) return []; - // 모선 후보 (모든 선박의 이름 → Ship 매핑) + // 모선 후보 const nameMap = new Map(); for (const s of ships) { if (!gearPattern.test(s.name) && s.name) { - // 정확한 이름 매핑 nameMap.set(s.name.trim(), s); } } + const MAX_DIST_DEG = 0.15; // ~10NM — 이 이상 떨어지면 연결 안 함 + const MAX_LINKS = 200; // 브라우저 성능 보호 + const links: GearToParentLink[] = []; for (const gear of gearShips) { + if (links.length >= MAX_LINKS) break; + const parentName = extractParentName(gear.name); - if (!parentName) continue; + if (!parentName || parentName.length < 3) continue; // 너무 짧은 이름 제외 - // 정확히 일치하는 모선 찾기 - let parent = nameMap.get(parentName); + // 정확 매칭만 (부분 매칭 제거 — 오탐 원인) + const parent = nameMap.get(parentName); + if (!parent) continue; - // 정확 매칭 없으면 부분 매칭 (앞부분이 같은 선박) - if (!parent) { - for (const [name, ship] of nameMap) { - if (name.startsWith(parentName) || parentName.startsWith(name)) { - parent = ship; - break; - } - } - } + // 거리 제한: ~10NM 이내만 연결 + const dlat = Math.abs(gear.lat - parent.lat); + const dlng = Math.abs(gear.lng - parent.lng); + if (dlat > MAX_DIST_DEG || dlng > MAX_DIST_DEG) continue; - if (parent) { - links.push({ gear, parent, parentName }); - } + links.push({ gear, parent, parentName }); } return links; }, [ships]); @@ -227,8 +224,8 @@ export function ChineseFishingOverlay({ ships }: Props) { ); })} - {/* 본선/부속선/어선 역할 라벨 */} - {analyzed.filter(a => a.role.role).map(({ ship, role }) => ( + {/* 본선/부속선/어선 역할 라벨 (본선/부속/운반만, 최대 100개) */} + {analyzed.filter(a => a.role.role && a.role.role !== 'FV').slice(0, 100).map(({ ship, role }) => (