release: 중국어선감시 연결선 폭발 수정 #95

병합
htlee develop 에서 main 로 2 commits 를 머지했습니다 2026-03-20 12:30:52 +09:00

파일 보기

@ -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<string, Ship>();
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 }) => (
<Marker key={`role-${ship.mmsi}`} longitude={ship.lng} latitude={ship.lat} anchor="top">
<div style={{
marginTop: 6,