refactor(map): Map3D 모듈 분리 + 버그 4건 수정 + 수심 색상 개선
Map3D.tsx 단일 파일(5752줄)에서 1200줄을 16개 모듈로 추출하여
탐색성과 유지보수성 개선.
모듈 구조:
- types.ts, constants.ts: 타입/상수 정의
- lib/: setUtils, geometry, featureIds, mlExpressions, shipUtils,
tooltips, globeShipIcon, mapCore, dashifyLine, layerHelpers, zoneUtils
- layers/: bathymetry, seamark
- hooks/: useHoverState
버그 수정:
- fix: Globe 선박 라벨 미표시 (permitted boolean→number + filter 갱신)
- fix: placement TypeError (isStyleLoaded 가드 + epoch change 시 remove 제거)
- fix: Globe easeTo 미지원 경고 (globe 모드에서 flyTo 사용)
- fix: 수심지도 얕은 구간 색상 미구분 (색상 팔레트 개선)
개선:
- 베이스맵 water 레이어 색상을 수심 그라데이션과 자연스럽게 연결
- 프로젝션 전환 settle 로직 최적화 (더블프레임→싱글프레임)
- glyphs URL 추가로 symbol 레이어 텍스트 렌더링 지원
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:57:38 +09:00
|
|
|
import maplibregl, {
|
|
|
|
|
type GeoJSONSourceSpecification,
|
|
|
|
|
type LayerSpecification,
|
|
|
|
|
} from 'maplibre-gl';
|
|
|
|
|
|
2026-02-16 00:41:11 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
refactor(map): Map3D 모듈 분리 + 버그 4건 수정 + 수심 색상 개선
Map3D.tsx 단일 파일(5752줄)에서 1200줄을 16개 모듈로 추출하여
탐색성과 유지보수성 개선.
모듈 구조:
- types.ts, constants.ts: 타입/상수 정의
- lib/: setUtils, geometry, featureIds, mlExpressions, shipUtils,
tooltips, globeShipIcon, mapCore, dashifyLine, layerHelpers, zoneUtils
- layers/: bathymetry, seamark
- hooks/: useHoverState
버그 수정:
- fix: Globe 선박 라벨 미표시 (permitted boolean→number + filter 갱신)
- fix: placement TypeError (isStyleLoaded 가드 + epoch change 시 remove 제거)
- fix: Globe easeTo 미지원 경고 (globe 모드에서 flyTo 사용)
- fix: 수심지도 얕은 구간 색상 미구분 (색상 팔레트 개선)
개선:
- 베이스맵 water 레이어 색상을 수심 그라데이션과 자연스럽게 연결
- 프로젝션 전환 settle 로직 최적화 (더블프레임→싱글프레임)
- glyphs URL 추가로 symbol 레이어 텍스트 렌더링 지원
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:57:38 +09:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|