Module 16 Spatial data and maps
Spatial data, also known as geospatial data, is a term used to describe any data related to or containing information about a specific location on a surface (often a map). We will consider distance matrix calculations for finding the shortest distance/travel time between a set of origins and destinations, geocoding which is the process of converting an address to geographic coordinates (latitude, longitude) and reverse geocoding which is the opposite process of converting a location as described by geographic coordinates (latitude, longitude) to a human-readable address or place name. Finally, we consider how to display spatial data on a map.
16.1 Learning outcomes
By the end of this module, you are expected to be able to:
- Calculate euclidean, manhattan, etc. distances.
- Calculate a distance matrix, i.e. shortest paths between places using the Google and Bing API.
- Geocode an address using the Google and Bing API.
- Add markers and routes (lines) on a map.
16.2 Services for obtatining spatial data
Often you need to connect to a service using an API for obtaining spatial data. The most common is Google and Bing (Microsoft) and to use the services you need an API key. Another service is also Here.
If you use Google Maps you can obtain an API key here. Modest to light use is free; however, you need a valid credit card. Note you must enable the APIs you intend to use. Google in fact has several services for geo-related solutions. For example, the Maps Static API provides map images, while the Geocoding API provides geocoding and reverse geocoding services. You need to enable the APIs before you use them. You will only need to do that once, and then they will be ready for you to use. Enabling the APIs just means clicking a few radio buttons on the Google Maps Platform web interface.
We will be using the ggmap package for Google services. You can add the API key using:
Note the key is stored in the environment object GGMAP_GOOGLE_API_KEY
. Moreover, it is saved in the file .Renviron
so that it is automatically reloaded when you restart R.
If you use Bing Maps you can obtain an API key here. No credit card is needed. You can add the API key using:
usethis::edit_r_environ() # opens the .Renviron file
Sys.setenv(BING_MAPS_API_KEY=[your key]) # so you don't have to restart R
Add the line BING_MAPS_API_KEY=[your key]
and save the file.
Note your API keys is private and unique to you, so be careful not to share it online!
16.3 Calculating distances
If you need to calculate euclidean, manhattan, etc. distances, you can use the dist
R function:
coord <- matrix(c(0,0, 0,1, 1,0, 1,1), ncol = 2, byrow = TRUE)
coord
#> [,1] [,2]
#> [1,] 0 0
#> [2,] 0 1
#> [3,] 1 0
#> [4,] 1 1
dist(coord)
#> 1 2 3
#> 2 1.00
#> 3 1.00 1.41
#> 4 1.41 1.00 1.00
as.matrix(dist(coord))
#> 1 2 3 4
#> 1 0.00 1.00 1.00 1.41
#> 2 1.00 0.00 1.41 1.00
#> 3 1.00 1.41 0.00 1.00
#> 4 1.41 1.00 1.00 0.00
However, euclidean distances are often a poor approximation of shortest path lengths.
16.3.1 Using Google Maps
Remember to have set your API key and activate the Distance Matrix API service on the Google Cloud Platform. We consider the following places:
library(ggmap)
#> ℹ Google's Terms of Service: <https://mapsplatform.google.com>
#> Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service>
#> OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles>
#> ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
library(tidyverse)
dat <- tibble::tribble(
~Id, ~Shop, ~Address,
1L, "Bilka Esbjerg", "Stormgade 157, 6715 Esbjerg, Denmark",
2L, "Bilka Herning", "Golfvej 5, 7400 Herning, Denmark",
3L, "Bilka Hillerød", "Slotsarkaderne 26, 3400 Hillerød, Denmark",
4L, "Bilka Hjørring", "A.F Heidemannsvej 20, 9800 Hjørring, Denmark",
5L, "Bilka Holstebro", "Nyholmvej 20, 7500 Holstebro, Denmark",
)
dat
#> # A tibble: 5 × 3
#> Id Shop Address
#> <int> <chr> <chr>
#> 1 1 Bilka Esbjerg Stormgade 157, 6715 Esbjerg, Denmark
#> 2 2 Bilka Herning Golfvej 5, 7400 Herning, Denmark
#> 3 3 Bilka Hillerød Slotsarkaderne 26, 3400 Hillerød, Denmark
#> 4 4 Bilka Hjørring A.F Heidemannsvej 20, 9800 Hjørring, Denmark
#> 5 5 Bilka Holstebro Nyholmvej 20, 7500 Holstebro, Denmark
To calculate a distance matrix we use the mapdist
function:
mapdist(from = "Stormgade 157, 6715 Esbjerg, Denmark",
to = "Golfvej 5, 7400 Herning, Denmark")
#> ℹ <https://maps.googleapis.com/maps/api/distancematrix/json?origins=Stormgade+157,+6715+Esbjerg,+Denmark&destinations=Golfvej+5,+7400+Herning,+Denmark&key=xxx-RC0wa23c&mode=driving>
#> # A tibble: 1 × 9
#> from to m km miles seconds minutes hours mode
#> <chr> <chr> <int> <dbl> <dbl> <int> <dbl> <dbl> <chr>
#> 1 Stormgade 157, 6715 Esbjerg, Denmark Golfvej 5, 740… 90193 90.2 56.0 4502 75.0 1.25 driv…
Note Google returns results for the fastest path between the two points. Let us try to define a function which calculate all the distances:
#' Calculate the distance matrix in long format.
#'
#' @param address A vector of addresses.
#' @param mode Driving, bicycling, walking, or transit.
#' @param symmetric Use symmetric distances (half the number of queries).
#' @return A data frame with the results
#' @note The API returns results for the fastest route.
goo_calc_distances <- function(address, mode = "driving", symmetric = TRUE) {
datDist <- expand_grid(id_from = 1:length(address), id_to = 1:length(address))
datDist <- datDist |> filter(id_from != id_to)
if (symmetric) datDist <- datDist |> filter(id_from < id_to)
datDist <- datDist |> mutate(from = address[id_from], to = address[id_to])
res <- mapdist(from = datDist |> pull(from), to = datDist |> pull(to), mode = mode)
return(left_join(datDist, res, by = c("from", "to")))
}
datDistGoo <- goo_calc_distances(dat$Address)
#> ℹ <https://maps.googleapis.com/maps/api/distancematrix/json?origins=A.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark&destinations=Nyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c&mode=driving>
#> ℹ <https://maps.googleapis.com/maps/api/distancematrix/json?origins=Golfvej+5,+7400+Herning,+Denmark&destinations=Slotsarkaderne+26,+3400+Hiller%C3%B8d,+Denmark%7CA.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark%7CNyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c&mode=driving>
#> ℹ <https://maps.googleapis.com/maps/api/distancematrix/json?origins=Slotsarkaderne+26,+3400+Hiller%C3%B8d,+Denmark&destinations=A.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark%7CNyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c&mode=driving>
#> ℹ <https://maps.googleapis.com/maps/api/distancematrix/json?origins=Stormgade+157,+6715+Esbjerg,+Denmark&destinations=Golfvej+5,+7400+Herning,+Denmark%7CSlotsarkaderne+26,+3400+Hiller%C3%B8d,+Denmark%7CA.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark%7CNyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c&mode=driving>
datDistGoo
#> # A tibble: 10 × 11
#> id_from id_to from to m km miles seconds minutes hours mode
#> <int> <int> <chr> <chr> <int> <dbl> <dbl> <int> <dbl> <dbl> <chr>
#> 1 1 2 Stormgade 157, 6715 Esbjerg, … Golf… 90193 90.2 56.0 4502 75.0 1.25 driv…
#> 2 1 3 Stormgade 157, 6715 Esbjerg, … Slot… 321055 321. 200. 11863 198. 3.30 driv…
#> 3 1 4 Stormgade 157, 6715 Esbjerg, … A.F … 322085 322. 200. 12037 201. 3.34 driv…
#> 4 1 5 Stormgade 157, 6715 Esbjerg, … Nyho… 102506 103. 63.7 5414 90.2 1.50 driv…
#> 5 2 3 Golfvej 5, 7400 Herning, Denm… Slot… 332609 333. 207. 12277 205. 3.41 driv…
#> 6 2 4 Golfvej 5, 7400 Herning, Denm… A.F … 178208 178. 111. 7940 132. 2.21 driv…
#> 7 2 5 Golfvej 5, 7400 Herning, Denm… Nyho… 44262 44.3 27.5 1891 31.5 0.525 driv…
#> 8 3 4 Slotsarkaderne 26, 3400 Hille… A.F … 355446 355. 221. 18087 301. 5.02 driv…
#> 9 3 5 Slotsarkaderne 26, 3400 Hille… Nyho… 375164 375. 233. 13857 231. 3.85 driv…
#> 10 4 5 A.F Heidemannsvej 20, 9800 Hj… Nyho… 181389 181. 113. 7982 133. 2.22 driv…
Note that only the calculated distances are returned. If you want to have the whole distance matrix we define function:
#' Convert the data frame returned from calling a `___calc_distances` function to a distance matrix.
#'
#' @param dat The data frame returned from calling `___calc_distances`.
#' @param value_col The column containing the distances.
#' @return The distance matrix
as_dist_matrix <- function(dat, value_col) {
lgt <- max(dat$id_from, dat$id_to)
distanceMat<-matrix(NA, nrow=lgt, ncol = lgt)
diag(distanceMat) <- 0
map(1:nrow(dat), function(r) {
distanceMat[dat$id_from[r], dat$id_to[r]] <<- dat[[value_col]][r]
})
idx <- which(is.na(distanceMat), arr.ind = TRUE)
map(1:nrow(idx), function(r) {
distanceMat[idx[r, "row"], idx[r, "col"]] <<- distanceMat[idx[r, "col"], idx[r, "row"]]
})
return(distanceMat)
}
as_dist_matrix(datDistGoo, "km") # distances in km
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0.0 90.2 321 322 102.5
#> [2,] 90.2 0.0 333 178 44.3
#> [3,] 321.1 332.6 0 355 375.2
#> [4,] 322.1 178.2 355 0 181.4
#> [5,] 102.5 44.3 375 181 0.0
as_dist_matrix(datDistGoo, "seconds") # travel time in seconds
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0 4502 11863 12037 5414
#> [2,] 4502 0 12277 7940 1891
#> [3,] 11863 12277 0 18087 13857
#> [4,] 12037 7940 18087 0 7982
#> [5,] 5414 1891 13857 7982 0
16.4 Geocoding and reverse geocoding
16.4.1 Using Google maps
Remember to enable the Geocoding API. To get the coordinates of an address we use the geocode
function:
geocode("Stormgade 157, 6715 Esbjerg, Denmark")
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Stormgade+157,+6715+Esbjerg,+Denmark&key=xxx-RC0wa23c>
#> # A tibble: 1 × 2
#> lon lat
#> <dbl> <dbl>
#> 1 8.46 55.5
We may use mutate_geocode
to add the coordinates to a dataset:
dat <- dat |>
mutate_geocode(Address)
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Stormgade+157,+6715+Esbjerg,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Golfvej+5,+7400+Herning,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Slotsarkaderne+26,+3400+Hiller%C3%B8d,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=A.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Nyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c>
dat
#> # A tibble: 5 × 5
#> Id Shop Address lon lat
#> <int> <chr> <chr> <dbl> <dbl>
#> 1 1 Bilka Esbjerg Stormgade 157, 6715 Esbjerg, Denmark 8.46 55.5
#> 2 2 Bilka Herning Golfvej 5, 7400 Herning, Denmark 9.01 56.1
#> 3 3 Bilka Hillerød Slotsarkaderne 26, 3400 Hillerød, Denmark 12.3 55.9
#> 4 4 Bilka Hjørring A.F Heidemannsvej 20, 9800 Hjørring, Denmark 10.0 57.4
#> 5 5 Bilka Holstebro Nyholmvej 20, 7500 Holstebro, Denmark 8.62 56.4
To reverse geocode use the revgeocode
function:
revgeocode(c(dat$lon[1], dat$lat[1]))
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?latlng=55.510949,8.4562446&key=xxx-RC0wa23c>
#> Warning: Multiple addresses found, the first will be returned:
#> ! Stormgade 157, 6715 Esbjerg, Denmark
#> ! Stormgade 157, 6715 Esbjerg N, Denmark
#> ! Stormgade 155, 6715 Esbjerg, Denmark
#> ! GF64+9F Esbjerg, Denmark
#> ! Stormgade 158-155, 6715 Esbjerg, Denmark
#> ! 6715 Esbjerg N, Denmark
#> ! Esbjerg N, 6715 Esbjerg N, Denmark
#> ! Esbjerg N, 6715 Esbjerg, Denmark
#> ! Esbjerg, Denmark
#> ! Esbjerg Municipality, Denmark
#> ! Region of Southern Denmark, Denmark
#> ! Denmark
#> [1] "Stormgade 157, 6715 Esbjerg, Denmark"
To apply it to the dataset use:
dat <- dat |>
mutate(AddressGeoGoo =
map_chr(1:n(), function(i) {
revgeocode(c(lon = lon[i], lat = lat[i]))
}))
#> ! Stormgade 157, 6715 Esbjerg, Denmark
#> ! Stormgade 157, 6715 Esbjerg N, Denmark
#> ! Stormgade 155, 6715 Esbjerg, Denmark
#> ! GF64+9F Esbjerg, Denmark
#> ! Stormgade 158-155, 6715 Esbjerg, Denmark
#> ! 6715 Esbjerg N, Denmark
#> ! Esbjerg N, 6715 Esbjerg N, Denmark
#> ! Esbjerg N, 6715 Esbjerg, Denmark
#> ! Esbjerg, Denmark
#> ! Esbjerg Municipality, Denmark
#> ! Region of Southern Denmark, Denmark
#> ! Denmark
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?latlng=56.1357174,9.0053714&key=xxx-RC0wa23c>
#> ! Golfvej 5, 7400 Herning, Denmark
#> ! 42P4+74 Herning, Denmark
#> ! Engdahlsvej, 7400 Herning, Denmark
#> ! 7400 Herning, Denmark
#> ! Herning, 7400 Herning, Denmark
#> ! Herning Municipality, Denmark
#> ! Central Denmark Region, Denmark
#> ! Denmark
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?latlng=55.9288332,12.2978145&key=xxx-RC0wa23c>
#> ! Slotsarkaderne 30, 3400 Hillerød, Denmark
#> ! Christians Torv 210, 3400 Hillerød, Denmark
#> ! Slotsarkaderne 26, 3400 Hillerød, Denmark
#> ! W7HX+G4 Hillerød, Denmark
#> ! Hillerød, 3400 Hillerød, Denmark
#> ! 3400 Hillerød, Denmark
#> ! Hillerød Municipality, Denmark
#> ! Capital Region of Denmark, Denmark
#> ! Denmark
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?latlng=57.4441555,10.0034928&key=xxx-RC0wa23c>
#> ! A F Heidemanns Vej 20, 9800 Hjørring, Denmark
#> ! A F Heidemanns Vej 21, 9800 Hjørring, Denmark
#> ! A F Heidemanns Vej 21C, 9800 Hjørring, Denmark
#> ! C2V3+M9 Hjørring, Denmark
#> ! 9800 Hjørring, Denmark
#> ! Hjørring, 9800 Hjørring, Denmark
#> ! Hjørring Municipality, Denmark
#> ! North Denmark Region, Denmark
#> ! Denmark
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?latlng=56.377168,8.621638&key=xxx-RC0wa23c>
#> ! Nyholmvej 20, 7500 Holstebro, Denmark
#> ! o Bilka, Nyholmvej 20 C, 7500 Holstebro, Denmark
#> ! Nyholmvej 5, 7500 Holstebro, Denmark
#> ! 9JGC+VM Holstebro, Denmark
#> ! Nyholmvej 7-5, 7500 Holstebro, Denmark
#> ! 7500 Holstebro, Denmark
#> ! Holstebro, 7500 Holstebro, Denmark
#> ! Holstebro Municipality, Denmark
#> ! Central Denmark Region, Denmark
#> ! Denmark
#> Warning: There were 5 warnings in `mutate()`.
#> The first warning was:
#> ℹ In argument: `AddressGeoGoo = map_chr(...)`.
#> Caused by warning:
#> ! Multiple addresses found, the first will be returned:
#> ℹ Run `dplyr::last_dplyr_warnings()` to see the 4 remaining warnings.
dat
#> # A tibble: 5 × 6
#> Id Shop Address lon lat AddressGeoGoo
#> <int> <chr> <chr> <dbl> <dbl> <chr>
#> 1 1 Bilka Esbjerg Stormgade 157, 6715 Esbjerg, Denmark 8.46 55.5 Stormgade 157, 671…
#> 2 2 Bilka Herning Golfvej 5, 7400 Herning, Denmark 9.01 56.1 Golfvej 5, 7400 He…
#> 3 3 Bilka Hillerød Slotsarkaderne 26, 3400 Hillerød, Denmark 12.3 55.9 Slotsarkaderne 30,…
#> 4 4 Bilka Hjørring A.F Heidemannsvej 20, 9800 Hjørring, Denmark 10.0 57.4 A F Heidemanns Vej…
#> 5 5 Bilka Holstebro Nyholmvej 20, 7500 Holstebro, Denmark 8.62 56.4 Nyholmvej 20, 7500…
16.5 Adding markers and routes to a map
Leaflet is an open-source JavaScript library for interactive maps. It’s used by websites ranging from The New York Times and The Washington Post to GitHub and Flickr, as well as GIS specialists like OpenStreetMap, Mapbox, and CartoDB. The leaflet package makes it easy to create Leaflet maps from R.
Note Leaflet is open-source and free so you do not need an API key for making maps. If you would like to use Google maps instead then have a look at the googleway package instead.
First let us create a map with two base layers
library(leaflet)
library(htmlwidgets)
m <- leaflet() |>
# Base maps
addTiles(group = "Map") |>
addProviderTiles('Esri.WorldImagery', group = "Satelite") |>
addProviderTiles("CartoDB.PositronOnlyLabels", group = "Map") |>
# Center and zoom
setView(10.2, 56.2, zoom = 7) |>
# Layer control
addLayersControl(
baseGroups = c("Map", "Satelite"),
options = layersControlOptions(collapsed = TRUE)
)
m
Next, let us add the places:
dat <- tibble::tribble(
~Id, ~Shop, ~Address,
1L, "Bilka Esbjerg", "Stormgade 157, 6715 Esbjerg, Denmark",
2L, "Bilka Herning", "Golfvej 5, 7400 Herning, Denmark",
3L, "Bilka Hillerød", "Slotsarkaderne 26, 3400 Hillerød, Denmark",
4L, "Bilka Hjørring", "A.F Heidemannsvej 20, 9800 Hjørring, Denmark",
5L, "Bilka Holstebro", "Nyholmvej 20, 7500 Holstebro, Denmark",
)
dat <- dat |> mutate_geocode(Address)
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Stormgade+157,+6715+Esbjerg,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Golfvej+5,+7400+Herning,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Slotsarkaderne+26,+3400+Hiller%C3%B8d,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=A.F+Heidemannsvej+20,+9800+Hj%C3%B8rring,+Denmark&key=xxx-RC0wa23c>
#> ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Nyholmvej+20,+7500+Holstebro,+Denmark&key=xxx-RC0wa23c>
m <- m |>
addMarkers(~lon, ~lat, popup = ~Address, label = ~str_c(Id, " - ", Shop), data = dat)
m
To add a line between a set of points use the addPolylines
function:
routeIds <- c(2, 5, 1, 2)
route <- dat[routeIds,]
m |> addPolylines(lng = ~lon, lat = ~lat, data = route, weight = 2, label = "Route 1")
A more advanced setup is to use the addFlows
function from the leaflet.minicharts package. First let us define some lines:
datLines <- tibble(FromId = c(2, 5, 1, 2, 4, 3), ToId = c(5, 1, 2, 4, 3, 2))
datLines <- left_join(datLines, dat |> select(-Shop, -Address), by = c("FromId" = "Id")) |>
rename("FromLat" = "lat", "FromLon" = "lon") |>
left_join(dat |> select(-Shop, -Address), by = c("ToId" = "Id")) |>
rename("ToLat" = "lat", "ToLon" = "lon")
datLines
#> # A tibble: 6 × 6
#> FromId ToId FromLon FromLat ToLon ToLat
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 5 9.01 56.1 8.62 56.4
#> 2 5 1 8.62 56.4 8.46 55.5
#> 3 1 2 8.46 55.5 9.01 56.1
#> 4 2 4 9.01 56.1 10.0 57.4
#> 5 4 3 10.0 57.4 12.3 55.9
#> 6 3 2 12.3 55.9 9.01 56.1
Note we have a from/to pair in each row. We add the lines to the map:
library(leaflet.minicharts)
m |> addFlows(datLines$FromLon, datLines$FromLat, datLines$ToLon, datLines$ToLat,
flow = 1, maxFlow = 20, opacity = 0.5, popup = popupArgs(noPopup = TRUE))
Let us define a function for adding routes:
add_route <- function(dat = NULL, route, solution = 1) {
route_id = if_else(is.null(dat), 1, max(dat$RouteId) + 1)
tmp <- tibble(From = route[1:(length(route)-1)], To = route[2:length(route)])
tmp <- tmp |> mutate(Sol = solution, RouteId = route_id)
dat <- bind_rows(dat, tmp)
}
datLines <- add_route(route = c(2, 5, 1, 2)) |>
add_route(route = c(2, 4, 3, 2)) |>
add_route(route = c(2, 3, 1, 2), solution = 2) |>
add_route(route = c(2, 5, 4, 2), solution = 2)
#> Warning in max(dat$RouteId): no non-missing arguments to max; returning -Inf
datLines <- left_join(datLines, dat |> select(-Shop, -Address), by = c("From" = "Id")) |>
rename("FromLat" = "lat", "FromLon" = "lon") |>
left_join(dat |> select(-Shop, -Address), by = c("To" = "Id")) |>
rename("ToLat" = "lat", "ToLon" = "lon") |>
mutate(group = str_c("Solution ", Sol))
datLines
#> # A tibble: 12 × 9
#> From To Sol RouteId FromLon FromLat ToLon ToLat group
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 2 5 1 1 9.01 56.1 8.62 56.4 Solution 1
#> 2 5 1 1 1 8.62 56.4 8.46 55.5 Solution 1
#> 3 1 2 1 1 8.46 55.5 9.01 56.1 Solution 1
#> 4 2 4 1 2 9.01 56.1 10.0 57.4 Solution 1
#> 5 4 3 1 2 10.0 57.4 12.3 55.9 Solution 1
#> 6 3 2 1 2 12.3 55.9 9.01 56.1 Solution 1
#> 7 2 3 2 3 9.01 56.1 12.3 55.9 Solution 2
#> 8 3 1 2 3 12.3 55.9 8.46 55.5 Solution 2
#> 9 1 2 2 3 8.46 55.5 9.01 56.1 Solution 2
#> 10 2 5 2 4 9.01 56.1 8.62 56.4 Solution 2
#> 11 5 4 2 4 8.62 56.4 10.0 57.4 Solution 2
#> 12 4 2 2 4 10.0 57.4 9.01 56.1 Solution 2
A map with Solution 1:
col_pal <- rainbow(max(datLines$RouteId))
datP <- datLines |> filter(Sol == 1)
m |> addFlows(datP$FromLon, datP$FromLat, datP$ToLon, datP$ToLat,
flow = datP$RouteId, maxThickness = 2, color = col_pal[datP$RouteId],
opacity = 0.5, popup = popupArgs(labels = "Route"))
A map with Solution 2:
col_pal <- rainbow(max(datLines$RouteId))
datP <- datLines |> filter(Sol == 2)
m |> addFlows(datP$FromLon, datP$FromLat, datP$ToLon, datP$ToLat,
flow = datP$RouteId, maxThickness = 2, color = col_pal[datP$RouteId],
opacity = 0.5, popup = popupArgs(labels = "Route"))
An interactive map with both solutions:
mm <- m
res <- map(1:nrow(datLines), function(i) {
tmp <- tibble(lat = c(datLines$FromLat[i], datLines$ToLat[i]), lon = c(datLines$FromLon[i], datLines$ToLon[i]))
mm <<- mm |> addPolylines(lng = ~lon, lat = ~lat,
color = col_pal[datLines$Sol[i]], group = datLines$group[i],
data = tmp, weight = 2, label = datLines$group[i])
return(invisible(NULL))
})
mm <- mm |>
# Layer control
addLayersControl(
baseGroups = c("Map", "Satelite"),
overlayGroups = unique(datLines$group),
options = layersControlOptions(collapsed = FALSE)
) |>
hideGroup(unique(datLines$group)) |>
showGroup("Solution 1")
mm
Note unfortunately arrows cannot be shown in this case.