kcg-monitoring/frontend/src/components/korea/NKLaunchLayer.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

90 lines
3.7 KiB
TypeScript

import { useState } from 'react';
import { Marker, Popup } from 'react-map-gl/maplibre';
import { NK_LAUNCH_SITES, NK_LAUNCH_TYPE_META } from '../../data/nkLaunchSites';
import type { NKLaunchSite } from '../../data/nkLaunchSites';
export function NKLaunchLayer() {
const [selected, setSelected] = useState<NKLaunchSite | null>(null);
return (
<>
{NK_LAUNCH_SITES.map(site => {
const meta = NK_LAUNCH_TYPE_META[site.type];
const isArtillery = site.type === 'artillery' || site.type === 'mlrs';
const size = isArtillery ? 14 : 18;
return (
<Marker key={site.id} longitude={site.lng} latitude={site.lat} anchor="center"
onClick={(e) => { e.originalEvent.stopPropagation(); setSelected(site); }}>
<div
className="flex flex-col items-center cursor-pointer"
style={{ filter: `drop-shadow(0 0 4px ${meta.color}aa)` }}
>
<div style={{
width: size, height: size,
borderRadius: isArtillery ? '50%' : 4,
background: 'rgba(0,0,0,0.7)',
border: `2px solid ${meta.color}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: isArtillery ? 8 : 10,
}}>
{meta.icon}
</div>
<div style={{
fontSize: 5, color: meta.color, marginTop: 1,
textShadow: '0 0 3px #000, 0 0 3px #000',
whiteSpace: 'nowrap', fontWeight: 700,
}}>
{site.nameKo.length > 10 ? site.nameKo.slice(0, 10) + '..' : site.nameKo}
</div>
</div>
</Marker>
);
})}
{selected && (() => {
const meta = NK_LAUNCH_TYPE_META[selected.type];
return (
<Popup longitude={selected.lng} latitude={selected.lat}
onClose={() => setSelected(null)} closeOnClick={false}
anchor="bottom" maxWidth="320px" className="gl-popup">
<div className="popup-body-sm" style={{ minWidth: 240 }}>
<div className="popup-header" style={{ background: meta.color, color: '#fff', gap: 6, padding: '6px 10px' }}>
<span style={{ fontSize: 16 }}>🇰🇵</span>
<strong style={{ fontSize: 13 }}>{meta.icon} {selected.nameKo}</strong>
</div>
<div style={{ display: 'flex', gap: 4, marginBottom: 6 }}>
<span style={{
background: meta.color, color: '#fff',
padding: '2px 8px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
{meta.label}
</span>
<span style={{
background: '#f97316', color: '#fff',
padding: '2px 8px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
</span>
</div>
<div style={{ fontSize: 11, color: '#ccc', lineHeight: 1.4, marginBottom: 6 }}>
{selected.description}
</div>
{selected.recentUse && (
<div style={{ fontSize: 10, color: '#f87171', marginBottom: 4 }}>
: {selected.recentUse}
</div>
)}
<div style={{ fontSize: 11, color: 'var(--kcg-muted)', marginBottom: 4 }}>
{selected.name}
</div>
<div style={{ fontSize: 10, color: '#999' }}>
{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E
</div>
</div>
</Popup>
);
})()}
</>
);
}