MapLibre GL
MapLibre로 렌더링할 때 특정 폴리곤 레이어가 깨져 보이는 문제 해결 방법
South-Korea PMTiles 베이스맵(Protomaps planetiler 추출본)을 MapLibre로 렌더링할 때 물(water) 영역이 깨져 보이는 문제의 진단과 해결 기록.
| | |
증상
지도 자체는 정상적으로 그려지나, 물 영역만 이상하게 출력됨. 폴리곤의 끝점이 중간에 끊어지거나, 가느다란/지그재그 모양의 칠이 지도를 가로지르는 형태로 나타남. 줌 레벨이 높을수록(z10 이상) 심해짐.
진단 결론
PMTiles 파일은 정상
go-pmtiles CLI로 추출본을 직접 검사한 결과 파일 자체에는 문제가 없음.
-
pmtiles verify통과, tile contents 161,203개, clustered - 헤더
center/bounds가 한국으로 올바르게 재계산됨 (center128.25, 35.76) -
min zoom 0 / max zoom 15, 저줌 오버뷰 타일 보존 -
vector_layers이름이 스타일이 가정한 Protomaps 스키마와 일치 - 워커의 gzip 해제 경로(
[email protected]+ CloudflareDecompressionStream)도 정상
따라서 원인은 파일이 아니라 스타일의 레이어 정의에 있음.
확정 원인
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/streamLineString으로 담음
즉 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의 오픈소스 포크