GeoPandas: Diğer veri çerçevesindeki en yakın noktayı bulun


20

2 coğrafi veri çerçevem ​​var:

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

ve ben gpd1 her satır için gpd2 en yakın noktanın adını bulmak istiyorum:

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

Ben lambda fonksiyonunu kullanarak bu çalışma almaya çalışıyorum:

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

ile

def min_dist(point, gpd2):

    geoseries = some_function()
    return geoseries

Bu yöntem benim için çalıştı: stackoverflow.com/questions/37402046/… bağlantıya bakın
Johnny Cheesecutter

Yanıtlar:


16

Doğrudan Shapely işlevini kullanabilirsiniz En yakın noktalar (GeoSeries geometrileri Shapely geometrileridir):

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

açıklama

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)

Bir şey benim için çalışmıyor ve çözemiyorum. Geometri sağlam olmasına rağmen işlev boş bir GeoSeries döndürür. Örneğin: sample_point = gpd2.geometry.unary_union[400] / sample_point in gpd2.geometry Bu True değerini döndürür. gpd2.geometry == sample_point Bu tamamen yanlış çıkıyor.
robroc

Yukarıdakilere ek olarak: gpd2.geometry.geom_equals(sample_point)çalışır.
robroc

13

Büyük veri kareleriniz varsa, scipycKDTree uzamsal dizin .queryyönteminin en yakın komşu aramalar için çok hızlı sonuçlar verdiğini fark ettim . Bir uzamsal indeks kullandığı için, veri karesinde döngüden daha hızlıdır ve daha sonra tüm mesafelerin minimumunu bulur. Aynı zamanda nearest_pointsRTree (jeopandalar aracılığıyla sağlanan uzamsal dizin yöntemi) ile düzgün kullanmaktan daha hızlıdır, çünkü cKDTree aramanızı vektörleştirmenize izin verirken diğer yöntem değildir.

İşte gpd2her noktadan en yakın komşunun mesafesini ve 'Adını' döndürecek bir yardımcı işlev gpd1. Her iki gdfs'nin de bir geometrysütun (nokta) olduğunu varsayar .

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)

Ve bir LineString'e en yakın noktayı bulmak istiyorsanız, işte tam bir örnek:

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)

Bu yöntemi kullanarak hatta en yakın noktayı vermek mümkün müdür? Örneğin, en yakın sokağa bir GPS konumu yapıştırmak için.
hyperknot

Bu cevap inanılmaz! Ancak, çizgiye en yakın noktaların kodu benim için bir hata üretir. Her nokta için en yakın çizgiden doğru mesafe döndürülmüş gibi görünüyor, ancak döndürülen satır kimliği yanlış. Bence bu idx hesaplaması, ama Python için oldukça yeniyim, bu yüzden başımı etrafına sarmayı başaramıyorum.
Shakedk

1

Anladım:

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

Tabii bazı eleştiriler memnuniyetle karşılanacaktır. Ben gpd1 her satır için gpd2 ['Dist'] yeniden hesaplama hayranı değilim ...


1

Gene'nin yanıtı benim için işe yaramadı. Son olarak, gpd2.geometry.unary_union'un toplam kabaca yaklaşık 150.000 puanımın yaklaşık 30.000'ini içeren bir geometriyle sonuçlandığını keşfettim. Aynı problemle karşılaşan herkes için, bunu nasıl çözdüm:

    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 mükemmel cevap kullanırken kendi verileri ile indeksleme hataları olan herkes için , benim sorunum benim dizinleri hizalanmış değildi. GdfA ve gdfB dizinini sıfırlamak sorunlarımı çözdü, belki de bu size yardımcı olabilir @ 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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.