GeoPandas : 다른 데이터 프레임에서 가장 가까운 지점 찾기


20

지리 데이터 프레임이 2 개 있습니다.

import geopandas as gpd
from shapely.geometry import Point
gpd1 = gpd.GeoDataFrame([['John',1,Point(1,1)],['Smith',1,Point(2,2)],['Soap',1,Point(0,2)]],columns=['Name','ID','geometry'])
gpd2 = gpd.GeoDataFrame([['Work',Point(0,1.1)],['Shops',Point(2.5,2)],['Home',Point(1,1.1)]],columns=['Place','geometry'])

gpd1의 각 행에 대해 gpd2에서 가장 가까운 점의 이름을 찾고 싶습니다.

desired_output = 

    Name  ID     geometry  Nearest
0   John   1  POINT (1 1)     Home
1  Smith   1  POINT (2 2)    Shops
2   Soap   1  POINT (0 2)     Work

람다 함수를 사용 하여이 작업을 시도했습니다.

gpd1['Nearest'] = gpd1.apply(lambda row: min_dist(row.geometry,gpd2)['Place'] , axis=1)

def min_dist(point, gpd2):

    geoseries = some_function()
    return geoseries

이 방법은 저에게
효과적

답변:


16

가장 가까운 점 의 Shapely 기능을 직접 사용할 수 있습니다 (GeoSeries의 도형은 Shapely 도형입니다).

from shapely.ops import nearest_points
# unary union of the gpd2 geomtries 
pts3 = gpd2.geometry.unary_union
def near(point, pts=pts3):
     # find the nearest point and return the corresponding Place value
     nearest = gpd2.geometry == nearest_points(point, pts)[1]
     return gpd2[nearest].Place.get_values()[0]
gpd1['Nearest'] = gpd1.apply(lambda row: near(row.geometry), axis=1)
gpd1
    Name  ID     geometry  Nearest
0   John   1  POINT (1 1)     Home
1  Smith   1  POINT (2 2)    Shops
2   Soap   1  POINT (0 2)     Work

설명

for i, row in gpd1.iterrows():
    print nearest_points(row.geometry, pts3)[0], nearest_points(row.geometry, pts3)[1]
 POINT (1 1) POINT (1 1.1)
 POINT (2 2) POINT (2.5 2)
 POINT (0 2) POINT (0 1.1)

무언가가 나를 위해 작동하지 않으며 알아낼 수 없습니다. 지오메트리가 단단하더라도이 함수는 빈 GeoSeries를 반환합니다. 예를 들면 : sample_point = gpd2.geometry.unary_union[400] / sample_point in gpd2.geometry True를 반환합니다. gpd2.geometry == sample_point 이것은 모두 거짓으로 나옵니다.
robroc

위의 추가 : gpd2.geometry.geom_equals(sample_point)작동합니다.
robroc

13

큰 데이터 프레임이있는 경우 scipycKDTree 공간 인덱스 .query방법이 가장 가까운 이웃 검색에 대해 매우 빠른 결과를 반환 한다는 것을 알았습니다 . 공간 인덱스를 사용하기 때문에 데이터 프레임을 루핑 한 다음 모든 거리의 최소값을 찾는 것보다 훨씬 빠릅니다. nearest_pointscKDTree를 사용하면 검색을 벡터화 할 수 있지만 다른 방법은 그렇지 않으므로 RTree (geopandas를 통해 사용 가능한 공간 인덱스 방법)에서 shapey를 사용하는 것보다 빠릅니다 .

다음은의 gpd2각 지점에서 가장 가까운 이웃의 거리와 '이름'을 반환하는 도우미 함수입니다 gpd1. 두 gdf geometry에 포인트 (열) 가 있다고 가정합니다 .

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)], ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', Point(0, 1.1)], ['Shops', Point(2.5, 2)],
                         ['Home', Point(1, 1.1)]],
                        columns=['Place', 'geometry'])

def ckdnearest(gdA, gdB):
    nA = np.array(list(zip(gdA.geometry.x, gdA.geometry.y)) )
    nB = np.array(list(zip(gdB.geometry.x, gdB.geometry.y)) )
    btree = cKDTree(nB)
    dist, idx = btree.query(nA, k=1)
    gdf = pd.concat(
        [gdA, gdB.loc[idx, gdB.columns != 'geometry'].reset_index(),
         pd.Series(dist, name='dist')], axis=1)
    return gdf

ckdnearest(gpd1, gpd2)

그리고 LineString에 가장 가까운 점을 찾으려면 전체 작동 예가 있습니다.

import itertools
from operator import itemgetter

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point, LineString

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)],
                         ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', LineString([Point(100, 0), Point(100, 1)])],
                         ['Shops', LineString([Point(101, 0), Point(101, 1), Point(102, 3)])],
                         ['Home',  LineString([Point(101, 0), Point(102, 1)])]],
                        columns=['Place', 'geometry'])


def ckdnearest(gdfA, gdfB, gdfB_cols=['Place']):
    A = np.concatenate(
        [np.array(geom.coords) for geom in gdfA.geometry.to_list()])
    B = [np.array(geom.coords) for geom in gdfB.geometry.to_list()]
    B_ix = tuple(itertools.chain.from_iterable(
        [itertools.repeat(i, x) for i, x in enumerate(list(map(len, B)))]))
    B = np.concatenate(B)
    ckd_tree = cKDTree(B)
    dist, idx = ckd_tree.query(A, k=1)
    idx = itemgetter(*idx)(B_ix)
    gdf = pd.concat(
        [gdfA, gdfB.loc[idx, gdfB_cols].reset_index(drop=True),
         pd.Series(dist, name='dist')], axis=1)
    return gdf

c = ckdnearest(gpd1, gpd2)

이 방법을 사용하여 선에서 가장 가까운 점을 지정할 수 있습니까? 예를 들어 GPS 위치를 가장 가까운 거리로 스냅합니다.
hyperknot

이 답변은 놀랍습니다! 그러나 가장 가까운 포인트 투 라인 코드는 나를 위해 버그를 생성합니다. 가장 가까운 선으로부터 정확한 거리가 각 점에 대해 반환되는 것처럼 보이지만 반환되는 선 ID는 잘못되었습니다. 나는 그것의 idx 계산을 생각하지만, 나는 파이썬을 처음 접했기 때문에 머리를 감쌀 수는 없다.
Shakedk

1

그것을 알아 냈습니다 :

def min_dist(point, gpd2):
    gpd2['Dist'] = gpd2.apply(lambda row:  point.distance(row.geometry),axis=1)
    geoseries = gpd2.iloc[gpd2['Dist'].argmin()]
    return geoseries

물론 어떤 비판도 환영합니다. 나는 gpd1의 모든 행에 대해 gpd2 [ 'Dist']를 다시 계산하는 팬이 아닙니다 ...


1

Gene의 대답은 저에게 효과적이지 않았습니다. 마지막으로 gpd2.geometry.unary_union은 내 약 150.000 포인트의 약 30.000 만 포함하는 지오메트리를 생성한다는 것을 발견했습니다. 다른 사람이 같은 문제를 겪고 있다면 다음과 같이 해결하십시오.

    from shapely.ops import nearest_points
    from shapely.geometry import MultiPoint

    gpd2_pts_list = gpd2.geometry.tolist()
    gpd2_pts = MultiPoint(gpd2_pts_list)
    def nearest(point, gpd2_pts, gpd2=gpd2, geom_col='geometry', src_col='Place'):
         # find the nearest point
         nearest_point = nearest_points(point, gpd2_pts)[1]
         # return the corresponding value of the src_col of the nearest point
         value = gpd2[gpd2[geom_col] == nearest_point][src_col].get_values()[0]
         return value

    gpd1['Nearest'] = gpd1.apply(lambda x: nearest(x.geometry, gpd2_pts), axis=1)

0

@ JHuw탁월한 답변 을 사용하는 동안 자신의 데이터로 인덱싱 오류가있는 사람 은 내 인덱스가 정렬되지 않았다는 것이 문제였습니다. gdfA 및 gdfB의 색인을 재설정하면 내 문제가 해결 되었으므로 @ Shakedk 도 도움이 될 수 있습니다 .

import itertools
from operator import itemgetter

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point, LineString

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)],
                         ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', LineString([Point(100, 0), Point(100, 1)])],
                         ['Shops', LineString([Point(101, 0), Point(101, 1), Point(102, 3)])],
                         ['Home',  LineString([Point(101, 0), Point(102, 1)])]],
                        columns=['Place', 'geometry'])


def ckdnearest(gdfA, gdfB, gdfB_cols=['Place']):
    # resetting the index of gdfA and gdfB here.
    gdfA = gdfA.reset_index(drop=True)
    gdfB = gdfB.reset_index(drop=True)
    A = np.concatenate(
        [np.array(geom.coords) for geom in gdfA.geometry.to_list()])
    B = [np.array(geom.coords) for geom in gdfB.geometry.to_list()]
    B_ix = tuple(itertools.chain.from_iterable(
        [itertools.repeat(i, x) for i, x in enumerate(list(map(len, B)))]))
    B = np.concatenate(B)
    ckd_tree = cKDTree(B)
    dist, idx = ckd_tree.query(A, k=1)
    idx = itemgetter(*idx)(B_ix)
    gdf = pd.concat(
        [gdfA, gdfB.loc[idx, gdfB_cols].reset_index(drop=True),
         pd.Series(dist, name='dist')], axis=1)
    return gdf

c = ckdnearest(gpd1, gpd2)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.