3. PIP (Point-In-Polygon) 쿼리#


 점(point)이 영역 내부 또는 외부에 있는지, 또는 선(line)이 다른 선 또는 다각형과 교차(intersect)하는지 판단하는 PIP (Point-In-Polygon) 쿼리는 다양한 공간 연산 중 빈번하게 사용됩니다. 특히 두 개의 공간 데이터셋 간의 공간 조인(spatial join)은 PIP 쿼리를 응용한 대표적인 사례입니다.

※ 참고

 PIP 및 도형 연산(geometric operation)에 대한 자세한 내용은 Smith, Goodchild & Longley가 집필한 Geospatial Analysis 6판의 4.2장에 정리되어 있습니다.

3.1. 레이 캐스팅 알고리즘 (Ray Casting Algorithm)#

 점이 다각형 내부에 위치하는지 계산하기 위해 사용되는 알고리즘 중 대표적으로 레이 캐스팅 알고리즘(Ray Casting Algorithm)이 있습니다. 이 알고리즘을 구현하기 위해 직접 함수를 만들지 않아도, Shapely 라이브러리를 사용하여 PIP 쿼리 외에도 기하학적 개체들 간의 다양한 연산을 수행할 수 있습니다.

3.2. Shapely에서 PIP 쿼리 사용하기#

 Shapely에서 PIP를 수행하는 두 가지 방법이 있습니다:

  1. 점이 다각형 내부에 있는지 확인하는 within() 함수를 사용

  2. 다각형이 점을 포함하고 있는지 확인하는 contains() 함수를 사용

Note

 여기에서는 PIP 쿼리에 대해서만 다루고 있지만, Shapely의 LineString 또는 다각형(Polygon) 개체가 다른 다각형(Polygon) 내부에 있는지 확인하는 것도 동일한 방법으로 가능합니다.

 먼저 두 개의 점 개체 point1point2를 만들어줍니다. 그리고 다각형 개체 polygon도 생성합니다.

import shapely.geometry as geom

# 점 개체 생성
point1 = geom.Point(24.952242, 60.1696017)
point2 = geom.Point(24.976567, 60.1612500)

# 다각형 개체 생성
polygon = geom.Polygon([(24.950899, 60.169158),
                        (24.953492, 60.169158),
                        (24.953510, 60.170104),
                        (24.950958, 60.169990)
                       ])

print(point1)
print(point2)
print(polygon)
POINT (24.952242 60.1696017)
POINT (24.976567 60.16125)
POLYGON ((24.950899 60.169158, 24.953492 60.169158, 24.95351 60.170104, 24.950958 60.16999, 24.950899 60.169158))

 두 점이 다각형 내부에 있는지 within() 함수를 사용하여 확인해 보겠습니다.

point1.within(polygon)
True
point2.within(polygon)
False

 첫 번째 점 point1은 다각형 내부(True)에 있지만, 두 번째 점 point2는 다각형 외부(False)에 있습니다. 이번에는 점이 다각형 내부에 있는지 확인하는 대신, 반대로 다각형이 점을 포함하는지 여부를 contains() 함수를 사용하여 확인해 보겠습니다.

polygon.contains(point1)
True
polygon.contains(point2)
False

3.3. Geopandas.GeoDataFrame에서 PIP 쿼리 사용하기#

 아래 예시는 지오코딩을 통해 얻은 주소 중 어느 지점이 서울의 특정 구역 내에 있는지 탐색합니다.

import pathlib
import geopandas as gpd

NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_DIRECTORY = NOTEBOOK_PATH / "data"

city_districts = gpd.read_file(DATA_DIRECTORY / "LARD_ADM_SECT_SGG_서울" / "LARD_ADM_SECT_SGG_11.shp",
                               encoding="cp949"
                              )
city_districts.head()
ADM_SECT_C SGG_NM SGG_OID COL_ADM_SE GID geometry
0 11740 강동구 NaN 11740 125 POLYGON ((971595.075 1952405.815, 971596.036 1...
1 11710 송파구 NaN 11710 126 POLYGON ((965821.957 1949386.153, 965816.737 1...
2 11680 강남구 NaN 11680 127 POLYGON ((959331.597 1948602.068, 959342.021 1...
3 11650 서초구 NaN 11650 128 POLYGON ((956982.039 1947144.037, 956982.518 1...
4 11620 관악구 NaN 11620 129 POLYGON ((949438.997 1944127.713, 949456.647 1...
city_districts.plot()
<Axes: >
../../_images/b507dfef786aa84a6abc2c3f2a84274a78a5bf1bbd6fb8b865bc1a003af0f070.png
city_districts.crs
<Projected CRS: EPSG:5179>
Name: Korea 2000 / Unified CS
Axis Info [cartesian]:
- X[north]: Northing (metre)
- Y[east]: Easting (metre)
Area of Use:
- name: Republic of Korea (South Korea) - onshore and offshore.
- bounds: (122.71, 28.6, 134.28, 40.27)
Coordinate Operation:
- name: Korea Unified Belt
- method: Transverse Mercator
Datum: Geocentric datum of Korea
- Ellipsoid: GRS 1980
- Prime Meridian: Greenwich

 서울특별시 은평구 내에 있는 지점을 찾고자 합니다. 이 구역에 대한 별도의 데이터셋를 얻고, 주소 데이터를 로드하고, 은평구와 모든 지점을 하나의 지도에 표시하는 다중 레이어 맵을 시각화 해보겠습니다.

ep_district = city_districts[city_districts.SGG_NM == "은평구"]
ep_district
ADM_SECT_C SGG_NM SGG_OID COL_ADM_SE GID geometry
13 11380 은평구 NaN 11380 138 POLYGON ((951637.438 1961851.784, 951649.915 1...
addresses = gpd.read_file(DATA_DIRECTORY / "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)
addresses.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
addresses = addresses.to_crs({"init": "EPSG:5179"})
C:\Users\BEGAS_15\PycharmProjects\geospatial_analysis\venv\lib\site-packages\pyproj\crs\crs.py:141: FutureWarning: '+init=<authority>:<code>' syntax is deprecated. '<authority>:<code>' is the preferred initialization method. When making the change, be mindful of axis order changes: https://pyproj4.github.io/pyproj/stable/gotchas.html#axis-order-changes-in-proj-6
  in_crs_string = _prepare_from_proj_string(in_crs_string)
axes = city_districts.plot(facecolor="grey")
ep_district.plot(ax=axes, facecolor="red")
addresses.plot(ax=axes, color="blue", markersize=5)
<Axes: >
../../_images/92b0e8e0bc92289fc27f27eb261992aee0492c27ec03ecdfbe3bfc1fd07b4d8b.png

 어떤 지점은 은평구 내에 있지만 그렇지 않은 지점들도 있습니다. 은평구 내부에 있는 항목을 찾기 위해, 이번에는 geopandas.GeoDataFrame에 대해 PIP 쿼리를 사용해 보겠습니다.

in_ep_district = addresses.within(ep_district.at[13, "geometry"])
in_ep_district
0     False
1     False
2     False
3     False
4     False
5     False
6     False
7     False
8     False
9     False
10    False
11    False
12    False
13    False
14    False
15    False
16     True
17    False
18    False
19    False
20    False
21    False
22    False
23    False
24    False
25    False
26    False
27    False
dtype: bool

 마스크 배열(mask array)이라고도 하는 이 Boolean 리스트를 사용하여 주소 데이터프레임 addresses를 필터링할 수 있습니다.

addresses_in_ep_district = addresses[in_ep_district]
addresses_in_ep_district
address 지점 지점주소 geometry
16 서울특별시 소방학교, 통일로, 03312, 통일로, 진관동, 대한민국 416 서울특별시 은평구 진관동 산26 POINT (948102.071 1959409.520)

 마지막으로, 위 주소 목록을 시각화 해 해당 주소가 실제로 은평구 내에 있는지 시각적으로 확인하겠습니다.

axes = city_districts.plot(facecolor="grey")
ep_district.plot(ax=axes, facecolor="red")

addresses_in_ep_district.plot(ax=axes, color="b", markersize=50, marker="*")
<Axes: >
../../_images/84e788b4ebbb94eb1704f3db3a40266a43e7aa451f01ab4b685e7627aa282dbc.png

 완벽합니다! 실제로 서울 은평구(빨간색 다각형) 구역 안에 있는 해당 주소를 표시한 파란색 점만 남았습니다.