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

112 lines
5.0 KiB
TypeScript

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Marker, Popup } from 'react-map-gl/maplibre';
import { KOREAN_AIRPORTS } from '../../services/airports';
import type { KoreanAirport } from '../../services/airports';
const COUNTRY_COLOR: Record<string, { intl: string; domestic: string; flag: string; label: string }> = {
KR: { intl: '#a78bfa', domestic: '#7c8aaa', flag: '🇰🇷', label: '한국' },
CN: { intl: '#ef4444', domestic: '#b91c1c', flag: '🇨🇳', label: '중국' },
JP: { intl: '#f472b6', domestic: '#9d174d', flag: '🇯🇵', label: '일본' },
KP: { intl: '#f97316', domestic: '#c2410c', flag: '🇰🇵', label: '북한' },
TW: { intl: '#10b981', domestic: '#059669', flag: '🇹🇼', label: '대만' },
};
function getColor(ap: KoreanAirport) {
const cc = COUNTRY_COLOR[ap.country || 'KR'] || COUNTRY_COLOR.KR;
return ap.intl ? cc.intl : cc.domestic;
}
function getCountryInfo(ap: KoreanAirport) {
return COUNTRY_COLOR[ap.country || 'KR'] || COUNTRY_COLOR.KR;
}
export function KoreaAirportLayer() {
const [selected, setSelected] = useState<KoreanAirport | null>(null);
const { t } = useTranslation();
return (
<>
{KOREAN_AIRPORTS.map(ap => {
const color = getColor(ap);
const size = ap.intl ? 20 : 16;
return (
<Marker key={ap.id} longitude={ap.lng} latitude={ap.lat} anchor="center"
onClick={(e) => { e.originalEvent.stopPropagation(); setSelected(ap); }}>
<div style={{
cursor: 'pointer',
filter: `drop-shadow(0 0 3px ${color}88)`,
}} className="flex flex-col items-center">
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" fill="rgba(0,0,0,0.5)" stroke={color} strokeWidth="1.2" />
<path d="M12 5 C11.4 5 11 5.4 11 5.8 L11 10 L6 13 L6 14.2 L11 12.5 L11 16.5 L9.2 17.8 L9.2 18.8 L12 18 L14.8 18.8 L14.8 17.8 L13 16.5 L13 12.5 L18 14.2 L18 13 L13 10 L13 5.8 C13 5.4 12.6 5 12 5Z"
fill={color} stroke="#fff" strokeWidth="0.3" />
</svg>
<div style={{
fontSize: 6,
textShadow: `0 0 3px ${color}, 0 0 2px #000`,
}} className="mt-px whitespace-nowrap font-bold tracking-wide text-white">
{ap.nameKo.replace('국제공항', '').replace('공항', '')}
</div>
</div>
</Marker>
);
})}
{selected && (() => {
const color = getColor(selected);
const info = getCountryInfo(selected);
return (
<Popup longitude={selected.lng} latitude={selected.lat}
onClose={() => setSelected(null)} closeOnClick={false}
anchor="bottom" maxWidth="280px" className="gl-popup">
<div className="popup-body-sm" style={{ minWidth: 200 }}>
<div className="popup-header" style={{ background: color, color: '#000', gap: 6, padding: '6px 10px' }}>
<span style={{ fontSize: 16 }}>{info.flag}</span>
<strong style={{ fontSize: 13 }}>{selected.nameKo}</strong>
</div>
<div style={{ display: 'flex', gap: 4, marginBottom: 6 }}>
{selected.intl && (
<span style={{
background: color, color: '#000',
padding: '2px 8px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
{t('airport.international')}
</span>
)}
{selected.domestic && (
<span style={{
background: '#555', color: '#ccc',
padding: '2px 8px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
{t('airport.domestic')}
</span>
)}
<span style={{
background: '#333', color: '#ccc',
padding: '2px 8px', borderRadius: 3, fontSize: 10,
}}>
{info.label}
</span>
</div>
<div className="popup-grid" style={{ gap: '2px 12px' }}>
<div><span className="popup-label">IATA : </span><strong>{selected.id}</strong></div>
<div><span className="popup-label">ICAO : </span><strong>{selected.icao}</strong></div>
</div>
<div style={{ marginTop: 6, fontSize: 10, color: '#999' }}>
{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E
</div>
<div style={{ marginTop: 4, fontSize: 10, textAlign: 'right' }}>
<a href={`https://www.flightradar24.com/airport/${selected.id.toLowerCase()}`}
target="_blank" rel="noopener noreferrer" style={{ color: '#60a5fa' }}>
Flightradar24 &rarr;
</a>
</div>
</div>
</Popup>
);
})()}
</>
);
}