기사 공유하기

[box type=”note”]D3.js를 이용해 [식신로드]에서 만점을 받은 서울지역 맛집을 시각화한 과정을 소개합니다. 글의 말미에는 필자와의 일문일답도 있으니 확인하세요. (편집자)[/box]

맛집과 같이 지리적 위치가 중요한 데이터는 지도상에 표시했을 때 훨씬 보기가 쉽다. (예: 이성빈 – 봉천동 맛집 대폭발공개) 비단 맛집뿐 아니라 병원이나, 여행 일정 같은 것들도 지도라는 시각화 방식을 사용하면 훨씬 효과적으로 정보를 전달할 수 있다.

게다가 지도상에 점을 찍는 것은 어렵지 않다. 가장 손쉽게는 MS 파워포인트를 이용할 수 있고, 디자이너분들께서 능숙하게 다루는 어도비 포토샵이나 일러스트레이터도 있지만, 그 밖에도 아주 손쉽게 사용할 수 있는, 지도에 특화된 아래와 같은 서비스도 있다.

또는 지도 특화 서비스는 아니지만, 포스퀘어 같은 맛집 서비스를 이용해서 나만의 지도를 큐레이션할 수도 있다. 이들은 모두 클릭 몇 번만 하면 화면 너머 블랙박스에서 돌아가는 마술로 지도 위에 점을 멋지게 뿌려준다.

그렇다면 맛집 지도를 웹 기반으로, 인터렉티브하게 만들 수는 없을까? 그동안 마이크 보스톡(Mike Bostock)느님이 미국 지도를 이용해서 다양하고 멋진 시각화를 수도 없이 많이 보여줬는데, 우리 지역의 지도로는 그렇게 만들 수 없나?

물론 할 수 있다. 여기서 총 4단계에 걸쳐 다음의 웹 기반 서울 맛집 지도를 그리는 방법에 대해 알아보자.

D3.js를 이용한 식신로드 만점 서울지역 맛집 시각화

0단계: 들어가기 전

웹 기반 인터렉티브 지도를 그리기 위해서는 몇 가지 기반 기술과 재료가 필요하다. 일단 HTML, CSS, 자바스크립트에 대한 사전지식이 조금 있어야 하고, 아래의 두 가지가 적어도 무슨 일을 하는 녀석들인지 알아야 한다.

  • D3.js: 자바스크립트 라이브러리로서, 데이터 기반으로 DOM을 편리하게 조작할 수 있게 해준다. 앞서 언급한 마이크 보스톡은 이 라이브러리를 능수능란, 자유자재로 다루기로 유명하다.
  • TopoJSON: 지리·공간적 데이터를 담는 포맷의 일종인데, GeoJSON 보다 훨씬 효율적인 버전이라고 할 수 있다.

이 두 가지가 뭔지 알았다면 실제 재료로는 1) 지도 데이터와 2) 맛집 목록이 필요하다.

먼저 지도 데이터는 서울시 셰이프 파일을 TopoJSON으로 변환한 파일을 썼고, 맛집 목록은 위키트리 김도담 기자의 식신로드, 서울지역 ‘만점 식당’ 20선을 이용했다. 이 리스트를 선택한 이유는 맛집의 이름뿐 아니라 주소까지 나와 있어서, 지오매핑(geomapping)이 아주 편하기 때문이다. (물론 식신로드 만점식당을 정주행해보고 싶다는 개인적인 소망도 크게 작용했다)

1단계: 맛집의 위·경도 구하기

주소가 있으면, 맛집의 위도와 경도를 아주 쉽게 구할 수 있다. 나는 다음과 같이 그냥 빨리 이용할 수 있는 네이버의 지도 API를 이용했다.

찍으려는 좌표가 몇 개 안 되거나 찍고자하는 점의 주소를 구하지 못한 경우에는 수작업으로 위·경도 정보를 얻는 방법도 있다. 일종의 꼼수지만, 다음과 같이 구글 지도에 상호명을 입력하면 대략적인 위·경도 정보가 URL에 뜬다:

구글 지도 활용 예

최종적인 맛집 데이터는 다음과 같이 식당 이름(name), 위도(lat; latitude), 경도(lon; longitude)만 최소한으로 담고 있으면 된다. (물론 필요에 따라 변수를 추가하면, 점의 색이나 모양 등을 그에 맞게 바꿀 수 있다.)

서울 지역에 대한 데이터이기 때문에 위도는 대부분 37도 부근, 경도는 127도 부근에 위치한다. 내 데이터가 부정확한 값을 담고 있으면 지도도 제대로 그릴 수 없다.

2단계: 지도를 펼쳐보자

맛집 데이터를 구했으니, 맛집을 지도에 뿌리기 전에 먼저 지도를 그려보자.

“index.html”이라는 이름으로 HTML 파일을 하나 생성하고 아래의 코드를 복사해 넣자. 앞에서 HTML에 대한 사전지식이 있어야 한다고 했지만, 우리가 사용할 HTML은 이게 전부고, 여기에 CSS와 자바스크립트만 조금(?) 채워 넣으면 된다.




    
    


    

10번째 줄에서 id가 chart인 div를 생성했는데, 자바스크립트를 이용해서 이곳에 우리의 지도를 넣을 것이다.

웹페이지에 “옷을 입히는 부분”인 CSS는 뒤에서 다시 살펴보고, 일단 자바스크립트부터 넣어보자. 11, 12번째 줄에서 각각 d3.v3.min.js와 topojson.v1.min.js를 불러오고 있다. 전자는 d3.js를 사용하기 위해 필요하고, 후자는 지도 데이터를 렌더링하는데 사용된다. 지금부터 우리가 작성하는 코드는 “자바스크립트는 여기에”라고 되어 있는 부분에 입력하면 된다.

먼저 d3를 이용해서 10번째 줄의 div 안에 크기가 800×600 픽셀인 svg를 생성해보자. 이 div의 id가 chart라는 성질을 이용했다.

var width = 800,
    height = 600;

var svg = d3.select("#chart").append("svg")
    .attr("width", width)
    .attr("height", height);

다음으로 svg 안에 지도 레이어 “map”과 맛집 레이어 “places”를 만들자.

var map = svg.append("g").attr("id", "map"),
    places = svg.append("g").attr("id", "places");

지도를 그릴 때 다양한 투영법에 대해 알면 좋다. 여기서는 메르카토르 투영법을 이용했고, 지도는 앞에서 언급한 서울시 셰이프 파일을 TopoJSON으로 변환한 파일을 사용해서 “map” 레이어에 각 지역구에 대한 “path”와 지역명을 표시한 “text” 요소를 생성했다.

var projection = d3.geo.mercator()
    .center([126.9895, 37.5651])
    .scale(100000)
    .translate([width/2, height/2]);

var path = d3.geo.path().projection(projection);

d3.json("seoul_municipalities_topo_simple.json", function(error, data) {
  var features = topojson.feature(data, data.objects.seoul_municipalities_geo).features;

  map.selectAll("path")
      .data(features)
    .enter().append("path")
      .attr("class", function(d) { console.log(); return "municipality c" + d.properties.code })
      .attr("d", path);

  map.selectAll("text")
      .data(features)
    .enter().append("text")
      .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
      .attr("dy", ".35em")
      .attr("class", "municipality-label")
      .text(function(d) { return d.properties.name; })
});

이제 터미널에서 파이썬3를 이용해 로컬 웹 서버를 돌리자.

python -m http.server 8888  #for Python2, `python -m SimpleHTTPServer`

위 명령을 입력한 후 명령이 종료되지 않는 것은 버그가 아니라 우리의 로컬 웹 서버가 정상적으로 돌아가고 있다는 의미니까 기다리고 있거나 놀라지 말자. 웹 브라우저를 열어서 http://localhost:8888에 들어가보자. 엇, 우리가 아는 서울시 모양을 볼 수 있다! 우측 상단에 “…구”라는 글자가 작게 보이는 것으로 봐서, 앞으로 CSS만 잘 입히면 지역명도 잘 출력될 듯하다.

서울 지도

3단계: 지도에 점을 찍자

이제 1단계에서 만든 맛집 위·경도를 데이터 places.csv를 이용해 맛집을 지도에 뿌려보자. (이 역시 “자바스크립트는 여기에” 부분에 계속 추가하면 된다.) 각 맛집에 대해 “places” 레이어에 “circle”과 “text”를 생성한 것을 볼 수 있다.

d3.csv("places.csv", function(data) {
  places.selectAll("circle")
      .data(data)
    .enter().append("circle")
      .attr("cx", function(d) { return projection([d.lon, d.lat])[0]; })
      .attr("cy", function(d) { return projection([d.lon, d.lat])[1]; })
      .attr("r", 10);

  places.selectAll("text")
      .data(data)
    .enter().append("text")
      .attr("x", function(d) { return projection([d.lon, d.lat])[0]; })
      .attr("y", function(d) { return projection([d.lon, d.lat])[1] + 8; })
      .text(function(d) { return d.name });
});

4단계: 지도에 옷을 입히자

맛집의 위치까지 점을 찍었지만 아직 우리 지도는 전체적으로 까맣기만 한데, “/* CSS는 여기에 */”라고 되어 있는 부분에 각 엘리먼트의 색을 입히면 멋진 지도가 완성된다.

나는 다음과 같은 값들을 줘서 지도를 완성했다.

svg circle {
  fill: orange;
  opacity: .5;
  stroke: white;
}
svg circle:hover {
  fill: red;
  stroke: #333;
}
svg text {
  pointer-events: none;
}
svg .municipality {
  fill: #efefef;
  stroke: #fff;
}
svg .municipality-label {
  fill: #bbb;
  font-size: 12px;
  font-weight: 300;
  text-anchor: middle;
}
svg #map text {
  color: #333;
  font-size: 10px;
  text-anchor: middle;
}
svg #places text {
  color: #777;
  font: 10px sans-serif;
  text-anchor: start;
}

끝이다! 코드 전체는 여기에서 볼 수 있고, 완성된 지도는 여기에서 볼 수 있다. 앞에서 적용한 CSS 때문에, 마우스를 점에 올려놓으면 점이 붉은 색으로 바뀐다.

D3를 이용해 그린 서울 시내 맛집 지도 캡처 (최종 모습)
D3를 이용해 그린 서울 시내 맛집 지도 캡처 (최종 모습)

지도 하나를 그렸을 뿐이지만 이렇게 한번 “내 코드”을 만들고 나면 재현성(reproducibility)이나 다른 영역에 대한 적용성(applicability) 등을 거머쥘 수 있게 되는 것은 큰 매력이다. 즉, 같은 서울시 지도를 이용해서 다른 데이터를 표시하는 것은 데이터만 바꿔 끼우면 되니까 너무도 간단한 일이 된다.

그뿐만 아니라 미세한 동작이나 세밀한 디자인 하나하나를 커스터마이징하는 재미도 있다. 이 지도를 응용해서 나는 서울시 스시야 기행도 그렸다. 기본적으로 같은 지도를 이용하고 같은 방식으로 시각화했지만 조금 다른 것들을 추가했다.

  1. 가격대에 따라 점마다 색을 다르게 하고,
  2. 색의 범례를 추가하고,
  3. 점마다 링크를 추가하고,
  4. 맛집의 이름이 겹치지 않도록 강제 레이아웃(forced layout)을 적용하는 등 기능 추가

전국 지도를 이용해서 같은 방식으로 그린 지도로는 롯데리아 보로노이가 있다. 여기에는 보로노이 다이어그램을 추가로 입혔다.

[box type=”note”]
마치면서 잠깐 광고 한 마디!

제가 번역한 [D3를 이용한 시각적 스토리텔링]에는 웹 기반 시각화 만들기에 대한 아주아주 쉽고 상세한 설명이 담겨 있어 탄탄한 기초를 마련할 수 있습니다. 그러니 혹시 이 글의 설명이 너무 어렵게 느껴졌다면 책을 보시는 것도 추천합니다.

더불어 이 책에는 서울시에서 한 걸음 나가 전국 지도를 활용하고, 점 찍는 것에서 한 걸음 나가 코로플래스를 그리는 법에 관한 설명도 부록으로 담았으니 많은 관심 가져주세요. :-) (필자)
[/box]

[box type=”info” head=”필자와의 일문일답”]
– 작업을 시작한 동기는 무엇인가요.

좀 뻔한 얘기만 단편적으로 말씀드리자면, 괜찮은 맛집 목록을 봤는데 글만 봐서는 우리 집에서 가장 가까운 곳이 어딘지 모르겠더라고요. 그래서 지도로 그려봐야겠다고 생각했어요. 그리고 그 과정을 간단하게라도 적어두면 다른 분들께서 보시고 또 지도를 만들어주셔서, 더 많은 맛집 정보를 얻을 수 있을 거란 생각에 글을 쓰게 되었습니다.

– 작업 중 가장 어려웠던 점은 무엇이었나요?

역시 디자인. 절대 색감 가진 디자이너분들 절대적으로 존경합니다. 코드 마음껏 가져가셔서 부디 예쁘게 만들어주세요.

– 작업에 참조한 롤모델(?)이 있나요?

D3의 아버지 마이크 보스톡느님.

– 작업에 직·간접적으로 도움을 준 사람들이 있나요?

좋은 컨텐츠를 만들어주신 식신로드 팀과 김도담 님, D3 책을 번역하게 해주신 도서출판 인사이트 관계자분들, 그리고 매일 읽을거리를 쏟아내 주시는 블로거들과 오픈소스 개발자님들입니다.

– 작업을 마친 뒤 아쉬운 점이 있다면?

색, 폰트… 역시 디자인.

– 작업과 연계해서 혹은 관련이 없더라도 앞으로의 계획이 있으면 알려주세요.

당장은 졸업과 취업이 가장 큰 계획이고요. D3.js로 또 작업한다면 이제 지도에 점 찍고 면 칠하는 것 말고 좀 더 재밌는 걸 해보고 싶어요. 결과물이 나온다면 lucypark.kr이나 bl.ocks.org/e9t에 올릴 것 같네요.

– 꼭 하고 싶은 말이 있으면 한마디 해주세요.

지도, 인구수, 우편번호 등은 뭐 하나 만들려고 할 때마다 필요한데, 그때마다 찾고 전처리하기 번거롭잖아요?

깃헙(github.com/southkorea)에 이 글에서 사용한 서울 지도를 비롯해 우리나라에 관한 프로그래머블한 자료를 소소하게 모으기 시작했습니다. 누구에게나 열려있는 프로젝트니 관심 있으신 분들은 조인해주세요!

마지막으로, 수많은 능력자분께서 한국 지도를 이용해 더 멋지고 재미있는 잉여 지도를 많이 만들어주셨으면 좋겠습니다. :)
[/box]

관련 글