3. 대화형 지도#


 대화형 지도(Interactive map)이란, 웹 상에서 지도의 확대/축소, 위치 이동, 지형지물 표시 등 다양한 정보를 제공하는 지도를 가리킵니다. 대화형 지도는 일반적으로 여러 애플리케이션에 사용되는 스크립트 언어인 자바스크립트(JavaScript), 에크마스크립트(ECMAScript)를 사용하여 구현됩니다.

 오픈소스로 공개된 대화형 지도 도구로는, 이 책에서 사용할 LeafletOpenLayers를 포함하여 대화형 웹 지도 제작을 위해 개발된 자바스크립트 기반의 여러 라이브러리가 있습니다. 자바스크립트 기반이라고 해서 걱정하지 않으셔도 됩니다. 파이썬의 Folium 패키지를 사용하면 자바스크립트를 작성하지 않고도 geopandas.GeoDataFrame에 저장된 데이터로 Leaflet 지도를 만들 수 있습니다.

Folium 참고자료

 Folium 공식 문서에서 패키지의 기능에 대한 자세한 내용을 확인 가능합니다.

3.1. 간단한 대화형 지도#

 우선, 배경 지도(base map)만 포함된 간단한 대화형 지도를 만드는 것부터 시작하겠습니다. folium.Map 객체를 생성하고, 지도에 표시될 중심 위치(location)와 초기 확대/축소 수준(zoom_start; 0 ~ 20 범위)을 지정합니다. control_scaleTrue로 설정하면, 지도에 스케일 막대를 표시하게 됩니다.

import pathlib
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_DIRECTORY = NOTEBOOK_PATH / "data"
# 대화형 지도 저장 경로
HTML_DIRECTORY = NOTEBOOK_PATH / "html"
HTML_DIRECTORY.mkdir(exist_ok=True)
import folium

interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=10,
    control_scale=True
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

3.1.1. 지도 저장#

 생성한 지도를 웹 브라우저에서 열 수 있는 HTML 파일로 저장하려면, folium.Map.save()를 사용합니다.

interactive_map.save(HTML_DIRECTORY / "base-map.html")

3.1.2. 배경 지도 변경#

 Folium에서는 기본적인 배경 지도로 OpenStreetMap을 지원합니다. 이외에 다른 레이어를 사용하려는 경우, folium.Maptiles 파라미터를 통해 내장된 지도 제공자 중 하나를 선택하거나 사용자정의 타일셋 URL을 설정할 수 있습니다. folium.Map에 내장된 지도 제공자는 아래와 같습니다.

  • OpenStreetMap

  • Stamen Terrain

  • Stamen Toner

  • Stamen Watercolor

  • CartoDB positron

  • CartoDB dark_matter

  • Cloudmade

  • Mapbox Bright

  • Mapbox Control Room

interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12,
    tiles="Stamen Terrain"
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook
interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12,
    tiles="Stamen Toner"
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook
interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12,
    tiles="Stamen Watercolor"
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook
interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12,
    tiles="CartoDB positron"
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook
interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12,
    tiles="CartoDB dark_matter"
)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

3.2. 포인트 마커 추가#

 Folium 지도에 마커를 추가하려면 folium.Marker를 사용합니다. icon 파라미터에 folium.Icon을 입력하여 마커 스타일을 지정하고, 마우스를 마커 위에 놓았을 때 유용한 텍스트가 표시되도록 툴팁(tooltip)을 설정합니다.

interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12
)
cityhall = folium.Marker(
    location=(37.566, 126.978),
    tooltip="서울시청",
    icon=folium.Icon(color="green", icon="ok-sign")
)
cityhall.add_to(interactive_map)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

3.3. 포인트 레이어 추가#

Foliumgeopandas.GeoDataFrame과 같은 레이어를 추가할 수 있도록 지원합니다. Foliumfolium.features.GeoJson 클래스에 Leaflet의 geoJSON 레이어를 구현합니다. 지리 데이터프레임을 사용하여 이러한 클래스 및 레이어를 초기화하고 이를 지도에 추가할 수 있습니다. 아래 예시에서는 3단원에서 생성한 address.gpkg 데이터셋을 사용해 보겠습니다.

import geopandas as gpd

CH3_DIRECTORY = pathlib.Path("../3. 지오코딩과 공간 쿼리/")
addresses = gpd.read_file(CH3_DIRECTORY / "data" / "addresses.gpkg")
addresses
address 지점 지점주소 geometry
0 서울특별시아동학대예방센터, 광평로34길, 06352, 광평로34길, 서울, 대한민국 400 서울특별시 강남구 일원동 580 POINT (127.08775 37.47971)
1 서초구립 라클라스 어린이집, 서초중앙로, 06601, 서초중앙로, 서울특별시 서초구... 401 서울특별시 서초구 서초동 1416번지 POINT (127.01410 37.50038)
2 서울특별시 동부기술교육원, 183, 고덕로, 05235, 고덕로, 고덕동, 대한민국 402 서울특별시 강동구 고덕로 183 POINT (127.14557 37.55604)
3 롯데월드, 240, 올림픽로, 05554, 올림픽로, 송파구, 대한민국 403 서울특별시 송파구 올림픽로 240 POINT (127.09823 37.51110)
4 월드메르디앙 201동, 양천로1길, 07602, 양천로1길, 서울, 대한민국 404 서울특별시 강서구 양천로 201 POINT (126.81027 37.57468)
5 405-298, 목동동로12길, 08005, 목동동로12길, 서울, 대한민국 405 서울특별시 양천구 목동동로 298 POINT (126.87355 37.52290)
6 서울특별시립도봉도서관, 시루봉로, 01375, 시루봉로, 쌍문4동, 대한민국 406 서울특별시 도봉구 시루봉로 173 POINT (127.02772 37.65291)
7 NaN 407 서울특별시 노원구 공릉동 사서함 230-3호 사서함 77호 None
8 서울시립대학교, 163, 서울시립대로, 02504, 서울시립대로, 서울특별시, 대한민국 408 서울특별시 동대문구 서울시립대로 163 POINT (127.05918 37.58302)
9 32, 면목로84길, 02162, 면목로84길, 서울시 중랑구, 대한민국 409 서울특별시 중랑구 면목로57길 32 POINT (127.08852 37.59191)
10 동작보라매자이더포레스트, 서울특별시 동작구 여의대방로22길 121, 대한민국 410 서울특별시 동작구 여의대방로16길 61 POINT (126.92597 37.49854)
11 NaN 411 서울특별시 마포구 창전동 산1-75 None
12 연세대학교 신촌캠퍼스, 50, 연세로, 03722, 연세로, 서울특별시, 대한민국 412 서울특별시 서대문구 연세로 50 POINT (126.93940 37.56778)
13 NaN 413 서울특별시 광진구 자양2동 680-67 None
14 국민대학교우편취급국, 77 종합복지관 2층, 정릉로, 02707, 정릉로, 정릉동,... 414 서울특별시 성북구 정릉로 77 POINT (126.99594 37.61029)
15 NaN 415 서울특별시 용산구 이촌로 255 None
16 서울특별시 소방학교, 통일로, 03312, 통일로, 진관동, 대한민국 416 서울특별시 은평구 진관동 산26 POINT (126.91181 37.63268)
17 서울독산초등학교, 31, 시흥대로104길, 08618, 시흥대로104길, 금천구, ... 417 서울특별시 금천구 시흥대로104길 31 POINT (126.90003 37.46524)
18 여의도 이랜드크루즈, 280, 여의동로, 07337, 여의동로, 서울, 대한민국 418 서울특별시 영등포구 여의동로 280 POINT (126.93841 37.52578)
19 83, 소파로, 04630, 소파로, 예장동, 대한민국 419 서울특별시 중구 소파로 83 \t POINT (126.98384 37.55617)
20 서울숲아이파크, 서울특별시 성동구 동일로 237, 대한민국 421 서울특별시 성동구 서울숲길 18 POINT (127.06943 37.55290)
21 종로경찰서, 종로17길, 03140, 종로17길, 서울특별시 종로구, 대한민국 422 서울특별시 종로구 북악산로267 POINT (126.98893 37.57054)
22 NaN 423 서울특별시 구로구 부일로 893 None
23 강북세일학원, 13, 정릉로48길, 02801, 정릉로48길, 서울특별시 성북구, ... 424 서울특별시 강북구 도봉로89길 13 POINT (127.02682 37.60338)
24 NaN 425 서울특별시 관악구 남현동 산100-1420003호 None
25 서울봉천동우체국, 249-1, 관악로, 08727, 관악로, 서울특별시 관악구, 대한민국 509 서울특별시 관악구 관악로 1 POINT (126.95671 37.48677)
26 맨하탄21리빙텔, 20, 국회대로74길, 07238, 국회대로74길, 서울특별시 영... 510 서울특별시 영등포구 국회대로53길 20 POINT (126.92153 37.52963)
27 김대중대통령묘소, 210, 현충로, 06984, 현충로, 서울, 대한민국 889 서울특별시 동작구 현충로 210 POINT (126.97018 37.49665)
interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12
)
addresses_layer = folium.features.GeoJson(
    addresses,
    name="서울시 내 기상관측소"
)
addresses_layer.add_to(interactive_map)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

3.4. 다각형 레이어 추가#

 이전 3단원에 작업했던 서울 인구밀도 격자 데이터셋을 다시 다뤄보겠습니다.

population_grid = gpd.read_file(CH3_DIRECTORY / "data" / "국토통계_인구정보_1KM_서울특별시_202304", encoding="utf-8")
population_grid
gid lbl val geometry
0 다사6453 9172.00 9172.0 POLYGON ((964000.000 1953000.000, 964000.000 1...
1 다사5651 12198.00 12198.0 POLYGON ((956000.000 1951000.000, 956000.000 1...
2 다사6157 28897.00 28897.0 POLYGON ((961000.000 1957000.000, 961000.000 1...
3 다사5858 23351.00 23351.0 POLYGON ((958000.000 1958000.000, 958000.000 1...
4 다사5347 11380.00 11380.0 POLYGON ((953000.000 1947000.000, 953000.000 1...
... ... ... ... ...
705 다사4053 NaN NaN POLYGON ((940000.000 1953000.000, 940000.000 1...
706 다사7249 NaN NaN POLYGON ((972000.000 1949000.000, 972000.000 1...
707 다사4552 NaN NaN POLYGON ((945000.000 1952000.000, 945000.000 1...
708 다사6265 NaN NaN POLYGON ((962000.000 1965000.000, 962000.000 1...
709 다사5259 NaN NaN POLYGON ((952000.000 1959000.000, 952000.000 1...

710 rows × 4 columns

 인구밀도를 표현하기 위해 folium.Choropleth를 사용하겠습니다. 단계구분(Choropleth) 지도도 마찬가지로 folium.features.GeoJson 레이어로 표시됩니다. 이 때 주의해야 할 점은 folium.Choropleth 클래스가 공간정보를 갖는 데이터셋과 표현하고자 하는 값을 갖는 데이터셋을 입력받아 결합하므로, 문자열(str) 타입의 인덱스 열이 있는 입력 데이터셋이 사용되어야 합니다. 아래 예시에서 geo_data가 공간정보를 갖는 데이터셋, data가 값을 갖는 데이터셋을 가리킵니다.

 이러한 열을 생성하는 좋은 방법은, 아래와 같이 데이터프레임의 인덱스를 새로운 열(id)에 문자열 타입으로 복사하는 것입니다.

population_grid["id"] = population_grid.index.astype(str)
population_grid
gid lbl val geometry id
0 다사6453 9172.00 9172.0 POLYGON ((964000.000 1953000.000, 964000.000 1... 0
1 다사5651 12198.00 12198.0 POLYGON ((956000.000 1951000.000, 956000.000 1... 1
2 다사6157 28897.00 28897.0 POLYGON ((961000.000 1957000.000, 961000.000 1... 2
3 다사5858 23351.00 23351.0 POLYGON ((958000.000 1958000.000, 958000.000 1... 3
4 다사5347 11380.00 11380.0 POLYGON ((953000.000 1947000.000, 953000.000 1... 4
... ... ... ... ... ...
705 다사4053 NaN NaN POLYGON ((940000.000 1953000.000, 940000.000 1... 705
706 다사7249 NaN NaN POLYGON ((972000.000 1949000.000, 972000.000 1... 706
707 다사4552 NaN NaN POLYGON ((945000.000 1952000.000, 945000.000 1... 707
708 다사6265 NaN NaN POLYGON ((962000.000 1965000.000, 962000.000 1... 708
709 다사5259 NaN NaN POLYGON ((952000.000 1959000.000, 952000.000 1... 709

710 rows × 5 columns

 이제 단계구분 다각형 레이어를 생성하고, 이를 지도 객체에 추가하면 됩니다. Folium이 조금은 복잡한 구조를 갖기 때문에, 다음과 같은 여러 파라미터를 입력해야 합니다.

  • geo_data, data : 공간정보를 갖는 데이터셋(geo_data)과 표현할 값을 갖는 데이터셋(data)이며, 동일한 geopandas.GeoDataFrame일 수 있음

  • columns : data에 입력한 데이터셋에서 사용할 열 이름으로, 인덱스 열과 표현할 값이 포함된 열

  • key_on : data와 조인에 사용할 geo_data의 열

interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12
)
population_grid_layer = folium.Choropleth(
    geo_data=population_grid,
    data=population_grid,
    columns=("id", "val"),
    key_on="id"
)
population_grid_layer.add_to(interactive_map)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

 지도를 더 멋지게 만들기 위해, 더 많은 범주(bins)로 나누고 색상 맵을 변경하고(fill_color), 선 두께(line_weight)를 0으로 설정하고, 범례에 레이어 이름(legend_name)을 추가해 보겠습니다.

interactive_map = folium.Map(
    location=(37.55, 127.0),
    zoom_start=12
)
population_grid_layer = folium.Choropleth(
    geo_data=population_grid,
    data=population_grid,
    columns=("id", "val"),
    key_on="id",

    bins=9,
    fill_color="YlOrRd",
    line_weight=0,
    legend_name="2023년 4월 인구밀도",

    highlight=True
)
population_grid_layer.add_to(interactive_map)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook

3.4.1. 단계구분도에 툴팁 추가#

 이러한 대화형 지도에서는, 마우스를 위에 올리면 각 격자 셀의 값을 표시하는 것이 정보전달 측면에서 좋습니다. Folium에서는 간단한 방법으로 이러한 기능을 추가할 수 있습니다. folium.features.GeoJson을 사용하여 투명한 다각형 레이어를 추가하고, 툴팁을 표시하도록 구성합니다.

 위에서 만든 지도 interactive_map을 유지하고, 여기에 새로운 레이어를 추가하기만 하면 됩니다.

def style_function(feature):
    return {
        "color": "transparent",
        "fillColor": "transparent"
    }

tooltip = folium.features.GeoJsonTooltip(
    fields=("val",),
    aliases=("Population:",)
)
tooltip_layer = folium.features.GeoJson(
    population_grid,
    style_function=style_function,
    tooltip=tooltip
)
tooltip_layer.add_to(interactive_map)
interactive_map
Make this Notebook Trusted to load map: File -> Trust Notebook