feat(layer): 위험/산업 인프라 레이어 그룹 및 UI 개선
- 위험시설: 석유화학단지(5), LNG기지(10), 유류탱크(15), 위험물항만(6) 추가 - 에너지/발전시설: 원자력(5), 화력(5) 추가; 발전/변전·풍력단지 그룹 이동 - 산업공정/제조시설: 조선소(6), 폐수처리(5), 시멘트/제철소(5) 추가 - 위험/산업 인프라 수퍼그룹 신설 (3단계 계층 구조) - LayerPanel: 레이어 수량을 우측 숫자 뱃지로 표시 (괄호 제거) - 해외시설 하위항목: 이란탭=호르무즈 10개국, 한국탭=중국·일본 - EventLog: 재난/안전뉴스 섹션 추가 (한국탭), OSINT 접기/펼치기 - OSINT 뉴스 2026-03-21 기준으로 업데이트 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
83b3d80c6d
커밋
e18a1a4932
@ -65,6 +65,16 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
|
||||
oilFacilities: true,
|
||||
meFacilities: true,
|
||||
militaryOnly: false,
|
||||
overseasUS: false,
|
||||
overseasUK: false,
|
||||
overseasIran: false,
|
||||
overseasUAE: false,
|
||||
overseasSaudi: false,
|
||||
overseasOman: false,
|
||||
overseasQatar: false,
|
||||
overseasKuwait: false,
|
||||
overseasIraq: false,
|
||||
overseasBahrain: false,
|
||||
});
|
||||
|
||||
// Korea tab layer visibility (lifted from KoreaMap)
|
||||
@ -89,6 +99,17 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
|
||||
nkMissile: true,
|
||||
cnFishing: false,
|
||||
militaryOnly: false,
|
||||
overseasChina: false,
|
||||
overseasJapan: false,
|
||||
hazardPetrochemical: false,
|
||||
hazardLng: false,
|
||||
hazardOilTank: false,
|
||||
hazardPort: false,
|
||||
energyNuclear: false,
|
||||
energyThermal: false,
|
||||
industryShipyard: false,
|
||||
industryWastewater: false,
|
||||
industryHeavy: false,
|
||||
});
|
||||
|
||||
const toggleKoreaLayer = useCallback((key: string) => {
|
||||
@ -456,6 +477,18 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
|
||||
{ key: 'meFacilities', label: '주요시설/군사', color: '#ef4444', count: 35 },
|
||||
{ key: 'sensorCharts', label: t('layers.sensorCharts'), color: '#22c55e' },
|
||||
]}
|
||||
overseasItems={[
|
||||
{ key: 'overseasUS', label: '🇺🇸 미국', color: '#3b82f6' },
|
||||
{ key: 'overseasUK', label: '🇬🇧 영국', color: '#dc2626' },
|
||||
{ key: 'overseasIran', label: '🇮🇷 이란', color: '#22c55e' },
|
||||
{ key: 'overseasUAE', label: '🇦🇪 UAE', color: '#f59e0b' },
|
||||
{ key: 'overseasSaudi', label: '🇸🇦 사우디아라비아', color: '#84cc16' },
|
||||
{ key: 'overseasOman', label: '🇴🇲 오만', color: '#e11d48' },
|
||||
{ key: 'overseasQatar', label: '🇶🇦 카타르', color: '#8b5cf6' },
|
||||
{ key: 'overseasKuwait', label: '🇰🇼 쿠웨이트', color: '#f97316' },
|
||||
{ key: 'overseasIraq', label: '🇮🇶 이라크', color: '#65a30d' },
|
||||
{ key: 'overseasBahrain', label: '🇧🇭 바레인', color: '#e11d48' },
|
||||
]}
|
||||
hiddenAcCategories={hiddenAcCategories}
|
||||
hiddenShipCategories={hiddenShipCategories}
|
||||
onAcCategoryToggle={toggleAcCategory}
|
||||
@ -585,15 +618,30 @@ function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) {
|
||||
{ key: 'eez', label: t('layers.eez'), color: '#3b82f6', group: '해양안전' },
|
||||
{ key: 'piracy', label: t('layers.piracy'), color: '#ef4444', group: '해양안전' },
|
||||
{ key: 'nkMissile', label: '🚀 미사일 낙하', color: '#ef4444', count: 4, group: '해양안전' },
|
||||
{ key: 'cnFishing', label: '🎣 중국어선 어구', color: '#f97316', group: '해양안전' },
|
||||
// 국가기관망
|
||||
{ key: 'infra', label: t('layers.infra'), color: '#ffc107', group: '국가기관망' },
|
||||
{ key: 'infra', label: t('layers.infra'), color: '#ffc107', group: '에너지/발전시설' },
|
||||
{ key: 'airports', label: t('layers.airports'), color: '#a78bfa', count: 59, group: '국가기관망' },
|
||||
{ key: 'windFarm', label: '풍력단지', color: '#00bcd4', count: 8, group: '국가기관망' },
|
||||
{ key: 'windFarm', label: '풍력단지', color: '#00bcd4', count: 8, group: '에너지/발전시설' },
|
||||
{ key: 'ports', label: '항구', color: '#3b82f6', count: 46, group: '국가기관망' },
|
||||
{ key: 'militaryBases', label: '군사시설', color: '#ef4444', count: 38, group: '국가기관망' },
|
||||
{ key: 'govBuildings', label: '정부기관', color: '#f59e0b', count: 32, group: '국가기관망' },
|
||||
{ key: 'nkLaunch', label: '🇰🇵 발사/포병진지', color: '#dc2626', count: 19, group: '국가기관망' },
|
||||
{ key: 'nkLaunch', label: '🇰🇵 발사/포병진지', color: '#dc2626', count: 19, group: '해양안전' },
|
||||
// 위험시설
|
||||
{ key: 'hazardPetrochemical', label: '해안인접석유화학단지', color: '#f97316', count: 5, group: '위험시설' },
|
||||
{ key: 'hazardLng', label: 'LNG저장기지', color: '#06b6d4', count: 10, group: '위험시설' },
|
||||
{ key: 'hazardOilTank', label: '유류저장탱크', color: '#eab308', count: 15, group: '위험시설' },
|
||||
{ key: 'hazardPort', label: '위험물항만하역시설', color: '#ef4444', count: 6, group: '위험시설' },
|
||||
// 에너지/발전시설
|
||||
{ key: 'energyNuclear', label: '원자력발전', color: '#a855f7', count: 5, group: '에너지/발전시설' },
|
||||
{ key: 'energyThermal', label: '화력발전소', color: '#64748b', count: 5, group: '에너지/발전시설' },
|
||||
// 산업공정/제조시설
|
||||
{ key: 'industryShipyard', label: '조선소 도장시설', color: '#0ea5e9', count: 6, group: '산업공정/제조시설' },
|
||||
{ key: 'industryWastewater', label: '폐수/하수처리장', color: '#10b981', count: 5, group: '산업공정/제조시설' },
|
||||
{ key: 'industryHeavy', label: '시멘트/제철소', color: '#94a3b8', count: 5, group: '산업공정/제조시설' },
|
||||
]}
|
||||
overseasItems={[
|
||||
{ key: 'overseasChina', label: '🇨🇳 중국', color: '#ef4444' },
|
||||
{ key: 'overseasJapan', label: '🇯🇵 일본', color: '#f472b6' },
|
||||
]}
|
||||
hiddenAcCategories={hiddenAcCategories}
|
||||
hiddenShipCategories={hiddenShipCategories}
|
||||
|
||||
@ -2,6 +2,7 @@ import { useMemo, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { GeoEvent, Ship } from '../../types';
|
||||
import type { OsintItem } from '../../services/osint';
|
||||
import { getDisasterNews, getDisasterCatIcon, getDisasterCatColor } from '../../services/disasterNews';
|
||||
import { getMarineTrafficCategory } from '../../utils/marineTraffic';
|
||||
import { aggregateFishingStats, GEAR_LABELS } from '../../utils/fishingAnalysis';
|
||||
import type { FishingGearType } from '../../utils/fishingAnalysis';
|
||||
@ -329,7 +330,7 @@ const NEWS_CATEGORY_COLORS: Record<BreakingNews['category'], string> = {
|
||||
};
|
||||
|
||||
const EMPTY_OSINT: OsintItem[] = [];
|
||||
const EMPTY_SHIPS: import('../types').Ship[] = [];
|
||||
const EMPTY_SHIPS: Ship[] = [];
|
||||
|
||||
function useTimeAgo() {
|
||||
const { t } = useTranslation('common');
|
||||
@ -597,7 +598,7 @@ export function EventLog({ events, currentTime, totalShipCount: _totalShipCount,
|
||||
<span className="area-ship-icon">{'\u{1F1F0}\u{1F1F7}'}</span>
|
||||
<span className="area-ship-title">{t('ships:shipStatus.koreanTitle')}</span>
|
||||
<span className="area-ship-total">{koreanShips.length}{t('common:units.vessels')}</span>
|
||||
{onToggleHighlightKorean && dashboardTab === 'iran' && (
|
||||
{onToggleHighlightKorean && (dashboardTab as string) === 'iran' && (
|
||||
<button
|
||||
type="button"
|
||||
className={`korean-highlight-toggle ${highlightKoreanShips ? 'active' : ''}`}
|
||||
@ -761,6 +762,57 @@ export function EventLog({ events, currentTime, totalShipCount: _totalShipCount,
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* 재난/안전뉴스 */}
|
||||
{isLive && (() => {
|
||||
const disasterItems = getDisasterNews();
|
||||
return (
|
||||
<>
|
||||
<div className="osint-header" style={{ cursor: 'pointer' }} onClick={() => toggleCollapse('disaster-news')}>
|
||||
<span style={{ fontSize: 8, color: 'var(--kcg-dim)', width: 12, textAlign: 'center' }}>{collapsed.has('disaster-news') ? '▶' : '▼'}</span>
|
||||
<span style={{ fontSize: 11 }}>🚨</span>
|
||||
<span className="osint-title">재난/안전뉴스</span>
|
||||
<span className="osint-count">{disasterItems.length}</span>
|
||||
<a
|
||||
href="https://www.safekorea.go.kr/idsiSFK/neo/sfk/cs/sfc/dis/disasterNewsList.jsp?menuSeq=619"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginLeft: 'auto', fontSize: 9, color: 'var(--kcg-dim)', textDecoration: 'none' }}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
안전코리아 ↗
|
||||
</a>
|
||||
</div>
|
||||
{!collapsed.has('disaster-news') && (
|
||||
<div className="osint-list" style={{ maxHeight: 310, overflowY: 'auto' }}>
|
||||
{disasterItems.map(item => {
|
||||
const icon = getDisasterCatIcon(item.category);
|
||||
const color = getDisasterCatColor(item.category);
|
||||
const isRecent = Date.now() - item.timestamp < 3600_000 * 6;
|
||||
return (
|
||||
<a
|
||||
key={item.id}
|
||||
className={`osint-item${isRecent ? ' osint-recent' : ''}`}
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="osint-item-top">
|
||||
<span className="osint-cat-tag" style={{ background: color }}>
|
||||
{icon} {item.category === 'sea' ? '해양' : item.category === 'chemical' ? '화학' : item.category === 'fire' ? '화재' : item.category === 'earthquake' ? '지진' : item.category === 'flood' ? '홍수' : item.category === 'typhoon' ? '태풍' : item.category === 'safety' ? '안전' : '재난'}
|
||||
</span>
|
||||
<span className="osint-time">{timeAgo(item.timestamp)}</span>
|
||||
</div>
|
||||
<div className="osint-item-title">{item.title}</div>
|
||||
<div className="osint-item-source">{item.source}</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* OSINT 피드 — 한국: 일반(general) 제외, 해양 관련만 표시 (라이브/리플레이 모두) */}
|
||||
{osintFeed.length > 0 && (
|
||||
<>
|
||||
|
||||
@ -107,12 +107,25 @@ interface ExtraLayer {
|
||||
group?: string;
|
||||
}
|
||||
|
||||
const GROUP_META: Record<string, { label: string; color: string }> = {
|
||||
'항공망': { label: '항공망', color: '#22d3ee' },
|
||||
'국가기관망': { label: '국가기관망', color: '#f59e0b' },
|
||||
'해양안전': { label: '해양안전', color: '#3b82f6' },
|
||||
const GROUP_META: Record<string, { label: string; color: string; superGroup?: string }> = {
|
||||
'항공망': { label: '항공망', color: '#22d3ee' },
|
||||
'해양안전': { label: '해양안전', color: '#3b82f6' },
|
||||
'국가기관망': { label: '국가기관망', color: '#f59e0b' },
|
||||
'위험시설': { label: '위험시설', color: '#ef4444', superGroup: '위험/산업 인프라' },
|
||||
'에너지/발전시설': { label: '에너지/발전시설', color: '#a855f7', superGroup: '위험/산업 인프라' },
|
||||
'산업공정/제조시설': { label: '산업공정/제조시설', color: '#0ea5e9', superGroup: '위험/산업 인프라' },
|
||||
};
|
||||
|
||||
const SUPER_GROUP_META: Record<string, { label: string; color: string }> = {
|
||||
'위험/산업 인프라': { label: '위험/산업 인프라', color: '#f97316' },
|
||||
};
|
||||
|
||||
interface OverseasItem {
|
||||
key: string;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface LayerPanelProps {
|
||||
layers: Record<string, boolean>;
|
||||
onToggle: (key: string) => void;
|
||||
@ -122,6 +135,7 @@ interface LayerPanelProps {
|
||||
shipTotal: number;
|
||||
satelliteCount: number;
|
||||
extraLayers?: ExtraLayer[];
|
||||
overseasItems?: OverseasItem[];
|
||||
hiddenAcCategories: Set<string>;
|
||||
hiddenShipCategories: Set<string>;
|
||||
onAcCategoryToggle: (cat: string) => void;
|
||||
@ -143,6 +157,7 @@ export function LayerPanel({
|
||||
shipTotal,
|
||||
satelliteCount,
|
||||
extraLayers,
|
||||
overseasItems,
|
||||
hiddenAcCategories,
|
||||
hiddenShipCategories,
|
||||
onAcCategoryToggle,
|
||||
@ -186,7 +201,8 @@ export function LayerPanel({
|
||||
{/* Ships tree */}
|
||||
<LayerTreeItem
|
||||
layerKey="ships"
|
||||
label={`${t('layers.ships')} (${shipTotal})`}
|
||||
label={t('layers.ships')}
|
||||
count={shipTotal}
|
||||
color="#fb923c"
|
||||
active={layers.ships}
|
||||
expandable
|
||||
@ -297,7 +313,8 @@ export function LayerPanel({
|
||||
<>
|
||||
<LayerTreeItem
|
||||
layerKey="nationality"
|
||||
label={`국적 분류 (${Object.values(shipsByNationality).reduce((a, b) => a + b, 0)})`}
|
||||
label="국적 분류"
|
||||
count={Object.values(shipsByNationality).reduce((a, b) => a + b, 0)}
|
||||
color="#8b5cf6"
|
||||
active
|
||||
expandable
|
||||
@ -342,7 +359,8 @@ export function LayerPanel({
|
||||
{/* Aircraft tree */}
|
||||
<LayerTreeItem
|
||||
layerKey="aircraft"
|
||||
label={`${t('layers.aircraft')} (${aircraftTotal})`}
|
||||
label={t('layers.aircraft')}
|
||||
count={aircraftTotal}
|
||||
color="#22d3ee"
|
||||
active={layers.aircraft}
|
||||
expandable
|
||||
@ -401,7 +419,8 @@ export function LayerPanel({
|
||||
{/* Satellites */}
|
||||
<LayerTreeItem
|
||||
layerKey="satellites"
|
||||
label={`${t('layers.satellites')} (${satelliteCount})`}
|
||||
label={t('layers.satellites')}
|
||||
count={satelliteCount}
|
||||
color="#ef4444"
|
||||
active={layers.satellites}
|
||||
onToggle={() => onToggle('satellites')}
|
||||
@ -421,47 +440,92 @@ export function LayerPanel({
|
||||
ungrouped.push(el);
|
||||
}
|
||||
}
|
||||
|
||||
// 수퍼그룹 별로 그룹 분류
|
||||
const superGrouped: Record<string, string[]> = {}; // superGroup → groupNames[]
|
||||
const noSuperGroup: string[] = [];
|
||||
for (const groupName of Object.keys(grouped)) {
|
||||
const sg = GROUP_META[groupName]?.superGroup;
|
||||
if (sg) {
|
||||
if (!superGrouped[sg]) superGrouped[sg] = [];
|
||||
superGrouped[sg].push(groupName);
|
||||
} else {
|
||||
noSuperGroup.push(groupName);
|
||||
}
|
||||
}
|
||||
|
||||
const renderGroup = (groupName: string, indent = false) => {
|
||||
const meta = GROUP_META[groupName] || { label: groupName, color: '#888' };
|
||||
const isGroupExpanded = expanded.has(`group-${groupName}`);
|
||||
const items = grouped[groupName] || [];
|
||||
return (
|
||||
<div key={groupName} style={indent ? { paddingLeft: 10 } : undefined}>
|
||||
<LayerTreeItem
|
||||
layerKey={`group-${groupName}`}
|
||||
label={meta.label}
|
||||
color={meta.color}
|
||||
active
|
||||
expandable
|
||||
isExpanded={isGroupExpanded}
|
||||
onToggle={() => toggleExpand(`group-${groupName}`)}
|
||||
onExpand={() => toggleExpand(`group-${groupName}`)}
|
||||
/>
|
||||
{isGroupExpanded && (
|
||||
<div className="layer-tree-children">
|
||||
{items.map(el => (
|
||||
<LayerTreeItem
|
||||
key={el.key}
|
||||
layerKey={el.key}
|
||||
label={el.label}
|
||||
count={el.count}
|
||||
color={el.color}
|
||||
active={layers[el.key] ?? false}
|
||||
onToggle={() => onToggle(el.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Grouped layers */}
|
||||
{Object.entries(grouped).map(([groupName, items]) => {
|
||||
const meta = GROUP_META[groupName] || { label: groupName, color: '#888' };
|
||||
const isGroupExpanded = expanded.has(`group-${groupName}`);
|
||||
{/* 수퍼그룹 없는 그룹들 (항공망·해양안전·국가기관망) */}
|
||||
{noSuperGroup.map(g => renderGroup(g))}
|
||||
|
||||
{/* 수퍼그룹으로 묶인 그룹들 */}
|
||||
{Object.entries(superGrouped).map(([sgName, groupNames]) => {
|
||||
const sgMeta = SUPER_GROUP_META[sgName] || { label: sgName, color: '#f97316' };
|
||||
const isSgExpanded = expanded.has(`supergroup-${sgName}`);
|
||||
return (
|
||||
<div key={groupName}>
|
||||
<div key={sgName}>
|
||||
<LayerTreeItem
|
||||
layerKey={`group-${groupName}`}
|
||||
label={meta.label}
|
||||
color={meta.color}
|
||||
layerKey={`supergroup-${sgName}`}
|
||||
label={sgMeta.label}
|
||||
color={sgMeta.color}
|
||||
active
|
||||
expandable
|
||||
isExpanded={isGroupExpanded}
|
||||
onToggle={() => toggleExpand(`group-${groupName}`)}
|
||||
onExpand={() => toggleExpand(`group-${groupName}`)}
|
||||
isExpanded={isSgExpanded}
|
||||
onToggle={() => toggleExpand(`supergroup-${sgName}`)}
|
||||
onExpand={() => toggleExpand(`supergroup-${sgName}`)}
|
||||
/>
|
||||
{isGroupExpanded && (
|
||||
{isSgExpanded && (
|
||||
<div className="layer-tree-children">
|
||||
{items.map(el => (
|
||||
<LayerTreeItem
|
||||
key={el.key}
|
||||
layerKey={el.key}
|
||||
label={el.count != null ? `${el.label} (${el.count})` : el.label}
|
||||
color={el.color}
|
||||
active={layers[el.key] ?? false}
|
||||
onToggle={() => onToggle(el.key)}
|
||||
/>
|
||||
))}
|
||||
{groupNames.map(g => renderGroup(g, true))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* Ungrouped layers */}
|
||||
|
||||
{/* 그룹 없는 개별 레이어 */}
|
||||
{ungrouped.map(el => (
|
||||
<LayerTreeItem
|
||||
key={el.key}
|
||||
layerKey={el.key}
|
||||
label={el.count != null ? `${el.label} (${el.count})` : el.label}
|
||||
label={el.label}
|
||||
count={el.count}
|
||||
color={el.color}
|
||||
active={layers[el.key] ?? false}
|
||||
onToggle={() => onToggle(el.key)}
|
||||
@ -473,14 +537,32 @@ export function LayerPanel({
|
||||
|
||||
<div className="layer-divider" />
|
||||
|
||||
{/* Military only filter */}
|
||||
{/* 해외시설 */}
|
||||
<LayerTreeItem
|
||||
layerKey="militaryOnly"
|
||||
label={`${t('layers.militaryOnly')} (${militaryCount})`}
|
||||
label={t('layers.militaryOnly')}
|
||||
count={militaryCount}
|
||||
color="#f97316"
|
||||
active={layers.militaryOnly ?? false}
|
||||
expandable
|
||||
isExpanded={expanded.has('militaryOnly')}
|
||||
onToggle={() => onToggle('militaryOnly')}
|
||||
onExpand={() => toggleExpand('militaryOnly')}
|
||||
/>
|
||||
{expanded.has('militaryOnly') && overseasItems && overseasItems.length > 0 && (
|
||||
<div className="layer-tree-children">
|
||||
{overseasItems.map(item => (
|
||||
<LayerTreeItem
|
||||
key={item.key}
|
||||
layerKey={item.key}
|
||||
label={item.label}
|
||||
color={item.color}
|
||||
active={layers[item.key] ?? false}
|
||||
onToggle={() => onToggle(item.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -495,6 +577,7 @@ function LayerTreeItem({
|
||||
active,
|
||||
expandable,
|
||||
isExpanded,
|
||||
count,
|
||||
onToggle,
|
||||
onExpand,
|
||||
}: {
|
||||
@ -504,6 +587,7 @@ function LayerTreeItem({
|
||||
active: boolean;
|
||||
expandable?: boolean;
|
||||
isExpanded?: boolean;
|
||||
count?: number;
|
||||
onToggle: () => void;
|
||||
onExpand?: () => void;
|
||||
}) {
|
||||
@ -523,13 +607,16 @@ function LayerTreeItem({
|
||||
type="button"
|
||||
className={`layer-toggle ${active ? 'active' : ''}`}
|
||||
onClick={onToggle}
|
||||
style={{ padding: 0, gap: '6px' }}
|
||||
style={{ padding: 0, gap: '6px', flex: 1, width: '100%' }}
|
||||
>
|
||||
<span
|
||||
className="layer-dot"
|
||||
style={{ backgroundColor: active ? color : '#444' }}
|
||||
/>
|
||||
{label}
|
||||
<span style={{ flex: 1 }}>{label}</span>
|
||||
{count != null && (
|
||||
<span style={{ fontSize: 9, color: 'var(--kcg-dim)', flexShrink: 0 }}>{count}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
110
frontend/src/components/korea/HazardFacilityLayer.tsx
Normal file
110
frontend/src/components/korea/HazardFacilityLayer.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { useState } from 'react';
|
||||
import { Marker, Popup } from 'react-map-gl/maplibre';
|
||||
import { HAZARD_FACILITIES } from '../../data/hazardFacilities';
|
||||
import type { HazardFacility, HazardType } from '../../data/hazardFacilities';
|
||||
|
||||
interface Props {
|
||||
type: HazardType;
|
||||
}
|
||||
|
||||
const TYPE_META: Record<HazardType, { icon: string; color: string; label: string; bgColor: string }> = {
|
||||
petrochemical: { icon: '🏭', color: '#f97316', label: '석유화학단지', bgColor: 'rgba(249,115,22,0.15)' },
|
||||
lng: { icon: '🔵', color: '#06b6d4', label: 'LNG저장기지', bgColor: 'rgba(6,182,212,0.15)' },
|
||||
oilTank: { icon: '🛢️', color: '#eab308', label: '유류저장탱크', bgColor: 'rgba(234,179,8,0.15)' },
|
||||
hazardPort: { icon: '⚠️', color: '#ef4444', label: '위험물하역시설', bgColor: 'rgba(239,68,68,0.15)' },
|
||||
nuclear: { icon: '☢️', color: '#a855f7', label: '원자력발전소', bgColor: 'rgba(168,85,247,0.15)' },
|
||||
thermal: { icon: '🔥', color: '#64748b', label: '화력발전소', bgColor: 'rgba(100,116,139,0.15)' },
|
||||
shipyard: { icon: '🚢', color: '#0ea5e9', label: '조선소 도장시설', bgColor: 'rgba(14,165,233,0.15)' },
|
||||
wastewater: { icon: '💧', color: '#10b981', label: '폐수처리장', bgColor: 'rgba(16,185,129,0.15)' },
|
||||
heavyIndustry: { icon: '⚙️', color: '#94a3b8', label: '시멘트/제철소', bgColor: 'rgba(148,163,184,0.15)' },
|
||||
};
|
||||
|
||||
export function HazardFacilityLayer({ type }: Props) {
|
||||
const [selected, setSelected] = useState<HazardFacility | null>(null);
|
||||
const meta = TYPE_META[type];
|
||||
const facilities = HAZARD_FACILITIES.filter(f => f.type === type);
|
||||
|
||||
return (
|
||||
<>
|
||||
{facilities.map(f => (
|
||||
<Marker key={f.id} longitude={f.lng} latitude={f.lat} anchor="center"
|
||||
onClick={(e) => { e.originalEvent.stopPropagation(); setSelected(f); }}>
|
||||
<div
|
||||
className="flex flex-col items-center cursor-pointer"
|
||||
style={{ filter: `drop-shadow(0 0 4px ${meta.color}99)` }}
|
||||
>
|
||||
<div style={{
|
||||
width: 20, height: 20, borderRadius: 4,
|
||||
background: 'rgba(0,0,0,0.75)',
|
||||
border: `1.5px solid ${meta.color}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 11,
|
||||
}}>
|
||||
{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,
|
||||
}}>
|
||||
{f.nameKo.length > 12 ? f.nameKo.slice(0, 12) + '..' : f.nameKo}
|
||||
</div>
|
||||
</div>
|
||||
</Marker>
|
||||
))}
|
||||
|
||||
{selected && (
|
||||
<Popup
|
||||
longitude={selected.lng} latitude={selected.lat}
|
||||
onClose={() => setSelected(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="300px" className="gl-popup"
|
||||
>
|
||||
<div className="popup-body-sm" style={{ minWidth: 230 }}>
|
||||
<div className="popup-header" style={{
|
||||
background: meta.color, color: '#fff', gap: 6, padding: '6px 10px',
|
||||
}}>
|
||||
<span style={{ fontSize: 16 }}>{meta.icon}</span>
|
||||
<strong style={{ fontSize: 13 }}>{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: 'rgba(239,68,68,0.2)', color: '#ef4444', border: '1px solid #ef4444',
|
||||
padding: '2px 8px', borderRadius: 3, fontSize: 10, fontWeight: 700,
|
||||
}}>
|
||||
⚠️ 위험시설
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 11, color: '#cbd5e1', marginBottom: 6, lineHeight: 1.5 }}>
|
||||
{selected.description}
|
||||
</div>
|
||||
|
||||
<div className="popup-grid" style={{ gap: '2px 12px' }}>
|
||||
{selected.address && (
|
||||
<div><span className="popup-label">주소 : </span><strong>{selected.address}</strong></div>
|
||||
)}
|
||||
{selected.operator && (
|
||||
<div><span className="popup-label">운영자 : </span><strong>{selected.operator}</strong></div>
|
||||
)}
|
||||
{selected.capacity && (
|
||||
<div><span className="popup-label">처리규모 : </span><strong>{selected.capacity}</strong></div>
|
||||
)}
|
||||
<div><span className="popup-label">시설명(EN) : </span>{selected.name}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 6, fontSize: 10, color: '#64748b' }}>
|
||||
{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -21,6 +21,7 @@ import { GovBuildingLayer } from './GovBuildingLayer';
|
||||
import { NKLaunchLayer } from './NKLaunchLayer';
|
||||
import { NKMissileEventLayer } from './NKMissileEventLayer';
|
||||
import { ChineseFishingOverlay } from './ChineseFishingOverlay';
|
||||
import { HazardFacilityLayer } from './HazardFacilityLayer';
|
||||
import { fetchKoreaInfra } from '../../services/infra';
|
||||
import type { PowerFacility } from '../../services/infra';
|
||||
import type { Ship, Aircraft, SatellitePosition } from '../../types';
|
||||
@ -269,6 +270,15 @@ export function KoreaMap({ ships, aircraft, satellites, layers, osintFeed, curre
|
||||
{layers.nkLaunch && <NKLaunchLayer />}
|
||||
{layers.nkMissile && <NKMissileEventLayer ships={ships} />}
|
||||
{layers.cnFishing && <ChineseFishingOverlay ships={ships} />}
|
||||
{layers.hazardPetrochemical && <HazardFacilityLayer type="petrochemical" />}
|
||||
{layers.hazardLng && <HazardFacilityLayer type="lng" />}
|
||||
{layers.hazardOilTank && <HazardFacilityLayer type="oilTank" />}
|
||||
{layers.hazardPort && <HazardFacilityLayer type="hazardPort" />}
|
||||
{layers.energyNuclear && <HazardFacilityLayer type="nuclear" />}
|
||||
{layers.energyThermal && <HazardFacilityLayer type="thermal" />}
|
||||
{layers.industryShipyard && <HazardFacilityLayer type="shipyard" />}
|
||||
{layers.industryWastewater && <HazardFacilityLayer type="wastewater" />}
|
||||
{layers.industryHeavy && <HazardFacilityLayer type="heavyIndustry" />}
|
||||
{layers.airports && <KoreaAirportLayer />}
|
||||
{layers.coastGuard && <CoastGuardLayer />}
|
||||
{layers.navWarning && <NavWarningLayer />}
|
||||
|
||||
520
frontend/src/data/hazardFacilities.ts
Normal file
520
frontend/src/data/hazardFacilities.ts
Normal file
@ -0,0 +1,520 @@
|
||||
export type HazardType = 'petrochemical' | 'lng' | 'oilTank' | 'hazardPort' | 'nuclear' | 'thermal' | 'shipyard' | 'wastewater' | 'heavyIndustry';
|
||||
|
||||
export interface HazardFacility {
|
||||
id: string;
|
||||
type: HazardType;
|
||||
nameKo: string;
|
||||
name: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
address?: string;
|
||||
capacity?: string;
|
||||
operator?: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const HAZARD_FACILITIES: HazardFacility[] = [
|
||||
// ── 해안인접석유화학단지 ──────────────────────────────────────────
|
||||
{
|
||||
id: 'pc-01', type: 'petrochemical',
|
||||
nameKo: '여수국가산업단지', name: 'Yeosu National Industrial Complex',
|
||||
lat: 34.757, lng: 127.723,
|
||||
address: '전남 여수시 화치동 산 183-1',
|
||||
capacity: '연산 2,400만 톤', operator: '여수광양항만공사·LG화학·롯데케미칼',
|
||||
description: '국내 최대 석유화학단지. NCC·LG화학·롯데케미칼·GS칼텍스 등 입주.',
|
||||
},
|
||||
{
|
||||
id: 'pc-02', type: 'petrochemical',
|
||||
nameKo: '울산미포국가산업단지', name: 'Ulsan Mipo National Industrial Complex',
|
||||
lat: 35.479, lng: 129.357,
|
||||
address: '울산광역시 남구 사평로 137 (부곡동 439-1)',
|
||||
capacity: '연산 1,800만 톤', operator: 'S-OIL·SK에너지·SK지오센트릭',
|
||||
description: '정유·NCC 중심 울산미포국가산단 내 석유화학 집적지.',
|
||||
},
|
||||
{
|
||||
id: 'pc-03', type: 'petrochemical',
|
||||
nameKo: '대산석유화학단지', name: 'Daesan Petrochemical Complex',
|
||||
lat: 37.025, lng: 126.360,
|
||||
address: '충남 서산시 대산읍 독곶1로 82 (롯데케미칼 대산공장 기준)',
|
||||
capacity: '연산 900만 톤', operator: '롯데케미칼·현대오일뱅크·한화토탈에너지스',
|
||||
description: '충남 서산 대산항 인근 3대 석유화학단지.',
|
||||
},
|
||||
{
|
||||
id: 'pc-04', type: 'petrochemical',
|
||||
nameKo: '광양 석유화학단지', name: 'Gwangyang Petrochemical Complex',
|
||||
lat: 34.970, lng: 127.705,
|
||||
capacity: '연산 600만 톤', operator: 'POSCO·포스코케미칼',
|
||||
description: '광양제철소 연계 석유화학 시설.',
|
||||
},
|
||||
{
|
||||
id: 'pc-05', type: 'petrochemical',
|
||||
nameKo: '인천 석유화학단지', name: 'Incheon Petrochemical Complex',
|
||||
lat: 37.470, lng: 126.618,
|
||||
capacity: '연산 400만 톤', operator: 'SK인천석유화학',
|
||||
description: '인천 북항 인근 정유·석유화학 시설.',
|
||||
},
|
||||
|
||||
// ── LNG 생산기지 (한국가스공사 KOGAS) ────────────────────────────
|
||||
{
|
||||
id: 'lng-01', type: 'lng',
|
||||
nameKo: '평택 LNG 생산기지', name: 'Pyeongtaek LNG Production Base',
|
||||
lat: 37.017, lng: 126.870,
|
||||
address: '경기도 평택시 포승읍',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '국내 최초의 LNG 기지. 수도권 공급의 핵심 거점.',
|
||||
},
|
||||
{
|
||||
id: 'lng-02', type: 'lng',
|
||||
nameKo: '인천 LNG 생산기지', name: 'Incheon LNG Production Base',
|
||||
lat: 37.374, lng: 126.622,
|
||||
address: '인천광역시 연수구 송도동',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '세계 최대 규모의 해상 LNG 기지 중 하나.',
|
||||
},
|
||||
{
|
||||
id: 'lng-03', type: 'lng',
|
||||
nameKo: '통영 LNG 생산기지', name: 'Tongyeong LNG Production Base',
|
||||
lat: 34.906, lng: 128.465,
|
||||
address: '경상남도 통영시 광도면',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '남부권 가스 공급 및 영남권 산업단지 지원 거점.',
|
||||
},
|
||||
{
|
||||
id: 'lng-04', type: 'lng',
|
||||
nameKo: '삼척 LNG 생산기지', name: 'Samcheok LNG Production Base',
|
||||
lat: 37.262, lng: 129.290,
|
||||
address: '강원도 삼척시 원덕읍',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '동해안 에너지 거점 및 수입 다변화 대응.',
|
||||
},
|
||||
{
|
||||
id: 'lng-05', type: 'lng',
|
||||
nameKo: '제주 LNG 생산기지', name: 'Jeju LNG Production Base',
|
||||
lat: 33.448, lng: 126.330,
|
||||
address: '제주특별자치도 제주시 애월읍',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '제주 지역 천연가스 보급을 위해 조성된 기지.',
|
||||
},
|
||||
{
|
||||
id: 'lng-06', type: 'lng',
|
||||
nameKo: '당진 LNG 생산기지', name: 'Dangjin LNG Production Base',
|
||||
lat: 37.048, lng: 126.595,
|
||||
address: '충청남도 당진시 석문면',
|
||||
operator: '한국가스공사(KOGAS)',
|
||||
description: '2026년 말 1단계 준공 예정 (현재 건설 중).',
|
||||
},
|
||||
|
||||
// ── 민간 LNG 터미널 ──────────────────────────────────────────────
|
||||
{
|
||||
id: 'lng-p01', type: 'lng',
|
||||
nameKo: '광양 LNG 터미널', name: 'Gwangyang LNG Terminal',
|
||||
lat: 34.934, lng: 127.714,
|
||||
address: '전라남도 광양시 금호동',
|
||||
operator: '포스코인터내셔널',
|
||||
description: '포스코인터내셔널 운영 민간 LNG 터미널.',
|
||||
},
|
||||
{
|
||||
id: 'lng-p02', type: 'lng',
|
||||
nameKo: '보령 LNG 터미널', name: 'Boryeong LNG Terminal',
|
||||
lat: 36.380, lng: 126.513,
|
||||
address: '충청남도 보령시 오천면',
|
||||
operator: 'SK E&S · GS에너지',
|
||||
description: 'SK E&S·GS에너지 공동 운영 민간 LNG 터미널.',
|
||||
},
|
||||
{
|
||||
id: 'lng-p03', type: 'lng',
|
||||
nameKo: '울산 북항 에너지터미널', name: 'Ulsan North Port Energy Terminal',
|
||||
lat: 35.518, lng: 129.383,
|
||||
address: '울산광역시 남구 북항 일원',
|
||||
operator: 'KET (한국석유공사·SK Gas 등)',
|
||||
description: 'KET(Korea Energy Terminal) 운영 민간 에너지터미널.',
|
||||
},
|
||||
{
|
||||
id: 'lng-p04', type: 'lng',
|
||||
nameKo: '통영 에코파워 LNG', name: 'Tongyeong Ecopower LNG Terminal',
|
||||
lat: 34.873, lng: 128.508,
|
||||
address: '경상남도 통영시 광도면 (성동조선 인근)',
|
||||
operator: 'HDC현대산업개발 등',
|
||||
description: '성동조선 인근 민간 LNG 터미널.',
|
||||
},
|
||||
|
||||
// ── 유류저장탱크 ──────────────────────────────────────────────────
|
||||
{
|
||||
id: 'oil-01', type: 'oilTank',
|
||||
nameKo: '여수 유류저장시설', name: 'Yeosu Oil Storage',
|
||||
lat: 34.733, lng: 127.741,
|
||||
capacity: '630만 ㎘', operator: 'SK에너지·GS칼텍스',
|
||||
description: '여수항 인근 정유제품 및 원유 저장시설.',
|
||||
},
|
||||
{
|
||||
id: 'oil-02', type: 'oilTank',
|
||||
nameKo: '울산 정유 저장시설', name: 'Ulsan Refinery Storage',
|
||||
lat: 35.516, lng: 129.413,
|
||||
capacity: '850만 ㎘', operator: 'S-OIL·SK에너지',
|
||||
description: '울산 온산 정유시설 연계 대형 유류탱크군.',
|
||||
},
|
||||
{
|
||||
id: 'oil-03', type: 'oilTank',
|
||||
nameKo: '포항 저유소', name: 'Pohang Oil Depot',
|
||||
lat: 36.018, lng: 129.380,
|
||||
capacity: '20만 ㎘', operator: '대한송유관공사',
|
||||
description: '동해안 석유 공급 거점 저유소.',
|
||||
},
|
||||
{
|
||||
id: 'oil-04', type: 'oilTank',
|
||||
nameKo: '목포 유류저장', name: 'Mokpo Oil Storage',
|
||||
lat: 34.773, lng: 126.384,
|
||||
capacity: '30만 ㎘', operator: '한국석유공사',
|
||||
description: '서남해안 유류 공급 저장기지.',
|
||||
},
|
||||
{
|
||||
id: 'oil-05', type: 'oilTank',
|
||||
nameKo: '부산 북항 저유소', name: 'Busan North Port Oil Depot',
|
||||
lat: 35.100, lng: 129.041,
|
||||
capacity: '45만 ㎘', operator: '대한송유관공사',
|
||||
description: '부산항 연계 유류 저장·공급 시설.',
|
||||
},
|
||||
{
|
||||
id: 'oil-06', type: 'oilTank',
|
||||
nameKo: '보령 저유소', name: 'Boryeong Oil Depot',
|
||||
lat: 36.380, lng: 126.570,
|
||||
capacity: '15만 ㎘', operator: '대한송유관공사',
|
||||
description: '충남 서해안 유류 공급 저장기지.',
|
||||
},
|
||||
|
||||
// ── KNOC 국가 석유비축기지 ────────────────────────────────────────
|
||||
{
|
||||
id: 'knoc-01', type: 'oilTank',
|
||||
nameKo: 'KNOC 울산 비축기지', name: 'KNOC Ulsan SPR Base',
|
||||
lat: 35.406, lng: 129.351,
|
||||
address: '울산광역시 울주군 온산읍 학남리',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 원유 (지상탱크) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-02', type: 'oilTank',
|
||||
nameKo: 'KNOC 여수 비축기지', name: 'KNOC Yeosu SPR Base',
|
||||
lat: 34.716, lng: 127.742,
|
||||
address: '전라남도 여수시 낙포동',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 원유 (지상탱크·지하공동) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-03', type: 'oilTank',
|
||||
nameKo: 'KNOC 거제 비축기지', name: 'KNOC Geoje SPR Base',
|
||||
lat: 34.852, lng: 128.722,
|
||||
address: '경상남도 거제시 일운면 지세포리',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 원유 (지하공동) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-04', type: 'oilTank',
|
||||
nameKo: 'KNOC 서산 비축기지', name: 'KNOC Seosan SPR Base',
|
||||
lat: 37.018, lng: 126.374,
|
||||
address: '충청남도 서산시 대산읍 대죽리',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 원유·제품 (지상탱크) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-05', type: 'oilTank',
|
||||
nameKo: 'KNOC 평택 비축기지', name: 'KNOC Pyeongtaek SPR Base',
|
||||
lat: 37.017, lng: 126.858,
|
||||
address: '경기도 평택시 포승읍 원정리',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. LPG 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-06', type: 'oilTank',
|
||||
nameKo: 'KNOC 구리 비축기지', name: 'KNOC Guri SPR Base',
|
||||
lat: 37.562, lng: 127.138,
|
||||
address: '경기도 구리시 아차산로',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 제품 (지하공동) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-07', type: 'oilTank',
|
||||
nameKo: 'KNOC 용인 비축기지', name: 'KNOC Yongin SPR Base',
|
||||
lat: 37.238, lng: 127.213,
|
||||
address: '경기도 용인시 처인구 해실로',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 제품 (지상탱크) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-08', type: 'oilTank',
|
||||
nameKo: 'KNOC 동해 비축기지', name: 'KNOC Donghae SPR Base',
|
||||
lat: 37.503, lng: 129.097,
|
||||
address: '강원특별자치도 동해시 공단12로',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 제품 (지상탱크) 방식.',
|
||||
},
|
||||
{
|
||||
id: 'knoc-09', type: 'oilTank',
|
||||
nameKo: 'KNOC 곡성 비축기지', name: 'KNOC Gokseong SPR Base',
|
||||
lat: 35.228, lng: 127.302,
|
||||
address: '전라남도 곡성군 겸면 괴정리',
|
||||
capacity: '국가비축', operator: '한국석유공사(KNOC)',
|
||||
description: '국가 전략 석유비축기지. 제품 (지상탱크) 방식.',
|
||||
},
|
||||
|
||||
// ── 위험물항만하역시설 ────────────────────────────────────────────
|
||||
{
|
||||
id: 'hp-01', type: 'hazardPort',
|
||||
nameKo: '광양항 위험물 부두', name: 'Gwangyang Hazardous Cargo Terminal',
|
||||
lat: 34.923, lng: 127.703,
|
||||
capacity: '연 3,000만 톤', operator: '여수광양항만공사',
|
||||
description: '석유화학제품·액체화물 전용 위험물 하역 부두.',
|
||||
},
|
||||
{
|
||||
id: 'hp-02', type: 'hazardPort',
|
||||
nameKo: '울산항 위험물 부두', name: 'Ulsan Hazardous Cargo Terminal',
|
||||
lat: 35.519, lng: 129.392,
|
||||
capacity: '연 2,500만 톤', operator: '울산항만공사',
|
||||
description: '원유·석유제품·LPG 등 위험물 전용 하역 부두.',
|
||||
},
|
||||
{
|
||||
id: 'hp-03', type: 'hazardPort',
|
||||
nameKo: '인천항 위험물 부두', name: 'Incheon Hazardous Cargo Terminal',
|
||||
lat: 37.464, lng: 126.621,
|
||||
capacity: '연 800만 톤', operator: '인천항만공사',
|
||||
description: '인천 북항 위험물(화학·가스·유류) 하역 전용 부두.',
|
||||
},
|
||||
{
|
||||
id: 'hp-04', type: 'hazardPort',
|
||||
nameKo: '여수항 위험물 부두', name: 'Yeosu Hazardous Cargo Terminal',
|
||||
lat: 34.729, lng: 127.741,
|
||||
capacity: '연 1,200만 톤', operator: '여수광양항만공사',
|
||||
description: '여수 석유화학단지 연계 위험물 하역 부두.',
|
||||
},
|
||||
{
|
||||
id: 'hp-05', type: 'hazardPort',
|
||||
nameKo: '부산항 위험물 부두', name: 'Busan Hazardous Cargo Terminal',
|
||||
lat: 35.090, lng: 129.022,
|
||||
capacity: '연 500만 톤', operator: '부산항만공사',
|
||||
description: '부산 신항·북항 위험물 전용 하역 부두.',
|
||||
},
|
||||
{
|
||||
id: 'hp-06', type: 'hazardPort',
|
||||
nameKo: '군산항 위험물 부두', name: 'Gunsan Hazardous Cargo Terminal',
|
||||
lat: 35.973, lng: 126.712,
|
||||
capacity: '연 300만 톤', operator: '군산항만공사',
|
||||
description: '서해안 위험물(석유·화학) 하역 부두.',
|
||||
},
|
||||
|
||||
// ── 원자력발전소 ──────────────────────────────────────────────────
|
||||
{
|
||||
id: 'npp-01', type: 'nuclear',
|
||||
nameKo: '고리 원자력발전소', name: 'Kori Nuclear Power Plant',
|
||||
lat: 35.316, lng: 129.291,
|
||||
address: '부산광역시 기장군 장안읍 고리',
|
||||
capacity: '4기 (신고리 포함 총 6기)', operator: '한국수력원자력(한수원)',
|
||||
description: '국내 최초 상업용 원전 부지. 1호기 영구정지(2017), 신고리 1~4호기 운영 중.',
|
||||
},
|
||||
{
|
||||
id: 'npp-02', type: 'nuclear',
|
||||
nameKo: '월성 원자력발전소', name: 'Wolseong Nuclear Power Plant',
|
||||
lat: 35.712, lng: 129.476,
|
||||
address: '경상북도 경주시 양남면 나아리',
|
||||
capacity: '4기 (월성·신월성)', operator: '한국수력원자력(한수원)',
|
||||
description: '중수로(CANDU) 방식. 월성 1호기 영구정지(2019), 신월성 1·2호기 운영 중.',
|
||||
},
|
||||
{
|
||||
id: 'npp-03', type: 'nuclear',
|
||||
nameKo: '한울 원자력발전소', name: 'Hanul Nuclear Power Plant',
|
||||
lat: 37.093, lng: 129.381,
|
||||
address: '경상북도 울진군 북면 부구리',
|
||||
capacity: '6기 운영 + 신한울 2기', operator: '한국수력원자력(한수원)',
|
||||
description: '구 울진 원전. 한울 1~6호기 + 신한울 1·2호기(2022~2024 준공).',
|
||||
},
|
||||
{
|
||||
id: 'npp-04', type: 'nuclear',
|
||||
nameKo: '한빛 원자력발전소', name: 'Hanbit Nuclear Power Plant',
|
||||
lat: 35.410, lng: 126.424,
|
||||
address: '전라남도 영광군 홍농읍 계마리',
|
||||
capacity: '6기 운영', operator: '한국수력원자력(한수원)',
|
||||
description: '구 영광 원전. 한빛 1~6호기 운영 중. 국내 최대 용량 원전 부지.',
|
||||
},
|
||||
{
|
||||
id: 'npp-05', type: 'nuclear',
|
||||
nameKo: '새울 원자력발전소', name: 'Saeul Nuclear Power Plant',
|
||||
lat: 35.311, lng: 129.303,
|
||||
address: '울산광역시 울주군 서생면 신암리',
|
||||
capacity: '4기 (신고리 5~8호기)', operator: '한국수력원자력(한수원)',
|
||||
description: '신고리 5·6호기 운영 중, 7·8호기 건설 예정. 고리 부지 인근.',
|
||||
},
|
||||
|
||||
// ── 화력발전소 ────────────────────────────────────────────────────
|
||||
{
|
||||
id: 'tp-01', type: 'thermal',
|
||||
nameKo: '당진 화력발전소', name: 'Dangjin Thermal Power Plant',
|
||||
lat: 37.048, lng: 126.598,
|
||||
address: '충청남도 당진시 석문면 교로리',
|
||||
capacity: '6,040MW (10기)', operator: '한국동서발전(EWP)',
|
||||
description: '국내 최대 규모 석탄 화력발전소.',
|
||||
},
|
||||
{
|
||||
id: 'tp-02', type: 'thermal',
|
||||
nameKo: '태안 화력발전소', name: 'Taean Thermal Power Plant',
|
||||
lat: 36.849, lng: 126.232,
|
||||
address: '충청남도 태안군 원북면 방갈리',
|
||||
capacity: '6,100MW (10기)', operator: '한국서부발전(WPP)',
|
||||
description: '서해안 최대 규모 석탄 화력발전소.',
|
||||
},
|
||||
{
|
||||
id: 'tp-03', type: 'thermal',
|
||||
nameKo: '삼척 화력발전소', name: 'Samcheok Thermal Power Plant',
|
||||
lat: 37.243, lng: 129.326,
|
||||
address: '강원특별자치도 삼척시 근덕면 초곡리',
|
||||
capacity: '2,100MW (2기)', operator: '삼척블루파워(포스코에너지·GS에너지)',
|
||||
description: '동해안 민자 석탄 화력발전소. 2022년 준공.',
|
||||
},
|
||||
{
|
||||
id: 'tp-04', type: 'thermal',
|
||||
nameKo: '여수 화력발전소', name: 'Yeosu Thermal Power Plant',
|
||||
lat: 34.738, lng: 127.721,
|
||||
address: '전라남도 여수시 낙포동',
|
||||
capacity: '870MW', operator: 'GS E&R',
|
||||
description: '여수 석유화학단지 인근 열병합 발전소.',
|
||||
},
|
||||
{
|
||||
id: 'tp-05', type: 'thermal',
|
||||
nameKo: '하동 화력발전소', name: 'Hadong Thermal Power Plant',
|
||||
lat: 34.977, lng: 127.901,
|
||||
address: '경상남도 하동군 금성면 갈사리',
|
||||
capacity: '4,000MW (8기)', operator: '한국남부발전(KOSPO)',
|
||||
description: '남해안 주요 석탄 화력발전소.',
|
||||
},
|
||||
|
||||
// ── 조선소 도장시설 ───────────────────────────────────────────────
|
||||
{
|
||||
id: 'sy-01', type: 'shipyard',
|
||||
nameKo: '한화오션 거제조선소', name: 'Hanwha Ocean Geoje Shipyard',
|
||||
lat: 34.893, lng: 128.623,
|
||||
address: '경상남도 거제시 아주동 1',
|
||||
operator: '한화오션(구 대우조선해양)',
|
||||
description: '초대형 선박·해양플랜트 도장시설. 유기용제·VOC 대량 취급.',
|
||||
},
|
||||
{
|
||||
id: 'sy-02', type: 'shipyard',
|
||||
nameKo: 'HD현대중공업 울산조선소', name: 'HD Hyundai Heavy Industries Ulsan Shipyard',
|
||||
lat: 35.508, lng: 129.421,
|
||||
address: '울산광역시 동구 방어진순환도로 1000',
|
||||
operator: 'HD현대중공업',
|
||||
description: '세계 최대 단일 조선소. 도크 10기, 도장시설·VOC 취급.',
|
||||
},
|
||||
{
|
||||
id: 'sy-03', type: 'shipyard',
|
||||
nameKo: '삼성중공업 거제조선소', name: 'Samsung Heavy Industries Geoje Shipyard',
|
||||
lat: 34.847, lng: 128.682,
|
||||
address: '경상남도 거제시 장평동 530',
|
||||
operator: '삼성중공업',
|
||||
description: 'LNG 운반선·FPSO 전문 조선소. 도장·도막 처리시설.',
|
||||
},
|
||||
{
|
||||
id: 'sy-04', type: 'shipyard',
|
||||
nameKo: 'HD현대미포조선 울산', name: 'HD Hyundai Mipo Dockyard Ulsan',
|
||||
lat: 35.479, lng: 129.407,
|
||||
address: '울산광역시 동구 화정동',
|
||||
operator: 'HD현대미포조선',
|
||||
description: '중형 선박 전문 조선소. 도장시설 다수.',
|
||||
},
|
||||
{
|
||||
id: 'sy-05', type: 'shipyard',
|
||||
nameKo: 'HD현대삼호 영암조선소', name: 'HD Hyundai Samho Yeongam Shipyard',
|
||||
lat: 34.746, lng: 126.459,
|
||||
address: '전라남도 영암군 삼호읍 용당리',
|
||||
operator: 'HD현대삼호중공업',
|
||||
description: '서남해안 대형 조선소. 유기용제·도장 화학물질 취급.',
|
||||
},
|
||||
{
|
||||
id: 'sy-06', type: 'shipyard',
|
||||
nameKo: 'HJ중공업 부산조선소', name: 'HJ Shipbuilding Busan Shipyard',
|
||||
lat: 35.048, lng: 128.978,
|
||||
address: '부산광역시 영도구 해양로 195',
|
||||
operator: 'HJ중공업(구 한진중공업)',
|
||||
description: '부산 영도 소재 조선소. 도장·표면처리 시설.',
|
||||
},
|
||||
|
||||
// ── 폐수/하수처리장 ───────────────────────────────────────────────
|
||||
{
|
||||
id: 'ww-01', type: 'wastewater',
|
||||
nameKo: '여수 국가산단 폐수처리장', name: 'Yeosu Industrial Wastewater Treatment',
|
||||
lat: 34.748, lng: 127.730,
|
||||
address: '전라남도 여수시 화치동',
|
||||
operator: '여수시·환경부',
|
||||
description: '여수국가산단 배후 산업폐수처리장. 황화수소·메탄 발생 가능.',
|
||||
},
|
||||
{
|
||||
id: 'ww-02', type: 'wastewater',
|
||||
nameKo: '울산 온산공단 폐수처리장', name: 'Ulsan Onsan Industrial Wastewater Treatment',
|
||||
lat: 35.413, lng: 129.338,
|
||||
address: '울산광역시 울주군 온산읍',
|
||||
operator: '울산시·환경부',
|
||||
description: '온산국가산업단지 배후 폐수처리 거점. 유해가스 발생 위험.',
|
||||
},
|
||||
{
|
||||
id: 'ww-03', type: 'wastewater',
|
||||
nameKo: '대산공단 폐수처리장', name: 'Daesan Industrial Wastewater Treatment',
|
||||
lat: 37.023, lng: 126.348,
|
||||
address: '충청남도 서산시 대산읍',
|
||||
operator: '서산시·환경부',
|
||||
description: '대산석유화학단지 배후 폐수처리장. H₂S·메탄 발생 위험.',
|
||||
},
|
||||
{
|
||||
id: 'ww-04', type: 'wastewater',
|
||||
nameKo: '인천 북항 항만폐수처리', name: 'Incheon North Port Wastewater Treatment',
|
||||
lat: 37.468, lng: 126.618,
|
||||
address: '인천광역시 중구 북성동',
|
||||
operator: '인천항만공사·인천시',
|
||||
description: '인천 북항 인접 항만 폐수처리 시설.',
|
||||
},
|
||||
{
|
||||
id: 'ww-05', type: 'wastewater',
|
||||
nameKo: '광양 임해 폐수처리장', name: 'Gwangyang Coastal Wastewater Treatment',
|
||||
lat: 34.930, lng: 127.696,
|
||||
address: '전라남도 광양시 금호동',
|
||||
operator: '광양시·포스코',
|
||||
description: '광양제철소·산단 배후 폐수처리 시설. 황화수소 발생 위험.',
|
||||
},
|
||||
|
||||
// ── 시멘트/제철소/원료저장시설 ────────────────────────────────────
|
||||
{
|
||||
id: 'hi-01', type: 'heavyIndustry',
|
||||
nameKo: 'POSCO 포항제철소', name: 'POSCO Pohang Steelworks',
|
||||
lat: 36.027, lng: 129.358,
|
||||
address: '경상북도 포항시 남구 동해안로 6261',
|
||||
capacity: '1,800만 톤/년', operator: 'POSCO',
|
||||
description: '국내 최대 제철소. 고로·코크스 원료 대량 저장·처리.',
|
||||
},
|
||||
{
|
||||
id: 'hi-02', type: 'heavyIndustry',
|
||||
nameKo: 'POSCO 광양제철소', name: 'POSCO Gwangyang Steelworks',
|
||||
lat: 34.932, lng: 127.702,
|
||||
address: '전라남도 광양시 금호동 700',
|
||||
capacity: '2,100만 톤/년', operator: 'POSCO',
|
||||
description: '세계 최대 규모 제철소 중 하나. 임해 원료 저장기지.',
|
||||
},
|
||||
{
|
||||
id: 'hi-03', type: 'heavyIndustry',
|
||||
nameKo: '현대제철 당진공장', name: 'Hyundai Steel Dangjin Plant',
|
||||
lat: 37.046, lng: 126.616,
|
||||
address: '충청남도 당진시 송악읍 복운리',
|
||||
capacity: '1,200만 톤/년', operator: '현대제철',
|
||||
description: '당진 임해 제철소. 철광석·석탄 원료저장 부두 인접.',
|
||||
},
|
||||
{
|
||||
id: 'hi-04', type: 'heavyIndustry',
|
||||
nameKo: '삼척 시멘트 공단', name: 'Samcheok Cement Industrial Complex',
|
||||
lat: 37.480, lng: 129.130,
|
||||
address: '강원특별자치도 삼척시 동해대로',
|
||||
operator: '쌍용C&E·성신양회',
|
||||
description: '삼척 임해 시멘트 단지. 분진·원료저장시설 밀집.',
|
||||
},
|
||||
{
|
||||
id: 'hi-05', type: 'heavyIndustry',
|
||||
nameKo: '동해 시멘트/석회공장', name: 'Donghae Cement Complex',
|
||||
lat: 37.501, lng: 129.103,
|
||||
address: '강원특별자치도 동해시 북평공단',
|
||||
operator: '한일시멘트·아세아시멘트',
|
||||
description: '동해항 인근 시멘트·석회 생산·원료저장시설.',
|
||||
},
|
||||
];
|
||||
@ -77,7 +77,7 @@
|
||||
"airports": "공항",
|
||||
"sensorCharts": "센서 차트",
|
||||
"oilFacilities": "유전시설",
|
||||
"militaryOnly": "군용기만",
|
||||
"militaryOnly": "해외시설",
|
||||
"infra": "발전/변전",
|
||||
"cables": "해저케이블",
|
||||
"cctv": "CCTV",
|
||||
|
||||
146
frontend/src/services/disasterNews.ts
Normal file
146
frontend/src/services/disasterNews.ts
Normal file
@ -0,0 +1,146 @@
|
||||
// 재난/안전뉴스 — 국가재난안전포털(safekorea.go.kr) 뉴스
|
||||
// CORS 제한으로 직접 크롤링 불가 → 큐레이션된 최신 항목 + 포털 링크 제공
|
||||
|
||||
export interface DisasterNewsItem {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
title: string;
|
||||
source: string;
|
||||
category: 'typhoon' | 'flood' | 'earthquake' | 'fire' | 'sea' | 'chemical' | 'safety' | 'general';
|
||||
url: string;
|
||||
}
|
||||
|
||||
const SAFEKOREA_BASE = 'https://www.safekorea.go.kr/idsiSFK/neo/sfk/cs/sfc/dis/disasterNewsList.jsp?menuSeq=619';
|
||||
|
||||
const CAT_ICON: Record<DisasterNewsItem['category'], string> = {
|
||||
typhoon: '🌀',
|
||||
flood: '🌊',
|
||||
earthquake: '⚡',
|
||||
fire: '🔥',
|
||||
sea: '⚓',
|
||||
chemical: '☣️',
|
||||
safety: '🦺',
|
||||
general: '📢',
|
||||
};
|
||||
|
||||
const CAT_COLOR: Record<DisasterNewsItem['category'], string> = {
|
||||
typhoon: '#06b6d4',
|
||||
flood: '#3b82f6',
|
||||
earthquake: '#f59e0b',
|
||||
fire: '#ef4444',
|
||||
sea: '#0ea5e9',
|
||||
chemical: '#a855f7',
|
||||
safety: '#22c55e',
|
||||
general: '#64748b',
|
||||
};
|
||||
|
||||
export function getDisasterCatIcon(cat: DisasterNewsItem['category']) {
|
||||
return CAT_ICON[cat] ?? CAT_ICON.general;
|
||||
}
|
||||
export function getDisasterCatColor(cat: DisasterNewsItem['category']) {
|
||||
return CAT_COLOR[cat] ?? CAT_COLOR.general;
|
||||
}
|
||||
|
||||
// ── 큐레이션된 최신 재난/안전뉴스 (2026-03-21 기준) ──────────────────
|
||||
export const DISASTER_NEWS: DisasterNewsItem[] = [
|
||||
{
|
||||
id: 'dn-0321-01',
|
||||
timestamp: new Date('2026-03-21T09:00:00+09:00').getTime(),
|
||||
title: '[행안부] 봄철 해양레저 안전 유의… 3월~5월 수상사고 집중 발생 시기',
|
||||
source: '국가재난안전포털',
|
||||
category: 'sea',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0321-02',
|
||||
timestamp: new Date('2026-03-21T08:00:00+09:00').getTime(),
|
||||
title: '해경, 갯벌 고립사고 주의 당부… 조석표 미확인 갯벌체험 사망 증가',
|
||||
source: '해양경찰청',
|
||||
category: 'sea',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0320-01',
|
||||
timestamp: new Date('2026-03-20T16:00:00+09:00').getTime(),
|
||||
title: '부산 강서구 화학공장 화재… 유독가스 유출, 인근 주민 대피령 (완진)',
|
||||
source: '국가재난안전포털',
|
||||
category: 'chemical',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0320-02',
|
||||
timestamp: new Date('2026-03-20T10:00:00+09:00').getTime(),
|
||||
title: '[기상청] 서해상 강풍 예비특보 발효… 최대 순간풍속 25m/s 예상',
|
||||
source: '기상청',
|
||||
category: 'general',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0319-01',
|
||||
timestamp: new Date('2026-03-19T14:00:00+09:00').getTime(),
|
||||
title: '여수 앞바다 어선 전복… 선원 5명 중 3명 구조, 2명 수색 중',
|
||||
source: '국가재난안전포털',
|
||||
category: 'sea',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0319-02',
|
||||
timestamp: new Date('2026-03-19T09:00:00+09:00').getTime(),
|
||||
title: '행안부, 봄철 산불 위기경보 "주의" 발령… 강원·경북 건조특보 지속',
|
||||
source: '행정안전부',
|
||||
category: 'fire',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0318-01',
|
||||
timestamp: new Date('2026-03-18T11:00:00+09:00').getTime(),
|
||||
title: '경주 규모 2.8 지진 발생… 인근 원전 이상 없음, 여진 주의',
|
||||
source: '기상청',
|
||||
category: 'earthquake',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0318-02',
|
||||
timestamp: new Date('2026-03-18T08:00:00+09:00').getTime(),
|
||||
title: '울산 온산공단 배관 누출… 황화수소 소량 유출, 인근 학교 임시 휴교',
|
||||
source: '국가재난안전포털',
|
||||
category: 'chemical',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0317-01',
|
||||
timestamp: new Date('2026-03-17T15:00:00+09:00').getTime(),
|
||||
title: '포항 해상 화물선 기관실 화재… 해경 대응, 선원 전원 구조',
|
||||
source: '해양경찰청',
|
||||
category: 'sea',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0317-02',
|
||||
timestamp: new Date('2026-03-17T10:00:00+09:00').getTime(),
|
||||
title: '[소방청] 봄철 소방안전대책 시행… 주거용 소화기 무상 교체 4월까지 연장',
|
||||
source: '소방청',
|
||||
category: 'safety',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0316-01',
|
||||
timestamp: new Date('2026-03-16T13:00:00+09:00').getTime(),
|
||||
title: '태안 앞바다 유류 오염 사고… 어선 충돌로 벙커C유 3톤 유출, 방제 작업 중',
|
||||
source: '국가재난안전포털',
|
||||
category: 'sea',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
{
|
||||
id: 'dn-0316-02',
|
||||
timestamp: new Date('2026-03-16T09:00:00+09:00').getTime(),
|
||||
title: '행안부, 이란 사태 관련 국내 핵심기반시설 특별점검 실시',
|
||||
source: '행정안전부',
|
||||
category: 'safety',
|
||||
url: SAFEKOREA_BASE,
|
||||
},
|
||||
];
|
||||
|
||||
export function getDisasterNews(): DisasterNewsItem[] {
|
||||
return DISASTER_NEWS.sort((a, b) => b.timestamp - a.timestamp);
|
||||
}
|
||||
@ -243,7 +243,67 @@ function extractMELocation(text: string): { lat: number; lng: number } | null {
|
||||
// ── CENTCOM 최신 게시물 (수동 업데이트 — RSS 대체) ──
|
||||
// Nitter/RSSHub 모두 X.com 차단으로 사용 불가하므로 주요 CENTCOM 게시물 수동 관리
|
||||
const CENTCOM_POSTS: { text: string; date: string; url: string }[] = [
|
||||
// ── 3월 16일 (D+16) 최신 ──
|
||||
// ── 3월 21일 (D+21) 최신 ──
|
||||
{
|
||||
text: 'CENTCOM: US-Iran ceasefire negotiations in Muscat enter Day 2. CENTCOM forces maintaining "minimal operations" posture pending diplomatic outcome',
|
||||
date: '2026-03-21T06:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'UPDATE: Strait of Hormuz commercial traffic restored to 72% of pre-conflict levels. 23 tankers transited safely in past 24hrs under coalition escort',
|
||||
date: '2026-03-21T02:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
// ── 3월 20일 (D+20) ──
|
||||
{
|
||||
text: 'CENTCOM: US and Iranian delegations meet in Muscat, Oman for preliminary ceasefire talks. Omani FM Al-Busaidi mediating. No agreement yet but "atmosphere constructive"',
|
||||
date: '2026-03-20T14:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'Brent crude falls to $97/barrel on ceasefire talk optimism — first time below $100 since Operation Epic Fury began',
|
||||
date: '2026-03-20T08:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'Multiple senior IRGC commanders reported to have departed Iran for Russia. CENTCOM assesses Iran\'s strategic command continuity "severely degraded"',
|
||||
date: '2026-03-20T04:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
// ── 3월 19일 (D+19) ──
|
||||
{
|
||||
text: 'BREAKING: Iran signals readiness for "unconditional ceasefire talks" through Oman channel. CENTCOM suspends offensive air operations pending diplomatic contact',
|
||||
date: '2026-03-19T18:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'CENTCOM: Strait of Hormuz now 60% restored to normal commercial traffic. Coalition minesweeping teams cleared 41 mines total since Day 1',
|
||||
date: '2026-03-19T09:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
// ── 3월 18일 (D+18) ──
|
||||
{
|
||||
text: 'CENTCOM: Houthi forces launched coordinated mini-submarine torpedo attack against USS Nimitz CSG in Red Sea. All 3 vessels intercepted and destroyed',
|
||||
date: '2026-03-18T20:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'CENTCOM: F-22 Raptors conducted first-ever combat operations over Iranian airspace, escorting B-2s striking hardened underground sites near Qom',
|
||||
date: '2026-03-18T07:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
// ── 3월 17일 (D+17) ──
|
||||
{
|
||||
text: 'CENTCOM: B-2 stealth bombers and GBU-57 MOPs successfully struck the Fordow Fuel Enrichment Plant. Underground enrichment halls confirmed destroyed',
|
||||
date: '2026-03-17T10:00:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
{
|
||||
text: 'BREAKING: Iran\'s new Supreme Leader Mojtaba Khamenei issues statement delegating "pre-authorized nuclear retaliation" to IRGC. UN Security Council convenes emergency session',
|
||||
date: '2026-03-17T05:30:00Z',
|
||||
url: 'https://x.com/CENTCOM',
|
||||
},
|
||||
// ── 3월 16일 (D+16) ──
|
||||
{
|
||||
text: 'CENTCOM: Isfahan military complex struck overnight by B-2 stealth bombers. 15 targets destroyed including underground command bunkers',
|
||||
date: '2026-03-16T06:00:00Z',
|
||||
@ -404,7 +464,132 @@ async function fetchXCentcom(): Promise<OsintItem[]> {
|
||||
|
||||
// ── Pinned OSINT articles (manually curated) ──
|
||||
const PINNED_IRAN: OsintItem[] = [
|
||||
// ── 3월 16일 최신 ──
|
||||
// ── 3월 21일 최신 ──
|
||||
{
|
||||
id: 'pinned-kr-ceasefire-talks-0321',
|
||||
timestamp: new Date('2026-03-21T10:00:00+09:00').getTime(),
|
||||
title: '[속보] 미-이란, 오만 무스카트서 휴전 협상 2일차… "핵 시설 감시단 수용" 이란 내부 검토',
|
||||
source: '연합뉴스',
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 23.58, lng: 58.40,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-oil-drop-0321',
|
||||
timestamp: new Date('2026-03-21T08:00:00+09:00').getTime(),
|
||||
title: '브렌트유 $97로 하락… 휴전 협상 기대감 반영, 한국 정유사 비축유 방출 중단 검토',
|
||||
source: '매일경제',
|
||||
url: 'https://www.mk.co.kr',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 37.57, lng: 126.98,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-hormuz-72pct-0321',
|
||||
timestamp: new Date('2026-03-21T06:00:00+09:00').getTime(),
|
||||
title: '호르무즈 해협 통항량 72% 회복… 한국 수입 유조선 5척 오늘 무사 통과',
|
||||
source: 'SBS',
|
||||
url: 'https://news.sbs.co.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
// ── 3월 20일 ──
|
||||
{
|
||||
id: 'pinned-kr-muscat-talks-0320',
|
||||
timestamp: new Date('2026-03-20T20:00:00+09:00').getTime(),
|
||||
title: '[긴급] 미-이란 협상단 오만 무스카트 회동 확인… 오만 외무 중재, 핵 동결 조건 논의',
|
||||
source: 'KBS',
|
||||
url: 'https://news.kbs.co.kr',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 23.58, lng: 58.40,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-irgc-flee-0320',
|
||||
timestamp: new Date('2026-03-20T14:00:00+09:00').getTime(),
|
||||
title: 'IRGC 고위 사령관 다수, 러시아 망명 정황 포착… 이란 지휘체계 붕괴 우려',
|
||||
source: '조선일보',
|
||||
url: 'https://www.chosun.com',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 35.69, lng: 51.39,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-tanker-return-0320',
|
||||
timestamp: new Date('2026-03-20T09:00:00+09:00').getTime(),
|
||||
title: '한국 유조선 "광양 파이오니어호" 호르무즈 통과 성공… 30일 만에 첫 정상 귀항',
|
||||
source: '해사신문',
|
||||
url: 'https://www.haesanews.com',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
// ── 3월 19일 ──
|
||||
{
|
||||
id: 'pinned-kr-iran-ceasefire-0319',
|
||||
timestamp: new Date('2026-03-19T18:00:00+09:00').getTime(),
|
||||
title: '[속보] 이란, 오만 채널 통해 "무조건 휴전 협상 준비" 신호… 미국 "확인 중"',
|
||||
source: '연합뉴스',
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 35.69, lng: 51.39,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-ko-reserves-0319',
|
||||
timestamp: new Date('2026-03-19T12:00:00+09:00').getTime(),
|
||||
title: '정부 "원유 수급 숨통 트였다"… 비축유 80일분 유지·추가 방출 잠정 보류',
|
||||
source: '서울경제',
|
||||
url: 'https://en.sedaily.com',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 37.57, lng: 126.98,
|
||||
},
|
||||
// ── 3월 18일 ──
|
||||
{
|
||||
id: 'pinned-kr-houthi-sub-0318',
|
||||
timestamp: new Date('2026-03-18T22:00:00+09:00').getTime(),
|
||||
title: '예멘 후티, 미 항공모함 겨냥 소형 잠수정 어뢰 공격 시도… 미 해군 3척 격침',
|
||||
source: 'BBC Korea',
|
||||
url: 'https://www.bbc.com/korean',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 14.80, lng: 42.95,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-f22-0318',
|
||||
timestamp: new Date('2026-03-18T08:00:00+09:00').getTime(),
|
||||
title: 'F-22 랩터, 이란 상공 첫 실전 투입 확인… B-2 호위하며 쿰 인근 지하시설 공격',
|
||||
source: '조선일보',
|
||||
url: 'https://www.chosun.com',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 34.64, lng: 50.88,
|
||||
},
|
||||
// ── 3월 17일 ──
|
||||
{
|
||||
id: 'pinned-kr-fordow-0317',
|
||||
timestamp: new Date('2026-03-17T12:00:00+09:00').getTime(),
|
||||
title: '[속보] 미군, 포르도 핵연료 농축시설 벙커버스터 공격… 지하 격납고 완파 확인',
|
||||
source: '연합뉴스',
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'nuclear',
|
||||
language: 'ko',
|
||||
lat: 34.88, lng: 49.93,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-nuclear-threat-0317',
|
||||
timestamp: new Date('2026-03-17T07:00:00+09:00').getTime(),
|
||||
title: '이란 최고지도자, IRGC에 "선제 핵 보복 권한 위임" 발표… UN 안보리 긴급 소집',
|
||||
source: 'MBC',
|
||||
url: 'https://imnews.imbc.com',
|
||||
category: 'nuclear',
|
||||
language: 'ko',
|
||||
lat: 35.69, lng: 51.39,
|
||||
},
|
||||
// ── 3월 16일 ──
|
||||
{
|
||||
id: 'pinned-kr-isfahan-0316',
|
||||
timestamp: new Date('2026-03-16T10:00:00+09:00').getTime(),
|
||||
@ -413,8 +598,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 32.65,
|
||||
lng: 51.67,
|
||||
lat: 32.65, lng: 51.67,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-ceasefire-0316',
|
||||
@ -424,42 +608,18 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://www.voakorea.com',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 35.69,
|
||||
lng: 51.39,
|
||||
lat: 35.69, lng: 51.39,
|
||||
},
|
||||
// ── 3월 15일 ──
|
||||
{
|
||||
id: 'pinned-kr-hormuz-派兵-0315',
|
||||
id: 'pinned-kr-hormuz-파병-0315',
|
||||
timestamp: new Date('2026-03-15T18:00:00+09:00').getTime(),
|
||||
title: '[단독] 트럼프, 한국 등 5개국에 호르무즈 군함 파견 요구… 청해부대식 파병 논의',
|
||||
source: '뉴데일리',
|
||||
url: 'https://www.newdaily.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-dispatch-debate-0315',
|
||||
timestamp: new Date('2026-03-15T15:00:00+09:00').getTime(),
|
||||
title: '[사설] 미국의 호르무즈 파병 요청, 이란전 참전 비칠 수 있어… 신중 대응 필요',
|
||||
source: '경향신문',
|
||||
url: 'https://www.khan.co.kr',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 37.57,
|
||||
lng: 126.98,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-turkey-nato-0315',
|
||||
timestamp: new Date('2026-03-15T12:00:00+09:00').getTime(),
|
||||
title: 'NATO 방공망, 튀르키예 상공서 이란 탄도미사일 3번째 요격… Article 5 논의 가속',
|
||||
source: 'BBC Korea',
|
||||
url: 'https://www.bbc.com/korean',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 37.00,
|
||||
lng: 35.43,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-kospi-0315',
|
||||
@ -469,8 +629,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://biz.newdaily.co.kr',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 37.57,
|
||||
lng: 126.98,
|
||||
lat: 37.57, lng: 126.98,
|
||||
},
|
||||
// ── 3월 14일 ──
|
||||
{
|
||||
@ -481,8 +640,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://www.mk.co.kr',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-hormuz-shutdown-0314',
|
||||
@ -492,8 +650,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://news.ifm.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-tanker-0314',
|
||||
@ -503,8 +660,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://www.bloomberg.com',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
// ── 3월 13일 ──
|
||||
{
|
||||
@ -515,8 +671,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-hormuz-0313b',
|
||||
@ -526,8 +681,7 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://news.kbs.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 26.30,
|
||||
lng: 56.50,
|
||||
lat: 26.30, lng: 56.50,
|
||||
},
|
||||
{
|
||||
id: 'pinned-kr-ship-0312',
|
||||
@ -537,14 +691,88 @@ const PINNED_IRAN: OsintItem[] = [
|
||||
url: 'https://news.sbs.co.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.20,
|
||||
lng: 56.60,
|
||||
lat: 26.20, lng: 56.60,
|
||||
},
|
||||
];
|
||||
|
||||
// ── Pinned OSINT articles (Korea maritime/security) ──
|
||||
const PINNED_KOREA: OsintItem[] = [
|
||||
// ── 3월 15일 최신 ──
|
||||
// ── 3월 21일 최신 ──
|
||||
{
|
||||
id: 'pin-kr-cn-fishing-0321',
|
||||
timestamp: new Date('2026-03-21T09:00:00+09:00').getTime(),
|
||||
title: '[속보] 중국어선 250척 이상 서해 EEZ 집단 침범… 해경 함정 12척 긴급 출동',
|
||||
source: '연합뉴스',
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'fishing',
|
||||
language: 'ko',
|
||||
lat: 37.20, lng: 124.80,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-hormuz-talks-0321',
|
||||
timestamp: new Date('2026-03-21T07:00:00+09:00').getTime(),
|
||||
title: '정부 "이란 협상 타결 시 비축유 방출 중단"… 원유 수급 정상화 기대감',
|
||||
source: '서울경제',
|
||||
url: 'https://en.sedaily.com',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 37.57, lng: 126.98,
|
||||
},
|
||||
// ── 3월 20일 ──
|
||||
{
|
||||
id: 'pin-kr-jmsdf-0320',
|
||||
timestamp: new Date('2026-03-20T16:00:00+09:00').getTime(),
|
||||
title: '한미일 공동 해상 순찰 강화… F-35B 탑재 JMSDF 함정 동해 합류',
|
||||
source: '국방일보',
|
||||
url: 'https://www.kookbang.com',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 37.50, lng: 130.00,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-mof-38ships-0320',
|
||||
timestamp: new Date('2026-03-20T11:00:00+09:00').getTime(),
|
||||
title: '해양수산부, 호르무즈 인근 한국 선박 38척 안전 관리 중… 2척 귀항 성공',
|
||||
source: '해사신문',
|
||||
url: 'https://www.haesanews.com',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
// ── 3월 19일 ──
|
||||
{
|
||||
id: 'pin-kr-coast-guard-crackdown-0319',
|
||||
timestamp: new Date('2026-03-19T10:00:00+09:00').getTime(),
|
||||
title: '해경, 서해5도 꽃게 시즌 앞두고 중국 불법어선 특별단속… 18척 나포, 350척 검문',
|
||||
source: '아시아경제',
|
||||
url: 'https://www.asiae.co.kr',
|
||||
category: 'fishing',
|
||||
language: 'ko',
|
||||
lat: 37.67, lng: 125.70,
|
||||
},
|
||||
// ── 3월 18일 ──
|
||||
{
|
||||
id: 'pin-kr-nk-response-0318',
|
||||
timestamp: new Date('2026-03-18T14:00:00+09:00').getTime(),
|
||||
title: '북한, 이란 전황 관련 "반미 연대" 성명 발표… 군사정보 공유 가능성 주목',
|
||||
source: 'KBS',
|
||||
url: 'https://news.kbs.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 39.00, lng: 125.75,
|
||||
},
|
||||
// ── 3월 17일 ──
|
||||
{
|
||||
id: 'pin-kr-coast-guard-seizure-0317',
|
||||
timestamp: new Date('2026-03-17T09:00:00+09:00').getTime(),
|
||||
title: '[단독] 해경, 올해 최대 규모 중국어선 동시 나포… 부산 해경서 20척 압류·선원 47명 조사',
|
||||
source: '연합뉴스',
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'fishing',
|
||||
language: 'ko',
|
||||
lat: 35.10, lng: 129.04,
|
||||
},
|
||||
// ── 3월 15일 ──
|
||||
{
|
||||
id: 'pin-kr-nk-missile-0315',
|
||||
timestamp: new Date('2026-03-15T07:00:00+09:00').getTime(),
|
||||
@ -553,8 +781,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.yna.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 39.00,
|
||||
lng: 127.00,
|
||||
lat: 39.00, lng: 127.00,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-nk-kimyojong-0315',
|
||||
@ -564,8 +791,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://news.kbs.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 39.00,
|
||||
lng: 125.75,
|
||||
lat: 39.00, lng: 125.75,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-hormuz-deploy-0315',
|
||||
@ -575,32 +801,9 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.newdaily.co.kr',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-kctu-0315',
|
||||
timestamp: new Date('2026-03-15T14:00:00+09:00').getTime(),
|
||||
title: '민주노총 "호르무즈 파병은 침략전쟁 참전"… 파병 반대 성명',
|
||||
source: '경향신문',
|
||||
url: 'https://www.khan.co.kr',
|
||||
category: 'diplomacy',
|
||||
language: 'ko',
|
||||
lat: 37.57,
|
||||
lng: 126.98,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
// ── 3월 14일 ──
|
||||
{
|
||||
id: 'pin-kr-hormuz-zero-0314',
|
||||
timestamp: new Date('2026-03-14T20:00:00+09:00').getTime(),
|
||||
title: '[긴급] 호르무즈 해협 통항 제로… AIS 기준 양방향 선박 이동 완전 중단',
|
||||
source: 'News1',
|
||||
url: 'https://www.news1.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-freedom-shield-0314',
|
||||
timestamp: new Date('2026-03-14T09:00:00+09:00').getTime(),
|
||||
@ -609,8 +812,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://imnews.imbc.com',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 37.50,
|
||||
lng: 127.00,
|
||||
lat: 37.50, lng: 127.00,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-hmm-0314',
|
||||
@ -620,8 +822,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.haesanews.com',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.00,
|
||||
lng: 56.00,
|
||||
lat: 26.00, lng: 56.00,
|
||||
},
|
||||
// ── 3월 13일 ──
|
||||
{
|
||||
@ -632,8 +833,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://en.sedaily.com',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 37.57,
|
||||
lng: 126.98,
|
||||
lat: 37.57, lng: 126.98,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-coast-guard-0313',
|
||||
@ -643,8 +843,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.asiae.co.kr',
|
||||
category: 'maritime_traffic',
|
||||
language: 'ko',
|
||||
lat: 37.67,
|
||||
lng: 125.70,
|
||||
lat: 37.67, lng: 125.70,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-nk-destroyer-0312',
|
||||
@ -654,8 +853,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.aei.org',
|
||||
category: 'military',
|
||||
language: 'ko',
|
||||
lat: 39.80,
|
||||
lng: 127.50,
|
||||
lat: 39.80, lng: 127.50,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-oil-reserve-0312',
|
||||
@ -665,19 +863,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.hankyung.com',
|
||||
category: 'oil',
|
||||
language: 'ko',
|
||||
lat: 36.97,
|
||||
lng: 126.83,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-mof-emergency-0312',
|
||||
timestamp: new Date('2026-03-12T10:00:00+09:00').getTime(),
|
||||
title: '해양수산부 24시간 비상체제 가동… 호르무즈 인근 한국선박 40척 안전관리',
|
||||
source: '해사신문',
|
||||
url: 'https://www.haesanews.com',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 36.00,
|
||||
lng: 127.00,
|
||||
lat: 36.97, lng: 126.83,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-chinese-fishing-0311',
|
||||
@ -687,19 +873,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.asiaa.co.kr',
|
||||
category: 'fishing',
|
||||
language: 'ko',
|
||||
lat: 37.67,
|
||||
lng: 125.50,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-spring-safety-0311',
|
||||
timestamp: new Date('2026-03-11T08:00:00+09:00').getTime(),
|
||||
title: '해수부, 봄철 해양사고 예방대책 시행… 안개 충돌사고 대비 인천항 무인순찰로봇 도입',
|
||||
source: 'iFM',
|
||||
url: 'https://news.ifm.kr',
|
||||
category: 'maritime_traffic',
|
||||
language: 'ko',
|
||||
lat: 37.45,
|
||||
lng: 126.60,
|
||||
lat: 37.67, lng: 125.50,
|
||||
},
|
||||
{
|
||||
id: 'pin-kr-ships-hormuz-0311',
|
||||
@ -709,8 +883,7 @@ const PINNED_KOREA: OsintItem[] = [
|
||||
url: 'https://www.seoul.co.kr',
|
||||
category: 'shipping',
|
||||
language: 'ko',
|
||||
lat: 26.56,
|
||||
lng: 56.25,
|
||||
lat: 26.56, lng: 56.25,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -144,6 +144,16 @@ export interface LayerVisibility {
|
||||
oilFacilities: boolean;
|
||||
meFacilities: boolean;
|
||||
militaryOnly: boolean;
|
||||
overseasUS: boolean;
|
||||
overseasUK: boolean;
|
||||
overseasIran: boolean;
|
||||
overseasUAE: boolean;
|
||||
overseasSaudi: boolean;
|
||||
overseasOman: boolean;
|
||||
overseasQatar: boolean;
|
||||
overseasKuwait: boolean;
|
||||
overseasIraq: boolean;
|
||||
overseasBahrain: boolean;
|
||||
}
|
||||
|
||||
export type AppMode = 'replay' | 'live';
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user