diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index a46dd8c..9239ad7 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -101,6 +101,10 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
militaryOnly: false,
overseasChina: false,
overseasJapan: false,
+ cnPower: false,
+ cnMilitary: false,
+ jpPower: false,
+ jpMilitary: false,
hazardPetrochemical: false,
hazardLng: false,
hazardOilTank: false,
@@ -640,8 +644,20 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
{ key: 'industryHeavy', label: '시멘트/제철소', color: '#94a3b8', count: 5, group: '산업공정/제조시설' },
]}
overseasItems={[
- { key: 'overseasChina', label: '🇨🇳 중국', color: '#ef4444' },
- { key: 'overseasJapan', label: '🇯🇵 일본', color: '#f472b6' },
+ {
+ key: 'overseasChina', label: '🇨🇳 중국', color: '#ef4444',
+ children: [
+ { key: 'cnPower', label: '발전소', color: '#a855f7' },
+ { key: 'cnMilitary', label: '주요군사시설', color: '#ef4444' },
+ ],
+ },
+ {
+ key: 'overseasJapan', label: '🇯🇵 일본', color: '#f472b6',
+ children: [
+ { key: 'jpPower', label: '발전소', color: '#a855f7' },
+ { key: 'jpMilitary', label: '주요군사시설', color: '#ef4444' },
+ ],
+ },
]}
hiddenAcCategories={hiddenAcCategories}
hiddenShipCategories={hiddenShipCategories}
diff --git a/frontend/src/components/common/LayerPanel.tsx b/frontend/src/components/common/LayerPanel.tsx
index fc85524..ee0c1da 100644
--- a/frontend/src/components/common/LayerPanel.tsx
+++ b/frontend/src/components/common/LayerPanel.tsx
@@ -124,6 +124,7 @@ interface OverseasItem {
key: string;
label: string;
color: string;
+ children?: OverseasItem[];
}
interface LayerPanelProps {
@@ -552,14 +553,32 @@ export function LayerPanel({
{expanded.has('militaryOnly') && overseasItems && overseasItems.length > 0 && (
{overseasItems.map(item => (
-
onToggle(item.key)}
- />
+
+
onToggle(item.key)}
+ onExpand={() => toggleExpand(`overseas-${item.key}`)}
+ />
+ {item.children?.length && expanded.has(`overseas-${item.key}`) && (
+
+ {item.children.map(child => (
+ onToggle(child.key)}
+ />
+ ))}
+
+ )}
+
))}
)}
diff --git a/frontend/src/components/korea/CnFacilityLayer.tsx b/frontend/src/components/korea/CnFacilityLayer.tsx
new file mode 100644
index 0000000..9209555
--- /dev/null
+++ b/frontend/src/components/korea/CnFacilityLayer.tsx
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+import { Marker, Popup } from 'react-map-gl/maplibre';
+import { CN_POWER_PLANTS, CN_MILITARY_FACILITIES, type CnFacility } from '../../data/cnFacilities';
+
+interface Props {
+ type: 'power' | 'military';
+}
+
+const SUBTYPE_META: Record = {
+ nuclear: { color: '#a855f7', icon: '☢', label: '핵발전' },
+ thermal: { color: '#f97316', icon: '⚡', label: '화력발전' },
+ naval: { color: '#3b82f6', icon: '⚓', label: '해군기지' },
+ airbase: { color: '#22d3ee', icon: '✈', label: '공군기지' },
+ army: { color: '#ef4444', icon: '★', label: '육군/사령부' },
+ shipyard: { color: '#f59e0b', icon: '⚙', label: '조선소' },
+};
+
+export function CnFacilityLayer({ type }: Props) {
+ const [popup, setPopup] = useState(null);
+ const facilities = type === 'power' ? CN_POWER_PLANTS : CN_MILITARY_FACILITIES;
+
+ return (
+ <>
+ {facilities.map(f => {
+ const meta = SUBTYPE_META[f.subType];
+ return (
+ { e.originalEvent.stopPropagation(); setPopup(f); }}
+ >
+
+ {meta.icon}
+
+
+ );
+ })}
+
+ {popup && (
+ setPopup(null)}
+ closeOnClick={false}
+ maxWidth="220px"
+ >
+
+
{popup.name}
+
+ {SUBTYPE_META[popup.subType].label}
+
+ {popup.operator && (
+
운영: {popup.operator}
+ )}
+ {popup.description && (
+
{popup.description}
+ )}
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/components/korea/JpFacilityLayer.tsx b/frontend/src/components/korea/JpFacilityLayer.tsx
new file mode 100644
index 0000000..0725ec0
--- /dev/null
+++ b/frontend/src/components/korea/JpFacilityLayer.tsx
@@ -0,0 +1,81 @@
+import { useState } from 'react';
+import { Marker, Popup } from 'react-map-gl/maplibre';
+import { JP_POWER_PLANTS, JP_MILITARY_FACILITIES, type JpFacility } from '../../data/jpFacilities';
+
+interface Props {
+ type: 'power' | 'military';
+}
+
+const SUBTYPE_META: Record = {
+ nuclear: { color: '#a855f7', icon: '☢', label: '핵발전' },
+ thermal: { color: '#f97316', icon: '⚡', label: '화력발전' },
+ naval: { color: '#3b82f6', icon: '⚓', label: '해군기지' },
+ airbase: { color: '#22d3ee', icon: '✈', label: '공군기지' },
+ army: { color: '#ef4444', icon: '★', label: '육군' },
+};
+
+export function JpFacilityLayer({ type }: Props) {
+ const [popup, setPopup] = useState(null);
+ const facilities = type === 'power' ? JP_POWER_PLANTS : JP_MILITARY_FACILITIES;
+
+ return (
+ <>
+ {facilities.map(f => {
+ const meta = SUBTYPE_META[f.subType];
+ return (
+ { e.originalEvent.stopPropagation(); setPopup(f); }}
+ >
+
+ {meta.icon}
+
+
+ );
+ })}
+
+ {popup && (
+ setPopup(null)}
+ closeOnClick={false}
+ maxWidth="220px"
+ >
+
+
{popup.name}
+
+ {SUBTYPE_META[popup.subType].label}
+
+ {popup.operator && (
+
운영: {popup.operator}
+ )}
+ {popup.description && (
+
{popup.description}
+ )}
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/components/korea/KoreaMap.tsx b/frontend/src/components/korea/KoreaMap.tsx
index 6991a87..6a79913 100644
--- a/frontend/src/components/korea/KoreaMap.tsx
+++ b/frontend/src/components/korea/KoreaMap.tsx
@@ -22,6 +22,8 @@ import { NKLaunchLayer } from './NKLaunchLayer';
import { NKMissileEventLayer } from './NKMissileEventLayer';
import { ChineseFishingOverlay } from './ChineseFishingOverlay';
import { HazardFacilityLayer } from './HazardFacilityLayer';
+import { CnFacilityLayer } from './CnFacilityLayer';
+import { JpFacilityLayer } from './JpFacilityLayer';
import { fetchKoreaInfra } from '../../services/infra';
import type { PowerFacility } from '../../services/infra';
import type { Ship, Aircraft, SatellitePosition } from '../../types';
@@ -279,6 +281,10 @@ export function KoreaMap({ ships, aircraft, satellites, layers, osintFeed, curre
{layers.industryShipyard && }
{layers.industryWastewater && }
{layers.industryHeavy && }
+ {layers.cnPower && }
+ {layers.cnMilitary && }
+ {layers.jpPower && }
+ {layers.jpMilitary && }
{layers.airports && }
{layers.coastGuard && }
{layers.navWarning && }
diff --git a/frontend/src/data/cnFacilities.ts b/frontend/src/data/cnFacilities.ts
new file mode 100644
index 0000000..c7b9dec
--- /dev/null
+++ b/frontend/src/data/cnFacilities.ts
@@ -0,0 +1,141 @@
+export interface CnFacility {
+ id: string;
+ name: string;
+ subType: 'nuclear' | 'thermal' | 'naval' | 'airbase' | 'army' | 'shipyard';
+ lat: number;
+ lng: number;
+ operator?: string;
+ description?: string;
+}
+
+export const CN_POWER_PLANTS: CnFacility[] = [
+ {
+ id: 'cn-npp-hongyanhe',
+ name: '홍옌허(红沿河) 핵발전소',
+ subType: 'nuclear',
+ lat: 40.87,
+ lng: 121.02,
+ operator: '中国大唐集团',
+ description: '가압경수로 6기, 라오닝성 — 한반도 최근접 핵발전소',
+ },
+ {
+ id: 'cn-npp-tianwan',
+ name: '톈완(田湾) 핵발전소',
+ subType: 'nuclear',
+ lat: 34.71,
+ lng: 119.45,
+ operator: '江苏核电',
+ description: '러시아 VVER-1000 설계, 장쑤성',
+ },
+ {
+ id: 'cn-npp-qinshan',
+ name: '진산(秦山) 핵발전소',
+ subType: 'nuclear',
+ lat: 30.44,
+ lng: 120.96,
+ operator: '中核集团',
+ description: '중국 최초 상업 핵발전소, 저장성',
+ },
+ {
+ id: 'cn-npp-ningde',
+ name: '닝더(宁德) 핵발전소',
+ subType: 'nuclear',
+ lat: 26.73,
+ lng: 120.12,
+ operator: '中国大唐集团',
+ description: '가압경수로 4기, 푸젠성',
+ },
+ {
+ id: 'cn-thermal-dalian',
+ name: '다롄 화력발전소',
+ subType: 'thermal',
+ lat: 38.85,
+ lng: 121.55,
+ operator: '大连电力',
+ description: '석탄화력, 라오닝성',
+ },
+ {
+ id: 'cn-thermal-qinhuangdao',
+ name: '친황다오 화력발전소',
+ subType: 'thermal',
+ lat: 39.93,
+ lng: 119.58,
+ operator: '华能国际',
+ description: '석탄화력 대형 기지, 허베이성',
+ },
+ {
+ id: 'cn-thermal-tianjin',
+ name: '톈진 화력발전소',
+ subType: 'thermal',
+ lat: 39.08,
+ lng: 117.20,
+ operator: '华能集团',
+ description: '석탄화력, 톈진시',
+ },
+];
+
+export const CN_MILITARY_FACILITIES: CnFacility[] = [
+ {
+ id: 'cn-mil-qingdao',
+ name: '칭다오 해군기지',
+ subType: 'naval',
+ lat: 36.07,
+ lng: 120.26,
+ operator: '해군 북부전구',
+ description: '항모전단 모항, 핵잠수함 기지',
+ },
+ {
+ id: 'cn-mil-lushun',
+ name: '뤼순(旅順) 해군기지',
+ subType: 'naval',
+ lat: 38.85,
+ lng: 121.24,
+ operator: '해군 북부전구',
+ description: '잠수함·구축함 기지, 보하이만 입구',
+ },
+ {
+ id: 'cn-mil-dalian-shipyard',
+ name: '다롄 조선소 (항모건조)',
+ subType: 'shipyard',
+ lat: 38.92,
+ lng: 121.62,
+ operator: '中国船舶重工',
+ description: '랴오닝함·산둥함 건조, 항모 4번함 건조 중',
+ },
+ {
+ id: 'cn-mil-shenyang-cmd',
+ name: '북부전구 사령부',
+ subType: 'army',
+ lat: 41.80,
+ lng: 123.42,
+ operator: '해방군 북부전구',
+ description: '한반도·동북아 담당, 선양시',
+ },
+ {
+ id: 'cn-mil-shenyang-air',
+ name: '선양 공군기지',
+ subType: 'airbase',
+ lat: 41.77,
+ lng: 123.49,
+ operator: '공군 북부전구',
+ description: 'J-16 전투기 배치, 북부전구 핵심기지',
+ },
+ {
+ id: 'cn-mil-dandong',
+ name: '단둥 군사시설',
+ subType: 'army',
+ lat: 40.13,
+ lng: 124.38,
+ operator: '해방군 육군',
+ description: '북중 접경 전진기지, 한반도 작전 담당',
+ },
+ {
+ id: 'cn-mil-zhoushan',
+ name: '저우산 해군기지',
+ subType: 'naval',
+ lat: 30.00,
+ lng: 122.10,
+ operator: '해군 동부전구',
+ description: '동중국해 주력 함대 기지',
+ },
+];
diff --git a/frontend/src/data/jpFacilities.ts b/frontend/src/data/jpFacilities.ts
new file mode 100644
index 0000000..e7d4f60
--- /dev/null
+++ b/frontend/src/data/jpFacilities.ts
@@ -0,0 +1,150 @@
+export interface JpFacility {
+ id: string;
+ name: string;
+ subType: 'nuclear' | 'thermal' | 'naval' | 'airbase' | 'army';
+ lat: number;
+ lng: number;
+ operator?: string;
+ description?: string;
+}
+
+export const JP_POWER_PLANTS: JpFacility[] = [
+ {
+ id: 'jp-npp-genkai',
+ name: '겐카이(玄海) 핵발전소',
+ subType: 'nuclear',
+ lat: 33.52,
+ lng: 129.84,
+ operator: '규슈전력',
+ description: '가압경수로 4기, 사가현 — 한반도 최근접 원전',
+ },
+ {
+ id: 'jp-npp-sendai',
+ name: '센다이(川内) 핵발전소',
+ subType: 'nuclear',
+ lat: 31.84,
+ lng: 130.19,
+ operator: '규슈전력',
+ description: '가압경수로 2기, 가고시마현',
+ },
+ {
+ id: 'jp-npp-ohi',
+ name: '오이(大飯) 핵발전소',
+ subType: 'nuclear',
+ lat: 35.53,
+ lng: 135.65,
+ operator: '간사이전력',
+ description: '가압경수로 4기, 후쿠이현 — 일본 최대 출력',
+ },
+ {
+ id: 'jp-npp-takahama',
+ name: '다카하마(高浜) 핵발전소',
+ subType: 'nuclear',
+ lat: 35.51,
+ lng: 135.50,
+ operator: '간사이전력',
+ description: '가압경수로 4기, 후쿠이현',
+ },
+ {
+ id: 'jp-npp-shika',
+ name: '시카(志賀) 핵발전소',
+ subType: 'nuclear',
+ lat: 37.07,
+ lng: 136.72,
+ operator: '호쿠리쿠전력',
+ description: '비등수형경수로 2기, 이시카와현 (2024 지진 피해)',
+ },
+ {
+ id: 'jp-npp-higashidori',
+ name: '히가시도리(東通) 핵발전소',
+ subType: 'nuclear',
+ lat: 41.18,
+ lng: 141.37,
+ operator: '도호쿠전력',
+ description: '비등수형경수로, 아오모리현',
+ },
+ {
+ id: 'jp-thermal-matsuura',
+ name: '마쓰우라(松浦) 화력발전소',
+ subType: 'thermal',
+ lat: 33.33,
+ lng: 129.73,
+ operator: '전원개발(J-Power)',
+ description: '석탄화력, 나가사키현 — 대한해협 인접',
+ },
+ {
+ id: 'jp-thermal-hekinan',
+ name: '헤키난(碧南) 화력발전소',
+ subType: 'thermal',
+ lat: 34.87,
+ lng: 136.95,
+ operator: '주부전력',
+ description: '석탄화력, 아이치현 — 일본 최대 석탄화력',
+ },
+];
+
+export const JP_MILITARY_FACILITIES: JpFacility[] = [
+ {
+ id: 'jp-mil-sasebo',
+ name: '사세보(佐世保) 해군기지',
+ subType: 'naval',
+ lat: 33.16,
+ lng: 129.72,
+ operator: '미 해군 / 해상자위대',
+ description: '미 7함대 상륙전단 모항, 한국 최근접 미군기지',
+ },
+ {
+ id: 'jp-mil-maizuru',
+ name: '마이즈루(舞鶴) 해군기지',
+ subType: 'naval',
+ lat: 35.47,
+ lng: 135.38,
+ operator: '해상자위대',
+ description: '동해 방면 주력기지, 호위함대 사령부',
+ },
+ {
+ id: 'jp-mil-yokosuka',
+ name: '요코스카(横須賀) 해군기지',
+ subType: 'naval',
+ lat: 35.29,
+ lng: 139.67,
+ operator: '미 해군 / 해상자위대',
+ description: '미 7함대 사령부, 항모 로널드 레이건 모항',
+ },
+ {
+ id: 'jp-mil-iwakuni',
+ name: '이와쿠니(岩国) 공군기지',
+ subType: 'airbase',
+ lat: 34.15,
+ lng: 132.24,
+ operator: '미 해병대 / 항공자위대',
+ description: 'F/A-18 및 F-35B 배치, 야마구치현',
+ },
+ {
+ id: 'jp-mil-kadena',
+ name: '가데나(嘉手納) 공군기지',
+ subType: 'airbase',
+ lat: 26.36,
+ lng: 127.77,
+ operator: '미 공군',
+ description: 'F-15C/D, KC-135 배치, 아시아 최대 미 공군기지',
+ },
+ {
+ id: 'jp-mil-ashiya',
+ name: '아시야(芦屋) 항공기지',
+ subType: 'airbase',
+ lat: 33.88,
+ lng: 130.66,
+ operator: '항공자위대',
+ description: '대한해협 인접, 후쿠오카현',
+ },
+ {
+ id: 'jp-mil-naha',
+ name: '나하(那覇) 항공기지',
+ subType: 'airbase',
+ lat: 26.21,
+ lng: 127.65,
+ operator: '항공자위대',
+ description: 'F-15 배치, 남서항공방면대 사령부',
+ },
+];