diff --git a/apps/web/src/widgets/map3d/hooks/useZonesLayer.ts b/apps/web/src/widgets/map3d/hooks/useZonesLayer.ts index 6215356..ea1f29e 100644 --- a/apps/web/src/widgets/map3d/hooks/useZonesLayer.ts +++ b/apps/web/src/widgets/map3d/hooks/useZonesLayer.ts @@ -1,4 +1,4 @@ -import { useEffect, type MutableRefObject } from 'react'; +import { useEffect, useMemo, type MutableRefObject } from 'react'; import maplibregl, { type GeoJSONSource, type GeoJSONSourceSpecification, @@ -54,6 +54,12 @@ export function useZonesLayer( ) { const { zones, overlays, projection, baseMap, hoveredZoneId, mapSyncEpoch } = opts; + // globe용 간소화 데이터를 미리 캐싱 — ensure() 내 매번 재계산 방지 + const simplifiedZones = useMemo( + () => (zones ? simplifyZonesForGlobe(zones) : null), + [zones], + ); + useEffect(() => { const map = mapRef.current; if (!map) return; @@ -75,26 +81,32 @@ export function useZonesLayer( zoneLabelExpr.push(['coalesce', ['get', 'zoneName'], ['get', 'zoneLabel'], ['get', 'NAME'], '수역']); const ensure = () => { - if (projectionBusyRef.current) return; + // 소스 데이터 간소화 — projectionBusy 중에도 실행해야 함 + // globe 전환 시 projectionBusy 가드 뒤에서만 실행하면 MapLibre가 + // 원본(2100+ vertex) 데이터로 globe tessellation → 73,000+ vertex → 노란 막대 + const sourceData = projection === 'globe' ? simplifiedZones : zones; + if (sourceData) { + try { + const existing = map.getSource(srcId) as GeoJSONSource | undefined; + if (existing) existing.setData(sourceData); + } catch { /* ignore — source may not exist yet */ } + } + const visibility: 'visible' | 'none' = overlays.zones ? 'visible' : 'none'; - // globe 모드에서 fill polygon은 tessellation으로 vertex 65535 초과 → 숨김 - // (해안선 디테일 2100+ vertex가 globe에서 100,000+로 폭증하여 노란 막대 아티팩트 발생) const fillVisibility: 'visible' | 'none' = projection === 'globe' ? 'none' : visibility; guardedSetVisibility(map, fillId, fillVisibility); guardedSetVisibility(map, lineId, visibility); guardedSetVisibility(map, labelId, visibility); + if (projectionBusyRef.current) return; if (!zones) return; if (!map.isStyleLoaded()) return; try { - // globe: 서브샘플링된 데이터로 vertex 폭증 방지, mercator: 원본 데이터 - const sourceData = projection === 'globe' ? simplifyZonesForGlobe(zones) : zones; - const existing = map.getSource(srcId) as GeoJSONSource | undefined; - if (existing) { - existing.setData(sourceData); - } else { - map.addSource(srcId, { type: 'geojson', data: sourceData } as GeoJSONSourceSpecification); + // 소스가 아직 없으면 생성 (setData는 위에서 이미 처리됨) + if (!map.getSource(srcId)) { + const data = projection === 'globe' ? simplifiedZones ?? zones : zones; + map.addSource(srcId, { type: 'geojson', data: data! } as GeoJSONSourceSpecification); } const style = map.getStyle(); @@ -247,5 +259,5 @@ export function useZonesLayer( return () => { stop(); }; - }, [zones, overlays.zones, projection, baseMap, hoveredZoneId, mapSyncEpoch, reorderGlobeFeatureLayers]); + }, [zones, simplifiedZones, overlays.zones, projection, baseMap, hoveredZoneId, mapSyncEpoch, reorderGlobeFeatureLayers]); }