From 297d8aa56df2ec91e6b615acdc7f843a24550fd8 Mon Sep 17 00:00:00 2001 From: Nan Kyung Lee Date: Tue, 24 Mar 2026 15:30:39 +0900 Subject: [PATCH] =?UTF-8?q?refactor(korea):=20=EC=9E=91=EC=A0=84=EA=B0=80?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=E2=86=92=20=EC=8B=A4=EC=A0=84=ED=98=95=20?= =?UTF-8?q?=EC=88=9C=EC=B0=B0=20=EB=A3=A8=ED=8A=B8=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해경 기지 선택 → 주변 불법어선·어구 자동 탐지 - 탐색 반경 10~100NM 설정 가능 - 중국 선박 대상 위험도 자동 판정 (CRITICAL/HIGH/MEDIUM) - 비허가 수역 진입 → CRITICAL - 수역I 저인망 의심 → HIGH - 다크베셀 (AIS 비정상) → HIGH - 어구/어망 AIS 신호 → HIGH - 조업 추정 (2~6kn) → MEDIUM - 운반선/환적 의심 → MEDIUM - 우선순위 정렬: 위험도 → 거리순 - 선박 클릭 → 지도 이동 (flyTo) - 순찰 루트 제안 (가장 가까운 고위험 대상부터) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/App.tsx | 6 +- .../src/components/korea/OpsGuideModal.tsx | 405 +++++++++--------- 2 files changed, 218 insertions(+), 193 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a50d4a4..6e14fc3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -666,7 +666,11 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) { setShowReport(false)} /> )} {showOpsGuide && ( - setShowOpsGuide(false)} /> + setShowOpsGuide(false)} + onFlyTo={(lat, lng, zoom) => setFlyToTarget({ lat, lng, zoom })} + /> )} void; + onFlyTo?: (lat: number, lng: number, zoom: number) => void; } -type Tab = 'overview' | 'pt' | 'gn' | 'ps' | 'fc' | 'gear' | 'alert'; +interface SuspectVessel { + ship: Ship; + distance: number; // NM from selected KCG + reasons: string[]; + riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM'; +} -const C = { - bg: '#0a0f1a', card: '#111827', border: '#1e293b', - text: '#e2e8f0', dim: '#64748b', accent: '#3b82f6', - green: '#22c55e', red: '#ef4444', yellow: '#f59e0b', cyan: '#06b6d4', -}; +function haversineNM(lat1: number, lng1: number, lat2: number, lng2: number): number { + const R = 3440.065; // Earth radius in NM + 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 2 * R * Math.asin(Math.sqrt(a)); +} -const TABS: { key: Tab; label: string; icon: string }[] = [ - { key: 'overview', label: '작전 개요', icon: '🗺' }, - { key: 'pt', label: 'PT 저인망', icon: '🔴' }, - { key: 'gn', label: 'GN 유자망', icon: '🟡' }, - { key: 'ps', label: 'PS 위망', icon: '🟣' }, - { key: 'fc', label: 'FC 운반선', icon: '🟠' }, - { key: 'gear', label: '어구 수거', icon: '🪤' }, - { key: 'alert', label: '조치 기준', icon: '🚨' }, -]; +const RISK_COLOR = { CRITICAL: '#ef4444', HIGH: '#f59e0b', MEDIUM: '#3b82f6' }; +const RISK_ICON = { CRITICAL: '🔴', HIGH: '🟡', MEDIUM: '🔵' }; -export function OpsGuideModal({ onClose }: Props) { - const [tab, setTab] = useState('overview'); +export function OpsGuideModal({ ships, onClose, onFlyTo }: Props) { + const [selectedKCG, setSelectedKCG] = useState(null); + const [searchRadius, setSearchRadius] = useState(30); // NM + + // 해경서/지방청만 (파출소 제외) + const kcgBases = useMemo(() => + COAST_GUARD_FACILITIES.filter(f => ['hq', 'regional', 'station', 'navy'].includes(f.type)) + .sort((a, b) => a.name.localeCompare(b.name)), + []); + + // 선택된 해경 기지 주변 의심 선박 탐지 + const suspects = useMemo(() => { + if (!selectedKCG) return []; + + const results: SuspectVessel[] = []; + + for (const ship of ships) { + if (ship.flag !== 'CN') continue; + const dist = haversineNM(selectedKCG.lat, selectedKCG.lng, ship.lat, ship.lng); + if (dist > searchRadius) continue; + + const cat = getMarineTrafficCategory(ship.typecode, ship.category); + const isFishing = cat === 'fishing' || ship.category === 'fishing' || ship.typecode === '30'; + const isGear = /[_]\d+[_]|%$/.test(ship.name); + const analysis = isFishing ? analyzeFishing(ship) : null; + const zone = classifyFishingZone(ship.lat, ship.lng); + + const reasons: string[] = []; + let riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM' = 'MEDIUM'; + + // 수역 외 어선 + if (isFishing && zone.zone === 'OUTSIDE') { + reasons.push('비허가 수역 진입'); + riskLevel = 'CRITICAL'; + } + + // 수역 I에 PT/OT 선박 + if (isFishing && zone.zone === 'ZONE_I' && ship.speed >= 2 && ship.speed <= 5) { + reasons.push('수역I 저인망 의심 (PT/OT 비허가)'); + riskLevel = 'HIGH'; + } + + // 다크베셀 의심 (속도 0, 방향 0) + if (isFishing && ship.speed === 0 && (!ship.heading || ship.heading === 0)) { + reasons.push('AIS 비정상 (다크베셀 의심)'); + if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; + } + + // 조업 중 (2-6kn) + if (isFishing && ship.speed >= 2 && ship.speed <= 6) { + reasons.push(`조업 추정 (${ship.speed.toFixed(1)}kn)`); + } + + // 어구/어망 + if (isGear) { + reasons.push('어구/어망 AIS 신호'); + if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; + } + + // 대형 선박 (운반선 의심) + if (!isFishing && (cat === 'cargo' || cat === 'unspecified') && ship.speed < 3) { + reasons.push('운반선/환적 의심 (저속 대형)'); + } + + if (reasons.length > 0) { + results.push({ ship, distance: dist, reasons, riskLevel }); + } + } + + return results.sort((a, b) => { + const riskOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2 }; + return riskOrder[a.riskLevel] - riskOrder[b.riskLevel] || a.distance - b.distance; + }); + }, [selectedKCG, ships, searchRadius]); + + const criticalCount = suspects.filter(s => s.riskLevel === 'CRITICAL').length; + const highCount = suspects.filter(s => s.riskLevel === 'HIGH').length; return (
-
e.stopPropagation()}> +
e.stopPropagation()}> {/* Header */} -
+
- 경비함정 작전 가이드 - GC-KCG-2026-001 제7장 기반 - + 경비함정 작전 가이드 + 해경 기지 기준 주변 불법어선·어구 탐지 +
- {/* Tabs */} -
- {TABS.map(t => ( - - ))} + {/* Controls */} +
+ + + + + + + {selectedKCG && ( +
+ 🔴 CRITICAL {criticalCount} + 🟡 HIGH {highCount} + 🔵 TOTAL {suspects.length} +
+ )}
{/* Content */} -
- {tab === 'overview' && } - {tab === 'pt' && } - {tab === 'gn' && } - {tab === 'ps' && } - {tab === 'fc' && } - {tab === 'gear' && } - {tab === 'alert' && } +
+ {!selectedKCG ? ( +
+
+
출동 기지를 선택하면 주변 불법어선·어구를 자동 탐지합니다
+
해경서/지방청/해군부대 기준 반경 내 중국 선박 분석
+
+ ) : suspects.length === 0 ? ( +
+
+
{selectedKCG.name} 반경 {searchRadius}NM 내 의심 선박 없음
+
+ ) : ( +
+ {/* Route summary */} +
+
+ 📍 {selectedKCG.name} → 순찰 루트 제안 ({suspects.length}건) +
+
+ 우선순위: CRITICAL → HIGH → MEDIUM | 거리순 정렬 | 가장 가까운 고위험 대상부터 순찰 +
+ {criticalCount > 0 && ( +
+ ⚠ CRITICAL {criticalCount}건 — 즉시 출동 권고 +
+ )} +
+ + {/* Suspect list */} + {suspects.map((s, i) => ( +
onFlyTo?.(s.ship.lat, s.ship.lng, 12)} + > +
+ #{i + 1} + {RISK_ICON[s.riskLevel]} + {s.riskLevel} + {s.ship.name || s.ship.mmsi} + MMSI: {s.ship.mmsi} + {s.distance.toFixed(1)} NM +
+
+ {s.reasons.map((r, j) => ( + {r} + ))} +
+
+ SOG: {s.ship.speed?.toFixed(1) ?? '-'} kn + HDG: {s.ship.heading ?? '-'}° + {s.ship.lat.toFixed(4)}°N, {s.ship.lng.toFixed(4)}°E + {onFlyTo && 클릭 → 지도 이동} +
+
+ ))} +
+ )} +
+ + {/* Footer */} +
+ 한중어업협정 허가현황 (906척) 기반 자동 분석 | 수역 판정: Point-in-Polygon | 위험도: 속도·위치·AIS 패턴 종합
); } - -// ── Styles ── -const h2: React.CSSProperties = { fontSize: 13, color: '#60a5fa', borderLeft: '3px solid #3b82f6', paddingLeft: 8, margin: '16px 0 8px' }; -const tbl: React.CSSProperties = { borderCollapse: 'collapse', width: '100%', fontSize: 10, margin: '6px 0' }; -const th: React.CSSProperties = { border: '1px solid #1e293b', padding: '4px 8px', background: '#1e293b', color: '#e2e8f0', fontSize: 9, fontWeight: 700, textAlign: 'left' }; -const td: React.CSSProperties = { border: '1px solid #1e293b', padding: '3px 8px', fontSize: 10 }; -const tdB: React.CSSProperties = { ...td, fontWeight: 700, color: '#e2e8f0' }; -const step: React.CSSProperties = { background: '#1e293b', borderRadius: 4, padding: '8px 12px', margin: '6px 0' }; -const stepNum: React.CSSProperties = { display: 'inline-block', background: '#3b82f6', color: '#fff', borderRadius: 3, padding: '1px 6px', fontSize: 9, fontWeight: 700, marginRight: 6 }; -const warn: React.CSSProperties = { background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 4, padding: '6px 10px', margin: '8px 0', fontSize: 10, color: '#fca5a5' }; - -function OverviewTab() { - return (<> -

함정 톤급별 작전 구역 및 운용 체계

- - - - - - - - -
함정 등급작전 기간담당 구역주요 임무
소형 (500톤급 이하)5~7일연안~근해 소구역연안 정치망 불법어구 수거. 소형 중국어선 무선 경고. 수역IV 유자망 순찰.
중형 (1,000톤급)7~10일근해 중구역 (2개 사각형)허가어선 허가증·조업일지 검사. GN 다크베셀 탐색. 불법어구 수거.
대형 (3,000톤급)10~14일원해 대구역 (광역)PT 본선·부속선 분리 감시·나포. PS 선단 추적. 야간 집어등 EO 탐지.
대형 경비함 (5,000톤급)10~14일작전 수역 전체작전 지휘함. 위망 선단 집중 감시. 나포 인계. 헬기·드론 운용.
-

작전 스케줄 (중형 7~10일)

- - - - - - - - - - -
일차주요 임무세부 활동
D+1구역 진입·초기 탐색AIS 현황 파악, 중국 허가어선 목록 확인, 부표 탐색
D+2~3 (주간)집중 순찰PT 쌍 이격거리 확인, GN 투망 의심 추적, 허가증 검사
D+2~3 (야간)야간 탐색EO·레이더 집어등 탐색, PS 원형 패턴 감시, AIS 소실 SAR 확인
D+4~5불법어구 수거표지 없는 자망·정치망 발견·수거, GPS 기록·사진
D+5~6나포·검문위반 선박 정선, 허가증·어획량 확인, 나포 결정
D+7구역 정리·복귀잔여 어구 수거, 보고서 작성, 후속 인계, 항구 귀환
- ); -} - -function PTTab() { - return (<> -

2척식 저인망 (PT) 대응 절차

-
⚠ 망을 예인하는 선미(船尾) 방향 접근 절대 금지 — 예인삭이 수중으로 길게 뻗어 스크루 감김 위험
-
STEP 1탐지 및 식별
AIS MMSI 조회 → 허가현황 DB 대조. 허가 여부·업종·조업 기간 즉시 확인. 본선(PT)·부속선(PT-S) 쌍 여부 확인, 분리 거리 측정.
-
STEP 2접근 및 경고
전방 측면(선수 45° 방향)으로 접근. VHF Ch.16 무선 경고 3회. 중국어 경고: "请立即停船接受检查"
-
STEP 3정선 후 승선 검사
①허가증(许可证) 원본 — 허가번호(C21-xxxxx) 및 유효기간
②조업일지 어획량 — 할당량(100톤/척) 대비 누적량
③망목 규격 실측 — 54mm 미만 시 금지 어구
-
STEP 4위반 유형 판정
①휴어기 조업 (4/16~10/15) → 즉시 나포
②할당량 100톤 초과 → 나포 또는 압수
③부속선 분리 독립조업 → 양선 동시 나포
-
STEP 5나포 또는 방면
위반 확정: 나포 후 목포·여수·제주·태안 입항 또는 대형 함정 인계
경미 위반: 현장 경고·시정 후 방면. 감시 시스템 알람 기록 등록
- ); -} - -function GNTab() { - return (<> -

유자망 (GN) 대응 — 다크베셀 주의

-
⚠ 야간 부표 야광등 없으면 식별 어려움 — 레이더로 부표 군집 위치 선(先) 확인 후 외곽 접근
-
STEP 1다크베셀 탐지
AIS 공백 선박 레이더 탐색. SAR 위성 요청. 부표 다수 발견 시 자망 설치 선박 근접 정박 — 부표 반경 1NM 집중 수색
-
STEP 2그물 범위 확인 후 접근
부표 배치 방향으로 자망 연장선 추정. 그물 설치 방향 수직(90°)으로 외곽 접근
-
STEP 3AIS 재가동 요구
"请打开AIS" 경고. 재개 확인 후 MMSI 기록. 거부 시 강제 임검
-
STEP 4승선 검사
①허가증(C25-xxxxx)
②수역 확인 — GN은 수역IV까지 허가, 수역I 발견 시 즉시 위반
③어획물 — 할당량 평균 28톤/척
④망목 규격 및 그물 규모(길이·폭)
-
STEP 5불법 어구 판정
허가 구역 외 자망: 즉시 수거/절단
망목 기준 미달: 어구 전량 압수
자망 위치(GPS)·수거량 촬영 기록
- ); -} - -function PSTab() { - return (<> -

위망 (PS) 선단 대응 — 선단 분산 주의

-
⚠ 단독 접근 금지 — 조명선 시야 교란, 선단 분산 도주 전술에 대비. 대형 함정 지원 요청 후 선단 전체 동시 제압
-
STEP 1선단 확인 및 보고
원형 궤적 + 고속(8~10kn)→저속({'<'}3kn) 패턴 탐지. 3척+ AIS 클러스터 확인. 즉시 상급 함정·관제센터 보고. 단독 접근 금지
-
STEP 2야간 집어등 식별
EO 탐지 또는 육안 집어등 확인. 조명선 MMSI 기록. 조명선 차단은 선단 제압 최후 단계
-
STEP 3선단 포위 (대형 함정 합류)
모선·운반선·조명선 동시 포위. 탈주 방향(서방 중국 측) 차단 우선 배치. VHF 경고 → 정선 요구 → 지속 도주 시 경고 사격
-
STEP 4선단 일제 임검
모선: 허가증(C23-xxxxx)·어획물·냉동 설비. 할당량 1,500톤/척(4척만)
운반선·조명선: 할당량 0톤 → 어획물 적재 시 불법 운반 증거
-
STEP 5나포 및 증거 확보
어획물 중량·어종 촬영. 냉동 설비 온도·저장 용량 기록. 宁波海裕 선단 VHF 교신 확보 권고. 대형 함정이 목포·여수항 인계
-

고위험 선박

- - - - - - -
선박톤수비고
宁渔 22490톤할당량 0톤 — 해상 냉동·집하 거점(모선) 의심
宁渔 23541톤할당량 0톤 — 해상 냉동·집하 거점(모선) 의심
- ); -} - -function FCTab() { - return (<> -

운반선 (FC) 및 환적 현장 대응

-
STEP 1환적 의심 알람 수신
감시 시스템: FC + 조업선 0.5NM 이내 + 양쪽 2kn 이하 + 30분 이상 → HIGH 알람. 해당 좌표 즉시 이동. 항공기·드론 선행 확인 요청
-
STEP 2현장 증거 촬영
두 선박 접현(接舷) 또는 고무보트 운반 확인. 드론 항공 촬영. 양선 MMSI·선명·선적항 기록. 접현 흔적(로프·충격 자국)
-
STEP 3양선 동시 정선·임검
운반선: 어획물 적재 내역·출발지·도착지. 냉동 화물 규모·어종
조업선: 허가량 대비 실어획량. PT-S 부속선이 운반선 역할 시 이중 위반
-
STEP 4증거 확보 및 조치
어획물 사진·중량 확보. 필요 시 전량 압수. 도주 시 경고 사격 절차. 나포 후 최근접 항구 입항
-

환적 신뢰도 판정

- - - - - - - -
지속 시간신뢰도등급
30분60%HIGH
60분80%HIGH
120분+95%CRITICAL
- ); -} - -function GearTab() { - return (<> -

불법 어구 수거 절차

-
⚠ 방치 자망에 스크루 감김 → 표류 사고 위험. 수거 전 반드시 엔진 정지 또는 저속. 야간 수거는 익일로 연기 원칙
-
STEP 1어구 발견 및 초기 기록
GPS 좌표(WGS84). 어구 종류 추정·사진 촬영. 소유자 번호·허가번호 확인. 규모(길이·폭·그물코) 육안 추정
-
STEP 2중국 어구 여부 판단
중국어 표기 부표 또는 광폭·장형 자망 확인. 인근 중국 어선 존재 여부. 판단 불가 시 수거 후 항구 감식
-
STEP 3수거 실행
RIB 또는 크레인 장비 활용. 어획물 있으면 어종·중량 기록 후 전량 압수. 절단 불가피 시 위치·잔존 기록
-
STEP 4수거 후 보고
일시·위치·종류·규모·어획물 현황 → 감시 시스템 등록. 항구 감식·증거 보존. 반복 발견 위치 → 집중 감시 구역 지정 요청
-

어구 종류별 식별

- - - - - - - -
어구외형발견 위치수거 방법
불법 자망 (流刺網)수직 그물, 부표 나열, 광폭·장형수면 직하~중층 표류RIB + 양망기
불법 정치망 (張網)사각 프레임, 묘박 고정해저 고정, 좁은 수로크레인·양망기
불법 통발 (笼壶)원통/상자형, 로프 연결해저, 부표 표시RIB + 인력 인양
- ); -} - -function AlertTab() { - return (<> -

단속 상황별 조치 기준

- - - - - - - - - - - - -
위반 유형판정 기준즉시 조치알람
미등록 선박MMSI 허가 DB 미등록즉시 정선·나포◉ CRITICAL
휴어기 조업C21·C22: 4/16~10/15
C25: 6/2~8/31
즉시 나포◉ CRITICAL
허가 수역 이탈비허가 수역 진입경고 후 나포⚠ HIGH
PT 부속선 분리본선 이격 3NM+양선 동시 나포 검토⚠ HIGH→CRITICAL
환적 현장 포착FC+조업선 0.5NM+2kn+30분현장 촬영 후 양선 나포⚠ HIGH
불법 어구 발견표지 없음 또는 미허가즉시 수거·기록자체 판단
할당량 초과80~100% 초과어획물 계량·압수◉ CRITICAL
다크베셀AIS 공백 6시간+접근·임검 후 확인⚠ HIGH
-

감시 강화 시기

- - - - - - - - -
시기상황대응
7~8월PS 16척만 조업 허가C21·C22·C25 발견 시 전원 비허가. 최대 감시 집중
5월GN만 허가수역 내 저인망(C21·C22) 탐지 시 즉시 위반
4월·10월기간 경계 시점4/16, 10/16 기준일 전후 집중 모니터링
1~3월·11~12월전 업종 가능수역 이탈 및 할당량 초과 중심 감시
- ); -}