Skip to content

MapLibre GL

MapLibre로 렌더링할 때 특정 폴리곤 레이어가 깨져 보이는 문제 해결 방법

South-Korea PMTiles 베이스맵(Protomaps planetiler 추출본)을 MapLibre로 렌더링할 때 물(water) 영역이 깨져 보이는 문제의 진단과 해결 기록.

MapLibre_GL_-_Water_Layer_Problem_1.png
MapLibre_GL_-_Water_Layer_Problem_2.png

증상

지도 자체는 정상적으로 그려지나, 물 영역만 이상하게 출력됨. 폴리곤의 끝점이 중간에 끊어지거나, 가느다란/지그재그 모양의 칠이 지도를 가로지르는 형태로 나타남. 줌 레벨이 높을수록(z10 이상) 심해짐.

진단 결론

PMTiles 파일은 정상

go-pmtiles CLI로 추출본을 직접 검사한 결과 파일 자체에는 문제가 없음.

  • pmtiles verify 통과, tile contents 161,203개, clustered
  • 헤더 center/bounds가 한국으로 올바르게 재계산됨 (center 128.25, 35.76)
  • min zoom 0 / max zoom 15, 저줌 오버뷰 타일 보존
  • vector_layers 이름이 스타일이 가정한 Protomaps 스키마와 일치
  • 워커의 gzip 해제 경로([email protected] + Cloudflare DecompressionStream)도 정상

따라서 원인은 파일이 아니라 스타일의 레이어 정의에 있음.

확정 원인

water 레이어의 혼합 지오메트리

Protomaps의 water source-layer에는 지오메트리 타입이 섞여 있음.

  • Polygon: ocean, bay, strait, water(호수/저수지), swimming_pool → 면으로 채워야 하는 대상
  • LineString: river, stream선으로 그려야 하는 하천
  • Point: 라벨용 (간헐적)

타일 디코드 증거

추출본의 실제 타일을 @mapbox/vector-tile로 디코드한 결과:

타일 (z/x/y)

water 레이어 내용 (kind / geomType)

7/109/49

ocean·bay·water = Polygon 5, Point 1

8/219/101

ocean·strait·water = Polygon 4, Point 1

10/875/403

river = LineString 13, water = Polygon 3

12/3492/1587

river = LineString 5, water·swimming_pool = Polygon 5

13/6984/3175

stream = LineString 6, water·swimming_pool = Polygon 3

렌더링 메커니즘

수정 전 스타일의 water 레이어는 필터 없는 type: 'fill'이었음.

{ id: 'water', type: 'fill', source: SOURCE, 'source-layer': 'water',
  paint: {'fill-color': c.water} }   // 필터 없음 → water의 모든 피처를 채움

MapLibre의 fill 렌더러는 LineString 피처를 받으면 classifyRings로 그 선을 폴리곤 링으로 간주하고 시작점-끝점을 암묵적으로 이어 삼각분할(earcut)해 채움. 그 결과:

  • 하천 경로를 따라 가느다란/지그재그 폴리곤이 칠해짐
  • 강의 끝점을 잇는 닫힘선이 지도를 가로질러 그려짐

→ 사용자가 본 "물 영역이 이상하게, 끝점이 중간에 끊어진" 현상과 정확히 일치. 하천이 많은 z10+에서 특히 심함.

부수 원인: 죽은 physical-line 레이어

스타일에는 하천을 선으로 그리려는 physical-line 레이어(source-layer: 'physical_line')가 있었으나, 이 빌드에는 physical_line 레이어가 존재하지 않음.

  • vector_layers: boundaries / buildings / earth / landcover / landuse / places / pois / roads / water
  • 이 Protomaps 빌드는 waterway(강/개천)를 physical_line이 아니라 water 레이어에 kind:river/stream LineString으로 담음

physical-line은 존재하지 않는 레이어를 가리키는 죽은 레이어였고, 하천 선은 어디서도 제대로 그려지지 않으면서 water fill에 잘못 흡수되어 깨진 채로 칠해짐 (스키마 가정 어긋남).

해결

적용한 패치

src/worker/lib/basemap-style.ts

1) water fill을 폴리곤으로 한정 (Multi 포함 위해 geometry-type 대신 $type 사용 — geometry-type은 MultiPolygon을 'Polygon'으로 보지 않아 누락 위험)

{ id: 'water', type: 'fill', source: SOURCE, 'source-layer': 'water',
  filter: ['==', '$type', 'Polygon'],          // 추가
  paint: {'fill-color': c.water} }

2) 죽은 physical-line을 water 기반 하천 선 레이어로 교체

{ id: 'waterway', type: 'line', source: SOURCE, 'source-layer': 'water',
  filter: ['==', '$type', 'LineString'],
  minzoom: 9,
  layout: {'line-cap': 'round', 'line-join': 'round'},
  paint: {
    'line-color': c.water,
    'line-width': ['interpolate', ['linear'], ['zoom'], 9, 0.4, 14, 2],
  } }

검증

  • ./npm run build 통과 (타입/컴파일 정상)
  • 스타일은 워커가 생성하며 응답에 Cache-Control: no-cache가 붙어 있어 다음 style.json 요청부터 즉시 반영. 타일 바이트는 불변이라 엣지 캐시 무효화 불필요.

참고

  • MapLibre fill 레이어는 기본적으로 지오메트리 타입을 필터링하지 않으므로, 혼합 지오메트리 source-layer에는 항상 $type 필터를 명시해야 함.
  • $type 필터는 Point / LineString / Polygon만 구분(Multi 구분 없음)하므로 면 필터에 안전. geometry-type 표현식은 MultiPolygon을 별도로 반환하므로 면 필터에는 부적합.
  • 상세 진단 전문: docs/plans/basemap-south-korea-render-diagnosis.md

See also

  • GIS
  • Mapkick.js - 한줄의 JS코드로 아름답고 인터랙티브한 지도 만들기
  • Mapbox-GL-js는 더이상 3조항 BSD 라이센스가 아닙니다
  • MapLibre GL - MapboxGL JS의 오픈소스 포크