- FontScaleContext + FontScalePanel: 시설/선박/분석/지역 4그룹 × 0.5~2.0 범위 - LAYERS 패널 하단 슬라이더 UI, localStorage 영속화 - Korea static 14개 + Iran 4개 + 분석 3개 + KoreaMap 5개 TextLayer 적용 - MapLibre 선박 라벨/국가명 실시간 반영 - 모든 useMemo deps + updateTriggers에 fontScale 포함
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import { IconLayer, TextLayer } from '@deck.gl/layers';
|
|
import type { Layer, PickingInfo } from '@deck.gl/core';
|
|
import { svgToDataUri } from '../../utils/svgToDataUri';
|
|
import { middleEastAirports } from '../../data/airports';
|
|
import type { Airport } from '../../data/airports';
|
|
|
|
export { type Airport };
|
|
|
|
export const IRAN_AIRPORT_COUNT = middleEastAirports.length;
|
|
|
|
const US_BASE_ICAOS = new Set([
|
|
'OMAD', 'OTBH', 'OKAJ', 'LTAG', 'OEPS', 'ORAA', 'ORBD', 'OBBS', 'OMTH', 'HDCL',
|
|
]);
|
|
|
|
function getAirportColor(airport: Airport): string {
|
|
const isMil = airport.type === 'military';
|
|
const isUS = isMil && US_BASE_ICAOS.has(airport.icao);
|
|
if (isUS) return '#3b82f6';
|
|
if (isMil) return '#ef4444';
|
|
if (airport.type === 'international') return '#f59e0b';
|
|
return '#7c8aaa';
|
|
}
|
|
|
|
function airportSvg(color: string, size: number): string {
|
|
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="12" cy="12" r="10" fill="rgba(0,0,0,0.5)" stroke="${color}" stroke-width="1.2"/>
|
|
<path d="M12 5 C11.4 5 11 5.4 11 5.8 L11 10 L6 13 L6 14.2 L11 12.5 L11 16.5 L9.2 17.8 L9.2 18.8 L12 18 L14.8 18.8 L14.8 17.8 L13 16.5 L13 12.5 L18 14.2 L18 13 L13 10 L13 5.8 C13 5.4 12.6 5 12 5Z"
|
|
fill="${color}" stroke="#fff" stroke-width="0.3"/>
|
|
</svg>`;
|
|
}
|
|
|
|
const iconCache = new Map<string, string>();
|
|
|
|
function getIconUrl(airport: Airport): string {
|
|
const color = getAirportColor(airport);
|
|
const size = airport.type === 'military' && US_BASE_ICAOS.has(airport.icao) ? 48 : 40;
|
|
const key = `${color}-${size}`;
|
|
if (!iconCache.has(key)) {
|
|
iconCache.set(key, svgToDataUri(airportSvg(color, size)));
|
|
}
|
|
return iconCache.get(key)!;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
export interface IranAirportLayerConfig {
|
|
visible: boolean;
|
|
sc: number;
|
|
fs?: number;
|
|
onPick: (airport: Airport) => void;
|
|
}
|
|
|
|
export function createIranAirportLayers(config: IranAirportLayerConfig): Layer[] {
|
|
const { visible, sc, onPick } = config;
|
|
const fs = config.fs ?? 1;
|
|
if (!visible) return [];
|
|
|
|
const iconLayer = new IconLayer<Airport>({
|
|
id: 'iran-airport-icon',
|
|
data: middleEastAirports,
|
|
getPosition: (d) => [d.lng, d.lat],
|
|
getIcon: (d) => {
|
|
const isMilUS = d.type === 'military' && US_BASE_ICAOS.has(d.icao);
|
|
const sz = isMilUS ? 48 : 40;
|
|
return { url: getIconUrl(d), width: sz, height: sz, anchorX: sz / 2, anchorY: sz / 2 };
|
|
},
|
|
getSize: (d) => (d.type === 'military' && US_BASE_ICAOS.has(d.icao) ? 20 : 16) * sc,
|
|
updateTriggers: { getSize: [sc] },
|
|
pickable: true,
|
|
onClick: (info: PickingInfo<Airport>) => {
|
|
if (info.object) onPick(info.object);
|
|
return true;
|
|
},
|
|
});
|
|
|
|
const labelLayer = new TextLayer<Airport>({
|
|
id: 'iran-airport-label',
|
|
data: middleEastAirports,
|
|
getPosition: (d) => [d.lng, d.lat],
|
|
getText: (d) => {
|
|
const nameKo = d.nameKo ?? d.name;
|
|
return nameKo.length > 10 ? nameKo.slice(0, 10) + '..' : nameKo;
|
|
},
|
|
getSize: 11 * sc * fs,
|
|
updateTriggers: { getSize: [sc] },
|
|
getColor: (d) => hexToRgba(getAirportColor(d)),
|
|
getTextAnchor: 'middle',
|
|
getAlignmentBaseline: 'top',
|
|
getPixelOffset: [0, 10],
|
|
fontFamily: 'monospace',
|
|
fontWeight: 600,
|
|
fontSettings: { sdf: true },
|
|
outlineWidth: 8,
|
|
outlineColor: [0, 0, 0, 255],
|
|
billboard: false,
|
|
characterSet: 'auto',
|
|
});
|
|
|
|
return [iconLayer, labelLayer];
|
|
}
|