簡體   English   中英

使用 Geopandas 計算到最近特征的距離

[英]Calculate Distance to Nearest Feature with Geopandas

我正在尋找使用 Geopandas / Shapely 做相當於 ArcPy Generate Near Table 的工作 我對 Geopandas 和 Shapely 很陌生,並且開發了一種有效的方法,但我想知道是否有更有效的方法。

我有兩個點文件數據集 - 人口普查塊質心和餐館。 我正在尋找,對於每個人口普查區塊質心,到它最近的餐廳的距離。 同一餐廳是多個街區最近的餐廳沒有限制。

這對我來說變得有點復雜的原因是因為Geopandas 距離函數根據索引計算元素匹配。 因此,我的一般方法是將餐廳文件變成多點文件,然后將塊文件的索引設置為相同的值。 然后所有的塊質心和餐館都具有相同的索引值。

import pandas as pd
import geopandas as gpd
from shapely.geometry import Polygon, Point, MultiPoint

現在閱讀塊質心和餐廳形狀文件:

Blocks=gpd.read_file(BlockShp)
Restaurants=gpd.read_file(RestaurantShp)

由於 Geopandas 距離函數按元素計算距離,因此我將 Restaurant GeoSeries 轉換為 MultiPoint GeoSeries:

RestMulti=gpd.GeoSeries(Restaurants.unary_union)
RestMulti.crs=Restaurants.crs
RestMulti.reset_index(drop=True)

然后我將 Blocks 的索引設置為等於 0(與餐館多點的值相同)作為元素計算的解決方法。

Blocks.index=[0]*len(Blocks)

最后,我使用 Geopandas 距離函數來計算每個 Block 質心到最近餐廳的距離。

Blocks['Distance']=Blocks.distance(RestMulti)

請就如何改進這方面的任何方面提出任何建議。 我與使用 Geopandas 或 Shapely 無關,但我正在尋找 ArcPy 的替代方案。

謝謝您的幫助!

如果我正確理解您的問題,街區和餐廳可以有非常不同的維度。 出於這個原因,嘗試通過重新索引來強制轉換為表格格式可能是一種不好的方法。

我只會遍歷街區並獲得到餐館的最小距離(正如@shongololo 所建議的那樣)。

我會稍微通用一點(因為我已經寫下了這段代碼)並且從點到線做一段距離,但是相同的代碼應該從點到點或從多邊形到多邊形工作。 我將從點的GeoDataFrame開始,然后創建一個與線的距離最小的新列。

%matplotlib inline
import matplotlib.pyplot as plt
import shapely.geometry as geom
import numpy as np
import pandas as pd
import geopandas as gpd

lines = gpd.GeoSeries(
    [geom.LineString(((1.4, 3), (0, 0))),
        geom.LineString(((1.1, 2.), (0.1, 0.4))),
        geom.LineString(((-0.1, 3.), (1, 2.)))])

# 10 points
n  = 10
points = gpd.GeoSeries([geom.Point(x, y) for x, y in np.random.uniform(0, 3, (n, 2))])

# Put the points in a dataframe, with some other random column
df_points = gpd.GeoDataFrame(np.array([points, np.random.randn(n)]).T)
df_points.columns = ['Geometry', 'Property1']

points.plot()
lines.plot()

在此處輸入圖片說明

現在獲取點到線的距離,並只保存每個點的最小距離(請參閱下面的應用版本)

min_dist = np.empty(n)
for i, point in enumerate(points):
    min_dist[i] = np.min([point.distance(line) for line in lines])
df_points['min_dist_to_lines'] = min_dist
df_points.head(3)

這使

    Geometry                                       Property1    min_dist_to_lines
0   POINT (0.2479424516236574 2.944916965334865)    2.621823    0.193293
1   POINT (1.465768457667432 2.605673714922998)     0.6074484   0.226353
2   POINT (2.831645235202689 1.125073838462032)     0.657191    1.940127

- - 編輯 - -

(取自 github 問題)使用apply更好,更符合您在pandas做法:

def min_distance(point, lines):
    return lines.distance(point).min()

df_points['min_dist_to_lines'] = df_points.geometry.apply(min_distance, df_lines)

編輯:至少從 2019-10-04 開始,pandas 的變化似乎需要在最后一個代碼塊中使用不同的輸入,利用.apply()中的args參數:

df_points['min_dist_to_lines'] = df_points.geometry.apply(min_distance, args=(df_lines,))

我將在具有不同維度的 geopandas 中使用兩個示例數據集來演示。

import geopandas as gpd

# read geodata for five nyc boroughs
gdf_nyc = gpd.read_file(gpd.datasets.get_path('nybb'))
# read geodata for international cities
gdf_cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))

# convert to a meter projection
gdf_nyc.to_crs(epsg=3857, inplace=True)
gdf_cities.to_crs(epsg=3857, inplace=True)

我們可以簡單地將 lambda 函數應用於 GeoSeries。 例如,如果我們想要獲得每個紐約市行政區(多邊形)與其最近的國際城市(點)之間的最小距離。 我們可以執行以下操作:

gdf_nyc.geometry.apply(lambda x: gdf_cities.distance(x).min())

這會給我們

0    384422.953323
1    416185.725507
2    412520.308816
3    419511.323677
4    440292.945096
Name: geometry, dtype: float64

同樣,如果我們想要每個國際城市與其最近的紐約市行政區之間的最小距離。 我們可以執行以下操作:

gdf_cities.geometry.apply(lambda x: gdf_nyc.distance(x).min())

這會給我們

0      9.592104e+06
1      9.601345e+06
2      9.316354e+06
3      8.996945e+06
4      2.614927e+07
           ...     
197    1.177410e+07
198    2.377188e+07
199    8.559704e+06
200    8.902146e+06
201    2.034579e+07
Name: geometry, Length: 202, dtype: float64

筆記:

  1. 在計算距離之前,將您的 GeoDataFrame 轉換為笛卡爾投影。 在示例中,我使用了epsg:3857 ,因此距離將以米為單位。 如果您使用橢球(基於 lon/lat 的)投影,結果將是度數。 先轉換您的投影,然后再進行其他操作,例如獲取多邊形的質心。
  2. 兩點之間只有一個距離。 當您想要獲得點和線之間的距離時, .distance()方法返回的最小距離將是有意義的。 換句話說, .distance()方法可以計算任何兩個地理對象之間的距離。
  3. 當 GeoDataFrame 中有多個geometry列時,請確保將 lambda 函數應用於所需的 GeoSeries,並從所需的 GeoSeries 調用.distance()方法。 在示例中,我直接從 GeoDataFrame 調用該方法,因為它們都只有一個 GeoSeries 列。

您的代碼缺少細節, args = (df_lines)

def min_distance(point, lines):
    return lines.distance(point).min()

df_points['min_dist_to_lines'] = df_points.geometry.apply(min_distance, args=(df_lines,))# Notice the change to this line

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM