6

What I like to do

I like to plot isochrones from multiple locations on a map so I can visually find the travel time from an arbitrary town to the closest location. It should look like a kernel density 2D plot:

library(purrr)
library(ggmap)

locations <- tibble::tribble(
  ~city,     ~lon,      ~lat,
  "Hamburg",  9.992246, 53.550354,
  "Berlin",  13.408163, 52.518527,
  "Rostock", 12.140776, 54.088581
)

data <- map2_dfr(locations$lon, locations$lat, ~ data.frame(lon = rnorm(10000, .x, 0.8),
                                                            lat = rnorm(10000, .y, 0.7)))
ger <- c(left = min(locations$lon) - 1,  bottom = min(locations$lat) - 1, 
         right = max(locations$lon) + 1, top = max(locations$lat) + 1)
get_stamenmap(ger, zoom = 7, maptype = "toner-lite") %>% 
  ggmap() + 
  stat_density_2d(data = data, aes(x= lon, y = lat, fill = ..level.., alpha = ..level..), 
                  geom = "polygon") +
  scale_fill_distiller(palette = "Blues", direction = 1, guide = FALSE) +
  scale_alpha_continuous(range = c(0.1,0.3), guide = FALSE)

What I tried

You can easily get isochrones via osrm and plot them with leaflet. However, these isochrones are independent from each other. When I plot them they overlap each other.

library(osrm)
library(leaflet)
library(purrr)
library(ggmap)

locations <- tibble::tribble(
  ~city,     ~lon,      ~lat,
  "Hamburg",  9.992246, 53.550354,
  "Berlin",  13.408163, 52.518527,
  "Rostock", 12.140776, 54.088581
)


isochrone <- map2(locations$lon, locations$lat, 
                  ~ osrmIsochrone(loc = c(.x, .y),
                                  breaks = seq(0, 120, 30))) %>%
  do.call(what = rbind)

isochrone@data$drive_times <- factor(paste(isochrone@data$min, "bis", 
                                           isochrone@data$max, "Minuten"))

factpal <- colorFactor("Blues", isochrone@data$drive_times, reverse = TRUE)

leaflet() %>% 
  setView(mean(locations$lon), mean(locations$lat), zoom = 7) %>%
  addProviderTiles("Stamen.TonerLite") %>%
  addPolygons(fill = TRUE, stroke = TRUE, color = "black",
              fillColor = ~factpal(isochrone@data$drive_times),
              weight = 0.5, fillOpacity = 0.6,
              data = isochrone, popup = isochrone@data$drive_times,
              group = "Drive Time") %>% 
  addLegend("bottomright", pal = factpal, values = isochrone@data$drive_time,   
            title = "Fahrtzeit")

How can I merge these isochrone so that they don't overlap?

Birger
  • 1,111
  • 7
  • 17

2 Answers2

2

Really cool question. What you want to do is merge the shapes by ID, so all the 0-30 minute areas are one shape, all the 30-60 minute areas are another, and so on. There are ways to do this with other spatial packages, but it seems well-suited to sf, which uses dplyr-style functions.

After you create isochrone, you can convert it to a sf object, make the same type of distance label, group by ID, and call summarise. The default when you summarize sf objects is just a spatial union, so you don't need to supply a function there.

library(sf)
library(dplyr)

iso_sf <- st_as_sf(isochrone)

iso_union <- iso_sf %>%
  mutate(label = paste(min, max, sep = "-")) %>%
  group_by(id, label) %>%
  summarise()

I didn't have leaflet handy, so here's just the default print method:

plot(iso_union["label"], pal = RColorBrewer::brewer.pal(4, "Blues"))

I'm not sure what's up with the areas that have abrupt vertical edges, but those are in your plot as well.

camille
  • 16,432
  • 18
  • 38
  • 60
  • In overlapping areas minimum value is correct. Right to the left center there is a jump from 30--60 minutes to 90--120 minutes.The same is true left from the top-right center. The top-right and bottom-right looks good. Do you know a way to add that logic? – Birger Jun 03 '19 at 05:24
  • Yeah I don't know why there are those jumps. They seem to be in the data as it comes back--maybe take a look at the API docs to see if there's an issue? When I call `osrmIsochrone` with just the Hamburg coordinates, plotting it shows the same abrupt vertical lines. Also might be an issue with the breaks: when I set the breaks to `seq(0, 60, 10`, there aren't just strange jumps. Beyond that, I'm not super familiar with the API – camille Jun 03 '19 at 17:07
  • Found a solution with with `st_difference` from `sf` package. Marked your answer as correct because you pointed me in the right direction. – Birger Jun 04 '19 at 18:50
  • Cool, if you figured out how to finish up what I wasn't able to get, you can post an answer to your own question and accept that one – camille Jun 04 '19 at 19:48
0

I had a hard time using the map2 method you used because it does both a union as well as, I think, another set theory like function to create specific intervals. Instead, I would recommend creating a raster layer of the layers you create and apply one opacity to that one raster, like the ggmap example does. There's an excellent blog post that I stole alot of code from here (along with from user:camille).

It uses a different API that requires mapbox but it is free. Another limitation is that it won't return isocrones that are the size you like but I recreated it in another location where three points are closer together to prove the method.

I also didn't bother vectorizing the process of creating the isocrone web request so I leave that to someone smarter.

# First be sure to get your mapbox token

library(fasterize)
library(sf)
library(mapboxapi)
library(leaflet)
#mapboxapi::mb_access_token("Go get the token and put it here",
#                           install = TRUE, overwrite = TRUE)

isos1 <- mb_isochrone(
  location = c("-149.883234, 61.185765"),
  profile = "driving",
  time = c(5,10,15),
)

isos2 <- mb_isochrone(
  location = c("-149.928200, 61.191227"),
  profile = "driving",
  time = c(5,10,15),
)
isos3 <- mb_isochrone(
  location = c("-149.939484, 61.160192"),
  profile = "driving",
  time = c(5,10,15),
)

library(sf)
library(dplyr)

isocrones <- rbind(isos1,isos2,isos3)

iso_sf <- st_as_sf(isocrones)

iso_union <- iso_sf %>%
  group_by(time) %>%
  summarise()

isos_proj <- st_transform(iso_sf, 32615)

template <- raster(isos_proj, resolution = 100)

iso_surface <- fasterize(isos_proj, template, field = "time", fun = "min")

pal <- colorNumeric("viridis", isos_proj$time, na.color = "transparent")
leaflet() %>%
  addTiles() %>%
  addRasterImage(iso_surface, colors = pal, opacity = 0.5) %>%
  addLegend(values = isos_proj$time, pal = pal,
            title = "Minutes of Travel") %>% 
  addMarkers(lat = c(61.185765, 61.191227, 61.160192), lng = c(-149.883234, -149.928200, -149.939484))

cylondude
  • 1,816
  • 1
  • 22
  • 55