refactor: Phase 3 — StaticFacilityPopup 추출 (KoreaMap 935줄→742줄)

- StaticFacilityPopup: 시설 클릭 Popup 로직 독립 컴포넌트 (207줄)
- KoreaMap: IIFE Popup 블록(~200줄) → 컴포넌트 호출 3줄로 대체
- SUB_META/KIND_DEFAULT 상수 + 배지/필드 렌더링 모두 이동
This commit is contained in:
htlee 2026-03-23 10:46:24 +09:00
부모 aff17588b2
커밋 728936439b
2개의 변경된 파일212개의 추가작업 그리고 198개의 파일을 삭제

파일 보기

@ -1,6 +1,6 @@
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Map, NavigationControl, Marker, Popup, Source, Layer } from 'react-map-gl/maplibre';
import { Map, NavigationControl, Marker, Source, Layer } from 'react-map-gl/maplibre';
import type { MapRef } from 'react-map-gl/maplibre';
import { ScatterplotLayer, TextLayer } from '@deck.gl/layers';
import { DeckGLOverlay } from '../layers/DeckGLOverlay';
@ -20,6 +20,7 @@ import { EezLayer } from './EezLayer';
// PiracyLayer, WindFarmLayer, PortLayer, MilitaryBaseLayer, GovBuildingLayer,
// NKLaunchLayer, NKMissileEventLayer → useStaticDeckLayers로 전환됨
import { ChineseFishingOverlay } from './ChineseFishingOverlay';
import { StaticFacilityPopup } from './StaticFacilityPopup';
// HazardFacilityLayer, CnFacilityLayer, JpFacilityLayer → useStaticDeckLayers로 전환됨
import { AnalysisOverlay } from './AnalysisOverlay';
import { FleetClusterLayer } from './FleetClusterLayer';
@ -641,203 +642,9 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
...analysisDeckLayers,
].filter(Boolean)} />
{/* 정적 마커 클릭 Popup — 통합 리치 디자인 */}
{staticPickInfo && (() => {
const obj = staticPickInfo.object;
const kind = staticPickInfo.kind;
const lat = obj.lat ?? obj.launchLat ?? 0;
const lng = obj.lng ?? obj.launchLng ?? 0;
if (!lat || !lng) return null;
// ── kind + subType 기반 메타 결정 ──
const SUB_META: Record<string, Record<string, { icon: string; color: string; label: string }>> = {
hazard: {
petrochemical: { icon: '🏭', color: '#f97316', label: '석유화학단지' },
lng: { icon: '🔵', color: '#06b6d4', label: 'LNG저장기지' },
oilTank: { icon: '🛢️', color: '#eab308', label: '유류저장탱크' },
hazardPort: { icon: '⚠️', color: '#ef4444', label: '위험물항만하역' },
nuclear: { icon: '☢️', color: '#a855f7', label: '원자력발전소' },
thermal: { icon: '🔥', color: '#64748b', label: '화력발전소' },
shipyard: { icon: '🚢', color: '#0ea5e9', label: '조선소' },
wastewater: { icon: '💧', color: '#10b981', label: '폐수처리장' },
heavyIndustry: { icon: '⚙️', color: '#94a3b8', label: '시멘트/제철소' },
},
overseas: {
nuclear: { icon: '☢️', color: '#ef4444', label: '핵발전소' },
thermal: { icon: '🔥', color: '#f97316', label: '화력발전소' },
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
airbase: { icon: '✈️', color: '#22d3ee', label: '공군기지' },
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
shipyard:{ icon: '🚢', color: '#94a3b8', label: '조선소' },
},
militaryBase: {
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
airforce:{ icon: '✈️', color: '#22d3ee', label: '공군기지' },
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
missile: { icon: '🚀', color: '#ef4444', label: '미사일기지' },
joint: { icon: '⭐', color: '#f59e0b', label: '합동사령부' },
},
govBuilding: {
executive: { icon: '🏛️', color: '#f59e0b', label: '행정부' },
legislature: { icon: '🏛️', color: '#3b82f6', label: '입법부' },
military_hq: { icon: '⭐', color: '#ef4444', label: '군사령부' },
intelligence: { icon: '🔍', color: '#8b5cf6', label: '정보기관' },
foreign: { icon: '🌐', color: '#06b6d4', label: '외교부' },
maritime: { icon: '⚓', color: '#0ea5e9', label: '해양기관' },
defense: { icon: '🛡️', color: '#dc2626', label: '국방부' },
},
nkLaunch: {
icbm: { icon: '🚀', color: '#dc2626', label: 'ICBM 발사장' },
irbm: { icon: '🚀', color: '#ef4444', label: 'IRBM/MRBM' },
srbm: { icon: '🎯', color: '#f97316', label: '단거리탄도미사일' },
slbm: { icon: '🔱', color: '#3b82f6', label: 'SLBM 발사' },
cruise: { icon: '✈️', color: '#8b5cf6', label: '순항미사일' },
artillery: { icon: '💥', color: '#eab308', label: '해안포/장사정포' },
mlrs: { icon: '💥', color: '#f59e0b', label: '방사포(MLRS)' },
},
coastGuard: {
hq: { icon: '🏢', color: '#3b82f6', label: '본청' },
regional: { icon: '🏢', color: '#60a5fa', label: '지방청' },
station: { icon: '🚔', color: '#22d3ee', label: '해양경찰서' },
substation: { icon: '🏠', color: '#94a3b8', label: '파출소' },
vts: { icon: '📡', color: '#f59e0b', label: 'VTS센터' },
navy: { icon: '⚓', color: '#3b82f6', label: '해군부대' },
},
airport: {
international: { icon: '✈️', color: '#a78bfa', label: '국제공항' },
domestic: { icon: '🛩️', color: '#60a5fa', label: '국내공항' },
military: { icon: '✈️', color: '#ef4444', label: '군용비행장' },
},
navWarning: {
danger: { icon: '⚠️', color: '#ef4444', label: '위험' },
caution: { icon: '⚠️', color: '#eab308', label: '주의' },
info: { icon: '', color: '#3b82f6', label: '정보' },
},
piracy: {
critical: { icon: '☠️', color: '#ef4444', label: '극고위험' },
high: { icon: '☠️', color: '#f97316', label: '고위험' },
moderate: { icon: '☠️', color: '#eab308', label: '주의' },
},
};
const KIND_DEFAULT: Record<string, { icon: string; color: string; label: string }> = {
port: { icon: '⚓', color: '#3b82f6', label: '항구' },
windFarm: { icon: '🌀', color: '#00bcd4', label: '풍력단지' },
militaryBase: { icon: '🪖', color: '#ef4444', label: '군사시설' },
govBuilding: { icon: '🏛️', color: '#f59e0b', label: '정부기관' },
nkLaunch: { icon: '🚀', color: '#dc2626', label: '북한 발사장' },
nkMissile: { icon: '💣', color: '#ef4444', label: '미사일 낙하' },
coastGuard: { icon: '🚔', color: '#4dabf7', label: '해양경찰' },
airport: { icon: '✈️', color: '#a78bfa', label: '공항' },
navWarning: { icon: '⚠️', color: '#eab308', label: '항행경보' },
piracy: { icon: '☠️', color: '#ef4444', label: '해적위험' },
infra: { icon: '⚡', color: '#ffeb3b', label: '발전/변전' },
hazard: { icon: '⚠️', color: '#ef4444', label: '위험시설' },
cnFacility: { icon: '📍', color: '#ef4444', label: '중국시설' },
jpFacility: { icon: '📍', color: '#f472b6', label: '일본시설' },
};
// subType 키 결정
const subKey = obj.type ?? obj.subType ?? obj.level ?? '';
const subGroup = kind === 'cnFacility' || kind === 'jpFacility' ? 'overseas' : kind;
const meta = SUB_META[subGroup]?.[subKey] ?? KIND_DEFAULT[kind] ?? { icon: '📍', color: '#64748b', label: kind };
// 국가 플래그
const COUNTRY_FLAG: Record<string, string> = { CN: '🇨🇳', JP: '🇯🇵', KP: '🇰🇵', KR: '🇰🇷', TW: '🇹🇼' };
const flag = kind === 'cnFacility' ? '🇨🇳' : kind === 'jpFacility' ? '🇯🇵' : COUNTRY_FLAG[obj.country] ?? '';
const countryName = kind === 'cnFacility' ? '중국' : kind === 'jpFacility' ? '일본'
: { CN: '중국', JP: '일본', KP: '북한', KR: '한국', TW: '대만' }[obj.country] ?? '';
// 이름 결정
const title = obj.nameKo || obj.name || obj.launchNameKo || obj.title || kind;
return (
<Popup longitude={lng} latitude={lat} anchor="bottom"
onClose={() => setStaticPickInfo(null)} closeOnClick={false}
maxWidth="280px" className="gl-popup"
>
<div className="popup-body-sm" style={{ minWidth: 200 }}>
{/* 컬러 헤더 */}
<div className="popup-header" style={{ background: meta.color, color: '#000', gap: 6, padding: '4px 8px' }}>
<span>{meta.icon}</span> {title}
</div>
{/* 배지 행 */}
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
<span style={{
background: meta.color, color: '#000',
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
{meta.label}
</span>
{flag && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
{flag} {countryName}
</span>
)}
{kind === 'hazard' && (
<span style={{
background: 'rgba(239,68,68,0.15)', color: '#ef4444',
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 600,
border: '1px solid rgba(239,68,68,0.3)',
}}> </span>
)}
{kind === 'port' && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
{obj.type === 'major' ? '주요항' : '중소항'}
</span>
)}
{kind === 'airport' && obj.intl && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}></span>
)}
</div>
{/* 설명 */}
{obj.description && (
<div style={{ fontSize: 10, color: '#999', marginBottom: 4, lineHeight: 1.5 }}>{obj.description}</div>
)}
{obj.detail && (
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.detail}</div>
)}
{obj.note && (
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.note}</div>
)}
{/* 필드 그리드 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{obj.operator && <div><span className="popup-label">: </span>{obj.operator}</div>}
{obj.capacity && <div><span className="popup-label">: </span><strong>{obj.capacity}</strong></div>}
{obj.output && <div><span className="popup-label">: </span><strong>{obj.output}</strong></div>}
{obj.source && <div><span className="popup-label">: </span>{obj.source}</div>}
{obj.capacityMW && <div><span className="popup-label">: </span><strong>{obj.capacityMW}MW</strong></div>}
{obj.turbines && <div><span className="popup-label">: </span>{obj.turbines}</div>}
{obj.status && <div><span className="popup-label">: </span>{obj.status}</div>}
{obj.year && <div><span className="popup-label">: </span>{obj.year}</div>}
{obj.region && <div><span className="popup-label">: </span>{obj.region}</div>}
{obj.org && <div><span className="popup-label">: </span>{obj.org}</div>}
{obj.area && <div><span className="popup-label">: </span>{obj.area}</div>}
{obj.altitude && <div><span className="popup-label">: </span>{obj.altitude}</div>}
{obj.address && <div><span className="popup-label">: </span>{obj.address}</div>}
{obj.recentUse && <div><span className="popup-label"> : </span>{obj.recentUse}</div>}
{obj.recentIncidents != null && <div><span className="popup-label"> 1: </span><strong>{obj.recentIncidents}</strong></div>}
{obj.icao && <div><span className="popup-label">ICAO: </span>{obj.icao}</div>}
{kind === 'nkMissile' && (
<>
{obj.typeKo && <div><span className="popup-label">: </span>{obj.typeKo}</div>}
{obj.date && <div><span className="popup-label">: </span>{obj.date} {obj.time}</div>}
{obj.distanceKm && <div><span className="popup-label">: </span>{obj.distanceKm}km</div>}
{obj.altitudeKm && <div><span className="popup-label">: </span>{obj.altitudeKm}km</div>}
{obj.flightMin && <div><span className="popup-label">: </span>{obj.flightMin}</div>}
{obj.launchNameKo && <div><span className="popup-label">: </span>{obj.launchNameKo}</div>}
</>
)}
{obj.name && obj.nameKo && obj.name !== obj.nameKo && (
<div><span className="popup-label">: </span>{obj.name}</div>
)}
<div style={{ fontSize: 9, color: '#666', marginTop: 2 }}>
{lat.toFixed(4)}°N, {lng.toFixed(4)}°E
</div>
</div>
</div>
</Popup>
);
})()}
{staticPickInfo && (
<StaticFacilityPopup pickInfo={staticPickInfo} onClose={() => setStaticPickInfo(null)} />
)}
{layers.osint && <OsintMapLayer osintFeed={osintFeed} currentTime={currentTime} />}
{layers.eez && <EezLayer />}

파일 보기

@ -0,0 +1,207 @@
import { Popup } from 'react-map-gl/maplibre';
import type { StaticPickInfo } from '../../hooks/layers/types';
interface StaticFacilityPopupProps {
pickInfo: StaticPickInfo;
onClose: () => void;
}
const StaticFacilityPopup = ({ pickInfo, onClose }: StaticFacilityPopupProps) => {
const obj = pickInfo.object as any; // eslint-disable-line @typescript-eslint/no-explicit-any -- StaticPickedObject union requires loose access
const kind = pickInfo.kind;
const lat = obj.lat ?? obj.launchLat ?? 0;
const lng = obj.lng ?? obj.launchLng ?? 0;
if (!lat || !lng) return null;
// ── kind + subType 기반 메타 결정 ──
const SUB_META: Record<string, Record<string, { icon: string; color: string; label: string }>> = {
hazard: {
petrochemical: { icon: '🏭', color: '#f97316', label: '석유화학단지' },
lng: { icon: '🔵', color: '#06b6d4', label: 'LNG저장기지' },
oilTank: { icon: '🛢️', color: '#eab308', label: '유류저장탱크' },
hazardPort: { icon: '⚠️', color: '#ef4444', label: '위험물항만하역' },
nuclear: { icon: '☢️', color: '#a855f7', label: '원자력발전소' },
thermal: { icon: '🔥', color: '#64748b', label: '화력발전소' },
shipyard: { icon: '🚢', color: '#0ea5e9', label: '조선소' },
wastewater: { icon: '💧', color: '#10b981', label: '폐수처리장' },
heavyIndustry: { icon: '⚙️', color: '#94a3b8', label: '시멘트/제철소' },
},
overseas: {
nuclear: { icon: '☢️', color: '#ef4444', label: '핵발전소' },
thermal: { icon: '🔥', color: '#f97316', label: '화력발전소' },
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
airbase: { icon: '✈️', color: '#22d3ee', label: '공군기지' },
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
shipyard:{ icon: '🚢', color: '#94a3b8', label: '조선소' },
},
militaryBase: {
naval: { icon: '⚓', color: '#3b82f6', label: '해군기지' },
airforce:{ icon: '✈️', color: '#22d3ee', label: '공군기지' },
army: { icon: '🪖', color: '#22c55e', label: '육군기지' },
missile: { icon: '🚀', color: '#ef4444', label: '미사일기지' },
joint: { icon: '⭐', color: '#f59e0b', label: '합동사령부' },
},
govBuilding: {
executive: { icon: '🏛️', color: '#f59e0b', label: '행정부' },
legislature: { icon: '🏛️', color: '#3b82f6', label: '입법부' },
military_hq: { icon: '⭐', color: '#ef4444', label: '군사령부' },
intelligence: { icon: '🔍', color: '#8b5cf6', label: '정보기관' },
foreign: { icon: '🌐', color: '#06b6d4', label: '외교부' },
maritime: { icon: '⚓', color: '#0ea5e9', label: '해양기관' },
defense: { icon: '🛡️', color: '#dc2626', label: '국방부' },
},
nkLaunch: {
icbm: { icon: '🚀', color: '#dc2626', label: 'ICBM 발사장' },
irbm: { icon: '🚀', color: '#ef4444', label: 'IRBM/MRBM' },
srbm: { icon: '🎯', color: '#f97316', label: '단거리탄도미사일' },
slbm: { icon: '🔱', color: '#3b82f6', label: 'SLBM 발사' },
cruise: { icon: '✈️', color: '#8b5cf6', label: '순항미사일' },
artillery: { icon: '💥', color: '#eab308', label: '해안포/장사정포' },
mlrs: { icon: '💥', color: '#f59e0b', label: '방사포(MLRS)' },
},
coastGuard: {
hq: { icon: '🏢', color: '#3b82f6', label: '본청' },
regional: { icon: '🏢', color: '#60a5fa', label: '지방청' },
station: { icon: '🚔', color: '#22d3ee', label: '해양경찰서' },
substation: { icon: '🏠', color: '#94a3b8', label: '파출소' },
vts: { icon: '📡', color: '#f59e0b', label: 'VTS센터' },
navy: { icon: '⚓', color: '#3b82f6', label: '해군부대' },
},
airport: {
international: { icon: '✈️', color: '#a78bfa', label: '국제공항' },
domestic: { icon: '🛩️', color: '#60a5fa', label: '국내공항' },
military: { icon: '✈️', color: '#ef4444', label: '군용비행장' },
},
navWarning: {
danger: { icon: '⚠️', color: '#ef4444', label: '위험' },
caution: { icon: '⚠️', color: '#eab308', label: '주의' },
info: { icon: '', color: '#3b82f6', label: '정보' },
},
piracy: {
critical: { icon: '☠️', color: '#ef4444', label: '극고위험' },
high: { icon: '☠️', color: '#f97316', label: '고위험' },
moderate: { icon: '☠️', color: '#eab308', label: '주의' },
},
};
const KIND_DEFAULT: Record<string, { icon: string; color: string; label: string }> = {
port: { icon: '⚓', color: '#3b82f6', label: '항구' },
windFarm: { icon: '🌀', color: '#00bcd4', label: '풍력단지' },
militaryBase: { icon: '🪖', color: '#ef4444', label: '군사시설' },
govBuilding: { icon: '🏛️', color: '#f59e0b', label: '정부기관' },
nkLaunch: { icon: '🚀', color: '#dc2626', label: '북한 발사장' },
nkMissile: { icon: '💣', color: '#ef4444', label: '미사일 낙하' },
coastGuard: { icon: '🚔', color: '#4dabf7', label: '해양경찰' },
airport: { icon: '✈️', color: '#a78bfa', label: '공항' },
navWarning: { icon: '⚠️', color: '#eab308', label: '항행경보' },
piracy: { icon: '☠️', color: '#ef4444', label: '해적위험' },
infra: { icon: '⚡', color: '#ffeb3b', label: '발전/변전' },
hazard: { icon: '⚠️', color: '#ef4444', label: '위험시설' },
cnFacility: { icon: '📍', color: '#ef4444', label: '중국시설' },
jpFacility: { icon: '📍', color: '#f472b6', label: '일본시설' },
};
// subType 키 결정
const subKey = obj.type ?? obj.subType ?? obj.level ?? '';
const subGroup = kind === 'cnFacility' || kind === 'jpFacility' ? 'overseas' : kind;
const meta = SUB_META[subGroup]?.[subKey] ?? KIND_DEFAULT[kind] ?? { icon: '📍', color: '#64748b', label: kind };
// 국가 플래그
const COUNTRY_FLAG: Record<string, string> = { CN: '🇨🇳', JP: '🇯🇵', KP: '🇰🇵', KR: '🇰🇷', TW: '🇹🇼' };
const flag = kind === 'cnFacility' ? '🇨🇳' : kind === 'jpFacility' ? '🇯🇵' : COUNTRY_FLAG[obj.country] ?? '';
const countryName = kind === 'cnFacility' ? '중국' : kind === 'jpFacility' ? '일본'
: { CN: '중국', JP: '일본', KP: '북한', KR: '한국', TW: '대만' }[obj.country] ?? '';
// 이름 결정
const title = obj.nameKo || obj.name || obj.launchNameKo || obj.title || kind;
return (
<Popup longitude={lng} latitude={lat} anchor="bottom"
onClose={onClose} closeOnClick={false}
maxWidth="280px" className="gl-popup"
>
<div className="popup-body-sm" style={{ minWidth: 200 }}>
{/* 컬러 헤더 */}
<div className="popup-header" style={{ background: meta.color, color: '#000', gap: 6, padding: '4px 8px' }}>
<span>{meta.icon}</span> {title}
</div>
{/* 배지 행 */}
<div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
<span style={{
background: meta.color, color: '#000',
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 700,
}}>
{meta.label}
</span>
{flag && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
{flag} {countryName}
</span>
)}
{kind === 'hazard' && (
<span style={{
background: 'rgba(239,68,68,0.15)', color: '#ef4444',
padding: '1px 6px', borderRadius: 3, fontSize: 10, fontWeight: 600,
border: '1px solid rgba(239,68,68,0.3)',
}}> </span>
)}
{kind === 'port' && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}>
{obj.type === 'major' ? '주요항' : '중소항'}
</span>
)}
{kind === 'airport' && obj.intl && (
<span style={{ background: '#333', color: '#ccc', padding: '1px 6px', borderRadius: 3, fontSize: 10 }}></span>
)}
</div>
{/* 설명 */}
{obj.description && (
<div style={{ fontSize: 10, color: '#999', marginBottom: 4, lineHeight: 1.5 }}>{obj.description}</div>
)}
{obj.detail && (
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.detail}</div>
)}
{obj.note && (
<div style={{ fontSize: 10, color: '#888', marginBottom: 4, lineHeight: 1.4 }}>{obj.note}</div>
)}
{/* 필드 그리드 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{obj.operator && <div><span className="popup-label">: </span>{obj.operator}</div>}
{obj.capacity && <div><span className="popup-label">: </span><strong>{obj.capacity}</strong></div>}
{obj.output && <div><span className="popup-label">: </span><strong>{obj.output}</strong></div>}
{obj.source && <div><span className="popup-label">: </span>{obj.source}</div>}
{obj.capacityMW && <div><span className="popup-label">: </span><strong>{obj.capacityMW}MW</strong></div>}
{obj.turbines && <div><span className="popup-label">: </span>{obj.turbines}</div>}
{obj.status && <div><span className="popup-label">: </span>{obj.status}</div>}
{obj.year && <div><span className="popup-label">: </span>{obj.year}</div>}
{obj.region && <div><span className="popup-label">: </span>{obj.region}</div>}
{obj.org && <div><span className="popup-label">: </span>{obj.org}</div>}
{obj.area && <div><span className="popup-label">: </span>{obj.area}</div>}
{obj.altitude && <div><span className="popup-label">: </span>{obj.altitude}</div>}
{obj.address && <div><span className="popup-label">: </span>{obj.address}</div>}
{obj.recentUse && <div><span className="popup-label"> : </span>{obj.recentUse}</div>}
{obj.recentIncidents != null && <div><span className="popup-label"> 1: </span><strong>{obj.recentIncidents}</strong></div>}
{obj.icao && <div><span className="popup-label">ICAO: </span>{obj.icao}</div>}
{kind === 'nkMissile' && (
<>
{obj.typeKo && <div><span className="popup-label">: </span>{obj.typeKo}</div>}
{obj.date && <div><span className="popup-label">: </span>{obj.date} {obj.time}</div>}
{obj.distanceKm && <div><span className="popup-label">: </span>{obj.distanceKm}km</div>}
{obj.altitudeKm && <div><span className="popup-label">: </span>{obj.altitudeKm}km</div>}
{obj.flightMin && <div><span className="popup-label">: </span>{obj.flightMin}</div>}
{obj.launchNameKo && <div><span className="popup-label">: </span>{obj.launchNameKo}</div>}
</>
)}
{obj.name && obj.nameKo && obj.name !== obj.nameKo && (
<div><span className="popup-label">: </span>{obj.name}</div>
)}
<div style={{ fontSize: 9, color: '#666', marginTop: 2 }}>
{lat.toFixed(4)}°N, {lng.toFixed(4)}°E
</div>
</div>
</div>
</Popup>
);
};
export { StaticFacilityPopup };