// ═══ 중국어선 선단(Fleet) 탐지 — GC-KCG-2026-001 기반 ═══ import type { Ship } from '../types'; import { getMarineTrafficCategory } from './marineTraffic'; export type FleetRole = 'mothership' | 'subsidiary' | 'carrier' | 'lighting' | 'pair'; export interface FleetMember { ship: Ship; role: FleetRole; roleKo: string; distanceNm: number; reason: string; } export interface FleetConnection { selectedShip: Ship; members: FleetMember[]; fleetType: 'trawl_pair' | 'purse_seine_fleet' | 'transship' | 'unknown'; fleetTypeKo: string; } /** 두 지점 사이 거리(NM) */ function distNm(lat1: number, lng1: number, lat2: number, lng2: number): number { const R = 3440.065; // 지구 반경 (해리) const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2; return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } /** * 선택한 중국어선 주변의 선단 구성을 탐지 * * 보고서 기준: * - PT 2척식 저인망: 본선+부속선 3NM 이내, 유사 속도(2~5kn), 유사 방향 * - PS 위망 선단: 3척+ 클러스터, 모선+운반선+조명선 * - FC 운반선 환적: 0.5NM 이내 접근, 양쪽 2kn 이하 */ export function detectFleet(selectedShip: Ship, allShips: Ship[]): FleetConnection | null { if (selectedShip.flag !== 'CN') return null; const _mtCat = getMarineTrafficCategory(selectedShip.typecode, selectedShip.category); const members: FleetMember[] = []; // 주변 중국 선박 탐색 (10NM 반경) const nearby = allShips.filter(s => s.mmsi !== selectedShip.mmsi && s.flag === 'CN' && distNm(selectedShip.lat, selectedShip.lng, s.lat, s.lng) < 10 ); for (const s of nearby) { const d = distNm(selectedShip.lat, selectedShip.lng, s.lat, s.lng); const sCat = getMarineTrafficCategory(s.typecode, s.category); const speedDiff = Math.abs(selectedShip.speed - s.speed); let headingDiff = Math.abs(selectedShip.heading - s.heading); if (headingDiff > 180) headingDiff = 360 - headingDiff; // === PT 본선-부속선 쌍 탐지 === // 3NM 이내 + 유사 속도(차이 1kn 미만) + 유사 방향(20° 미만) + 둘 다 2~5kn if (d < 3 && speedDiff < 1 && headingDiff < 20 && selectedShip.speed >= 2 && selectedShip.speed <= 5 && s.speed >= 2 && s.speed <= 5) { members.push({ ship: s, role: 'pair', roleKo: '부속선 (PT-S)', distanceNm: d, reason: `속도 ${s.speed.toFixed(1)}kn, 방향차 ${headingDiff.toFixed(0)}°, 거리 ${d.toFixed(1)}NM`, }); continue; } // === FC 운반선 환적 탐지 === // 0.5NM 이내 + 양쪽 2kn 이하 if (d < 0.5 && selectedShip.speed <= 2 && s.speed <= 2) { const isCarrier = sCat === 'cargo' || sCat === 'unspecified' || s.name.includes('运') || s.name.includes('冷'); if (isCarrier) { members.push({ ship: s, role: 'carrier', roleKo: '운반선 (FC)', distanceNm: d, reason: `환적 의심 — ${d.toFixed(2)}NM, 양쪽 저속`, }); continue; } } // === PS 선단 멤버 탐지 === // 2NM 이내 중국어선 클러스터 if (d < 2 && sCat === 'fishing') { // 속도 차이로 역할 추정 if (s.speed < 1 && selectedShip.speed > 5) { members.push({ ship: s, role: 'lighting', roleKo: '조명선', distanceNm: d, reason: `정지 중 — 집어등 추정`, }); } else { members.push({ ship: s, role: 'subsidiary', roleKo: '선단 멤버', distanceNm: d, reason: `${d.toFixed(1)}NM, ${s.speed.toFixed(1)}kn`, }); } continue; } // === 일반 근접 중국 선박 (5NM 이내) === if (d < 5 && (sCat === 'fishing' || sCat === 'unspecified')) { members.push({ ship: s, role: 'subsidiary', roleKo: '인근 어선', distanceNm: d, reason: `${d.toFixed(1)}NM`, }); } } if (members.length === 0) return null; // 선단 유형 판별 const hasPair = members.some(m => m.role === 'pair'); const hasCarrier = members.some(m => m.role === 'carrier'); const hasLighting = members.some(m => m.role === 'lighting'); let fleetType: FleetConnection['fleetType'] = 'unknown'; let fleetTypeKo = '인근 선박 그룹'; if (hasPair) { fleetType = 'trawl_pair'; fleetTypeKo = '2척식 저인망 (본선·부속선)'; } else if (hasCarrier) { fleetType = 'transship'; fleetTypeKo = '환적 의심 (운반선 접근)'; } else if (hasLighting || members.length >= 3) { fleetType = 'purse_seine_fleet'; fleetTypeKo = '위망 선단 (모선·운반·조명)'; } // 거리순 정렬, 최대 10개 members.sort((a, b) => a.distanceNm - b.distanceNm); return { selectedShip, members: members.slice(0, 10), fleetType, fleetTypeKo, }; }