본문 바로가기

데이터와 함께 탱고를/데이터 시각화

[지도 데이터 시각화] Part 6. pydeck 살펴보기

deck.gl 은 Uber 에서 만든 자바스크립트 공간 데이터 시각화 라이브러리입니다.
pydeck 은 이 라이브러리를 파이썬에서도 쓸 수 있도록 만든 패키지입니다.
이 또한 Uber 팀에서 공식적으로 만들었습니다.

특징으로는 대용량 데이터도 거뜬하게 렌더링할 수 있으며, 렌더링이 좀 버거울 수도 있는 일부 레이어는 GPU 연산을 제공합니다. (즉 대용량 처리 및 렌더링에 매우 용이하다는 말입니다.)

이전에 말씀드렸다싶이, 지금까지 소개한 2개의 파이썬 패키지(folium, mapboxgl)에 비해 상대적으로 매우 최근 라이브러리고, 기술적으로도 훨씬 진보한 라이브러리라, 꼭 알고가셨으면 좋겠습니다.
다만, 이전보다 좀 더 섬세하게 다룰 수 있는 부분이 많아, 조금 더 어려울 수는 있습니다.
아직 geojson 및 지도 데이터에 익숙치 않으신 분들은 이전 포스팅을 먼저 보기를 권장드립니다.

그럼 이제 시작해보겠습니다.

1. 설치 & 세팅

먼저 pip 를 통해 설치해줍니다.

pip install pydeck

이후 다음의 명령어로 nbextensions 에 추가해줍니다.

jupyter nbextension install --sys-prefix --symlink --overwrite --py pydeck
jupyter nbextension enable --sys-prefix --py pydeck

mapboxgl 때와 마찬가지로, 환경 변수에 자신의 mapbox api token 이 있어야 합니다.
https://www.mapbox.com/ 에 접속하시고 계정을 만드신 뒤, account 에 들어가시면 자신의 token 값에 대해 알 수 있습니다.

환경 변수로 다음과 같이 추가해줍니다.

MAPBOX_API_KEY = "자신의 mapbox api token (pk... 로 시작함)"

이제 쥬피터 노트북을 열어 pydeck 을 임포트합니다.

import pydeck as pdk

 


2. 샘플 하나 해보기

데이터는 이전처럼 서울시 행정동별 고령인구 지도 데이터를 사용합니다.
파일 내부에 데이터가 아래와 같이 저장되어 있습니다.

geo_data = 'data/older_seoul.geojson'
{'type': 'FeatureCollection',
 'crs': {'type': 'name',
 'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
 'features': [{
   'type': 'Feature',
   'properties': {
    '시': '서울특별시',
    '구': '종로구',
    '동': '사직동',
    '행정동코드': 11110530,
    '인구': 9700,
    '남자': 4375,
    '여자': 5325},
   'geometry': {'type': 'MultiPolygon',
    'coordinates': [[[[126.97688884274817, 37.575650779448786],
       [126.9770344988775, 37.569194530054546],
       [126.97597472821249, 37.569336299425764],
       ....
     ]]]
   }, {
   'type': 'Feature',
   'properties': {
    '시': '서울특별시',
    '구': '종로구',
    '동': '삼청동',
    '행정동코드': 11110540,
    '인구': 3013,
    '남자': 1443,
    '여자': 1570},
   'geometry': {'type': 'MultiPolygon',
    'coordinates': [[[[126.98268938649305, 37.5950655194224],
       [126.98337258456999, 37.59435192551688],
       [126.98386809792802, 37.59385046812643],
       ....
     ]]]
   }]
}

MultiPolygon 단위로, 시군구 이름, 인구, 남자, 여자 데이터를 가지고 있는 일반적인 geojson 데이터 입니다.

pydeck 은 geojson 보다는 주로 pandas dataframe 을 input으로 받습니다.
따라서, 기존의 이 geojson 데이터를 dataframe 형태로 받아보겠습니다.

import geopandas as gpd

df = gpd.read_file(geo_data)
df.head()
  행정동코드 인구 남자 여자 geometry
0 서울특별시 종로구 사직동 11110530 9700 4375 5325 (POLYGON ((126.9768888427482 37.57565077944879...
1 서울특별시 종로구 삼청동 11110540 3013 1443 1570 (POLYGON ((126.982689386493 37.5950655194224, ...
2 서울특별시 종로구 부암동 11110550 10525 5002 5523 (POLYGON ((126.9758511377569 37.59656422224408...
3 서울특별시 종로구 평창동 11110560 18830 8817 10013 (POLYGON ((126.9750746678809 37.63138628651299...
4 서울특별시 종로구 무악동 11110570 8745 4078 4667 (POLYGON ((126.960673532739 37.58079784202972,...

잘 읽어온 것을 확인할 수 있습니다.

그런데 하나 수정해야할 것이 있습니다.
geometry 컬럼입니다. 현재 여기에 값들은 shapely.geometry.multipolygon 형태인데, pydeck 은 이런 형태를 읽지 못합니다.
pydeck 에서는 항상 geometry 에 연속된 포인트들을 갖는 리스트 값이 있어야 합니다.
따라서, 다소 귀찮지만 다음과 같은 작업들을 해줘야합니다.

def multipolygon_to_coordinates(x):
    lon, lat = x[0].exterior.xy
    return [[x, y] for x, y in zip(lon, lat)]

df['coordinates'] = df['geometry'].apply(multipolygon_to_coordinates)
del df['geometry']

이후 렌더링할 지도에는 '인구' 별로 색을 다르게 하려고 합니다.
인구 값에 따른 색상 지정을 데이터프레임에서 미리 작업해줄 필요가 있습니다. (사실 pydeck 은 데이터프레임만 보고 렌더링하기 때문에, 렌더링 전에 색상, 크기 시각화 하고싶은 기준들을 새로운 컬럼으로 추가해줘야 합니다.)

# 인구를 0 ~ 1 사이의 값으로 변환시킵니다.
df['정규화인구'] = df['인구'] / df['인구'].max()

한편, 지금 데이터프레임은 geopandas.dataframe 입니다.
pydeck 은 pandas.dataframe 형태만 인식 가능합니다. 따라서 데이터프레임을 변환하겠습니다.

import pandas as pd

df = pd.DataFrame(df)

최종적으로, pydeck 에 input 으로 줄 데이터프레임은 다음과 같이 생겼습니다.

df.head()
  행정동코드 인구 남자 여자 coordinates 정규화인구
0 서울특별시 종로구 사직동 11110530 9700 4375 5325 [[126.97688884274817, 37.575650779448786], [12... 0.167132
1 서울특별시 종로구 삼청동 11110540 3013 1443 1570 [[126.98268938649305, 37.5950655194224], [126.... 0.051914
2 서울특별시 종로구 부암동 11110550 10525 5002 5523 [[126.97585113775686, 37.59656422224408], [126... 0.181347
3 서울특별시 종로구 평창동 11110560 18830 8817 10013 [[126.97507466788086, 37.63138628651299], [126... 0.324443
4 서울특별시 종로구 무악동 11110570 8745 4078 4667 [[126.96067353273895, 37.580797842029725], [12... 0.150677

이제 pydeck 을 이용하여 각 데이터를 Choropleth 로 시각화 해보겠습니다.
pydeck 에서는 Choropleth 로 부르지않고 PolygonLayer 라고 부릅니다.

# Make layer
layer = pdk.Layer(
    'PolygonLayer', # 사용할 Layer 타입
    df, # 시각화에 쓰일 데이터프레임
    get_polygon='coordinates', # geometry 정보를 담고있는 컬럼 이름
    get_fill_color='[0, 255*정규화인구, 0]', # 각 데이터 별 rgb 또는 rgba 값 (0~255)
    pickable=True, # 지도와 interactive 한 동작 on
    auto_highlight=True # 마우스 오버(hover) 시 박스 출력
)

# Set the viewport location
center = [126.986, 37.565]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=10)

# Render
r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

pickable=True, auto_highlight=True 를 주면 위와 같이 마우스 오버시 데이터 정보가 뜹니다.

데이터 별 지도 정보에 높이를 주어 3d 로도 볼 수 있습니다.
색과 마찬가지로 '인구' 기준으로 높이를 줘보겠습니다.

layer.extruded = True;
layer.get_elevation = '인구';
layer.elevation_scale = 0.05

view_state.bearing=15
view_state.pitch=45

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

제대로 시각화 된 것을 확인하실 수 있습니다.


3. 동작 살펴보기

위 코드 기준으로 동작을 하나씩 살펴보겠습니다.

3.1. pydeck class

pdk.Layer

우리가 어떤 레이어를 지도 위에 올릴 때, 항상 이 클래스를 통해서 만듭니다.
위 코드에 주석으로도 설명해두었지만, 좀 더 구체적으로 어떤 파라미터(속성) 들이 있는지, 필요한 것만 살펴보겠습니다.

pdk.Layer(
    type = "미리 정의된 레이어 타입",
    id = "이 레이어의 아이디 (optional)",
    data = "pandas.DataFrame 또는 geojson url",
)

일단 이 위의 파라미터들이 기본입니다.
특히 id 의 경우 같은 타입의 여러 개의 레이어를 지도에 쌓는 경우, 각 레이어를 다른 id 로 설정해줘야 합니다.
예를 들어, 지도에 PolygonLayer 가 여러 개 있는 경우, 각 레이어의 id 를 'polygon1', 'polygon2' 이런 식으로 주어야 합니다.

pdk.ViewState

folium 이나 mapboxgl 에서도 흔히 본, 익숙한 파라미터들이 있습니다.
지도 렌더링할 때, 초기화 뷰에 대한 설정입니다.
익숙하실거라 생각하여 별도의 설명은 하지않겠습니다.

pdk.ViewState(
    longitude = "중심 경도 (default 0)",
    latitude = "중심 위도 (default 0)",
    zoom = "줌 레벨 (default 0)",
    pitch = (default 0),
    bearing = (default 0),
    **kwargs,
)

pdk.Deck

만들어진 레이어, 뷰 정보, 맵 스타일 등 모든 요소를 모아 출력시킬 수 있는 클래스 입니다.
기본 값으로 다음의 값들이 들어가있습니다.

pdk.Deck(
    layers=[],
    views=[{"controller": true, "type": "MapView"}],
    map_style='mapbox://styles/mapbox/dark-v9',
    mapbox_key=None,
    initial_view_state={"bearing": 0, 
                        "latitude": 0.0, 
                        "longitude": 0.0,
                        "maxZoom": 20,
                        "minZoom": 0, 
                        "pitch": 0, 
                        "zoom": 1},
    width='100%',
    height=500,
    tooltip=True,
)

레이어를 쌓고싶다면, layers 리스트에 추가시키면 됩니다.

3.2. pydeck 의 핵심

입력 데이터를 pandas.DataFrame 을 사용한다.

물론 geojson 을 사용할 수도 있습니다만, 대부분 pandas.DataFrame 사용하고있습니다.
mapboxgl 의 경우 geojson 을 파싱해와, 바로 데이터 값에 따른 색상, 높이 등의 시각화 요소를 다르게 할 수 있었지만, pydeck 은 이게 안됩니다.
입력 데이터를 어떻게든 pandas.DataFrame 로 만드는게 전처리에서 가장 중요한 부분이라 할 수 있습니다.
만약 렌더링이 잘 안되는 경우 pandas.DataFrame 이 맞는지 우선적으로 확인해볼 필요가 있습니다.

다양한 레이어가 많다.

말 그대로 입니다. 아래에서 더 살펴보겠습니다.

레이어를 쌓을 수 있다.

이게 mapboxgl 과 비교되는 가장 큰 장점입니다.
pdk.Decklayers 를 통해 레이어를 쌓을 수 있습니다!


4. 다른 레이어들 살펴보기

4.1. Point 단위

샘플 데이터는 서울시내 공중화장실 데이터를 사용하겠습니다.

geo_data = 'data/toilet_seoul_sample.geojson'

위 파일을 열어보면 다음과 같이 데이터가 저장되어 있습니다.

{'type': 'FeatureCollection',
 'features': [{'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.158647, 37.501401]},
   'properties': {'구명': '송파구', '법정동명': '마천동', '이용량': 248}},
  {'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.073728, 37.644937]},
   'properties': {'구명': '노원구', '법정동명': '하계동', '이용량': 252}},
  {'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.077637, 37.640335]},
   'properties': {'구명': '노원구', '법정동명': '하계동', '이용량': 192}},
  {'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.020449, 37.645724]},
   'properties': {'구명': '강북구', '법정동명': '수유동', '이용량': 409}},
  {'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.031187, 37.659783]},
   'properties': {'구명': '도봉구', '법정동명': '방학동', '이용량': 333}},
  {'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [127.123028, 37.502339]},
   'properties': {'구명': '송파구', '법정동명': '가락동', '이용량': 333}},
   ...
]}

Point 단위로 구명, 법정동명, 이용량을 가지고 있는 일반적인 geojson 데이터 입니다.
(이용량은 실제 값이 아니고, 샘플용으로 제가 랜덤으로 준 값입니다.)

먼저 데이터를 읽어와 df 에 저장하겠습니다.

import geopandas as gpd

df = gpd.read_file(geo_data)
df['lat'] = df['geometry'].apply(lambda coord: coord.y)
df['lng'] = df['geometry'].apply(lambda coord: coord.x)
del df['geometry']

df = pd.DataFrame(df)
df.head()
  구명 법정동명 이용량 lat lng
0 송파구 마천동 248 37.501401 127.158647
1 노원구 하계동 252 37.644937 127.073728
2 노원구 하계동 192 37.640335 127.077637
3 강북구 수유동 409 37.645724 127.020449
4 도봉구 방학동 333 37.659783 127.031187

ScatterplotLayer

모든 Point 들을 지도에 그대로 출력합니다.

layer = pdk.Layer(
    'ScatterplotLayer',
    df,
    get_position='[lng, lat]',
    get_radius=50,
    get_fill_color='[255, 255, 255]',
    pickable=True,
    auto_highlight=True
)

center = [126.986, 37.565]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=10)

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

서울시내 공중화장실 데이를 모두 시각화 했습니다.

이용량에 따라 색을 다르게 하고싶으면, 다음과 같이 get_fill_color 를 수정해주면 됩니다.
이 때, 색상은 0~255 값을 갖는 rgba 임을 잊지 말아주세요.

df['정규화이용량'] = df['이용량'] / df['이용량'].max()
layer.get_fill_color = '[255*정규화이용량, 255*정규화이용량, 255]'

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

이용량에 따라 색상을 다르게하여 시각화 했습니다.

이 외에 각 Point 의 지름 반지름 설정, 높이 설정 등은 get_radius, get_elevation 으로 바꿀 수 있습니다.
방법은 위와 동일합니다.

HeatmapLayer

Point 들의 밀집도를 한 눈에 파악할 수 있습니다.
밀도가 높은 곳은 더 진한 색상으로 표시됩니다.

layer = pdk.Layer(
    'HeatmapLayer',
    df,
    get_position='[lng, lat]'
)

center = [126.986, 37.565]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=10)

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

colorRangeintensity, getWeight 등의 추가적인 파라미터들을 통해 색상, 밀도 설정, 가중치를 섬세하게 설정할 수 있습니다.
이런 추가적인 파라미터 사용법은 이후에 설명드리겠습니다.

Grid Layers

1) CPUGridLayer (GPUGridLayer)

pydeck 에서만 제공하는 아주 강력한 레이어 중 하나입니다.
Point 들을 일정한 그리드 단위로 묶어서 표현합니다.

layer = pdk.Layer(
    'CPUGridLayer', # 대용량 데이터의 경우 'GPUGridLayer'
    df,
    get_position='[lng, lat]',
    pickable=True,
    auto_highlight=True
)

center = [126.986, 37.565]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=10)

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

특히, 다음과 같이 높이도 추가로 줄 수 있습니다.

layer.extruded = True
layer.elevation_scale = 3 # default 1

view_state.bearing = -15
view_state.pitch = 45

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

2) HexagonLayer

위와 동일하지만 모양이 육각형(Hexagon) 입니다.

layer.type = 'HexagonLayer'

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

3) ScreenGridLayer

위와 동일하지만 모양이 정사각형 스크린(Sreen) 입니다.

layer.type = 'ScreenGridLayer'
layer.cellSizePixels = 10 # default 100

view_state.bearing = 0
view_state.pitch = 0

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

TextLayer

데이터 Point 좌표에 텍스트를 출력합니다.
다만, 한글은 렌더링 되지 않습니다.
공식문서에 따르면 only characters in the Ascii code range 32-128 값만 렌더링이 된다고 합니다.

df['text'] = 'text'
layer = pdk.Layer(
    'TextLayer',
    df[:100],
    get_position='[lng, lat]',
    get_text='text',
    get_color='[0, 255, 255]',
    font_family='consolas',
    sizeScale=0.5,
    pickable=True,
    auto_highlight=True
)

center = [126.986, 37.565]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=10)

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

4.2. Points (Path or Polygon) 단위

geometry 데이터가 2개 이상의 좌표가 들어간 데이터들에 대해서 다룹니다.
Points(Path) 단위의 레이어들은 geometry 정보가 OD, LineString(또는 Polygon, MultiPolygon) 단위의 데이터들입니다.

OD layers

OD Path 레이어는 Point(Origin)와 Point(Destination) 를 연결하는 레이어입니다.

먼저 샘플로 따릉이 OD 데이터를 사용해보겠습니다.
다음과 같이 출발지와 도착지의 위도, 경도를 가지고 있는 데이터 입니다.

import pandas as pd

df = pd.read_csv('data/seoul_bike_sample.csv')
df.head()
  대여대여소명 반납대여소명 이용 이용시간(분) 이용거리(M) 대여대여소위도 대여대여소경도 대여대여소구명 반납대여소위도 반납대여소경도 반납대여소구명
0 여의나루역 1번출구 앞 DMC산학협력연구센터 앞 1 47.000000 11160.000000 37.526989 126.932098 영등포구 37.575802 126.890739 마포구
1 여의나루역 1번출구 앞 IFC몰 3 46.333333 5853.333333 37.526989 126.932098 영등포구 37.526066 126.925537 영등포구
2 여의나루역 1번출구 앞 KBS 앞 2 13.000000 2110.000000 37.526989 126.932098 영등포구 37.524666 126.918022 영등포구
3 여의나루역 1번출구 앞 KT앞 8 20.250000 1692.500000 37.526989 126.932098 영등포구 37.521908 126.918953 영등포구
4 여의나루역 1번출구 앞 NH농협은행 앞 7 9.428571 1262.857143 37.526989 126.932098 영등포구 37.522079 126.930367 영등포구
df['정규화이용'] = df['이용'] / df['이용'].max()
df['정규화이용시간'] = df['이용시간(분)'] / df['이용시간(분)'].max()

1) LineLayer

o-d 를 잇는 직선을 그립니다.

이용 값이 클수록 선의 굵기를 굵게하고,
이용시간 값에 따라 색상을 다르게 해보겠습니다.

layer = pdk.Layer(
    'LineLayer',
    df,
    get_source_position='[대여대여소경도, 대여대여소위도]',
    get_target_position='[반납대여소경도, 반납대여소위도]',
    get_width='1 + 10 * 정규화_이용',
    get_color='[255, 255 * 정규화이용시간, 120]',
    pickable=True,
    auto_highlight=True
)

# pydeck.data_utils.compute_view 는 Points 들의 경도, 위도를 리스트로 주면, 알아서 view_state 를 만들어줍니다.
view_state = pydeck.data_utils.compute_view(df[['대여대여소경도', '대여대여소위도']].values)
view_state.zoom = 13

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

2) ArcLayer

o-d 를 잇는 곡선을 그립니다.

이용 값이 클수록 선의 굵기를 굵게하고,
origin 과 destination 에 따라 색을 다르게 해보겠습니다.

layer = pdk.Layer(
    'ArcLayer',
    df,
    get_source_position='[대여대여소경도, 대여대여소위도]',
    get_target_position='[반납대여소경도, 반납대여소위도]',
    get_width='1 + 10 * 정규화_이용',
    get_source_color='[255, 255, 120]',
    get_target_color='[255, 0, 0]',
    pickable=True,
    auto_highlight=True
)

# pydeck.data_utils.compute_view 는 Points 들의 경도, 위도를 리스트로 주면, 알아서 view_state 를 만들어줍니다.
view_state = pydeck.data_utils.compute_view(df[['대여대여소경도', '대여대여소위도']].values)
view_state.zoom = 12
view_state.bearing = -15
view_state.pitch = 45

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

Path Layer

Path layer 는 한 데이터가 2개 이상의 Point 들을 가지고 있습니다.
즉, Point 들을 차례대로 연결시켜 Path 를 그려냅니다.

먼저, 샘플 데이터로 사용할 서울시 특정 지역의 도로 데이터를 가져오겠습니다.

geo_data = 'data/dongjak_road.geojson'

이 데이터는 대략 다음과 같이 생겼습니다.

{'type': 'FeatureCollection',
 'crs': {'type': 'name',
  'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
 'features': [
  {'type': 'Feature',
   'properties': {'도로명': '만양로8길', '도로길이': 53, '도로폭': 7},
   'geometry': {'type': 'LineString',
    'coordinates': [[126.94777636499998, 37.50835655899999],
     [126.94782061399997, 37.50831506600002],
     [126.94786056500004, 37.50822010600001],
     [126.94791028600002, 37.50814785199998],
     [126.94795418900003, 37.508099371000014],
     [126.94805295399999, 37.508003151000025],
     [126.94809408200001, 37.507960638999975]]}},
  {'type': 'Feature',
   'properties': {'도로명': '동작대로39가길', '도로길이': 450, '도로폭': 5},
   'geometry': {'type': 'LineString',
    'coordinates': [[126.98097487099994, 37.493643061],
     [126.98093601699998, 37.49368160099999],
     [126.98089868099999, 37.493756701999985],
     [126.98072013800004, 37.493820518],
     [126.98048858200002, 37.493856463999975],
     [126.98042777299997, 37.49387360600002],
     ....

이를 pandas.dataframe 형태로 불러오겠습니다.

import geopandas as gpd
import shapely

# Shapely 형태의 데이터를 받아 내부 좌표들을 List안에 반환합니다.
def line_string_to_coordinates(line_string):
    if isinstance(line_string, shapely.geometry.linestring.LineString):
        lon, lat = line_string.xy
        return [[x, y] for x, y in zip(lon, lat)]
    elif isinstance(line_string, shapely.geometry.multilinestring.MultiLineString):
        ret = []
        for i in range(len(line_string)):
            lon, lat = line_string[i].xy
            for x, y in zip(lon, lat):
                ret.append([x, y])
        return ret

df = gpd.read_file(geo_data)
df['geometry'] = df['geometry'].apply(line_string_to_coordinates)
df = pd.DataFrame(df) # geopanadas 가 아닌 pandas 의 데이터프레임으로 꼭 바꿔줘야 합니다.
df.head()
  도로명 도로길이 도로폭 geometry
0 만양로8길 53 7 [[126.94777636499998, 37.50835655899999], [126...
1 동작대로39가길 450 5 [[126.98097487099994, 37.493643061], [126.9809...
2 등용로6길 65 4 [[126.93344854600002, 37.50607434300002], [126...
3 여의대방로10가길 249 6 [[126.91484482600004, 37.493450825000025], [12...
4 국사봉1나길 147 4 [[126.93478510399996, 37.49486913700002], [126...

이제, 도로폭에 따라 색과 굵기를 달리하여 시각화 해보겠습니다.

df['정규화도로폭'] = df['도로폭'] / df['도로폭'].max()
layer = pdk.Layer(
    'PathLayer',
    df,
    get_path='geometry',
    get_width='도로폭',
    get_color='[255, 255 * 정규화도로폭, 120]',
    pickable=True,
    auto_highlight=True
)

center = [126.950, 37.495]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=12)

r = pdk.Deck(layers=[layer], initial_view_state=view_state)
r.show()

Trips Layer

Trips Layer 는 Path Layer 와 동일하되 시간의 개념이 들어갑니다.
즉, [시간지점, 위도, 경도] 를 담고 있는 데이터를 입력으로 주면 시간에 따라 어떻게 움직이는지 시각화 해줍니다.

쥬피터에서 구현이 다소 복잡할 것 같아, 여기서는 이런게 있다는 정로도만 남겨두겠습니다.
어떤건지 보고싶으신 분은 여기서 확인하실 수 있습니다.


5. 맵 스타일링

5.1. 레이어별 스타일링과 파라미터

기본적으로 맵 스타일링은, 각 레이어별로 해야합니다.
위에서 보셨다 싶이, 색상은 항상 rgba (0~255값) 으로 해야하고,
그 외 파라미터들은 레이어마다 다릅니다.

각 레이어별 정확한 파라미터들은 deck.gl 공식 도큐먼트에서 확인하실 수 있습니다.
다만, 도큐먼트는 자바스크립트 기준으로 쓰인거라 파이썬에서는 조금 다릅니다.

  • camelCased 이 아닌 snake_cased 으로 바꿔야 합니다.
    • 예를 들어, deck.gl 에서 getPosition 이라는 파라미터는 pydeck 에서는 get_position 으로 쓰입니다.
    • 자바스크립트에서는 변수규칙을 camelCased 쓰는데, 파이썬에서는 snake_cased 규칙을 써서 그렇습니다.
  • 레이어 타입 이름은 deck.gl 과 동일합니다.
    • 예를 들어, deck.gl 에서 ArcLayer 는 pydeck 에서도 ArcLayer 입니다.

5.2. 베이스맵

pdk.Deck 클래스 내 map_style 값을 다르게 줌으로써 수정이 가능합니다.
미리 지정된 값들은 다음이 있습니다.

- mapbox://styles/mapbox/streets-v11
- mapbox://styles/mapbox/outdoors-v11
- mapbox://styles/mapbox/light-v10
- mapbox://styles/mapbox/dark-v10
- mapbox://styles/mapbox/satellite-v9
- mapbox://styles/mapbox/satellite-streets-v11
- mapbox://styles/mapbox/navigation-preview-day-v4
- mapbox://styles/mapbox/navigation-preview-night-v4
- mapbox://styles/mapbox/navigation-guidance-day-v4
- mapbox://styles/mapbox/navigation-guidance-night-v4

예를 들어, mapbox://styles/mapbox/outdoors-v11 로 값을 주면 다음과 같이 바뀝니다.

center = [126.950, 37.495]
view_state = pdk.ViewState(
    longitude=center[0],
    latitude=center[1],
    zoom=12)

r = pdk.Deck(layers=[], 
             initial_view_state=view_state,
             map_style='mapbox://styles/mapbox/outdoors-v11')
r.show()

map_style='mapbox://styles/mapbox/outdoors-v11'


6. 마무리

pydeck 에서 다루는 레이어는 얼추 대부분 다룬 듯 합니다.
세세한 스타일링 설정은 제가 모두 설명 못드릴만큼, pydeck 아니 deck.gl 자체가 조금 내용이 있습니다.
하지만, 위 정도의 설명이라면 당장 필요한 레이어는 써먹을 정도라고 생각이 드네요.
더 궁금하신 분들은 pydeck github 나 deck.gl 공식 사이트에서 추가적으로 확인하시면 좋을 것 같습니다.

드디어 pydeck 에 대한 설명이 끝났습니다.
이 외에 공간 데이터 시각툴로 Kepler.gl 도 있습니다.
이 역시 Uber 팀에서 만드는 매우 파워풀한 도구인데, Mapbox studio 와 비슷합니다. (오히려 상위 호환느낌 입니다.)
따로 포스팅하지는 않고, 이런게 있다 정도만 소개해드립니다.
코드보다 GUI 가 더 편하신 분들은 검색해서 찾아보시길 추천드립니다.

이 외에 공간데이터 시각화에 지속적으로 관심있으신 분들은 아래 블로그들을 구독하길 추천드립니다.
공간 데이터 분석을 통해, 로컬 인사이트(Location Inteligence) 를 얻는 과정들이 종종 포스팅되고 있는 블로그들입니다.