Distancia geográfica/geoespacial entre 2 listas de puntos de latitud/longitud (coordenadas)
Tengo 2 listas ( list1
, list2
) con latitud/longitud de varias ubicaciones. Una lista ( list2
) tiene nombres de localidades que list1
no tiene.
También quiero una localidad aproximada para cada punto de la lista1. Entonces quiero tomar un punto en list1
, intentar buscar el punto más cercano list2
y tomar esa localidad. Repito para cada punto en list1
. También quiere la distancia (en metros) y el índice del punto (en list1
), para poder crear algunas reglas comerciales a su alrededor; esencialmente, estas son 2 nuevas columnas que deben agregarse a list1
( near_dist
, indx
).
Estoy usando la gdist
función, pero no puedo hacer que funcione con entradas de marco de datos.
Listas de entrada de ejemplo:
list1 <- data.frame(longitude = c(80.15998, 72.89125, 77.65032, 77.60599,
72.88120, 76.65460, 72.88232, 77.49186,
72.82228, 72.88871),
latitude = c(12.90524, 19.08120, 12.97238, 12.90927,
19.08225, 12.81447, 19.08241, 13.00984,
18.99347, 19.07990))
list2 <- data.frame(longitude = c(72.89537, 77.65094, 73.95325, 72.96746,
77.65058, 77.66715, 77.64214, 77.58415,
77.76180, 76.65460),
latitude = c(19.07726, 13.03902, 18.50330, 19.16764,
12.90871, 13.01693, 13.00954, 12.92079,
13.02212, 12.81447),
locality = c("A", "A", "B", "B", "C", "C", "C", "D", "D", "E"))
Para calcular la distancia geográfica entre dos puntos con coordenadas de latitud/longitud, puede utilizar varias fórmulas. El paquete geosphere
tiene distCosine
, distHaversine
y distVincentySphere
para distVincentyEllipsoid
calcular la distancia. De estos, el distVincentyEllipsoid
se considera el más preciso, pero es computacionalmente más intensivo que los demás.
Con una de estas funciones, puedes hacer una matriz de distancias. Con base en esa matriz, puede asignar locality
nombres según la distancia más corta which.min
y la distancia correspondiente min
(consulte la última parte de la respuesta) de esta manera:
library(geosphere)
# create distance matrix
mat <- distm(list1[,c('longitude','latitude')], list2[,c('longitude','latitude')], fun=distVincentyEllipsoid)
# assign the name to the point in list1 based on shortest distance in the matrix
list1$locality <- list2$locality[max.col(-mat)]
esto da:
> list1 longitude latitude locality 1 80.15998 12.90524 D 2 72.89125 19.08120 A 3 77.65032 12.97238 C 4 77.60599 12.90927 D 5 72.88120 19.08225 A 6 76.65460 12.81447 E 7 72.88232 19.08241 A 8 77.49186 13.00984 D 9 72.82228 18.99347 A 10 72.88871 19.07990 A
Otra posibilidad es asignar el locality
basado en los valores promedio de longitud y latitud de la locality
s en list2
:
library(dplyr)
list2a <- list2 %>% group_by(locality) %>% summarise_each(funs(mean)) %>% ungroup()
mat2 <- distm(list1[,c('longitude','latitude')], list2a[,c('longitude','latitude')], fun=distVincentyEllipsoid)
list1 <- list1 %>% mutate(locality2 = list2a$locality[max.col(-mat2)])
o con data.table
:
library(data.table)
list2a <- setDT(list2)[,lapply(.SD, mean), by=locality]
mat2 <- distm(setDT(list1)[,.(longitude,latitude)], list2a[,.(longitude,latitude)], fun=distVincentyEllipsoid)
list1[, locality2 := list2a$locality[max.col(-mat2)] ]
esto da:
> list1 longitude latitude locality locality2 1 80.15998 12.90524 D D 2 72.89125 19.08120 A B 3 77.65032 12.97238 C C 4 77.60599 12.90927 D C 5 72.88120 19.08225 A B 6 76.65460 12.81447 E E 7 72.88232 19.08241 A B 8 77.49186 13.00984 D C 9 72.82228 18.99347 A B 10 72.88871 19.07990 A B
Como puedes ver, esto lleva en la mayoría de ocasiones (7 de cada 10) a otro asignado locality
.
Puedes sumar la distancia con:
list1$near_dist <- apply(mat2, 1, min)
u otro enfoque con max.col
(que es muy probable más rápido):
list1$near_dist <- mat2[matrix(c(1:10, max.col(-mat2)), ncol = 2)]
# or using dplyr
list1 <- list1 %>% mutate(near_dist = mat2[matrix(c(1:10, max.col(-mat2)), ncol = 2)])
# or using data.table (if not already a data.table, convert it with 'setDT(list1)' )
list1[, near_dist := mat2[matrix(c(1:10, max.col(-mat2)), ncol = 2)] ]
el resultado:
> list1 longitude latitude locality locality2 near_dist 1: 80.15998 12.90524 D D 269966.8970 2: 72.89125 19.08120 A B 65820.2047 3: 77.65032 12.97238 C C 739.1885 4: 77.60599 12.90927 D C 9209.8165 5: 72.88120 19.08225 A B 66832.7223 6: 76.65460 12.81447 E E 0.0000 7: 72.88232 19.08241 A B 66732.3127 8: 77.49186 13.00984 D C 17855.3083 9: 72.82228 18.99347 A B 69456.3382 10: 72.88871 19.07990 A B 66004.9900
Créditos a Martin Haringa por esta solución para hacer que este camino sea más fácil cuando necesita que esta función se realice atravesando un marco de datos en el blog de Mark Needham.
library(dplyr)
library(geosphere)
df %>%
rowwise() %>%
mutate(newcolumn_distance = distHaversine(c(df$long1, df$lat1),
c(df$long2, df$lat2)))
Probé usando las dos funciones distm y distHaversine por separado en muestras grandes de conjuntos de datos del mundo real, y distHaversine parece funcionar mucho más rápido que la función distm. Me sorprende porque pensé que los dos eran simplemente la misma función en dos formatos.