refactor(korea): 작전가이드 → 실전형 순찰 루트 가이드로 변경
- 해경 기지 선택 → 주변 불법어선·어구 자동 탐지 - 탐색 반경 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) <noreply@anthropic.com>
This commit is contained in:
부모
f4ec6dd0f5
커밋
297d8aa56d
@ -666,7 +666,11 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
|
||||
<ReportModal ships={koreaData.ships} onClose={() => setShowReport(false)} />
|
||||
)}
|
||||
{showOpsGuide && (
|
||||
<OpsGuideModal onClose={() => setShowOpsGuide(false)} />
|
||||
<OpsGuideModal
|
||||
ships={koreaData.ships}
|
||||
onClose={() => setShowOpsGuide(false)}
|
||||
onFlyTo={(lat, lng, zoom) => setFlyToTarget({ lat, lng, zoom })}
|
||||
/>
|
||||
)}
|
||||
<KoreaMap
|
||||
ships={koreaFiltersResult.filteredShips}
|
||||
|
||||
@ -1,216 +1,237 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import type { Ship } from '../../types';
|
||||
import { COAST_GUARD_FACILITIES, CG_TYPE_LABEL } from '../../services/coastGuard';
|
||||
import type { CoastGuardFacility } from '../../services/coastGuard';
|
||||
import { getMarineTrafficCategory } from '../../utils/marineTraffic';
|
||||
import { analyzeFishing, classifyFishingZone } from '../../utils/fishingAnalysis';
|
||||
|
||||
interface Props {
|
||||
ships: Ship[];
|
||||
onClose: () => 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<Tab>('overview');
|
||||
export function OpsGuideModal({ ships, onClose, onFlyTo }: Props) {
|
||||
const [selectedKCG, setSelectedKCG] = useState<CoastGuardFacility | null>(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<SuspectVessel[]>(() => {
|
||||
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 (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,0.7)', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={onClose}>
|
||||
<div style={{ width: '92vw', maxWidth: 960, maxHeight: '90vh', overflow: 'hidden', background: C.bg, borderRadius: 8, border: `1px solid ${C.border}`, display: 'flex', flexDirection: 'column' }} onClick={e => e.stopPropagation()}>
|
||||
<div style={{ width: '92vw', maxWidth: 800, maxHeight: '90vh', overflow: 'hidden', background: '#0a0f1a', borderRadius: 8, border: '1px solid #1e293b', display: 'flex', flexDirection: 'column' }} onClick={e => e.stopPropagation()}>
|
||||
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 16px', borderBottom: `1px solid ${C.border}`, background: 'rgba(30,58,95,0.5)', flexShrink: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 16px', borderBottom: '1px solid #1e293b', background: 'rgba(30,58,95,0.5)', flexShrink: 0 }}>
|
||||
<span style={{ fontSize: 14 }}>⚓</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700, color: C.text }}>경비함정 작전 가이드</span>
|
||||
<span style={{ fontSize: 9, color: C.dim }}>GC-KCG-2026-001 제7장 기반</span>
|
||||
<button onClick={onClose} style={{ marginLeft: 'auto', background: 'rgba(255,82,82,0.1)', border: '1px solid rgba(255,82,82,0.4)', color: C.red, padding: '4px 14px', cursor: 'pointer', fontSize: 11, borderRadius: 2 }}>✕ 닫기</button>
|
||||
<span style={{ fontSize: 14, fontWeight: 700, color: '#e2e8f0' }}>경비함정 작전 가이드</span>
|
||||
<span style={{ fontSize: 9, color: '#64748b' }}>해경 기지 기준 주변 불법어선·어구 탐지</span>
|
||||
<button onClick={onClose} style={{ marginLeft: 'auto', background: 'rgba(255,82,82,0.1)', border: '1px solid rgba(255,82,82,0.4)', color: '#ef4444', padding: '4px 14px', cursor: 'pointer', fontSize: 11, borderRadius: 2 }}>✕ 닫기</button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', gap: 2, padding: '6px 16px', borderBottom: `1px solid ${C.border}`, flexShrink: 0, overflowX: 'auto' }}>
|
||||
{TABS.map(t => (
|
||||
<button key={t.key} onClick={() => setTab(t.key)} style={{
|
||||
padding: '4px 10px', fontSize: 10, fontWeight: 700, borderRadius: 3, cursor: 'pointer', whiteSpace: 'nowrap',
|
||||
background: tab === t.key ? 'rgba(59,130,246,0.2)' : 'transparent',
|
||||
border: tab === t.key ? '1px solid rgba(59,130,246,0.4)' : '1px solid transparent',
|
||||
color: tab === t.key ? '#60a5fa' : C.dim,
|
||||
}}>
|
||||
{t.icon} {t.label}
|
||||
</button>
|
||||
))}
|
||||
{/* Controls */}
|
||||
<div style={{ display: 'flex', gap: 8, padding: '8px 16px', borderBottom: '1px solid #1e293b', flexShrink: 0, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<label style={{ fontSize: 10, color: '#94a3b8', fontWeight: 700 }}>출동 기지:</label>
|
||||
<select
|
||||
value={selectedKCG?.id ?? ''}
|
||||
onChange={e => {
|
||||
const f = kcgBases.find(b => b.id === Number(e.target.value));
|
||||
setSelectedKCG(f || null);
|
||||
}}
|
||||
style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4, padding: '4px 8px', fontSize: 10, color: '#e2e8f0', minWidth: 180 }}
|
||||
>
|
||||
<option value="">-- 해경 기지 선택 --</option>
|
||||
{kcgBases.map(b => (
|
||||
<option key={b.id} value={b.id}>[{CG_TYPE_LABEL[b.type]}] {b.name}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label style={{ fontSize: 10, color: '#94a3b8', fontWeight: 700, marginLeft: 8 }}>탐색 반경:</label>
|
||||
<select value={searchRadius} onChange={e => setSearchRadius(Number(e.target.value))} style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4, padding: '4px 8px', fontSize: 10, color: '#e2e8f0' }}>
|
||||
<option value={10}>10 NM</option>
|
||||
<option value={20}>20 NM</option>
|
||||
<option value={30}>30 NM</option>
|
||||
<option value={50}>50 NM</option>
|
||||
<option value={100}>100 NM</option>
|
||||
</select>
|
||||
|
||||
{selectedKCG && (
|
||||
<div style={{ marginLeft: 'auto', display: 'flex', gap: 8, fontSize: 10 }}>
|
||||
<span style={{ color: '#ef4444', fontWeight: 700 }}>🔴 CRITICAL {criticalCount}</span>
|
||||
<span style={{ color: '#f59e0b', fontWeight: 700 }}>🟡 HIGH {highCount}</span>
|
||||
<span style={{ color: '#3b82f6', fontWeight: 700 }}>🔵 TOTAL {suspects.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '12px 20px', color: C.text, fontSize: 11, lineHeight: 1.8 }}>
|
||||
{tab === 'overview' && <OverviewTab />}
|
||||
{tab === 'pt' && <PTTab />}
|
||||
{tab === 'gn' && <GNTab />}
|
||||
{tab === 'ps' && <PSTab />}
|
||||
{tab === 'fc' && <FCTab />}
|
||||
{tab === 'gear' && <GearTab />}
|
||||
{tab === 'alert' && <AlertTab />}
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '8px 16px' }}>
|
||||
{!selectedKCG ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px 0', color: '#64748b' }}>
|
||||
<div style={{ fontSize: 32, marginBottom: 8 }}>⚓</div>
|
||||
<div style={{ fontSize: 12 }}>출동 기지를 선택하면 주변 불법어선·어구를 자동 탐지합니다</div>
|
||||
<div style={{ fontSize: 10, marginTop: 4 }}>해경서/지방청/해군부대 기준 반경 내 중국 선박 분석</div>
|
||||
</div>
|
||||
) : suspects.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px 0', color: '#22c55e' }}>
|
||||
<div style={{ fontSize: 32, marginBottom: 8 }}>✅</div>
|
||||
<div style={{ fontSize: 12 }}>{selectedKCG.name} 반경 {searchRadius}NM 내 의심 선박 없음</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{/* Route summary */}
|
||||
<div style={{ background: '#111827', borderRadius: 6, padding: '8px 12px', border: '1px solid #1e293b', marginBottom: 4 }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#60a5fa', marginBottom: 4 }}>
|
||||
📍 {selectedKCG.name} → 순찰 루트 제안 ({suspects.length}건)
|
||||
</div>
|
||||
<div style={{ fontSize: 9, color: '#94a3b8' }}>
|
||||
우선순위: CRITICAL → HIGH → MEDIUM | 거리순 정렬 | 가장 가까운 고위험 대상부터 순찰
|
||||
</div>
|
||||
{criticalCount > 0 && (
|
||||
<div style={{ fontSize: 10, color: '#ef4444', fontWeight: 700, marginTop: 4 }}>
|
||||
⚠ CRITICAL {criticalCount}건 — 즉시 출동 권고
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Suspect list */}
|
||||
{suspects.map((s, i) => (
|
||||
<div
|
||||
key={s.ship.mmsi}
|
||||
style={{
|
||||
background: '#111827', borderRadius: 6, padding: '8px 12px',
|
||||
border: `1px solid ${RISK_COLOR[s.riskLevel]}30`,
|
||||
borderLeft: `3px solid ${RISK_COLOR[s.riskLevel]}`,
|
||||
cursor: onFlyTo ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={() => onFlyTo?.(s.ship.lat, s.ship.lng, 12)}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: '#64748b', minWidth: 20 }}>#{i + 1}</span>
|
||||
<span style={{ fontSize: 9 }}>{RISK_ICON[s.riskLevel]}</span>
|
||||
<span style={{
|
||||
fontSize: 9, fontWeight: 700, padding: '1px 6px', borderRadius: 3,
|
||||
background: RISK_COLOR[s.riskLevel] + '20', color: RISK_COLOR[s.riskLevel],
|
||||
}}>{s.riskLevel}</span>
|
||||
<span style={{ fontSize: 11, fontWeight: 700, color: '#e2e8f0' }}>{s.ship.name || s.ship.mmsi}</span>
|
||||
<span style={{ fontSize: 9, color: '#64748b' }}>MMSI: {s.ship.mmsi}</span>
|
||||
<span style={{ marginLeft: 'auto', fontSize: 10, color: '#60a5fa', fontWeight: 700 }}>{s.distance.toFixed(1)} NM</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 4, marginTop: 4, flexWrap: 'wrap' }}>
|
||||
{s.reasons.map((r, j) => (
|
||||
<span key={j} style={{
|
||||
fontSize: 8, padding: '1px 5px', borderRadius: 2,
|
||||
background: 'rgba(255,255,255,0.06)', color: '#94a3b8',
|
||||
}}>{r}</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12, marginTop: 4, fontSize: 9, color: '#64748b' }}>
|
||||
<span>SOG: {s.ship.speed?.toFixed(1) ?? '-'} kn</span>
|
||||
<span>HDG: {s.ship.heading ?? '-'}°</span>
|
||||
<span>{s.ship.lat.toFixed(4)}°N, {s.ship.lng.toFixed(4)}°E</span>
|
||||
{onFlyTo && <span style={{ color: '#60a5fa', marginLeft: 'auto' }}>클릭 → 지도 이동</span>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div style={{ padding: '6px 16px', borderTop: '1px solid #1e293b', fontSize: 8, color: '#475569', flexShrink: 0 }}>
|
||||
한중어업협정 허가현황 (906척) 기반 자동 분석 | 수역 판정: Point-in-Polygon | 위험도: 속도·위치·AIS 패턴 종합
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 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 (<>
|
||||
<h2 style={h2}>함정 톤급별 작전 구역 및 운용 체계</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>함정 등급</th><th style={th}>작전 기간</th><th style={th}>담당 구역</th><th style={th}>주요 임무</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>소형 (500톤급 이하)</td><td style={td}>5~7일</td><td style={td}>연안~근해 소구역</td><td style={td}>연안 정치망 불법어구 수거. 소형 중국어선 무선 경고. 수역IV 유자망 순찰.</td></tr>
|
||||
<tr><td style={tdB}>중형 (1,000톤급)</td><td style={td}>7~10일</td><td style={td}>근해 중구역 (2개 사각형)</td><td style={td}>허가어선 허가증·조업일지 검사. GN 다크베셀 탐색. 불법어구 수거.</td></tr>
|
||||
<tr><td style={tdB}>대형 (3,000톤급)</td><td style={td}>10~14일</td><td style={td}>원해 대구역 (광역)</td><td style={td}>PT 본선·부속선 분리 감시·나포. PS 선단 추적. 야간 집어등 EO 탐지.</td></tr>
|
||||
<tr><td style={tdB}>대형 경비함 (5,000톤급)</td><td style={td}>10~14일</td><td style={td}>작전 수역 전체</td><td style={td}>작전 지휘함. 위망 선단 집중 감시. 나포 인계. 헬기·드론 운용.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 style={h2}>작전 스케줄 (중형 7~10일)</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>일차</th><th style={th}>주요 임무</th><th style={th}>세부 활동</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>D+1</td><td style={td}>구역 진입·초기 탐색</td><td style={td}>AIS 현황 파악, 중국 허가어선 목록 확인, 부표 탐색</td></tr>
|
||||
<tr><td style={tdB}>D+2~3 (주간)</td><td style={td}>집중 순찰</td><td style={td}>PT 쌍 이격거리 확인, GN 투망 의심 추적, 허가증 검사</td></tr>
|
||||
<tr><td style={tdB}>D+2~3 (야간)</td><td style={td}>야간 탐색</td><td style={td}>EO·레이더 집어등 탐색, PS 원형 패턴 감시, AIS 소실 SAR 확인</td></tr>
|
||||
<tr><td style={tdB}>D+4~5</td><td style={td}>불법어구 수거</td><td style={td}>표지 없는 자망·정치망 발견·수거, GPS 기록·사진</td></tr>
|
||||
<tr><td style={tdB}>D+5~6</td><td style={td}>나포·검문</td><td style={td}>위반 선박 정선, 허가증·어획량 확인, 나포 결정</td></tr>
|
||||
<tr><td style={tdB}>D+7</td><td style={td}>구역 정리·복귀</td><td style={td}>잔여 어구 수거, 보고서 작성, 후속 인계, 항구 귀환</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>);
|
||||
}
|
||||
|
||||
function PTTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>2척식 저인망 (PT) 대응 절차</h2>
|
||||
<div style={warn}>⚠ 망을 예인하는 선미(船尾) 방향 접근 절대 금지 — 예인삭이 수중으로 길게 뻗어 스크루 감김 위험</div>
|
||||
<div style={step}><span style={stepNum}>STEP 1</span><b>탐지 및 식별</b><br/>AIS MMSI 조회 → 허가현황 DB 대조. 허가 여부·업종·조업 기간 즉시 확인. 본선(PT)·부속선(PT-S) 쌍 여부 확인, 분리 거리 측정.</div>
|
||||
<div style={step}><span style={stepNum}>STEP 2</span><b>접근 및 경고</b><br/>전방 측면(선수 45° 방향)으로 접근. VHF Ch.16 무선 경고 3회. 중국어 경고: "请立即停船接受检查"</div>
|
||||
<div style={step}><span style={stepNum}>STEP 3</span><b>정선 후 승선 검사</b><br/>①허가증(许可证) 원본 — 허가번호(C21-xxxxx) 및 유효기간<br/>②조업일지 어획량 — 할당량(100톤/척) 대비 누적량<br/>③망목 규격 실측 — 54mm 미만 시 금지 어구</div>
|
||||
<div style={step}><span style={stepNum}>STEP 4</span><b>위반 유형 판정</b><br/>①휴어기 조업 (4/16~10/15) → 즉시 나포<br/>②할당량 100톤 초과 → 나포 또는 압수<br/>③부속선 분리 독립조업 → 양선 동시 나포</div>
|
||||
<div style={step}><span style={stepNum}>STEP 5</span><b>나포 또는 방면</b><br/>위반 확정: 나포 후 목포·여수·제주·태안 입항 또는 대형 함정 인계<br/>경미 위반: 현장 경고·시정 후 방면. 감시 시스템 알람 기록 등록</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
function GNTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>유자망 (GN) 대응 — 다크베셀 주의</h2>
|
||||
<div style={warn}>⚠ 야간 부표 야광등 없으면 식별 어려움 — 레이더로 부표 군집 위치 선(先) 확인 후 외곽 접근</div>
|
||||
<div style={step}><span style={stepNum}>STEP 1</span><b>다크베셀 탐지</b><br/>AIS 공백 선박 레이더 탐색. SAR 위성 요청. 부표 다수 발견 시 자망 설치 선박 근접 정박 — 부표 반경 1NM 집중 수색</div>
|
||||
<div style={step}><span style={stepNum}>STEP 2</span><b>그물 범위 확인 후 접근</b><br/>부표 배치 방향으로 자망 연장선 추정. 그물 설치 방향 수직(90°)으로 외곽 접근</div>
|
||||
<div style={step}><span style={stepNum}>STEP 3</span><b>AIS 재가동 요구</b><br/>"请打开AIS" 경고. 재개 확인 후 MMSI 기록. 거부 시 강제 임검</div>
|
||||
<div style={step}><span style={stepNum}>STEP 4</span><b>승선 검사</b><br/>①허가증(C25-xxxxx)<br/>②수역 확인 — GN은 수역IV까지 허가, 수역I 발견 시 즉시 위반<br/>③어획물 — 할당량 평균 28톤/척<br/>④망목 규격 및 그물 규모(길이·폭)</div>
|
||||
<div style={step}><span style={stepNum}>STEP 5</span><b>불법 어구 판정</b><br/>허가 구역 외 자망: 즉시 수거/절단<br/>망목 기준 미달: 어구 전량 압수<br/>자망 위치(GPS)·수거량 촬영 기록</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
function PSTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>위망 (PS) 선단 대응 — 선단 분산 주의</h2>
|
||||
<div style={warn}>⚠ 단독 접근 금지 — 조명선 시야 교란, 선단 분산 도주 전술에 대비. 대형 함정 지원 요청 후 선단 전체 동시 제압</div>
|
||||
<div style={step}><span style={stepNum}>STEP 1</span><b>선단 확인 및 보고</b><br/>원형 궤적 + 고속(8~10kn)→저속({'<'}3kn) 패턴 탐지. 3척+ AIS 클러스터 확인. 즉시 상급 함정·관제센터 보고. 단독 접근 금지</div>
|
||||
<div style={step}><span style={stepNum}>STEP 2</span><b>야간 집어등 식별</b><br/>EO 탐지 또는 육안 집어등 확인. 조명선 MMSI 기록. 조명선 차단은 선단 제압 최후 단계</div>
|
||||
<div style={step}><span style={stepNum}>STEP 3</span><b>선단 포위 (대형 함정 합류)</b><br/>모선·운반선·조명선 동시 포위. 탈주 방향(서방 중국 측) 차단 우선 배치. VHF 경고 → 정선 요구 → 지속 도주 시 경고 사격</div>
|
||||
<div style={step}><span style={stepNum}>STEP 4</span><b>선단 일제 임검</b><br/>모선: 허가증(C23-xxxxx)·어획물·냉동 설비. 할당량 1,500톤/척(4척만)<br/>운반선·조명선: 할당량 0톤 → 어획물 적재 시 불법 운반 증거</div>
|
||||
<div style={step}><span style={stepNum}>STEP 5</span><b>나포 및 증거 확보</b><br/>어획물 중량·어종 촬영. 냉동 설비 온도·저장 용량 기록. 宁波海裕 선단 VHF 교신 확보 권고. 대형 함정이 목포·여수항 인계</div>
|
||||
<h2 style={h2}>고위험 선박</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>선박</th><th style={th}>톤수</th><th style={th}>비고</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>宁渔 22</td><td style={td}>490톤</td><td style={td}>할당량 0톤 — 해상 냉동·집하 거점(모선) 의심</td></tr>
|
||||
<tr><td style={tdB}>宁渔 23</td><td style={td}>541톤</td><td style={td}>할당량 0톤 — 해상 냉동·집하 거점(모선) 의심</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>);
|
||||
}
|
||||
|
||||
function FCTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>운반선 (FC) 및 환적 현장 대응</h2>
|
||||
<div style={step}><span style={stepNum}>STEP 1</span><b>환적 의심 알람 수신</b><br/>감시 시스템: FC + 조업선 0.5NM 이내 + 양쪽 2kn 이하 + 30분 이상 → HIGH 알람. 해당 좌표 즉시 이동. 항공기·드론 선행 확인 요청</div>
|
||||
<div style={step}><span style={stepNum}>STEP 2</span><b>현장 증거 촬영</b><br/>두 선박 접현(接舷) 또는 고무보트 운반 확인. 드론 항공 촬영. 양선 MMSI·선명·선적항 기록. 접현 흔적(로프·충격 자국)</div>
|
||||
<div style={step}><span style={stepNum}>STEP 3</span><b>양선 동시 정선·임검</b><br/>운반선: 어획물 적재 내역·출발지·도착지. 냉동 화물 규모·어종<br/>조업선: 허가량 대비 실어획량. PT-S 부속선이 운반선 역할 시 이중 위반</div>
|
||||
<div style={step}><span style={stepNum}>STEP 4</span><b>증거 확보 및 조치</b><br/>어획물 사진·중량 확보. 필요 시 전량 압수. 도주 시 경고 사격 절차. 나포 후 최근접 항구 입항</div>
|
||||
<h2 style={h2}>환적 신뢰도 판정</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>지속 시간</th><th style={th}>신뢰도</th><th style={th}>등급</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={td}>30분</td><td style={td}>60%</td><td style={tdB}>HIGH</td></tr>
|
||||
<tr><td style={td}>60분</td><td style={td}>80%</td><td style={tdB}>HIGH</td></tr>
|
||||
<tr><td style={td}>120분+</td><td style={td}>95%</td><td style={{ ...tdB, color: C.red }}>CRITICAL</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>);
|
||||
}
|
||||
|
||||
function GearTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>불법 어구 수거 절차</h2>
|
||||
<div style={warn}>⚠ 방치 자망에 스크루 감김 → 표류 사고 위험. 수거 전 반드시 엔진 정지 또는 저속. 야간 수거는 익일로 연기 원칙</div>
|
||||
<div style={step}><span style={stepNum}>STEP 1</span><b>어구 발견 및 초기 기록</b><br/>GPS 좌표(WGS84). 어구 종류 추정·사진 촬영. 소유자 번호·허가번호 확인. 규모(길이·폭·그물코) 육안 추정</div>
|
||||
<div style={step}><span style={stepNum}>STEP 2</span><b>중국 어구 여부 판단</b><br/>중국어 표기 부표 또는 광폭·장형 자망 확인. 인근 중국 어선 존재 여부. 판단 불가 시 수거 후 항구 감식</div>
|
||||
<div style={step}><span style={stepNum}>STEP 3</span><b>수거 실행</b><br/>RIB 또는 크레인 장비 활용. 어획물 있으면 어종·중량 기록 후 전량 압수. 절단 불가피 시 위치·잔존 기록</div>
|
||||
<div style={step}><span style={stepNum}>STEP 4</span><b>수거 후 보고</b><br/>일시·위치·종류·규모·어획물 현황 → 감시 시스템 등록. 항구 감식·증거 보존. 반복 발견 위치 → 집중 감시 구역 지정 요청</div>
|
||||
<h2 style={h2}>어구 종류별 식별</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>어구</th><th style={th}>외형</th><th style={th}>발견 위치</th><th style={th}>수거 방법</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>불법 자망 (流刺網)</td><td style={td}>수직 그물, 부표 나열, 광폭·장형</td><td style={td}>수면 직하~중층 표류</td><td style={td}>RIB + 양망기</td></tr>
|
||||
<tr><td style={tdB}>불법 정치망 (張網)</td><td style={td}>사각 프레임, 묘박 고정</td><td style={td}>해저 고정, 좁은 수로</td><td style={td}>크레인·양망기</td></tr>
|
||||
<tr><td style={tdB}>불법 통발 (笼壶)</td><td style={td}>원통/상자형, 로프 연결</td><td style={td}>해저, 부표 표시</td><td style={td}>RIB + 인력 인양</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>);
|
||||
}
|
||||
|
||||
function AlertTab() {
|
||||
return (<>
|
||||
<h2 style={h2}>단속 상황별 조치 기준</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>위반 유형</th><th style={th}>판정 기준</th><th style={th}>즉시 조치</th><th style={th}>알람</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>미등록 선박</td><td style={td}>MMSI 허가 DB 미등록</td><td style={td}>즉시 정선·나포</td><td style={{ ...td, color: C.red }}>◉ CRITICAL</td></tr>
|
||||
<tr><td style={tdB}>휴어기 조업</td><td style={td}>C21·C22: 4/16~10/15<br/>C25: 6/2~8/31</td><td style={td}>즉시 나포</td><td style={{ ...td, color: C.red }}>◉ CRITICAL</td></tr>
|
||||
<tr><td style={tdB}>허가 수역 이탈</td><td style={td}>비허가 수역 진입</td><td style={td}>경고 후 나포</td><td style={{ ...td, color: C.yellow }}>⚠ HIGH</td></tr>
|
||||
<tr><td style={tdB}>PT 부속선 분리</td><td style={td}>본선 이격 3NM+</td><td style={td}>양선 동시 나포 검토</td><td style={{ ...td, color: C.yellow }}>⚠ HIGH→CRITICAL</td></tr>
|
||||
<tr><td style={tdB}>환적 현장 포착</td><td style={td}>FC+조업선 0.5NM+2kn+30분</td><td style={td}>현장 촬영 후 양선 나포</td><td style={{ ...td, color: C.yellow }}>⚠ HIGH</td></tr>
|
||||
<tr><td style={tdB}>불법 어구 발견</td><td style={td}>표지 없음 또는 미허가</td><td style={td}>즉시 수거·기록</td><td style={td}>자체 판단</td></tr>
|
||||
<tr><td style={tdB}>할당량 초과</td><td style={td}>80~100% 초과</td><td style={td}>어획물 계량·압수</td><td style={{ ...td, color: C.red }}>◉ CRITICAL</td></tr>
|
||||
<tr><td style={tdB}>다크베셀</td><td style={td}>AIS 공백 6시간+</td><td style={td}>접근·임검 후 확인</td><td style={{ ...td, color: C.yellow }}>⚠ HIGH</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 style={h2}>감시 강화 시기</h2>
|
||||
<table style={tbl}>
|
||||
<thead><tr><th style={th}>시기</th><th style={th}>상황</th><th style={th}>대응</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td style={tdB}>7~8월</td><td style={td}>PS 16척만 조업 허가</td><td style={{ ...td, color: C.red }}>C21·C22·C25 발견 시 전원 비허가. 최대 감시 집중</td></tr>
|
||||
<tr><td style={tdB}>5월</td><td style={td}>GN만 허가</td><td style={td}>수역 내 저인망(C21·C22) 탐지 시 즉시 위반</td></tr>
|
||||
<tr><td style={tdB}>4월·10월</td><td style={td}>기간 경계 시점</td><td style={td}>4/16, 10/16 기준일 전후 집중 모니터링</td></tr>
|
||||
<tr><td style={tdB}>1~3월·11~12월</td><td style={td}>전 업종 가능</td><td style={td}>수역 이탈 및 할당량 초과 중심 감시</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>);
|
||||
}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user