web-dev-qa-db-fra.com

Distance géographique/géospatiale entre 2 listes de points lat/lon (coordonnées)

J'ai 2 listes (list1, list2) avec latitude/longitudes de divers endroits. Une liste (list2) a des noms de localité que list1 n'a pas. 

Je veux aussi une localité approximative pour chaque point de la liste1. Donc, je veux prendre un point dans list1, essayer de rechercher le point le plus proche dans list2 et prendre cette localité. Je répète pour chaque point dans list1. Il souhaite également connaître la distance (en mètres) et l'index du point (dans list1) afin que je puisse créer des règles commerciales autour de ce point. Il s'agit essentiellement de 2 nouvelles colonnes qui doivent être ajoutées à list1 (near_dist, indx). 

J'utilise la fonction gdist, mais je ne parviens pas à le faire fonctionner avec des entrées de trame de données. 

Exemple de listes d'entrées: 

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"))
29
Gaurav Chaturvedi

Pour calculer la distance géographique entre deux points avec des coordonnées de latitude/longitude, vous pouvez utiliser plusieurs formules. Le package geosphere a les distCosine, distHaversine, distVincentySphere et distVincentyEllipsoid pour le calcul de la distance. Parmi ceux-ci, la distVincentyEllipsoid est considérée comme la plus précise, mais elle est plus intensive en calcul que les autres.

Avec l'une de ces fonctions, vous pouvez créer une matrice de distance. Sur la base de cette matrice, vous pouvez ensuite affecter des noms locality en fonction de la distance la plus courte avec which.min et de la distance correspondante avec min (voir à cet effet la dernière partie de la réponse), comme suit:

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)]

cela donne:

> 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

Une autre possibilité consiste à affecter locality en fonction des valeurs moyennes de longitude et de latitude de localitys dans 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)])

ou avec 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)] ]

cela donne:

> 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

Comme vous pouvez le constater, cela mène dans la plupart des cas (7 sur 10) à une autre variable locality.


Vous pouvez ajouter la distance avec:

list1$near_dist <- apply(mat2, 1, min)

ou une autre approche avec max.col (ce qui est hautement probable plus rapidement):

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)] ]

le résultat:

> 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
49
Jaap

Crédits à Martin Harringa pour cette solution facilitant cette tâche lorsque vous avez besoin de cette fonction en parcourant un bloc de données sur Le blog de Mark Needham

library(dplyr)

df %>%
rowwise() %>%
mutate(newcolumn_distance = distHaversine(c(df$long1, df$lat1), 
                                          c(df$long2, df$lat2)))

J'ai testé à l'aide des deux fonctions distm et distHaversine séparément sur de grands échantillons de jeux de données réels, et distHaversine semble sortir beaucoup plus rapidement que la fonction distm. Je suis surpris car je pensais que les deux étaient simplement la même fonction dans deux formats.

0
Alexander Kielland