From 3a001ca9b62a2bd7294d6999d83a1f0429a4bbb3 Mon Sep 17 00:00:00 2001 From: htlee Date: Mon, 16 Feb 2026 15:28:23 +0900 Subject: [PATCH] =?UTF-8?q?fix(map):=20fill=203-tier=20LOD=EB=A1=9C=20?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20seam=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 심해 fill 폴리곤이 globe 타일 경계에서 seam 아티팩트 발생 - bathymetry-fill: z3-7 (depth >= -2000, 천해만) - bathymetry-fill-medium: z7-9 (depth >= -4000) - bathymetry-fill-deep: z9+ (전체 depth) - applyDepthGradient: 3개 fill 레이어 모두 적용 Co-Authored-By: Claude Opus 4.6 --- .../map3d/hooks/useMapStyleSettings.ts | 12 ++--- .../src/widgets/map3d/layers/bathymetry.ts | 45 ++++++++++++++++--- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts b/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts index 1ea7049..01b4282 100644 --- a/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts +++ b/apps/web/src/widgets/map3d/hooks/useMapStyleSettings.ts @@ -102,7 +102,6 @@ function applyWaterBaseColor(map: maplibregl.Map, fillColor: string) { } function applyDepthGradient(map: maplibregl.Map, stops: DepthColorStop[]) { - if (!map.getLayer('bathymetry-fill')) return; const depth = ['to-number', ['get', 'depth']]; const sorted = [...stops].sort((a, b) => a.depth - b.depth); if (sorted.length === 0) return; @@ -115,10 +114,13 @@ function applyDepthGradient(map: maplibregl.Map, stops: DepthColorStop[]) { if (shallowest.depth < 0) { expr.push(0, lightenHex(shallowest.color, 1.8)); } - try { - map.setPaintProperty('bathymetry-fill', 'fill-color', expr as never); - } catch { - // ignore + for (const layerId of ['bathymetry-fill', 'bathymetry-fill-medium', 'bathymetry-fill-deep']) { + if (!map.getLayer(layerId)) continue; + try { + map.setPaintProperty(layerId, 'fill-color', expr as never); + } catch { + // ignore + } } } diff --git a/apps/web/src/widgets/map3d/layers/bathymetry.ts b/apps/web/src/widgets/map3d/layers/bathymetry.ts index 86074f3..a1cb02e 100644 --- a/apps/web/src/widgets/map3d/layers/bathymetry.ts +++ b/apps/web/src/widgets/map3d/layers/bathymetry.ts @@ -10,7 +10,9 @@ export const SHALLOW_WATER_FILL_DEFAULT = '#14606e'; export const SHALLOW_WATER_LINE_DEFAULT = '#114f5c'; const BATHY_ZOOM_RANGES: BathyZoomRange[] = [ - { id: 'bathymetry-fill', mercator: [3, 24], globe: [3, 24] }, + { id: 'bathymetry-fill', mercator: [3, 7], globe: [3, 7] }, + { id: 'bathymetry-fill-medium', mercator: [7, 9], globe: [7, 9] }, + { id: 'bathymetry-fill-deep', mercator: [9, 24], globe: [9, 24] }, { id: 'bathymetry-borders-major', mercator: [3, 7], globe: [3, 7] }, { id: 'bathymetry-borders', mercator: [7, 24], globe: [7, 24] }, { id: 'bathymetry-lines-coarse', mercator: [5, 7], globe: [5, 7] }, @@ -111,18 +113,47 @@ export function injectOceanBathymetryLayers(style: StyleSpecification, maptilerK const depthIn = (depths: number[]) => ['in', depth, ['literal', depths]] as unknown[]; - // === Fill (contour polygons) — 전체 depth, 줌에 따라 자연스럽게 표시 === + // === Fill (contour polygons) — 3-tier LOD === + // 심해 폴리곤이 여러 벡터 타일에 걸칠 때 globe tessellation 타일 경계에서 + // seam 아티팩트 발생 → 줌아웃에서는 shallow만, 줌인에서 점진적으로 심해 포함 + const bathyFillPaint = { + 'fill-color': bathyFillColor, + 'fill-opacity': ['interpolate', ['linear'], ['zoom'], 0, 0.9, 5, 0.88, 8, 0.84, 10, 0.78], + }; + + // z3-7: depth >= -2000 (천해만 — 타일 seam 방지) const bathyFill: LayerSpecification = { id: 'bathymetry-fill', type: 'fill', source: oceanSourceId, 'source-layer': 'contour', minzoom: 3, + maxzoom: 7, + filter: ['>=', depth, -2000] as unknown as unknown[], + paint: bathyFillPaint, + } as unknown as LayerSpecification; + + // z7-9: depth >= -4000 (중심해 포함) + const bathyFillMedium: LayerSpecification = { + id: 'bathymetry-fill-medium', + type: 'fill', + source: oceanSourceId, + 'source-layer': 'contour', + minzoom: 7, + maxzoom: 9, + filter: ['>=', depth, -4000] as unknown as unknown[], + paint: bathyFillPaint, + } as unknown as LayerSpecification; + + // z9+: 전체 depth (풀 디테일 — 뷰포트가 작아 타일 seam 무관) + const bathyFillDeep: LayerSpecification = { + id: 'bathymetry-fill-deep', + type: 'fill', + source: oceanSourceId, + 'source-layer': 'contour', + minzoom: 9, maxzoom: 24, - paint: { - 'fill-color': bathyFillColor, - 'fill-opacity': ['interpolate', ['linear'], ['zoom'], 0, 0.9, 5, 0.88, 8, 0.84, 10, 0.78], - }, + paint: bathyFillPaint, } as unknown as LayerSpecification; // === Borders (contour polygon edges) — 2-tier LOD === @@ -326,6 +357,8 @@ export function injectOceanBathymetryLayers(style: StyleSpecification, maptilerK const toInsert = [ bathyFill, + bathyFillMedium, + bathyFillDeep, bathyBordersMajor, bathyBorders, bathyLinesCoarse,