Optimasi jarak GeoPandas
Saya telah membuat fungsi berikut yang mengukur jarak dari satu titik di GDF ke semua titik di GDF lain dan mengembalikan tabel kembali dengan jarak terpendek untuk setiap titik. Ini bekerja dengan baik untuk satu poin namun saya mengabaikan fakta bahwa saya memiliki tabel 4000 poin dan jadi butuh 10 menit. Saya telah menjalankannya di PostGIS dan dapat menurunkannya menjadi kurang dari satu detik. Apakah ada cara untuk melakukan ini dengan Python yang bisa menyamai kecepatan PostGIS?
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
tabel masukan saya terlihat seperti ini
dan tabel keluaran terlihat seperti ini
Jawaban
Untuk operasi spasial apa pun seperti ini, Anda harus selalu mencoba menggunakan indeks spasial. Jika Anda hanya tertarik pada jarak minimal, berikut ini akan memberi Anda opsi yang relatif berkinerja baik.
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)
Untuk 1000 sampai 1000 poin, itu kurang dari satu detik, dibandingkan dengan sekitar satu menit kode gen @.
Untuk membuatnya efisien, Anda harus menebak initial_bufferjarak yang menurut Anda hanya beberapa poin. Jika tidak ada yang masuk, maka buffer akan diperluas hingga mencapai beberapa.
Secara umum, jika Anda menginginkan kinerja terbaik dari GeoPandas, Anda harus menggunakan versi terbaru (kode ini memerlukan 0.8) dan pygeos dependensi opsional (https://geopandas.readthedocs.io/en/latest/getting_started/install.html#using-the-optional-pygeos-dependency), yang dapat mempercepat kode di atas sesuai urutan besarnya.
Iterasi baris dalam (Geo) DataFrame di (Geo) Pandas sangat lambat, lihat Pendekatan optimal untuk iterasi melalui DataFrame misalnya
Iterasi di Pandas adalah anti-pola dan merupakan sesuatu yang hanya boleh Anda lakukan jika Anda telah menghabiskan setiap opsi lainnya. ( Cara mengulang baris dalam DataFrame di Pandas )
Anda dapat mencoba menggunakan (Geo)DataFrame.apply()dan berbentuk: titik terdekat seperti di GeoPandas: Temukan titik terdekat di kerangka data lain tanpa foriterasi (lihat komentar)
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)
Hitung jarak
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