import { IconLayer, TextLayer } from '@deck.gl/layers'; import type { PickingInfo, Layer } from '@deck.gl/core'; import { svgToDataUri } from '../../utils/svgToDataUri'; import { ME_ENERGY_HAZARD_FACILITIES, SUB_TYPE_META, layerKeyToSubType, layerKeyToCountry, type EnergyHazardFacility, type FacilitySubType, } from '../../data/meEnergyHazardFacilities'; // LayerVisibility overseas key → countryKey mapping const COUNTRY_KEY_TO_LAYER_KEY: Record = { us: 'overseasUS', ir: 'overseasIran', ae: 'overseasUAE', sa: 'overseasSaudi', om: 'overseasOman', qa: 'overseasQatar', kw: 'overseasKuwait', iq: 'overseasIraq', bh: 'overseasBahrain', // il (Israel) is shown when meFacilities is true (no dedicated overseas key) il: 'meFacilities', }; function isFacilityVisible(f: EnergyHazardFacility, layers: Record): boolean { const countryLayerKey = COUNTRY_KEY_TO_LAYER_KEY[f.countryKey]; if (!countryLayerKey || !layers[countryLayerKey]) return false; // Check sub-type toggle if present, otherwise fall through to country-level toggle // Sub-type keys: e.g. "irPower", "ilNuclear", "usOilTank" const subTypeKey = f.countryKey + capitalizeFirst(f.subType.replace('_', '')); if (subTypeKey in layers) return !!layers[subTypeKey]; // Check category-level parent key: e.g. "irEnergy", "usHazard" const categoryKey = f.countryKey + capitalizeFirst(f.category); if (categoryKey in layers) return !!layers[categoryKey]; // Fall back to country-level toggle return true; } function capitalizeFirst(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } // Pre-build layer-key → subType entries from layerKeyToSubType/layerKeyToCountry // for reference — the actual filter uses the above isFacilityVisible logic. // Exported for re-use elsewhere if needed. export { layerKeyToSubType, layerKeyToCountry }; export interface MELayerConfig { layers: Record; sc: number; fs?: number; onPick: (facility: EnergyHazardFacility) => void; } function hexToRgba(hex: string, alpha = 255): [number, number, number, number] { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return [r, g, b, alpha]; } // ─── SVG icon functions ──────────────────────────────────────────────────────── function powerSvg(color: string, size: number): string { return ` `; } function windSvg(color: string, size: number): string { return ` `; } function nuclearSvg(color: string, size: number): string { return ` `; } function thermalSvg(color: string, size: number): string { return ` `; } function petrochemSvg(color: string, size: number): string { return ` `; } function lngSvg(color: string, size: number): string { return ` `; } function oilTankSvg(color: string, size: number): string { return ` `; } function hazPortSvg(color: string, size: number): string { return ` `; } const SUB_TYPE_SVG_FN: Record string> = { power: powerSvg, wind: windSvg, nuclear: nuclearSvg, thermal: thermalSvg, petrochem: petrochemSvg, lng: lngSvg, oil_tank: oilTankSvg, haz_port: hazPortSvg, }; const iconCache = new Map(); function getIconUrl(subType: FacilitySubType): string { if (!iconCache.has(subType)) { const color = SUB_TYPE_META[subType].color; iconCache.set(subType, svgToDataUri(SUB_TYPE_SVG_FN[subType](color, 64))); } return iconCache.get(subType)!; } export function createMEEnergyHazardLayers(config: MELayerConfig): Layer[] { const { layers, sc, onPick } = config; const fs = config.fs ?? 1; const visibleFacilities = ME_ENERGY_HAZARD_FACILITIES.filter(f => isFacilityVisible(f, layers), ); if (visibleFacilities.length === 0) return []; const iconLayer = new IconLayer({ id: 'me-energy-hazard-icon', data: visibleFacilities, getPosition: (d) => [d.lng, d.lat], getIcon: (d) => ({ url: getIconUrl(d.subType), width: 64, height: 64, anchorX: 32, anchorY: 32 }), getSize: (d) => (d.category === 'hazard' ? 20 : 18) * sc, updateTriggers: { getSize: [sc] }, pickable: true, onClick: (info: PickingInfo) => { if (info.object) onPick(info.object); return true; }, }); const labelLayer = new TextLayer({ id: 'me-energy-hazard-label', data: visibleFacilities, getPosition: (d) => [d.lng, d.lat], getText: (d) => d.nameKo.length > 12 ? d.nameKo.slice(0, 12) + '..' : d.nameKo, getSize: 12 * sc * fs, updateTriggers: { getSize: [sc] }, getColor: (d) => hexToRgba(SUB_TYPE_META[d.subType].color, 200), getTextAnchor: 'middle', getAlignmentBaseline: 'top', getPixelOffset: [0, 12], fontFamily: 'monospace', fontWeight: 600, fontSettings: { sdf: true }, outlineWidth: 8, outlineColor: [0, 0, 0, 255], billboard: false, characterSet: 'auto', }); return [iconLayer, labelLayer]; }