← 읽을거리

지도 API를 걷어내고 세계지도를 직접 그렸다

방구석콜럼버스는 처음에 Google Maps JavaScript API 위에 만들어져 있었습니다. 지금은 지도를 직접 그립니다. 왜 걷어냈고 무엇으로 바꿨는지 적어 둡니다.

걷어낸 이유

Maps JavaScript API에서 지도를 한 번 띄우는 것은 페이지 로드마다 과금되는 항목입니다. 월 무료 한도가 있고 개인 프로젝트가 그걸 넘길 일은 흔치 않지만, 문제는 상한이 아니라 구조였습니다. 이 서비스에서 지도가 하는 일이 그만한 무게를 정당화하지 못했습니다.

필요한 것은 이 정도였습니다.

  • 세계 육지 덩어리를 어두운 색으로 채우기
  • 나라 경계선을 얇게 긋기
  • 다녀온 나라만 다른 색으로 칠하기
  • 그 위에 경로 곡선과 비행기 모양을 얹기

도로도, 위성 사진도, 실시간 교통도, 확대해서 보는 상점 정보도 쓰지 않습니다. 그런데 그걸 위해 수백 KB의 SDK를 받고, 타일을 요청하고, 페이지 로드마다 과금 카운터를 올리고 있었습니다. 게다가 대권 거리 계산을 위해 geometry 라이브러리를 부르느라 여정을 만들 때마다 Maps JS를 통째로 받아야 했습니다.

무엇으로 바꿨나

도형은 Natural Earth를 씁니다. 퍼블릭 도메인이고, TopoJSON으로 압축된 형태가 공개돼 있습니다. 이 프로젝트가 쓰는 두 가지는 이렇습니다.

파일 원본 gzip
countries-110m.json (저해상도) 105KB 38KB
countries-50m.json (고해상도) 739KB 225KB

저해상도를 먼저 그려 지도가 곧바로 뜨게 하고, 고해상도는 뒤에서 받아 조용히 교체합니다. 처음부터 고해상도만 받으면 느린 회선에서 한참 빈 화면이 남습니다.

투영경로 계산d3-geo가 맡습니다. 등장방형 도법으로 위경도를 화면 좌표로 옮기고, geoPath로 캔버스에 직접 긋습니다. 대권 거리와 보간도 같은 라이브러리로 합니다 — 이제 Maps JS는 내장 사전에 없는 장소를 검색할 때만 로드됩니다.

그리기는 캔버스 2D입니다. SVG로 나라 폴리곤 수백 개를 DOM에 올리면 애니메이션 프레임마다 그걸 다시 계산하게 됩니다. 캔버스는 한 번에 다 긋고 지웁니다. 고해상도 화면을 위해 devicePixelRatio를 곱하되 2에서 자릅니다. 3배로 그리면 픽셀 수가 2.25배로 늘어나는데 눈으로는 구분되지 않습니다.

옮기면서 걸린 것

육지를 채울 때 mesh를 쓰면 안 됩니다. TopoJSON에는 mergemesh가 있는데, mesh는 선(MultiLineString)을 돌려줍니다. 겉보기에는 나라 모양이 다 나오니 맞는 것 같은데, 그 선을 fill하면 대륙을 가로지르는 쐐기 모양 얼룩이 생깁니다. 채우기에는 나라들을 하나의 면으로 합치는 merge를 써야 합니다. 경계선을 그을 때만 mesh를 씁니다.

등장방형 도법은 고위도를 가로로 늘립니다. 늘어나는 비율은 위도의 코사인 역수입니다.

위도 가로 늘어남
0° (적도) 1.00배
37.57° (서울) 1.26배
60° 2.00배
80° 5.76배

그래서 서울에서 유럽으로 가는 대권 경로가 지도 위에서 위로 크게 부풀어 보입니다. 선이 휜 게 아니라 지도가 그 자리를 잡아 늘린 것입니다.

남은 비용

지도 자체는 이제 공짜입니다. 유료로 남은 것은 목적지 검색뿐이고, 그것도 366곳짜리 내장 사전과 34,000곳짜리 정적 인덱스를 먼저 뒤진 뒤에야 외부 API로 넘어갑니다. 대부분의 검색은 거기까지 가지 않습니다.

지도 API가 나쁘다는 이야기가 아닙니다. 길찾기나 실시간 교통이 필요하면 직접 만들 이유가 없습니다. 다만 필요한 것이 "어두운 배경에 대륙 모양"이라면, 그건 공개된 도형과 20여 줄의 캔버스 코드로 충분합니다.