使用 Geopandas 计算到最近要素的距离

Calculate Distance to Nearest Feature with Geopandas

我正在寻找使用 Geopandas / Shapely 做 ArcPy Generate Near Table 的等价物。我对 Geopandas 和 Shapely 还很陌生,并且开发了一种有效的方法,但我想知道是否有更有效的方法。

我有两个点文件数据集 - 人口普查块质心和餐馆。我正在寻找,对于每个人口普查区质心,到它最近的餐馆的距离。对于多个街区最近的同一家餐厅没有限制。

这对我来说变得有点复杂的原因是因为 Geopandas Distance function 计算元素,基于索引进行匹配。因此,我一般的方法是将Restaurants文件变成一个多点文件,然后将blocks文件的索引设置为相同的值。那么所有的块质心和餐厅都有相同的索引值。

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

现在读入 Block Centroid 和 Restaurant Shapefiles:

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(与 Restaurants 多点的值相同)作为元素计算的解决方法。

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

最后,我使用 Geopandas 距离函数计算每个 Block 质心到最近餐厅的距离。

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

请就如何改进这方面的任何方面提出任何建议。我不依赖于使用 Geopandas 或 Shapely,但我希望学习 ArcPy 的替代方法。

感谢您的帮助!

如果我对您的问题的理解正确,街区和餐厅的维度可能会有很大不同。出于这个原因,尝试通过重新索引强制进入 table 格式可能是一种糟糕的方法。

我会遍历街区并获得到餐馆的最小距离(就像@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()

现在获取点到线的距离,只保存每个点的最小距离(有apply的版本见下文)

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 中的更改似乎需要在最后一个代码块中进行不同的输入,利用 args 中的参数 .apply():

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

您的代码缺少一个细节,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

我将使用两个不同维度的 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 列。