GeoPandas: ค้นหาจุดที่ใกล้ที่สุดในดาต้าเฟรมอื่น


20

ฉันมี 2 geodataframes:

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'])

และฉันต้องการค้นหาชื่อของจุดที่ใกล้ที่สุดใน gpd2 สำหรับแต่ละแถวใน gpd1:

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

ฉันพยายามทำให้การทำงานนี้ใช้ฟังก์ชัน lambda:

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

กับ

def min_dist(point, gpd2):

    geoseries = some_function()
    return geoseries

วิธีนี้ใช้ได้ผลกับฉัน: stackoverflow.com/questions/37402046/…ดูที่ลิงก์
Johnny Cheesecutter

คำตอบ:


16

คุณสามารถใช้ฟังก์ชันShapely ได้โดยตรงจุดที่ใกล้ที่สุด (รูปทรงเรขาคณิตของ GeoSeries คือรูปทรงเรขาคณิตของหุ่นดี):

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

หากคุณมีไฟล์ข้อมูลขนาดใหญ่ฉันพบว่าวิธีscipyดัชนีอวกาศของ cKDTree .queryส่งคืนผลลัพธ์ที่รวดเร็วมากสำหรับการค้นหาเพื่อนบ้านที่ใกล้ที่สุด เนื่องจากมันใช้ดัชนีเชิงพื้นที่คำสั่งของขนาดจะเร็วกว่าวนลูปแม้ว่าดาต้าเฟรมแล้วหาระยะทางขั้นต่ำทั้งหมด นอกจากนี้ยังเร็วกว่าการใช้หุ่นดีnearest_pointsด้วย RTree (วิธีดัชนีเชิงพื้นที่ที่มีให้ผ่าน geopandas) เพราะ cKDTree ช่วยให้คุณสามารถปรับการค้นหาของคุณให้เป็นเวกเตอร์ได้ในขณะที่วิธีอื่นไม่มี

นี่คือฟังก์ชั่นผู้ช่วยที่จะกลับระยะทางและ 'ชื่อ' ของเพื่อนบ้านที่ใกล้ที่สุดในจากแต่ละจุดในgpd2 gpd1มันถือว่าทั้ง gdfs มี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

คำตอบนี้วิเศษมาก! อย่างไรก็ตามรหัสสำหรับจุดที่ใกล้ที่สุดในบรรทัดสร้างข้อผิดพลาดสำหรับฉัน ดูเหมือนว่าระยะห่างที่ถูกต้องจากเส้นที่ใกล้ที่สุดนั้นจะถูกส่งคืนสำหรับแต่ละจุด แต่รหัสบรรทัดที่ส่งคืนนั้นผิด ฉันคิดว่ามันคือการคำนวณ idx แต่ฉันค่อนข้างใหม่กับ Python ดังนั้นฉันจึงไม่สามารถห่อหัวของฉันรอบ ๆ มันได้
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

แน่นอนว่าการวิจารณ์ก็ยินดีต้อนรับ ฉันไม่ใช่แฟนของการคำนวณใหม่ gpd2 ['Dist'] สำหรับทุก ๆ แถวของ gpd1 ...


1

คำตอบของยีนไม่ได้ผลสำหรับฉัน ในที่สุดฉันก็ค้นพบว่า gpd2.geometry.unary_union ส่งผลให้มีรูปทรงเรขาคณิตที่มีเพียงประมาณ 30,000 คะแนนจากทั้งหมดของฉันประมาณ 150.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.