65 votos

Encontrar el punto más cercano en otro GeoDataFrame utilizando GeoPandas

Tengo dos geodataframes:

import geopandas as gpd
from shapely.geometry import Point

gpd1 = gpd.GeoDataFrame([['John',1,Point(1,1)],['Smith',1,Point(2,2)],['Soap',1,Point(0,2)]],columns=['Name','ID','geometry'])

gpd2 = gpd.GeoDataFrame([['Work',Point(0,1.1)],['Shops',Point(2.5,2)],['Home',Point(1,1.1)]],columns=['Place','geometry'])

y quiero encontrar el nombre del punto más cercano en gpd2 para cada fila de g pd1 :

desired_output = 

    Name  ID     geometry  Nearest
0   John   1  POINT (1 1)     Home
1  Smith   1  POINT (2 2)    Shops
2   Soap   1  POINT (0 2)     Work

He intentado que esto funcione utilizando una función lambda:

gpd1['Nearest'] = gpd1.apply(lambda row: min_dist(row.geometry,gpd2)['Place'] , axis=1)

con

def min_dist(point, gpd2):
    geoseries = some_function(...)
    return geoseries

0 votos

Este método me ha funcionado: stackoverflow.com/questions/37402046/ mira el enlace

52voto

user1889941 Puntos 21

Si tiene marcos de datos grandes, he descubierto que scipy índice espacial cKDTree .query devuelve resultados muy rápidos para las búsquedas de vecinos más cercanos. Como utiliza un índice espacial, es mucho más rápido que recorrer el marco de datos y encontrar el mínimo de todas las distancias. También es más rápido que utilizar el método shapely nearest_points con RTree (el método de índice espacial disponible a través de geopandas) porque cKDTree permite vectorizar la búsqueda mientras que el otro método no.

Aquí hay una función de ayuda que devolverá la distancia y el 'Nombre' del vecino más cercano en gpd2 desde cada punto de gpd1 . Asume que ambos gdfs tienen un geometry columna (de puntos).

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)], ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', Point(0, 1.1)], ['Shops', Point(2.5, 2)],
                         ['Home', Point(1, 1.1)]],
                        columns=['Place', 'geometry'])

def ckdnearest(gdA, gdB):

    nA = np.array(list(gdA.geometry.apply(lambda x: (x.x, x.y))))
    nB = np.array(list(gdB.geometry.apply(lambda x: (x.x, x.y))))
    btree = cKDTree(nB)
    dist, idx = btree.query(nA, k=1)
    gdB_nearest = gdB.iloc[idx].drop(columns="geometry").reset_index(drop=True)
    gdf = pd.concat(
        [
            gdA.reset_index(drop=True),
            gdB_nearest,
            pd.Series(dist, name='dist')
        ], 
        axis=1)

    return gdf

ckdnearest(gpd1, gpd2)

Y si quieres encontrar el punto más cercano a un LineString, aquí tienes un ejemplo completo que funciona:

import itertools
from operator import itemgetter

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point, LineString

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)],
                         ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', LineString([Point(100, 0), Point(100, 1)])],
                         ['Shops', LineString([Point(101, 0), Point(101, 1), Point(102, 3)])],
                         ['Home',  LineString([Point(101, 0), Point(102, 1)])]],
                        columns=['Place', 'geometry'])

def ckdnearest(gdfA, gdfB, gdfB_cols=['Place']):
    A = np.concatenate(
        [np.array(geom.coords) for geom in gdfA.geometry.to_list()])
    B = [np.array(geom.coords) for geom in gdfB.geometry.to_list()]
    B_ix = tuple(itertools.chain.from_iterable(
        [itertools.repeat(i, x) for i, x in enumerate(list(map(len, B)))]))
    B = np.concatenate(B)
    ckd_tree = cKDTree(B)
    dist, idx = ckd_tree.query(A, k=1)
    idx = itemgetter(*idx)(B_ix)
    gdf = pd.concat(
        [gdfA, gdfB.loc[idx, gdfB_cols].reset_index(drop=True),
         pd.Series(dist, name='dist')], axis=1)
    return gdf

c = ckdnearest(gpd1, gpd2)

1 votos

¿Es posible dar también el punto más cercano de la recta utilizando este método? Por ejemplo, para ajustar una ubicación GPS a la calle más cercana.

1 votos

Esta respuesta es asombrosa. Sin embargo, el código para los puntos más cercanos a la línea me produce un error. Parece que la distancia correcta desde la línea más cercana se devuelve para cada punto, pero el id de línea que se devuelve es incorrecto. Creo que es el cálculo idx, pero soy bastante nuevo en Python, así que no puedo llegar a envolver mi cabeza alrededor de ella.

0 votos

¿Qué es el gdfB_cols ¿parámetro para? ¿Seleccionará las columnas de LineString a concatenar con el Punto? RecursionError: maximum recursion depth exceeded while calling a Python object y import sys; sys.setrecursionlimit(10000) estrellándose. ¿Hay algo para optimizar, mejorar en el punto más cercano de Point a LineString?

31voto

GreyCat Puntos 146

Puede utilizar directamente la función Shapely Puntos más cercanos (las geometrías de las GeoSeries son geometrías Shapely):

from shapely.ops import nearest_points
# unary union of the gpd2 geomtries 
pts3 = gpd2.geometry.unary_union
def near(point, pts=pts3):
     # find the nearest point and return the corresponding Place value
     nearest = gpd2.geometry == nearest_points(point, pts)[1]
     return gpd2[nearest].Place.get_values()[0]
gpd1['Nearest'] = gpd1.apply(lambda row: near(row.geometry), axis=1)
gpd1
    Name  ID     geometry  Nearest
0   John   1  POINT (1 1)     Home
1  Smith   1  POINT (2 2)    Shops
2   Soap   1  POINT (0 2)     Work

Explicación

for i, row in gpd1.iterrows():
    print nearest_points(row.geometry, pts3)[0], nearest_points(row.geometry, pts3)[1]
 POINT (1 1) POINT (1 1.1)
 POINT (2 2) POINT (2.5 2)
 POINT (0 2) POINT (0 1.1)

0 votos

Hay algo que no me funciona y no consigo averiguarlo. La función devuelve un GeoSeries vacío aunque la geometría sea sólida. Por ejemplo: sample_point = gpd2.geometry.unary_union[400] / sample_point in gpd2.geometry Devuelve True. gpd2.geometry == sample_point Esto sale todo Falso.

1 votos

Además de lo anterior: gpd2.geometry.geom_equals(sample_point) funciona.

9voto

Steve Robbins Puntos 285

Lo he descubierto:

def min_dist(point, gpd2):
    gpd2['Dist'] = gpd2.apply(lambda row:  point.distance(row.geometry),axis=1)
    geoseries = gpd2.iloc[gpd2['Dist'].argmin()]
    return geoseries

Por supuesto, algunas críticas son bienvenidas. No soy partidario de recalcular gpd2['Dist'] para cada fila de gpd1...

6voto

tapkin Puntos 132

A partir de la v0.10.0 geopandas soporta sjoin_nearest de forma nativa - ver aquí

0 votos

Tiene muy buena pinta. No puedo creer que esta pregunta todavía recibe visitas sin embargo.

4voto

fest13er Puntos 21

Para quien tenga errores de indexación con sus propios datos al utilizar el excelente respuesta de @ JHuw mi problema era que mis índices no se alineaban. Reajustando el índice de gdfA y gdfB resolvió mis problemas, tal vez esto puede ayudar a usted también @ Shakedk .

import itertools
from operator import itemgetter

import geopandas as gpd
import numpy as np
import pandas as pd

from scipy.spatial import cKDTree
from shapely.geometry import Point, LineString

gpd1 = gpd.GeoDataFrame([['John', 1, Point(1, 1)],
                         ['Smith', 1, Point(2, 2)],
                         ['Soap', 1, Point(0, 2)]],
                        columns=['Name', 'ID', 'geometry'])
gpd2 = gpd.GeoDataFrame([['Work', LineString([Point(100, 0), Point(100, 1)])],
                         ['Shops', LineString([Point(101, 0), Point(101, 1), Point(102, 3)])],
                         ['Home',  LineString([Point(101, 0), Point(102, 1)])]],
                        columns=['Place', 'geometry'])

def ckdnearest(gdfA, gdfB, gdfB_cols=['Place']):
    # resetting the index of gdfA and gdfB here.
    gdfA = gdfA.reset_index(drop=True)
    gdfB = gdfB.reset_index(drop=True)
    A = np.concatenate(
        [np.array(geom.coords) for geom in gdfA.geometry.to_list()])
    B = [np.array(geom.coords) for geom in gdfB.geometry.to_list()]
    B_ix = tuple(itertools.chain.from_iterable(
        [itertools.repeat(i, x) for i, x in enumerate(list(map(len, B)))]))
    B = np.concatenate(B)
    ckd_tree = cKDTree(B)
    dist, idx = ckd_tree.query(A, k=1)
    idx = itemgetter(*idx)(B_ix)
    gdf = pd.concat(
        [gdfA, gdfB.loc[idx, gdfB_cols].reset_index(drop=True),
         pd.Series(dist, name='dist')], axis=1)
    return gdf

c = ckdnearest(gpd1, gpd2)

0 votos

En caso de comparar la distancia entre dos pares de lat y long ¿cómo leer la distancia?

0 votos

Puede utilizar GeoSeries.distance() para obtener la distancia entre dos puntos. Véase geopandas.org/docs/reference/api/

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X