import { useEffect, useRef, type MutableRefObject } from 'react'; import maplibregl from 'maplibre-gl'; import type { OceanMapSettings, OceanDepthStop, OceanDepthLabelSize, OceanLabelLanguage } from '../model/types'; import type { BaseMapId } from '../../../widgets/map3d/types'; import { kickRepaint, onMapStyleReady } from '../../../widgets/map3d/lib/mapCore'; import { discoverOceanLayers } from '../lib/oceanLayerIds'; /* ── Depth font size presets ──────────────────────────────────────── */ const OCEAN_DEPTH_FONT_SIZE: Record = { small: ['interpolate', ['linear'], ['zoom'], 5, 8, 8, 10, 11, 12], medium: ['interpolate', ['linear'], ['zoom'], 5, 10, 8, 12, 11, 15], large: ['interpolate', ['linear'], ['zoom'], 5, 12, 8, 15, 11, 18], }; /* ── Apply functions (Ocean 전용 — enhanced 코드와 공유 없음) ────── */ function applyOceanDepthColors(map: maplibregl.Map, layers: string[], stops: OceanDepthStop[], opacity: number) { if (layers.length === 0) return; const depth = ['to-number', ['get', 'depth']]; const sorted = [...stops].sort((a, b) => a.depth - b.depth); if (sorted.length < 2) return; const expr: unknown[] = ['interpolate', ['linear'], depth]; for (const s of sorted) { expr.push(s.depth, s.color); } for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setPaintProperty(id, 'fill-color', expr as never); map.setPaintProperty(id, 'fill-opacity', opacity); } catch { // ignore } } } function applyOceanContourStyle( map: maplibregl.Map, layers: string[], visible: boolean, color: string, opacity: number, width: number, ) { for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none'); if (visible) { map.setPaintProperty(id, 'line-color', color); map.setPaintProperty(id, 'line-opacity', opacity); map.setPaintProperty(id, 'line-width', width); } } catch { // ignore } } } function applyOceanDepthLabels( map: maplibregl.Map, layers: string[], visible: boolean, color: string, size: OceanDepthLabelSize, ) { const sizeExpr = OCEAN_DEPTH_FONT_SIZE[size]; for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none'); if (visible) { map.setPaintProperty(id, 'text-color', color); map.setLayoutProperty(id, 'text-size', sizeExpr); } } catch { // ignore } } } function applyOceanHillshade( map: maplibregl.Map, layers: string[], visible: boolean, exaggeration: number, color: string, ) { for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none'); if (visible) { map.setPaintProperty(id, 'hillshade-exaggeration', exaggeration); map.setPaintProperty(id, 'hillshade-shadow-color', color); } } catch { // ignore } } } function applyOceanLandformLabels( map: maplibregl.Map, layers: string[], visible: boolean, color: string, ) { for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none'); if (visible) { map.setPaintProperty(id, 'text-color', color); } } catch { // ignore } } } function applyOceanBackground(map: maplibregl.Map, layers: string[], color: string) { for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setPaintProperty(id, 'background-color', color); } catch { // ignore } } try { map.getCanvas().style.background = color; } catch { // ignore } } function applyOceanLabelLanguage(map: maplibregl.Map, layers: string[], lang: OceanLabelLanguage) { const textField = lang === 'local' ? ['get', 'name'] : ['coalesce', ['get', `name:${lang}`], ['get', 'name']]; for (const id of layers) { if (!map.getLayer(id)) continue; try { map.setLayoutProperty(id, 'text-field', textField); } catch { // ignore } } } /* ── Hook ──────────────────────────────────────────────────────────── */ export function useOceanMapSettings( mapRef: MutableRefObject, settings: OceanMapSettings | undefined, opts: { baseMap: BaseMapId; mapSyncEpoch: number }, ) { const settingsRef = useRef(settings); useEffect(() => { settingsRef.current = settings; }); const { baseMap, mapSyncEpoch } = opts; useEffect(() => { // Ocean 전용 — enhanced 모드에서는 즉시 return if (baseMap !== 'ocean') return; const map = mapRef.current; const s = settingsRef.current; if (!map || !s) return; const stop = onMapStyleReady(map, () => { const oceanLayers = discoverOceanLayers(map); applyOceanDepthColors(map, oceanLayers.depthFill, s.depthStops, s.depthOpacity); applyOceanContourStyle(map, oceanLayers.contourLine, s.contourVisible, s.contourColor, s.contourOpacity, s.contourWidth); applyOceanDepthLabels(map, oceanLayers.depthLabel, s.depthLabelsVisible, s.depthLabelColor, s.depthLabelSize); applyOceanHillshade(map, oceanLayers.hillshade, s.hillshadeVisible, s.hillshadeExaggeration, s.hillshadeColor); applyOceanLandformLabels(map, oceanLayers.landformLabel, s.landformLabelsVisible, s.landformLabelColor); applyOceanBackground(map, oceanLayers.background, s.backgroundColor); applyOceanLabelLanguage(map, oceanLayers.allSymbol, s.labelLanguage); kickRepaint(map); }); return () => stop(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [settings, baseMap, mapSyncEpoch]); }