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를 수행하는 두 가지 방법이 있습니다:
점이 다각형 내부에 있는지 확인하는 within() 함수를 사용
다각형이 점을 포함하고 있는지 확인하는 contains() 함수를 사용
Note
여기에서는 PIP 쿼리에 대해서만 다루고 있지만, Shapely의 LineString 또는 다각형(Polygon) 개체가 다른 다각형(Polygon) 내부에 있는지 확인하는 것도 동일한 방법으로 가능합니다.
먼저 두 개의 점 개체 point1
와 point2
를 만들어줍니다. 그리고 다각형 개체 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: >
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: >
어떤 지점은 은평구 내에 있지만 그렇지 않은 지점들도 있습니다. 은평구 내부에 있는 항목을 찾기 위해, 이번에는 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: >
완벽합니다! 실제로 서울 은평구(빨간색 다각형) 구역 안에 있는 해당 주소를 표시한 파란색 점만 남았습니다.