Optimasi jarak GeoPandas

Oct 15 2020

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

3 martinfleis Oct 15 2020 at 17:07

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.

2 gene Oct 15 2020 at 15:22

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