PMTiles 지도 아카이브 393MB를 86MB로: 그리지 않는 줌과 속성 덜어내기
핵심 요약
- 건물 외곽선은 z16부터만 그리는데 아카이브의 69%(271.6MB)가 z13~15 타일이었고, z16만 남기고 건물 id를 빼니 393MB가 86MB가 됐어요.
--simplify-only-low-zooms를 빼서 z16 촬영 범위 폴리곤의 꼭짓점을 4,194만 개에서 1,672만 개로 줄였고, 기본 허용오차는 선과 다각형을 타일 격자 한 칸(약 0.12m) 안에 두려는 값이에요.- 점 레이어에는 같은 방법을 쓰지 않았어요.
tippecanoe가 최대 줌 아래에서 점을 솎아 내서 z14에는 258,376곳 중 55,706곳만 들어 있었기 때문이에요.
보는지도는 지도 데이터를 PMTiles 파일 두 개로 서비스해요. 하나는 CCTV 위치와 추정 촬영 범위를 담은 아카이브이고, 다른 하나는 촬영 범위를 건물로 자를 때 쓴 건물 외곽선 아카이브예요. 2026년 10월 15일에 두 파일을 다시 만들어 건물 아카이브를 393MB에서 86MB로, CCTV 아카이브를 164MB에서 139MB로 줄였어요. 지도에 그려지는 위치와 촬영 범위의 수는 그대로이고, 건물은 이전 아카이브에 빠져 있던 6천여 동이 채워졌어요.
한 일은 스타일이 실제로 그리는 줌과 속성만 남긴 것이에요. 다만 같은 방법을 점 레이어에 쓰면 화면이 달라지는 함정이 있었어요. 그 이야기도 함께 적어요.
줌마다 몇 바이트인지 먼저 세기
PMTiles는 파일 하나에 타일 디렉터리와 타일을 함께 담아요. 디렉터리만 읽어도 줌별 타일 수와 바이트를 셀 수 있어요. 건물 아카이브를 세어 보니 이랬어요.
| 줌 | 타일 수 | 용량 | 평균 타일 |
|---|---|---|---|
| z13 | 6,658 | 81.5MB | 12,239바이트 |
| z14 | 19,211 | 89.4MB | 4,655바이트 |
| z15 | 55,189 | 100.7MB | 1,825바이트 |
| z16 | 153,050 | 121.2MB | 792바이트 |
보는지도는 건물 외곽선을 z16부터만 그려요. 촬영 범위와 방향 표시가 나타나는 줌이 16이어서, 촬영 범위를 자른 건물도 그때 함께 보여 줘요. 그러니 z13~15 타일 271.6MB(전체의 69%)는 어느 화면에도 쓰이지 않았어요.
웹 지도는 범위 요청(Range 헤더)으로 필요한 타일만 읽어요. 그래서 이 낭비는 주로 서버 디스크에서, 그리고 백업이나 복사처럼 파일을 통째로 다룰 때 드러났어요.
줌 범위를 스타일에 맞추고 나머지는 오버줌
MapLibre는 소스의 최대 줌보다 더 확대하면 최대 줌 타일을 키워서 그려요. 스타일 명세도 최대 줌 타일의 데이터를 더 높은 줌을 그릴 때 쓴다고 설명해요(MapLibre 스타일 명세). PMTiles 아카이브는 헤더에 최소·최대 줌을 적어 두고, 이 값이 그대로 소스의 줌 범위가 돼요.
z16부터 그리는 레이어라면 z16 타일 하나로 충분해요. 그보다 확대한 화면은 z16 타일을 키워서 그리면 돼요. 그래서 건물 아카이브를 만드는 tippecanoe 명령에서 최소 줌을 바꿨어요.
# 이전: z13~16
tippecanoe -o buildings-kr.pmtiles --layer=buildings \
--minimum-zoom=13 --maximum-zoom=16 --drop-densest-as-needed buildings-kr.geojson
# 이후: z16만
tippecanoe -o buildings-kr.pmtiles --layer=buildings \
--minimum-zoom=16 --maximum-zoom=16 --drop-densest-as-needed buildings-kr.geojson.gz
입력이 .geojson.gz인 것도 바뀐 점이에요. 전국 촬영 범위는 도형만 GeoJSON으로 약 2.7GB라서, 서버 디스크를 아끼려고 모든 중간 파일을 gzip으로 쓰고 tippecanoe에 그대로 넘겨요. tippecanoe는 .gz 입력을 직접 읽어요.
그리지 않는 속성도 덜어내기
건물마다 id 속성과 피처 id가 들어 있었어요. 그런데 건물 외곽선은 선으로만 그리고, 누르거나 강조하는 기능이 없어요. 그래서 id를 읽는 코드도 없었어요. 이번에는 속성을 빈 객체로 쓰고 피처 id도 넣지 않았어요.
| 이전 | 이후 | |
|---|---|---|
| 저장한 줌 | z13~16 | z16 |
| z16 용량 | 121.2MB | 86.2MB |
| z16 평균 타일 | 792바이트 | 563바이트 |
| z16 건물 피처 | 4,476,895 | 4,484,447 |
| 아카이브 전체 | 393.3MB | 86.5MB |
피처 수는 오히려 7,552개 늘었어요. 9월 2일에 만든 이전 아카이브에는 지금 건물 테이블보다 6천여 동이 적게 들어 있었어요. z16의 서로 다른 id는 3,866,053개였고, 테이블은 3,872,512행이에요. 이번에 다시 만들면서 함께 채워졌어요. 건물 수는 늘었는데 z16 용량은 29% 줄었으니, 줄어든 몫은 id와 속성에서 나왔어요.
촬영 범위 폴리곤은 최대 줌에서도 단순화
CCTV 아카이브에는 CCTV 위치 점(cctv 레이어)과 촬영 범위 폴리곤(coverage 레이어)이 들어 있어요.
- 촬영 범위는 행정안전부 전국CCTV표준데이터에 공개된 방향·화각·거리로 그린 추정이에요.
- 방향을 공개한 곳은 258,376곳 중 792곳뿐이고, 나머지(방향 없음 또는 360도로 공개)는 원 전체로 그려요. 거리가 공개되지 않은 곳(대부분)은 55m, 방향은 있지만 화각이 없으면 68도를 써요. 그리고 건물에 막히는 곳에서 잘라요.
- 그래서 원이나 부채꼴의 호와 건물에 잘린 가장자리가 짧은 선분 여러 개로 이어지고, 꼭짓점이 많아요.
이전에는 tippecanoe에 --simplify-only-low-zooms를 줬어요. 최대 줌에서는 선과 다각형을 단순화하지 말라는 옵션이에요. 이 옵션을 빼면 최대 줌에도 기본 단순화가 걸려요.
tippecanoe문서는 기본 허용오차가 선과 다각형을 원래 위치에서 타일 단위 한 칸 안에 두려 한다고 설명해요(tippecanoe README).- z16 타일은 4096단위로 나뉘고, 위도 37.5도에서 타일 폭이 약 485m라 한 칸은 약 0.12m예요.
| 이전 | 이후 | |
|---|---|---|
| 촬영 범위 조각(z16) | 372,681 | 372,681 |
| 꼭짓점 | 41,937,134 | 16,720,868 |
| z16 평균 타일 | 1,413바이트 | 1,120바이트 |
| 아카이브 전체 | 164.1MB | 139.2MB |
꼭짓점이 60% 줄었고 다각형 수는 같아요. z5~15 타일은 타일 수와 용량이 이전과 같았어요. 촬영 범위는 z16에만 있고, 그 아래 줌의 점은 단순화할 선이 없기 때문이에요.
0.12m가 화면에서 얼마인지도 계산했어요.
- 웹 지도의 최대 줌 20에서 CSS 픽셀 하나는 약 0.06m라, 가장 크게 확대하면 선이 2픽셀 안팎 옮겨질 수 있어요.
- z18에서는 픽셀 하나가 약 0.24m라 반 픽셀이에요.
- 좌표는 원래 z16 타일 격자에 맞춰 저장되므로, 반 칸(약 0.06m)까지의 오차는 이전에도 있었어요.
- 촬영 범위 자체가 잰 값이 아니라 추정이라, 이 차이로 지도에서 읽는 내용이 바뀌지는 않는다고 판단했어요.
촬영 범위가 단순해지고 건물 타일이 가벼워져서, 브라우저가 z16 화면에서 받는 타일도 작아졌어요. 평균 타일 크기로 보면 CCTV와 건물 타일을 합쳐 약 2.2KB에서 1.7KB가 됐어요.
점 레이어에 오버줌을 쓰지 않은 이유
처음에는 점도 z14까지만 저장하고 그 위는 오버줌할 생각이었어요. 위치마다 속성이 최대 31개 붙어 있고, 같은 위치가 z14, z15, z16에 반복해서 저장되니까요. 그런데 줌별로 실제 위치 수를 세어 보니 계획이 틀어졌어요.
| 줌 | 아카이브에 든 위치 |
|---|---|
| z14 | 55,706곳 |
| z15 | 125,182곳 |
| z16 | 258,376곳 |
tippecanoe는 기본적으로 기준 줌(--base-zoom)보다 낮은 줌에서 점을 줄여요.
- 기준 줌의 기본값은 최대 줌이에요. 문서는 이 줌부터 모든 점이 들어간다고 설명해요.
- 줄이는 비율(
--drop-rate)의 기본값은 2.5예요. - 그래서 지도에 있는 258,376곳 가운데 z14에는 55,706곳, z15에는 125,182곳만 들어 있었어요(z13 이하는 클러스터로 묶여요).
점을 따로 아카이브로 빼서 최대 줌을 14로 두면 z14가 기준 줌이 돼서 모든 점이 z14 타일에 들어가요. 그러면 오버줌으로 그리는 z14~15 화면에 258,376곳이 전부 나타나서, 지금보다 훨씬 빽빽해져요. 용량은 줄지만 보이는 화면이 바뀌기 때문에 점 레이어는 그대로 뒀어요. 점 레이어의 최대 줌을 바꾸기 전에는 줌별 피처 수부터 세어 보세요.
측정 방법
- 2026년 10월 15일, 보는지도 서버에서 이전 아카이브와 새 아카이브의 타일을 하나도 빠짐없이 읽었어요(Python 표준 라이브러리로 gzip 벡터 타일을 해독).
- 줌마다 타일 수와 압축된 바이트를 셌고, 레이어마다 피처 수, 서로 다른 피처 id 수, 꼭짓점 수를 셌어요. 타일 경계에 걸친 피처는 타일마다 한 번씩 세요.
- 두 아카이브 모두
tippecanoe2.82.0으로 만들었어요. - CCTV 데이터는 행정안전부 전국CCTV표준데이터(2026년 9월 20일 내려받은 데이터), 건물은 Overture Maps의 건물 데이터예요.
- 새 아카이브는 지금 보는지도에서 쓰는 파일이에요. 화면 해상도는 MapLibre의 512픽셀 타일 기준으로, 위도 37.5도에서 계산했어요.