gc-wing/apps/web/src/widgets/map3d/lib/layerHelpers.ts
htlee c5d89c5641 refactor(map): Map3D.tsx hooks 추출 완료 (4558줄 → 510줄)
28개 useEffect + 30+ useCallback을 10개 커스텀 hook으로 추출:
- useMapInit: MapLibre 인스턴스 생성 + Deck 오버레이
- useProjectionToggle: Mercator↔Globe 전환
- useBaseMapToggle: 베이스맵 전환 + 수심/해도
- useZonesLayer: 수역 GeoJSON 레이어
- usePredictionVectors: 예측 벡터 레이어
- useGlobeShips: Globe 선박 아이콘/라벨/호버/클릭
- useGlobeOverlays: Globe pair/fc/fleet/range 레이어
- useGlobeInteraction: Globe 마우스 이벤트 + 툴팁
- useDeckLayers: Mercator + Globe Deck 레이어
- useFlyTo: 카메라 이동

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 00:41:11 +09:00

119 lines
2.6 KiB
TypeScript

import maplibregl, {
type GeoJSONSourceSpecification,
type LayerSpecification,
} from 'maplibre-gl';
export function removeLayerIfExists(map: maplibregl.Map, layerId: string | null | undefined) {
if (!layerId) return;
try {
if (map.getLayer(layerId)) {
map.removeLayer(layerId);
}
} catch {
// ignore
}
}
export function removeSourceIfExists(map: maplibregl.Map, sourceId: string) {
try {
if (map.getSource(sourceId)) {
map.removeSource(sourceId);
}
} catch {
// ignore
}
}
const GLOBE_NATIVE_LAYER_IDS = [
'ships-globe-halo',
'ships-globe-outline',
'ships-globe',
'ships-globe-label',
'ships-globe-hover-halo',
'ships-globe-hover-outline',
'ships-globe-hover',
'pair-lines-ml',
'fc-lines-ml',
'fleet-circles-ml-fill',
'fleet-circles-ml',
'pair-range-ml',
'deck-globe',
];
const GLOBE_NATIVE_SOURCE_IDS = [
'ships-globe-src',
'ships-globe-hover-src',
'pair-lines-ml-src',
'fc-lines-ml-src',
'fleet-circles-ml-src',
'fleet-circles-ml-fill-src',
'pair-range-ml-src',
];
export function clearGlobeNativeLayers(map: maplibregl.Map) {
for (const id of GLOBE_NATIVE_LAYER_IDS) {
removeLayerIfExists(map, id);
}
for (const id of GLOBE_NATIVE_SOURCE_IDS) {
removeSourceIfExists(map, id);
}
}
export function ensureGeoJsonSource(
map: maplibregl.Map,
sourceId: string,
data: GeoJSON.GeoJSON,
) {
const existing = map.getSource(sourceId);
if (existing) {
(existing as maplibregl.GeoJSONSource).setData(data);
} else {
map.addSource(sourceId, {
type: 'geojson',
data,
} satisfies GeoJSONSourceSpecification);
}
}
export function ensureLayer(
map: maplibregl.Map,
spec: LayerSpecification,
options?: { before?: string },
) {
if (map.getLayer(spec.id)) return;
const before = options?.before && map.getLayer(options.before) ? options.before : undefined;
map.addLayer(spec, before);
}
export function setLayerVisibility(map: maplibregl.Map, layerId: string, visible: boolean) {
if (!map.getLayer(layerId)) return;
try {
map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
} catch {
// ignore
}
}
export function cleanupLayers(
map: maplibregl.Map,
layerIds: string[],
sourceIds: string[],
) {
requestAnimationFrame(() => {
for (const id of layerIds) {
try {
if (map.getLayer(id)) map.removeLayer(id);
} catch {
// ignore
}
}
for (const id of sourceIds) {
try {
if (map.getSource(id)) map.removeSource(id);
} catch {
// ignore
}
}
});
}