GeoPandas Distanzoptimierung
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
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.
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