5

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

[![enter image description here][2]][2]

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?

6
  • 1
    For that you don't need OSRM because that's not based on road network + road restrictions + lua profile + ... its just basic point connection. You can do that using sf package
    – det
    Commented Feb 23, 2022 at 21:54
  • @ det: thank you for your reply! I have never used the sf package before, i will start reading about it. Is it compatible with the base leaflet map in this problem? Thank you so much!
    – stats_noob
    Commented Feb 23, 2022 at 22:00
  • Is it possible to adapt the code from this post over here? stackoverflow.com/questions/32275213/… I will try this!
    – stats_noob
    Commented Feb 23, 2022 at 22:01
  • see edit in my post
    – det
    Commented Feb 23, 2022 at 22:31
  • @ Det: thank you so much for your edit! I played around with your code to get the total distance displayed for the trip on each connection - is what I did correct? Thank you so much for all your help!
    – stats_noob
    Commented Feb 24, 2022 at 1:25

2 Answers 2

7

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.

12
  • @ det: thank you so much for your answer! Just a few things that I wanted to ask:
    – stats_noob
    Commented Feb 20, 2022 at 20:36
  • 1) Is it possible to use the same "icons" that I used? Is it possible to keep the numbers on the "circle icons"? So that it looks like this? i.sstatic.net/Pu2hM.jpg
    – stats_noob
    Commented Feb 20, 2022 at 20:38
  • 2) Is it possible to show the distance on the map itself? Just like what was shown in the stackoverflow post I linked (i.sstatic.net/Pu2hM.jpg)?
    – stats_noob
    Commented Feb 20, 2022 at 20:40
  • 3) Finally, what I meant by "circular route" - I just wanted to make "routes" similar to the Travelling Salesman Problem - something that looks like this: physics.aps.org/assets/a38de7c6-00ac-45fb-9bcf-3b3e14a72b41/… . What I am trying to do is connect each point to the next point: ( 1,2), (2,3), (3,4), (4,5), (5,1) . The route itself doesnt have to be "circular" - just connect the points in the right order
    – stats_noob
    Commented Feb 20, 2022 at 20:42
  • 1
    Don't see reason why not just replace addCircles with addCirclesMarkers like you have in code. res$routes[[1]]$legs %>% map_dbl("distance") will give you distances between two successive points. The route is circular, that does not mean it wont cross itself. The route you get will be the fastest route that visits your point in order with the option continue straight which you can set to false.
    – det
    Commented Feb 20, 2022 at 20:44
1

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!

Not the answer you're looking for? Browse other questions tagged or ask your own question.