import { IconLayer, TextLayer } from '@deck.gl/layers'; import type { PickingInfo, Layer } from '@deck.gl/core'; import { svgToDataUri } from '../../utils/svgToDataUri'; import { EAST_ASIA_PORTS } from '../../data/ports'; import type { Port } from '../../data/ports'; import { KOREA_WIND_FARMS } from '../../data/windFarms'; import type { WindFarm } from '../../data/windFarms'; import { hexToRgb } from './types'; import type { LayerFactoryConfig } from './types'; // ─── Port colors ────────────────────────────────────────────────────────────── const PORT_COUNTRY_COLOR: Record = { KR: '#3b82f6', CN: '#ef4444', JP: '#f472b6', KP: '#f97316', TW: '#10b981', }; // ─── Port SVG ──────────────────────────────────────────────────────────────── function portSvg(color: string, size: number): string { return ` `; } // ─── Wind Turbine SVG ───────────────────────────────────────────────────────── const WIND_COLOR = '#00bcd4'; function windTurbineSvg(size: number): string { return ` `; } // ─── Module-level icon caches ───────────────────────────────────────────────── const portIconCache = new Map(); const WIND_ICON_URL = svgToDataUri(windTurbineSvg(36)); export function createPortLayers( config: { ports: boolean; windFarm: boolean }, fc: LayerFactoryConfig, ): Layer[] { const layers: Layer[] = []; const sc = fc.sc; const onPick = fc.onPick; // ── Ports ─────────────────────────────────────────────────────────────── if (config.ports) { function getPortIconUrl(p: Port): string { const key = `${p.country}-${p.type}`; if (!portIconCache.has(key)) { const color = PORT_COUNTRY_COLOR[p.country] ?? PORT_COUNTRY_COLOR.KR; const size = p.type === 'major' ? 32 : 24; portIconCache.set(key, svgToDataUri(portSvg(color, size))); } return portIconCache.get(key)!; } layers.push( new IconLayer({ id: 'static-ports-icon', data: EAST_ASIA_PORTS, getPosition: (d) => [d.lng, d.lat], getIcon: (d) => ({ url: getPortIconUrl(d), width: d.type === 'major' ? 32 : 24, height: d.type === 'major' ? 32 : 24, anchorX: d.type === 'major' ? 16 : 12, anchorY: d.type === 'major' ? 16 : 12, }), getSize: (d) => (d.type === 'major' ? 16 : 12) * sc, updateTriggers: { getSize: [sc] }, pickable: true, onClick: (info: PickingInfo) => { if (info.object) onPick({ kind: 'port', object: info.object }); return true; }, }), new TextLayer({ id: 'static-ports-label', data: EAST_ASIA_PORTS, getPosition: (d) => [d.lng, d.lat], getText: (d) => d.nameKo.replace('항', ''), getSize: 12 * sc * fc.fs, updateTriggers: { getSize: [sc, fc.fs] }, getColor: (d) => [...hexToRgb(PORT_COUNTRY_COLOR[d.country] ?? PORT_COUNTRY_COLOR.KR), 255] as [number, number, number, number], getTextAnchor: 'middle', getAlignmentBaseline: 'top', getPixelOffset: [0, 8], fontFamily: 'monospace', fontWeight: 700, fontSettings: { sdf: true }, outlineWidth: 3, outlineColor: [0, 0, 0, 255], billboard: false, characterSet: 'auto', }), ); } // ── Wind Farms ───────────────────────────────────────────────────────── if (config.windFarm) { layers.push( new IconLayer({ id: 'static-windfarm-icon', data: KOREA_WIND_FARMS, getPosition: (d) => [d.lng, d.lat], getIcon: () => ({ url: WIND_ICON_URL, width: 36, height: 36, anchorX: 18, anchorY: 18 }), getSize: 18 * sc, updateTriggers: { getSize: [sc] }, pickable: true, onClick: (info: PickingInfo) => { if (info.object) onPick({ kind: 'windFarm', object: info.object }); return true; }, }), new TextLayer({ id: 'static-windfarm-label', data: KOREA_WIND_FARMS, getPosition: (d) => [d.lng, d.lat], getText: (d) => (d.name.length > 10 ? d.name.slice(0, 10) + '..' : d.name), getSize: 12 * sc * fc.fs, updateTriggers: { getSize: [sc, fc.fs] }, getColor: [...hexToRgb(WIND_COLOR), 255] as [number, number, number, number], getTextAnchor: 'middle', getAlignmentBaseline: 'top', getPixelOffset: [0, 10], fontFamily: 'monospace', fontWeight: 700, fontSettings: { sdf: true }, outlineWidth: 3, outlineColor: [0, 0, 0, 255], billboard: false, characterSet: 'auto', }), ); } return layers; }