How do you ensure the distance of the route includes the length of the segments that are traversed twice?

After retrieving the route from the optimization endpoint, it seems that the reported distance fails to consider instances where certain segments of the route are traveled twice. Precise distance reporting is essential for my use case. What steps can I take to address this issue?

Thanks for reaching out!

Would you mind providing a minimal reproducible example illustrating the issue?

Cheers,
Andrzej

I have attached here a dataset containing the locations of 11 shops in one of the regions we operate in. If you plot an optimized route from the office (point on row 1) to all the shops, you’ll find that the itinerary goes from shop 1 up to shop 5 towards the north, then the vehicle has to backtrack back to shop 1 and go southward to shop 6 → 11. So, this northward segment of the route is traversed twice. however, the distance doesn’t account for that fact.

Hey,

can you provide the corresponding call to the optimization endpoint and verify that it returns the solution you’re describing?

Working with the raw data, we might get different results than you do, since we might issue a different request based on some assumptions either of us may have about the problem at hand.

Best regards

1 Like

Actually, this is a shiny R app that uses the openrouteservice package to call the API.
So, here is the extract from the app that does the actual API request:

# List the required packages
required_pkgs = c("magrittr", "dplyr", "stringr", "tidyr", "openrouteservice", "janitor", "sf")

# Get the list of missing required packages and install them
missing_pkgs <- setdiff(required_pkgs, installed.packages()[, "Package"])
if(length(missing_pkgs) != 0){
  remotes::install_cran(missing_pkgs)
}
# load the libraries of required packages
lapply(required_pkgs, require, character.only = T)

remotes::install_github("GIScience/openrouteservice-r") # openrouteservice package is not yet available on CRAN

# Set my OpenRouteService API key as an environment variable for the session 
Sys.setenv(OPENROUTESERVICE_KEY = <your_API_key_here>)

# define a function that accepts source and destination coordinates and calculates the optimal route
# and returns the optimal route as an sf object
optimal_route <- function(origin, destinations){
  # harmonize coordinates column names
  colnames(source) <- lapply(colnames(source), function(x) ifelse(x %in% c("lat", "latitude", "y"), "lat", x))
  colnames(source) <- lapply(colnames(source), function(x) ifelse(x %in% c("lon", "long", "longitude", "homelong", "x"), "lon", x))
  colnames(destinations) <- lapply(colnames(destinations), function(x) ifelse(x %in% c("lat", "latitude", "homelat", "y"), "lat", x))
  colnames(destinations) <- lapply(colnames(destinations), function(x) ifelse(x %in% c("lon", "long", "longitude", "x"), "lon", x))
  
  # Convet input data to sf and define the projection to WGS84
  source %<>% st_as_sf(coords = c("lon", "lat"), crs = 4326) 
  destinations %<>% st_as_sf(coords = c("lon", "lat"), crs = 4326)
  
  # Create vehicle and jobs objects
  home_base <- as.vector(st_coordinates(source))
  stops <- destinations %>% st_coordinates()
  locations <- split(stops, seq(nrow(stops)))
  vehicle <- vehicles(id = 1, profile="driving-car", start=home_base) # in case we want a round trip, add the "end=home_base" argument
  jobs <- jobs(id = seq(nrow(stops)), location = locations, priority = rev(seq(nrow(stops))))
  
  # Get the job sequence and sort the destinations dataframe accordingly
  opt <- ors_optimization(jobs, vehicle, options = list(g = TRUE), output = "parsed")
  coords <- unname(lapply(opt$routes[[1]]$steps, "[[", "location"))

  # Calculate the optimal route and return it as an sf object
  route <- ors_directions(coordinates=coords, profile = "driving-car", output = "sf") 
  route %<>% select(summary, geometry) %>% mutate(summary = as.character(summary)) %>%
    separate(summary, sep = ",", into = c("distance", "duration")) %>% 
    mutate(across(c(distance, duration), ~str_replace_all(., "[^0-9]", "")))
  return(route)
}

# load the data
source_pt <- read.csv(<path_to_origin_CSV_file>) # https://docs.google.com/spreadsheets/d/1_dhNT2xYJXapmxzm4xaqMe2sSvZFu9E7QX4zwXayino
destinations <- read.csv(<path_to_destinations_CSV_file>) # https://docs.google.com/spreadsheets/d/1I-FjTGwUNwglCPs0iHwYHQM6EV4ykK6b7uqJGYVgEtQ

# call the optimal_route function
my_route <- optimal_route(source_pt, destinations)

You will find that the distance of the returned route is 187.5 Km which is smaller than the actual travel that will be made given a substantial segment of it will be traversed twice.

Thanks for providing some more context on your use case. Is the code snippet your actual production code? If so, you might have a :bug: in there, because apparently the origin argument is not used in the optimal_route function at all. I believe, the argument’s name should rather read source as this is what is being referred to in the function’s body.

I believe I use this argument when i define the vehicles.
See here:

But I digress; I’d rather like to know if this “distance” thing has been thought about and addressed somewhere.
Thanks

@andrzej Is there a solution you can propose here?

Hi @Gashakamba,

thanks for your patience. I’ve checked the distances returned by the optimization service based on the example provided in the openrouteservice R package vignette but could not spot any discrepancies. In fact, the reported distance from home base along the route agrees with the sum of individual distances between the way points computed by the matrix service, see below. Please note that the route involves two sections traversed twice in opposite directions: one from/to the home base, and another one around the third POI. Therefore, I don’t think there is an actual problem with openrouteservice backend or the R package, but rather suspect that you either misread the results or have a bug in your code. If you still disagree and believe this is a valid issue, I would appreciate it if you would consider providing a runnable example illustrating it.

Cheers,
Andrzej

library(openrouteservice)
library(magrittr)

home_base <- data.frame(lon = 2.370658, lat = 48.721666)

vehicles = vehicles(
  id = 1,
  profile = "driving-car",
  start = home_base,
  end = home_base
)

jobs = jobs(
  id = 1:3,
  location =  list(
    c(2.39719, 49.07611),
    c(2.41808, 49.22619),
    c(2.89357, 48.90736)
  )
)

res <- ors_optimization(jobs, vehicles, options = list(g = TRUE))
steps <- res[["routes"]][[1]][["steps"]]

distances_optimization <- sapply(steps, with, distance)[-1]

locations <- lapply(steps, with, location) %>% 
  do.call(rbind, .) %>% data.frame %>% setNames(c("lon", "lat"))

dm <- ors_matrix(locations, metrics = c("distance"))$distances %>% round

distances_matrix <- cumsum(c(dm[1,2], dm[2,3], dm[3,4], dm[4,5])) %>% as.integer

identical(distances_matrix, distances_optimization)
#> [1] TRUE

Created on 2024-04-25 with reprex v2.0.2

I like this example.
Running your code, I think the distances reported are from the home base to each stop, not from one stop to the next.
In any case, it’s easy to go from there to getting the total length of all the route segments combined.
Thank you for your time to look into this for me indeed.

1 Like