- 국적 분류 필터 추가 (한국/중국/북한/일본/미분류) - 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>
90 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
})()}
|
|
</>
|
|
);
|
|
}
|