GeoPandas Distanzoptimierung

Oct 15 2020

Ich habe die folgende Funktion erstellt, die den Abstand von einem Punkt in einer GDF zu allen Punkten in einer anderen GDF misst und eine Tabelle mit dem kürzesten Abstand für jeden Punkt zurückgibt. Es funktioniert gut für einen Punkt, aber ich habe die Tatsache vernachlässigt, dass ich eine Tabelle mit 4000 Punkten habe und es dauert 10 Minuten. Ich habe es in PostGIS ausgeführt und kann es auf weniger als eine Sekunde reduzieren. Gibt es eine Möglichkeit, dies in Python zu tun, die der PostGIS-Geschwindigkeit entspricht?

def get_distance_to(gdf_in, aoi_df, aoi):
   dist_df_list = list()
   for row in range(len(gdf_in)):
      single_row = gdf_in.iloc[row]
      distances = aoi_df.geometry.distance(single_row.geometry)
      dist_list = distances.to_list()
      closest_aoi = min(dist_list)
      single_row["dist_to_"+aoi] = closest_aoi
      df = single_row.to_frame().T
      dist_df_list.append(df)
completed_distances = pd.concat(dist_df_list, ignore_index=True, sort=False)
return completed_distances

Meine Eingabetabellen sehen ungefähr so ​​aus

und die Ausgabetabelle sieht so aus

Antworten

3 martinfleis Oct 15 2020 at 17:07

Für jede räumliche Operation dieser Art sollten Sie immer versuchen, einen räumlichen Index zu verwenden. Wenn Sie nur an der minimalen Entfernung interessiert sind, sollten Sie im Folgenden eine relativ performante Option erhalten.

import geopandas as gpd
from shapely.geometry import Point
import pandas as pd
import random

gdf = gpd.GeoDataFrame(geometry=[Point(random.randint(0, 1000), random.randint(0, 1000)) for _ in range(1000)])
gdf2 = gpd.GeoDataFrame(geometry=[Point(random.randint(0, 1000), random.randint(0, 1000)) for _ in range(1000)])

def get_nearest_distance(left, right, initial_buffer):
    """get distance from left to right"""
    buffered = left.buffer(initial_buffer)

    distances = []
    for i in range(len(buffered)):
        geom = buffered.geometry.iloc[i]
        query = right.sindex.query(geom)
        while query.size == 0:
            query = right.sindex.query(geom.buffer(b))
            b += initial_buffer
        distances.append(right.iloc[query].distance(left.geometry.iloc[i]).min())

    return pd.Series(distances, index=left.index)

gdf['distance_to_x'] = get_nearest_distance(gdf, gdf2, 50)

Für 1000 bis 1000 Punkte ist es weniger als eine Sekunde, verglichen mit ungefähr einer Minute, die der Code des @ Gens benötigt.

Um es effizient zu machen, sollten Sie die initial_bufferEntfernung erraten, in der sich Ihrer Meinung nach nur wenige Punkte befinden. Wenn keiner vorhanden ist, wird der Puffer erweitert, bis er auf einige trifft.

Wenn Sie die beste Leistung von GeoPandas erzielen möchten, sollten Sie im Allgemeinen die neueste Version (für diesen Code ist 0,8 erforderlich) und optionale Abhängigkeitspygeos (https://geopandas.readthedocs.io/en/latest/getting_started/install.html#using-the-optional-pygeos-dependency), was den obigen Code um eine Größenordnung beschleunigen kann.

2 gene Oct 15 2020 at 15:22

Iterate über Zeilen in a (Geo) Datenrahmen in (Geo) Pandas ist sehr langsam, sieht für die Iterieren Optimalen Ansatz über einen Datenrahmen zum Beispiel

Die Iteration in Pandas ist ein Anti-Muster und sollte nur durchgeführt werden, wenn Sie alle anderen Optionen ausgeschöpft haben. ( So durchlaufen Sie Zeilen in einem DataFrame in Pandas )

Sie können versuchen, den nächsten Punkt wie in GeoPandas zu verwenden (Geo)DataFrame.apply()und zu formen: Finden Sie den nächsten Punkt in einem anderen Datenrahmen ohne Iteration (siehe Kommentar).for

import geopandas as gpd
from shapely.geometry import Point
from shapely.ops import nearest_points
gpd1 = gpd.read_file("point1.shp") # red points
gpd2 = gpd.read_file("point2.shp") # blue points
pts3 = gpd2.geometry.unary_union
 def near(point, pts=pts3):
     # find the nearest point and return the corresponding value
     nearest = gpd2.geometry == nearest_points(point, pts)[1]
     return gpd2[nearest].id.values[0],gpd2[nearest].geometry.values[0]
 gpd1['Nearest'] = gpd1.apply(lambda row: near(row.geometry)[0], axis=1)
 gpd1['geom2'] = gpd1.apply(lambda row: near(row.geometry)[1], axis=1)
 print(gpd1)
         id         geometry         Nearest        geom2
 0   1   POINT (-0.99013 0.48096)      3     POINT (-0.77574 0.64739)
 1   2   POINT (-1.00987 0.08039)      4     POINT (-0.73060 0.10860)
 2   3   POINT (-0.71932 -0.13117)     5     POINT (-0.57827 -0.08039)
 3   4   POINT (-0.90268 -0.28914)     5     POINT (-0.57827 -0.08039)

Berechnen Sie die Entfernung

gpd1['distance'] = gpd1.apply(lambda row: row.geometry.distance(row.geom2), axis=1)
gpd1.drop('geom2', axis=1, inplace=True)
print(gpd1)
    id         geometry             Nearest     distance
 0   1   POINT (-0.99013 0.48096)      3        0.271406
 1   2   POINT (-1.00987 0.08039)      4        0.280688
 2   3   POINT (-0.71932 -0.13117)     5        0.149905
 3   4   POINT (-0.90268 -0.28914)     5        0.385759