fix(map): zone 간소화를 projectionBusy 앞으로 이동

소스 데이터 간소화가 projectionBusy 가드 뒤에 있어서
globe 전환 시 원본 데이터(2100+ vertex)로 tessellation 진행 →
73,000+ vertex 폭증. setData를 가드 앞으로 이동하고
useMemo로 간소화 데이터 캐싱.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-16 13:59:21 +09:00
부모 7bec1ae86d
커밋 d5700ba587

파일 보기

@ -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]);
}