2. 정적 지도#


 앞선 단원들에서 geopandas.GeoDataFrame.plot()을 사용하여 간단한 정적 지도(static map)를 그리는 방법에 대해 익혔습니다. 이번 단원에서는 앞서 배운 내용들을 활용하여 좀더 복잡한 정적 지도를 그려보겠습니다. 이 지도에는 노숙자 인구, 도로, 지하철 노선이 표시되며, 세 개의 레이어가 겹쳐져 표현됩니다.

2.1. 데이터#

 여러분은 세 가지의 다른 데이터셋을 사용할 것입니다. 아래의 데이터는 County of Los Angeles Enterprise GIS에서 다운로드 받을 수 있습니다.

  • 로스앤젤레스 구역별 노숙자 인구

  • 로스앤젤레스 지하철 노선

  • 로스앤젤레스의 주요 도로

2.1.1. 노숙자 인구 데이터#

import pathlib
import geopandas as gpd
# 데이터 경로
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_DIRECTORY = NOTEBOOK_PATH / "data"
# 노숙자 인구 데이터
homeless_grid = gpd.read_file(DATA_DIRECTORY / "Homeless_Counts_2020.geojson")
homeless_grid.head()
OBJECTID CSA_Label Total_Unsheltered_Pop Total_Sheltered_Pop Total_Pop Data_Source Square_Miles Density_Unsheltered Density_Sheltered Density_Total Shape__Area Shape__Length geometry
0 1 City of Agoura Hills 2 0 2 Los Angeles Homeless Services Authority (LAHSA) 5.427723 0.368479 0.000000 0.368479 2.058150e+07 21914.080903 POLYGON ((-118.74008 34.16798, -118.73567 34.1...
1 2 City of Alhambra 32 14 46 Los Angeles Homeless Services Authority (LAHSA) 7.630029 4.193955 1.834855 6.028811 2.888244e+07 25368.896059 POLYGON ((-118.12182 34.10507, -118.12174 34.1...
2 3 City of Arcadia 68 0 68 Los Angeles Homeless Services Authority (LAHSA) 10.240411 6.640358 0.000000 6.640358 3.881236e+07 35209.966257 POLYGON ((-118.01826 34.17572, -118.01843 34.1...
3 4 City of Artesia 24 0 24 Los Angeles Homeless Services Authority (LAHSA) 1.620773 14.807752 0.000000 14.807752 6.103736e+06 15015.336550 POLYGON ((-118.07491 33.88039, -118.07485 33.8...
4 5 City of Avalon 28 3 31 Los Angeles Homeless Services Authority (LAHSA) 1.233833 22.693504 2.431447 25.124951 4.589095e+06 12492.086289 POLYGON ((-118.33051 33.35552, -118.33047 33.3...

2.1.2. 지하철 노선#

# 지하철 노선
metro = gpd.read_file(DATA_DIRECTORY / "MTA_Metro_Lines.geojson")
metro
OBJECTID NAME STATUS TYPE LABEL SHAPE_Length geometry
0 513 Blue Line Existing Rail Metro A Line (Blue) 0.332035 MULTILINESTRING ((-118.18937 33.77684, -118.18...
1 515 Orange Line Existing Bus Metro G Line (Orange) 0.358219 MULTILINESTRING ((-118.39873 34.16851, -118.39...
2 524 K Line Existing Rail Metro K Line 0.093875 MULTILINESTRING ((-118.33509 34.02253, -118.33...
3 526 K Line Construction Rail Metro K Line 0.034949 MULTILINESTRING ((-118.37446 33.96202, -118.37...
4 528 Green Line Existing Rail Metro C Line (Green) 0.336354 MULTILINESTRING ((-118.10112 33.91435, -118.10...
5 530 Purple Line Ext 1 Construction Rail Metro D Line (Purple) 0.067698 MULTILINESTRING ((-118.37703 34.06537, -118.37...
6 531 Purple Line Ext 2 Construction Rail Metro D Line (Purple) 0.044267 MULTILINESTRING ((-118.37703 34.06537, -118.37...
7 532 Purple Line Ext 3 Construction Rail Metro D Line (Purple) 0.043554 MULTILINESTRING ((-118.41677 34.05807, -118.41...
8 533 Gold Line Construction Rail Metro L Line (Gold) 0.208126 MULTILINESTRING ((-117.69442 34.09395, -117.72...
9 534 Regional Connector Construction Rail Regional Connector 0.032172 MULTILINESTRING ((-118.23812 34.04897, -118.23...
10 925 Expo Line Existing Rail Metro E Line (Expo) 0.258328 MULTILINESTRING ((-118.28651 34.01823, -118.28...
11 926 Silver Line Existing Bus Metro J Line (Silver) 0.685290 MULTILINESTRING ((-118.28798 33.72698, -118.28...
12 927 Gold Line Existing Rail Metro L Line (Gold) 0.519101 MULTILINESTRING ((-117.88762 34.13747, -117.88...
13 929 Purple Line Existing Rail Metro D Line (Purple) 0.105507 MULTILINESTRING ((-118.22871 34.03723, -118.22...
14 930 Red Line Existing Rail Metro B Line (Red) 0.254514 MULTILINESTRING ((-118.37715 34.16906, -118.37...

2.1.3. 주요 도로#

# 주요 도로
roads = gpd.read_file(DATA_DIRECTORY / "Master_Plan_of_Highways.geojson")
roads.head()
OBJECTID LENGTH TYPE STRU STAT SURF PREF_ALF NAME_ALF SUFFABV_AL DIRSABV_AL ... TBMCITYL_A TBMCITYR_A NUMBER SYMBOL HCODE HCODE_NAME dbgis_USER2_GP_DRP_HWY_PLAN_29 NO_LANES ShapeSTLength geometry
0 22691 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN PARKWAY - PROPOSED NaN NaN 6094.916994 LINESTRING (-118.33371 33.72602, -118.33429 33...
1 22692 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN SECONDARY HIGHWAY - EXISTING NaN NaN 24.005160 LINESTRING (-118.31458 33.78747, -118.31458 33...
2 22693 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN LIMITED SECONDARY HIGHWAY - EXISTING NaN NaN 68.065228 LINESTRING (-118.53672 34.76006, -118.53649 34...
3 22694 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN MAJOR HIGHWAY - EXISTING NaN NaN 5349.044231 LINESTRING (-117.95205 34.66805, -117.95204 34...
4 22695 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN SECONDARY HIGHWAY - EXISTING NaN NaN 16045.314884 LINESTRING (-117.95170 34.61666, -117.95171 34...

5 rows × 32 columns

 서로 다른 GIS 데이터에 대해 geopandas.GeoDataFrame.plot()를 사용하여 하나의 지도에 나타내기 전에, 모든 GeoDataFrame이 동일한 좌표계를 갖도록 설정해주어야 합니다.

# 좌표 참조 시스템(CRS)이 동일한지 확인
assert homeless_grid.crs == metro.crs == roads.crs, "Input data sets’ CRS differs"
homeless_grid.crs
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

2.2. 다중레이어 지도 플롯#

geopandas.GeoDataFrame.plot()에 입력할 수 있는 옵션들을 알아두면 도움이 됩니다.

  • 다각형(Polygon) 레이어 스타일

    • scheme : 분류 체계

    • cmap : 색상 맵

    • alpha : 레이어의 투명도 (0은 완전 투명, 1은 완전 불투명)

  • 선(Line) 레이어 스타일

    • color : 선 색상

    • linewidth : 선 두께

 각각의 레이어가 표현하는 지도 상에서의 범위는 다양할 수 있습니다. 예를 들어, 여기서 사용할 지하철 노선 데이터 metro는 로스앤젤레스의 도심 지역만 표현해도 되지만, 도로 roads는 훨씬 넓은 영역(도심 외곽)까지 표현해야 할 수 있습니다. 이러한 경우, 축(ax)의 메서드인 set_xlim()set_ylim()을 사용하여 지도의 수평, 수직 범위를 설정할 수 있습니다.

import matplotlib.pyplot as plt
# 노숙자 인구 플롯
ax = homeless_grid.plot(
    figsize=(12, 8),
    
    column="Total_Pop",
    scheme="quantiles",
    cmap="Spectral",
    linewidth=0,
    alpha=0.8
)
# 지하철 노선 플롯
metro.plot(
    ax=ax,
    color="orange",
    linewidth=2.5
)
# 주요 도로 플롯
roads.plot(
    ax=ax,
    color="grey",
    linewidth=0.8
)
ax.set_ylim(33.6,)
plt.show()
../../_images/582bcc6eee329fa856b97e1515558c37ecaacf55efc7701daa4f3bc89ecd33ad.png

2.3. 범례(Legend) 추가#

 지도에 범례(legend)를 추가하려면 legend=True 파라미터를 추가해 줍니다. 분류 체계(scheme)가 없는 그림이라면 범례는 그라데이션 막대로 나타납니다. 범례는 matplotlib.pyplot.colorbar.Colorbar의 인스턴스이며, 범례를 스타일링 하기 위해서는 legend_kwds에 인수를 설정하여 다양한 스타일로 꾸밀 수 있습니다. 아래의 예시에서 label 인수를 사용하여 범례 제목을 설정하겠습니다.

# 연속형 범례 (분류 체계 없음)
ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    cmap="Spectral",
    linewidth=0,
    alpha=0.8,

    legend=True,
    legend_kwds={"label": "Homeless Counts 2020"}
)
ax.set_ylim(33.6,)
plt.show()
../../_images/768badcd21f2b8d45535dbfef77471e886eb27da83754673513c49f267009281.png

 반면에 분류 체계(scheme)를 사용하는 그림의 경우, 범례가 matplotlib.legend.Legend 인스턴스로 생성됩니다. 범례의 스타일을 설정하기 위해 이번에도 legend_kwds가 사용되지만 파라미터는 다를 수 있습니다. 예를 들어, 범례 제목을 설정하려면 label 대신 title을 사용해야 합니다.

# 분류 체계 있음
ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    scheme="quantiles",
    cmap="Spectral",
    linewidth=0,
    alpha=0.8,

    legend=True,
    legend_kwds={"title": "Homeless Counts 2020", "loc": "upper right"}
)
ax.set_ylim(33.6,)
plt.show()
../../_images/e4b16abc99b5951b229e57a92da501b048c97a2b524a4efc0615c1d6852b7002.png

2.4. 배경 지도(Base map) 추가#

 지도를 스타일링 할 때, 그림에 배경 지도(base map)를 추가하는 것이 가시성이나 정보 전달성 측면에서 좋습니다. 예를 들어, OpenStreetMap 또는 Stamen과 같은 지도 제공업체의 지도에는 거리명, 장소명, 기타 상황별 정보 등 다양한 정보들이 추가됩니다. 이러한 지도를 파이썬에서 활용하기 위해 contextily라는 패키지를 사용하며, 이를 통해 필요한 배경 지도 타일을 다운로드하고 geopandas 플롯에 렌더링하여 배경 지도를 추가할 수 있습니다.

웹 메르카토르(Mercator)

 온라인 지도 제공업체의 지도는 일반적으로 Web Mercator 투영법(EPSG:3857)입니다. 따라서, 함께 투영할 다른 레이어들도 EPSG:3857로 재투영하는 것이 좋습니다.

# 참조 좌표 시스템 EPSG:3857로 재투영
homeless_grid = homeless_grid.to_crs("EPSG:3857")
metro = metro.to_crs("EPSG:3857")
roads = roads.to_crs("EPSG:3857")

 기존 플롯에 배경 지도를 추가하려면 contextily.add_basemap() 함수를 사용합니다.

import contextily

ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    scheme="quantiles",
    cmap="Spectral",
    linewidth=0,
    alpha=0.8,

    legend=True,
    legend_kwds={"title": "Homeless Counts 2020", "loc": "upper right"}
)
ax.set_ylim(3.975*1e6,)
contextily.add_basemap(ax)
plt.show()
../../_images/c69fe1a6b8fe003447bbe01f6c35d7ae89b05e776ca3f56ba4af7e0dd50c03a8.png

 기본적으로 contextily에서는 Stamen Terrain을 사용하지만, 이외에 선택할 수 있는 다른 온라인 지도도 많이 있습니다. 다른 contextily.providers를 사용하기 위해서는 add_basemap()source 인자를 설정합니다. 예를 들어, OpenStreetMap의 Mapnik 스타일을 사용해 보겠습니다.

ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    scheme="quantiles",
    cmap="Spectral",
    linewidth=0,
    alpha=0.8,

    legend=True,
    legend_kwds={"title": "Homeless Counts 2020", "loc": "upper right"}
)
ax.set_ylim(3.975*1e6,)
contextily.add_basemap(
    ax,
    source=contextily.providers.OpenStreetMap.Mapnik
)
plt.show()
../../_images/2d3dc76677f99a2651f4c3508fb43fde03ea2c1350b65ebae6b523ea6a82638f.png

 이 확대/축소 수준에서는 OpenStreetMap 사용으로 인한 이점(예: 지명)이 최대한 활용되지 않습니다. 축(ax)의 범위를 set_xlim(), set_ylim()을 사용하여 더 확대해서 살펴보겠습니다.

ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    scheme="quantiles",
    k=7,
    cmap="Spectral",
    linewidth=0,
    alpha=0.4,

    legend=True,
    legend_kwds={"title": "Homeless Counts 2020", "loc": "upper right"}
)
ax.set_xlim(-1.318*1e7, -1.317*1e7)
ax.set_ylim(4.04*1e6, 4.05*1e6)
contextily.add_basemap(
    ax,
    source=contextily.providers.OpenStreetMap.Mapnik
)
plt.show()
../../_images/8484d07b40a480d2dd2b85f836cb870b4ad98953ed0aa82d7a5e68572df41162.png

 마지막으로 지도의 왼쪽 하단에 표시된 저작권 관련 속성을 수정할 수도 있습니다. 지도 제공업체의 이용 약관을 항상 준수해야 하며, contextily 패키지를 사용할 경우 자동으로 저작권 관련 속성을 지도의 왼쪽 하단에 표시합니다. 하지만 여기에서 사용한 노숙자 인구 데이터처럼 패키지에서 제공하지 않는 모든 레이어에 대한 데이터 출처도 표시해야 합니다.

ax = homeless_grid.plot(
    figsize=(12, 8),

    column="Total_Pop",
    scheme="quantiles",
    k=7,
    cmap="Spectral",
    linewidth=0,
    alpha=0.4,

    legend=True,
    legend_kwds={"title": "Homeless Counts 2020", "loc": "upper right"}
)
ax.set_xlim(-1.318*1e7, -1.317*1e7)
ax.set_ylim(4.04*1e6, 4.05*1e6)
contextily.add_basemap(
    ax,
    source=contextily.providers.OpenStreetMap.Mapnik,
    attribution=(
        "homeless counts 2020 data (c) County of Los Angeles, "
        "map data (c) OpenStreetMap contributors"
    )
)
plt.show()
../../_images/6a80f976661a5ff6ba5f3385c1b10e6ac2b9159dd2c9cb211bfc2c47e8178e9c.png