refactor: Phase 4-2 — shipClassification 유틸 추출 (ShipLayer 862줄→769줄)
- utils/shipClassification.ts: MT 색상/타입매핑/국기/사이즈 추출 (87줄) - ShipLayer: 로컬 상수/함수 93줄 → import 1줄로 교체
This commit is contained in:
부모
c6c3b5ffb9
커밋
2b009ca81a
@ -1,8 +1,9 @@
|
||||
import { memo, useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { Marker, Popup, Source, Layer, useMap } from 'react-map-gl/maplibre';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { Ship, ShipCategory, VesselAnalysisDto } from '../../types';
|
||||
import type { Ship, VesselAnalysisDto } from '../../types';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import { MT_TYPE_HEX, getMTType, NAVY_COLORS, FLAG_EMOJI, SIZE_MAP, isMilitary, getShipColor } from '../../utils/shipClassification';
|
||||
|
||||
interface Props {
|
||||
ships: Ship[];
|
||||
@ -14,100 +15,6 @@ interface Props {
|
||||
analysisMap?: Map<string, VesselAnalysisDto>;
|
||||
}
|
||||
|
||||
// ── MarineTraffic-style vessel type colors (CSS variable references) ──
|
||||
const MT_TYPE_COLORS: Record<string, string> = {
|
||||
cargo: 'var(--kcg-ship-cargo)',
|
||||
tanker: 'var(--kcg-ship-tanker)',
|
||||
passenger: 'var(--kcg-ship-passenger)',
|
||||
fishing: 'var(--kcg-ship-fishing)',
|
||||
fishing_gear: '#f97316',
|
||||
pleasure: 'var(--kcg-ship-pleasure)',
|
||||
military: 'var(--kcg-ship-military)',
|
||||
tug_special: 'var(--kcg-ship-tug)',
|
||||
other: 'var(--kcg-ship-other)',
|
||||
unknown: 'var(--kcg-ship-unknown)',
|
||||
};
|
||||
|
||||
// Resolved hex colors for MapLibre paint (which cannot use CSS vars)
|
||||
const MT_TYPE_HEX: Record<string, string> = {
|
||||
cargo: '#f0a830',
|
||||
tanker: '#e74c3c',
|
||||
passenger: '#4caf50',
|
||||
fishing: '#42a5f5',
|
||||
fishing_gear: '#f97316',
|
||||
pleasure: '#e91e8c',
|
||||
military: '#d32f2f',
|
||||
tug_special: '#2e7d32',
|
||||
other: '#5c6bc0',
|
||||
unknown: '#9e9e9e',
|
||||
};
|
||||
|
||||
// Map our internal ShipCategory + typecode → MT visual type
|
||||
function getMTType(ship: Ship): string {
|
||||
const tc = (ship.typecode || '').toUpperCase();
|
||||
const cat = ship.category;
|
||||
|
||||
// Military first
|
||||
if (cat === 'carrier' || cat === 'destroyer' || cat === 'warship' || cat === 'submarine' || cat === 'patrol') return 'military';
|
||||
if (tc === 'DDG' || tc === 'DDH' || tc === 'CVN' || tc === 'FFG' || tc === 'LCS' || tc === 'MCM' || tc === 'PC' || tc === 'LPH') return 'military';
|
||||
|
||||
// Tanker
|
||||
if (cat === 'tanker') return 'tanker';
|
||||
if (tc === 'VLCC' || tc === 'LNG' || tc === 'LPG') return 'tanker';
|
||||
if (tc.startsWith('A1')) return 'tanker';
|
||||
|
||||
// Cargo
|
||||
if (cat === 'cargo') return 'cargo';
|
||||
if (tc === 'CONT' || tc === 'BULK') return 'cargo';
|
||||
if (tc.startsWith('A2') || tc.startsWith('A3')) return 'cargo';
|
||||
|
||||
// Passenger
|
||||
if (tc === 'PASS' || tc.startsWith('B')) return 'passenger';
|
||||
|
||||
// Fishing
|
||||
if (tc.startsWith('C')) return 'fishing';
|
||||
|
||||
// Tug / Special
|
||||
if (tc.startsWith('D') || tc.startsWith('E')) return 'tug_special';
|
||||
|
||||
// Pleasure
|
||||
if (tc === 'SAIL' || tc === 'YACHT') return 'pleasure';
|
||||
|
||||
if (cat === 'civilian') return 'other';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Legacy navy flag colors (for popup header accent only)
|
||||
const NAVY_COLORS: Record<string, string> = {
|
||||
US: '#1e90ff', UK: '#e63946', FR: '#ffd60a', KR: '#00e5ff',
|
||||
IR: '#2ecc40', JP: '#ff6b6b', AU: '#f4a261', DE: '#b5b5b5', IN: '#ff9f43',
|
||||
};
|
||||
|
||||
const FLAG_EMOJI: Record<string, string> = {
|
||||
US: '\u{1F1FA}\u{1F1F8}', UK: '\u{1F1EC}\u{1F1E7}', FR: '\u{1F1EB}\u{1F1F7}',
|
||||
KR: '\u{1F1F0}\u{1F1F7}', IR: '\u{1F1EE}\u{1F1F7}', JP: '\u{1F1EF}\u{1F1F5}',
|
||||
AU: '\u{1F1E6}\u{1F1FA}', DE: '\u{1F1E9}\u{1F1EA}', IN: '\u{1F1EE}\u{1F1F3}',
|
||||
CN: '\u{1F1E8}\u{1F1F3}', PA: '\u{1F1F5}\u{1F1E6}', LR: '\u{1F1F1}\u{1F1F7}',
|
||||
MH: '\u{1F1F2}\u{1F1ED}', HK: '\u{1F1ED}\u{1F1F0}', SG: '\u{1F1F8}\u{1F1EC}',
|
||||
BZ: '\u{1F1E7}\u{1F1FF}', OM: '\u{1F1F4}\u{1F1F2}', AE: '\u{1F1E6}\u{1F1EA}',
|
||||
SA: '\u{1F1F8}\u{1F1E6}', BH: '\u{1F1E7}\u{1F1ED}', QA: '\u{1F1F6}\u{1F1E6}',
|
||||
};
|
||||
|
||||
// icon-size multiplier (symbol layer, base=64px)
|
||||
const SIZE_MAP: Record<ShipCategory, number> = {
|
||||
carrier: 0.32, destroyer: 0.22, warship: 0.22, submarine: 0.18, patrol: 0.16,
|
||||
tanker: 0.16, cargo: 0.16, fishing: 0.14, civilian: 0.14, unknown: 0.12,
|
||||
};
|
||||
|
||||
const MIL_CATEGORIES: ShipCategory[] = ['carrier', 'destroyer', 'warship', 'submarine', 'patrol'];
|
||||
|
||||
function isMilitary(category: ShipCategory): boolean {
|
||||
return MIL_CATEGORIES.includes(category);
|
||||
}
|
||||
|
||||
function getShipColor(ship: Ship): string {
|
||||
return MT_TYPE_COLORS[getMTType(ship)] || MT_TYPE_COLORS.unknown;
|
||||
}
|
||||
|
||||
function getShipHex(ship: Ship): string {
|
||||
return MT_TYPE_HEX[getMTType(ship)] || MT_TYPE_HEX.unknown;
|
||||
|
||||
84
frontend/src/utils/shipClassification.ts
Normal file
84
frontend/src/utils/shipClassification.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import type { Ship, ShipCategory } from '../types';
|
||||
|
||||
// ── MarineTraffic-style vessel type colors (CSS variable references) ──
|
||||
export const MT_TYPE_COLORS: Record<string, string> = {
|
||||
cargo: 'var(--kcg-ship-cargo)',
|
||||
tanker: 'var(--kcg-ship-tanker)',
|
||||
passenger: 'var(--kcg-ship-passenger)',
|
||||
fishing: 'var(--kcg-ship-fishing)',
|
||||
fishing_gear: '#f97316',
|
||||
pleasure: 'var(--kcg-ship-pleasure)',
|
||||
military: 'var(--kcg-ship-military)',
|
||||
tug_special: 'var(--kcg-ship-tug)',
|
||||
other: 'var(--kcg-ship-other)',
|
||||
unknown: 'var(--kcg-ship-unknown)',
|
||||
};
|
||||
|
||||
// Resolved hex colors for MapLibre paint (which cannot use CSS vars)
|
||||
export const MT_TYPE_HEX: Record<string, string> = {
|
||||
cargo: '#f0a830',
|
||||
tanker: '#e74c3c',
|
||||
passenger: '#4caf50',
|
||||
fishing: '#42a5f5',
|
||||
fishing_gear: '#f97316',
|
||||
pleasure: '#e91e8c',
|
||||
military: '#d32f2f',
|
||||
tug_special: '#2e7d32',
|
||||
other: '#5c6bc0',
|
||||
unknown: '#9e9e9e',
|
||||
};
|
||||
|
||||
export function getMTType(ship: Ship): string {
|
||||
const tc = (ship.typecode || '').toUpperCase();
|
||||
const cat = ship.category;
|
||||
|
||||
if (cat === 'carrier' || cat === 'destroyer' || cat === 'warship' || cat === 'submarine' || cat === 'patrol') return 'military';
|
||||
if (tc === 'DDG' || tc === 'DDH' || tc === 'CVN' || tc === 'FFG' || tc === 'LCS' || tc === 'MCM' || tc === 'PC' || tc === 'LPH') return 'military';
|
||||
|
||||
if (cat === 'tanker') return 'tanker';
|
||||
if (tc === 'VLCC' || tc === 'LNG' || tc === 'LPG') return 'tanker';
|
||||
if (tc.startsWith('A1')) return 'tanker';
|
||||
|
||||
if (cat === 'cargo') return 'cargo';
|
||||
if (tc === 'CONT' || tc === 'BULK') return 'cargo';
|
||||
if (tc.startsWith('A2') || tc.startsWith('A3')) return 'cargo';
|
||||
|
||||
if (tc === 'PASS' || tc.startsWith('B')) return 'passenger';
|
||||
if (tc.startsWith('C')) return 'fishing';
|
||||
if (tc.startsWith('D') || tc.startsWith('E')) return 'tug_special';
|
||||
if (tc === 'SAIL' || tc === 'YACHT') return 'pleasure';
|
||||
|
||||
if (cat === 'civilian') return 'other';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Legacy navy flag colors (for popup header accent only)
|
||||
export const NAVY_COLORS: Record<string, string> = {
|
||||
US: '#1e90ff', UK: '#e63946', FR: '#ffd60a', KR: '#00e5ff',
|
||||
IR: '#2ecc40', JP: '#ff6b6b', AU: '#f4a261', DE: '#b5b5b5', IN: '#ff9f43',
|
||||
};
|
||||
|
||||
export const FLAG_EMOJI: Record<string, string> = {
|
||||
US: '\u{1F1FA}\u{1F1F8}', UK: '\u{1F1EC}\u{1F1E7}', FR: '\u{1F1EB}\u{1F1F7}',
|
||||
KR: '\u{1F1F0}\u{1F1F7}', IR: '\u{1F1EE}\u{1F1F7}', JP: '\u{1F1EF}\u{1F1F5}',
|
||||
AU: '\u{1F1E6}\u{1F1FA}', DE: '\u{1F1E9}\u{1F1EA}', IN: '\u{1F1EE}\u{1F1F3}',
|
||||
CN: '\u{1F1E8}\u{1F1F3}', PA: '\u{1F1F5}\u{1F1E6}', LR: '\u{1F1F1}\u{1F1F7}',
|
||||
MH: '\u{1F1F2}\u{1F1ED}', HK: '\u{1F1ED}\u{1F1F0}', SG: '\u{1F1F8}\u{1F1EC}',
|
||||
BZ: '\u{1F1E7}\u{1F1FF}', OM: '\u{1F1F4}\u{1F1F2}', AE: '\u{1F1E6}\u{1F1EA}',
|
||||
SA: '\u{1F1F8}\u{1F1E6}', BH: '\u{1F1E7}\u{1F1ED}', QA: '\u{1F1F6}\u{1F1E6}',
|
||||
};
|
||||
|
||||
export const SIZE_MAP: Record<ShipCategory, number> = {
|
||||
carrier: 0.32, destroyer: 0.22, warship: 0.22, submarine: 0.18, patrol: 0.16,
|
||||
tanker: 0.16, cargo: 0.16, fishing: 0.14, civilian: 0.14, unknown: 0.12,
|
||||
};
|
||||
|
||||
export const MIL_CATEGORIES: ShipCategory[] = ['carrier', 'destroyer', 'warship', 'submarine', 'patrol'];
|
||||
|
||||
export function isMilitary(category: ShipCategory): boolean {
|
||||
return MIL_CATEGORIES.includes(category);
|
||||
}
|
||||
|
||||
export function getShipColor(ship: Ship): string {
|
||||
return MT_TYPE_COLORS[getMTType(ship)] || MT_TYPE_COLORS.unknown;
|
||||
}
|
||||
불러오는 중...
Reference in New Issue
Block a user