'R: Connecting Dots on a Map
I am working with the R programming language.
Using the "leaflet" library, I made the following map for these 5 cities:
library(dplyr)
library(leaflet)
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), type = c(1,2,3,4,5))
map_data$type = as.factor(map_data$type)
leaflet(map_data) %>%
addTiles() %>% addCircleMarkers(stroke = FALSE, label = ~type,fillOpacity = 0.8, labelOptions = labelOptions(direction = "center",style = list('color' = "white"),noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE))
On this above map that I have created, I would now like to "connect" all these "points" (i.e. cities) on the map (in a route) based on their "number" (e.g. connect 1 with 2, 2 with 3, 3 with 4, 4 with 5, 5 with 1), and output the "total distance" of the route. I found a previous post that shows how to do this: How to show path and distance on map with leaflet, shiny apps?
I tried to adapt the code from this post to suit my question:
library(osrm)
route = osrmRoute(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), overview = 'full')
route_summary = osrmRoute(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), overview = FALSE)
leaflet() %>% addTiles() %>%
addCircleMarkers(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), stroke = FALSE, label = ~type,fillOpacity = 0.8,
labelOptions = labelOptions(direction = "center",style = list('color' = "white"),noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE)) %>%
addPolylines(route$lon,route$lat,
label = paste(round(route_summary[1]/60), 'hr - ', round(route_summary[2]), 'km'),
labelOptions = labelOptions(noHide = TRUE))
But this returns the following error:
Error in UseMethod("metaData") :
no applicable method for 'metaData' applied to an object of class "NULL"
Can someone please show me how to fix this problem?
I would like to do this using "leaflet" and not using "rshiny". In the end, I would like the final map to look something like this (this is supposed to represent a "single path" from a Travelling Salesman Problem) :
Note: I am starting to think that problem might be that the "osrmRoute()" function might not be able to work for more than 2 points?
Solution 1:[1]
One way is for you to make API call:
https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md
I'll just outline how can you do it:
data
df <- data.frame(
lon = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957),
lat = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629)
)
url call
root <- "http://router.project-osrm.org/route/v1/driving/"
options <- c(
continue_straight = "true",
overview = "full",
annotations = "true",
steps = "true"
)
coords <- df %>%
slice(c(seq_len(n()), 1)) %>%
pmap_chr(str_c, sep = ",") %>% str_c(collapse = ";")
options <- options %>%
imap_chr(~str_c(.y, "=", .x)) %>%
str_c(collapse = "&") %>%
str_c("?", .)
res <- rjson::fromJSON(file = str_c(root, coords, options))
Note that I've added first point as 6th row to make circle route.
map
res$routes[[1]]$geometry %>%
googlePolylines::decode() %>%
.[[1]] %>%
leaflet() %>%
addTiles() %>%
addPolylines(lng = ~lon, lat = ~lat) %>%
addCircleMarkers(
data = df,
stroke = FALSE,
label = seq_len(nrow(df)),
fillOpacity = 0.8,
labelOptions = labelOptions(
direction = "center",
style = list('color' = "white"),
noHide = TRUE,
offset=c(0,0),
fill = TRUE,
opacity = 1,
weight = 10,
textOnly = TRUE
)
)
distance
res$routes[[1]]$distance
This is in meters (documentation)
EDIT
There probably is better way of labeling polyline but I don't have time now:
library(sf)
segment_df <- df %>% rbind(df[1,])
d <- segment_df %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
{st_distance(.[-6,], .[-1,], by_element = TRUE)} %>%
as.vector() %>%
round()
m <- leaflet() %>% addTiles()
for(i in seq_len(nrow(segment_df) - 1))
m <- m %>% addPolylines(
data = segment_df[i:(i+1),],
lng = ~lon, lat = ~lat, color = "red", label = paste(d[[i]], "m"),
labelOptions(noHide = TRUE, direction = 'top')
)
m <- m %>% addCircleMarkers(
data = df,
stroke = FALSE,
label = seq_len(nrow(df)),
fillOpacity = 0.8,
labelOptions = labelOptions(
direction = "center",
style = list('color' = "white"),
noHide = TRUE,
offset=c(0,0),
fill = TRUE,
opacity = 1,
weight = 10,
textOnly = TRUE
)
)
If you want only to show total distance then that is easier and does not require loop, just replace loop with:
segment_df %>%
leaflet() %>%
addTiles() %>%
addPolylines(
lng = ~lon, lat = ~lat, color = "red",
label = paste(sum(d), "m"),
labelOptions = labelOptions(noHide = TRUE, direction = 'top')
)
I hope you understand (and see from map) that this is not drivable.
Solution 2:[2]
Here is an answer I tried based on @det's answer:
library(sf)
library(geosphere)
library(dplyr)
library(leaflet)
library(data.table)
library(VPF)
#add a 6th row that is equal to the 1st row - so that the path loops back
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629, 43.6426), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957, -79.3871 ), type = c(1,2,3,4,5,1))
map_data$type = as.factor(map_data$type)
m1 = leaflet(map_data) %>% addTiles() %>% addCircleMarkers(stroke = FALSE, label = ~type,fillOpacity = 0.8,
color = ~ifelse(type==1,"red","blue"), labelOptions = labelOptions(direction = "center",style = list('color' = "white"),
noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE))
m1 %>% addTiles() %>%
addPolylines(data = map_data, lng = ~Long, lat = ~Lat, group = ~type)
Now, I want to calculate the total distance of the trip and have it displayed on the map:
#distances (https://stackoverflow.com/questions/42119438/calculate-distance-between-two-long-lat-coordinates-in-a-dataframe)
result = rbind(
cbind(map_data[1:nrow(map_data)-1,c(1,2)], map_data[-1,c(1,2)]),
cbind(map_data[nrow(map_data), c(1,2)], map_data[1,c(1,2)])
)
colnames(result) <- c("start_lat", "start_long", "end_lat", "end_long")
result$id = as.factor(c(1,2,3,4,5,1))
result = data.frame(result)
for (i in 1:nrow(result)) {
a<-result$start_long[i]
b<-result$start_lat[i]
c<-result$end_long[i]
d<-result$end_lat[i]
result$distance[i]<-distm(c(a,b),c(c,d), fun = distHaversine)
}
#total distance of trip in meters
d = result$distance
total_d = signif(sum(d),3)
m1 %>% addPolylines(
data = map_data,
lng = ~Long, lat = ~Lat, color = "blue", label = paste0(total_d, " meters"),
labelOptions(noHide = TRUE, direction = 'top')
)
I think I finally got it - thanks so much @ Det!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | stats_noob |






