kcg-monitoring/frontend/src/components/korea/NavWarningLayer.tsx
Nan Kyung Lee e9ce6ecdd2 feat(korea): 한국 현황 레이어 대규모 확장 — 국적 필터, 풍력단지, 항구, 군사시설, 정부기관, 미사일 낙하
- 국적 분류 필터 추가 (한국/중국/북한/일본/미분류)
- S&P Global / MarineTraffic 탭 디자인 개선
- CCTV 백엔드 프록시 연결 (CctvProxyController)
- 풍력단지 레이어 (8개소 해상풍력)
- 항구 레이어 (한국/중국/일본/북한/대만 46개)
- 공항 확장 (중국 20, 일본 18, 북한 5, 대만 9개 추가)
- 군사시설 레이어 (중국/일본/북한/대만 38개소)
- 정부기관 레이어 (중국/일본 32개소)
- 북한 발사/포병진지 레이어 (19개소)
- 북한 미사일 낙하 시각화 (2026년 4건, 궤적 라인, 인근 선박 감지)
- 항행정보/팝업 공통 스타일 정리
- 선박 현황 정렬 스타일 개선
- 레이어 패널 폰트 축소

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:34:16 +09:00

131 lines
4.9 KiB
TypeScript

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Marker, Popup } from 'react-map-gl/maplibre';
import { NAV_WARNINGS, NW_LEVEL_LABEL, NW_ORG_LABEL } from '../../services/navWarning';
import type { NavWarning, NavWarningLevel, TrainingOrg } from '../../services/navWarning';
const LEVEL_COLOR: Record<NavWarningLevel, string> = {
danger: '#ef4444',
caution: '#eab308',
info: '#3b82f6',
};
const ORG_COLOR: Record<TrainingOrg, string> = {
'해군': '#8b5cf6',
'해병대': '#22c55e',
'공군': '#f97316',
'육군': '#ef4444',
'해경': '#3b82f6',
'국과연': '#eab308',
};
function WarningIcon({ level, org, size }: { level: NavWarningLevel; org: TrainingOrg; size: number }) {
const color = ORG_COLOR[org];
if (level === 'danger') {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M12 3 L22 20 L2 20 Z" fill="rgba(0,0,0,0.5)" stroke={color} strokeWidth="1.5" />
<line x1="12" y1="9" x2="12" y2="14" stroke={color} strokeWidth="2" strokeLinecap="round" />
<circle cx="12" cy="17" r="1" fill={color} />
</svg>
);
}
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M12 2 L22 12 L12 22 L2 12 Z" fill="rgba(0,0,0,0.5)" stroke={color} strokeWidth="1.2" />
<line x1="12" y1="8" x2="12" y2="13" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<circle cx="12" cy="16" r="1" fill={color} />
</svg>
);
}
export function NavWarningLayer() {
const [selected, setSelected] = useState<NavWarning | null>(null);
const { t } = useTranslation();
return (
<>
{NAV_WARNINGS.map(w => {
const color = ORG_COLOR[w.org];
const size = w.level === 'danger' ? 16 : 14;
return (
<Marker key={w.id} longitude={w.lng} latitude={w.lat} anchor="center"
onClick={(e) => { e.originalEvent.stopPropagation(); setSelected(w); }}>
<div style={{
cursor: 'pointer',
filter: `drop-shadow(0 0 4px ${color}88)`,
}} className="flex flex-col items-center">
<WarningIcon level={w.level} org={w.org} size={size} />
<div style={{
fontSize: 5, color,
textShadow: `0 0 3px ${color}, 0 0 2px #000`,
}} className="whitespace-nowrap font-bold tracking-wide">
{w.id}
</div>
</div>
</Marker>
);
})}
{selected && (
<Popup longitude={selected.lng} latitude={selected.lat}
onClose={() => setSelected(null)} closeOnClick={false}
anchor="bottom" maxWidth="320px" className="gl-popup">
<div className="font-mono" style={{ minWidth: 240, fontSize: 12 }}>
<div style={{
background: ORG_COLOR[selected.org],
color: '#fff',
padding: '4px 8px',
fontSize: 12,
fontWeight: 700,
margin: '-10px -10px 0',
borderRadius: '5px 5px 0 0',
}}>
{selected.title}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 10, marginBottom: 6 }}>
<span style={{
background: LEVEL_COLOR[selected.level],
color: '#fff',
padding: '1px 6px', fontSize: 10, borderRadius: 3, fontWeight: 700,
}}>
{NW_LEVEL_LABEL[selected.level]}
</span>
<span style={{
background: ORG_COLOR[selected.org] + '33',
color: ORG_COLOR[selected.org],
border: `1px solid ${ORG_COLOR[selected.org]}44`,
padding: '1px 6px', fontSize: 10, borderRadius: 3, fontWeight: 700,
}}>
{NW_ORG_LABEL[selected.org]}
</span>
<span style={{
background: 'var(--kcg-card)', color: 'var(--kcg-muted)',
padding: '1px 6px', fontSize: 10, borderRadius: 3,
}}>
{selected.area}
</span>
</div>
<div style={{ fontSize: 11, color: '#ccc', lineHeight: 1.4, marginBottom: 6 }}>
{selected.description}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2, fontSize: 9, color: '#666' }}>
<div>{t('navWarning.altitude')}: {selected.altitude}</div>
<div>{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E</div>
<div>{t('navWarning.source')}: {selected.source}</div>
</div>
<a
href="https://www.khoa.go.kr/nwb/mainPage.do?lang=ko"
target="_blank"
rel="noopener noreferrer"
style={{ display: 'block', marginTop: 6, fontSize: 10, color: '#3b82f6', textDecoration: 'underline' }}
>{t('navWarning.khoaLink')}</a>
</div>
</Popup>
)}
</>
);
}