├── .gitignore ├── osrmc-sys ├── src │ ├── wrapper.h │ └── lib.rs ├── Cargo.toml └── build.rs ├── .gitmodules ├── Cargo.toml ├── .github ├── CODEOWNERS └── workflows │ └── dependency_enforcement.yml ├── prepare-test-data.sh ├── .travis.yml ├── src ├── macros.rs ├── route.rs ├── errors.rs ├── table.rs └── lib.rs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.rs.bk 3 | test-data/ 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /osrmc-sys/src/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "libosrmc/libosrmc/osrmc.h" 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "osrmc-sys/src/libosrmc"] 2 | path = osrmc-sys/src/libosrmc 3 | url = https://github.com/daniel-j-h/libosrmc 4 | -------------------------------------------------------------------------------- /osrmc-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "osrm" 3 | version = "0.0.2" 4 | description = "Bindings for Open Source Routing Machine (OSRM)." 5 | license = "MIT" 6 | authors = ["Michael Killough "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | osrmc-sys = { path = "osrmc-sys", version = "0.0.2" } 11 | -------------------------------------------------------------------------------- /osrmc-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "osrmc-sys" 3 | version = "0.0.2" 4 | description = "libosrmc C bindings." 5 | license = "MIT" 6 | authors = ["Michael Killough "] 7 | edition = "2018" 8 | 9 | [build-dependencies] 10 | bindgen = "0.59" 11 | cc = "1.0" 12 | pkg-config = "0.3" 13 | 14 | [dependencies] 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @deliveroo/consumer-signals 2 | 3 | /.hopper/ @deliveroo/reviewers-prodeng-nec # DO NOT MODIFY/REMOVE, AUTOGENERATED (#IM-2413) 4 | **/codeql*.yml @deliveroo/product-sec-eng # DO NOT MODIFY/REMOVE, AUTOGENERATED by Product Security 5 | **/dependency*.yml @deliveroo/product-sec-eng # DO NOT MODIFY/REMOVE, AUTOGENERATED by Product Security 6 | -------------------------------------------------------------------------------- /prepare-test-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir test-data 4 | cd test-data 5 | 6 | # Includes UAE, which has some unroutable routes. 7 | wget -N http://download.geofabrik.de/asia/gcc-states-latest.osm.pbf 8 | 9 | # This was confirmed to work with v5.26.0 which unfortunately did not have a dedicated tag at the 10 | # time of writing. Any future breakage could be caused by a newer version so try modifying the tag 11 | # from `latest` to `v5.26.0` which will hopefully be available. 12 | docker run -t -v $(pwd):/data osrm/osrm-backend:latest osrm-extract -p /opt/foot.lua /data/gcc-states-latest.osm.pbf 13 | docker run -t -v $(pwd):/data osrm/osrm-backend:latest osrm-contract /data/gcc-states-latest.osrm 14 | -------------------------------------------------------------------------------- /.github/workflows/dependency_enforcement.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # Reach out on Slack at '#secrity-support' to get help. 5 | # Link to the action: https://github.com/actions/dependency-review-action 6 | 7 | name: "Dependency Review" 8 | on: [pull_request] 9 | permissions: 10 | contents: read 11 | jobs: 12 | dependency-review: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: "Checkout Repository" 16 | uses: actions/checkout@v4 17 | - name: Dependency Review 18 | uses: actions/dependency-review-action@v4 19 | with: 20 | fail-on-severity: critical 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | services: 4 | - docker 5 | 6 | rust: 7 | - stable 8 | - beta 9 | - nightly 10 | 11 | matrix: 12 | allow_failures: 13 | - rust: nightly 14 | fast_finish: true 15 | 16 | cache: cargo 17 | 18 | before_install: 19 | - sudo apt-get update 20 | - sudo apt-get install -y build-essential curl file git libexpat1-dev 21 | 22 | # Install linuxbrew 23 | - sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)" 24 | - echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.profile 25 | 26 | - PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" brew install osrm-backend 27 | 28 | script: 29 | - ./prepare-test-data.sh 30 | - cargo build --verbose --all 31 | - cargo test --verbose --all 32 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Helper for implementing Drop for a libosrmc handle. 2 | macro_rules! impl_drop { 3 | ($ty:ident, $destructor:path) => { 4 | impl Drop for $ty { 5 | fn drop(&mut self) { 6 | unsafe { $destructor(self.handle) } 7 | } 8 | } 9 | }; 10 | } 11 | 12 | /// Helper for calling libosrmc methods which take an error as a final parameter. 13 | /// 14 | /// Takes care of passing in an empty error, and converts the response into a result. 15 | macro_rules! call_with_error { 16 | ($func:ident($( $arg:expr ),*)) => {{ 17 | let mut error = std::ptr::null_mut(); 18 | let result = unsafe { 19 | osrmc_sys::$func($($arg,)* &mut error) 20 | }; 21 | if !error.is_null() { 22 | Err(Error::from(error)) 23 | } else { 24 | Ok(result) 25 | } 26 | }}; 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Deliveroo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osrm-rs 2 | 3 | Rust bindings for Open Source Routing Machine (OSRM). 4 | 5 | ## Example 6 | 7 | ```rust 8 | let osrm = OSRM::new("./data/1.osrm")?; 9 | let result = osrm 10 | .table( 11 | &[Point { 12 | latitude: 51.5062628, 13 | longitude: -0.0996648, 14 | }], 15 | &[Point { 16 | latitude: 51.5062628, 17 | longitude: -0.124899, 18 | }], 19 | )?; 20 | assert_eq!(result.get_duration(0, 0)?, 0.0); 21 | ``` 22 | 23 | ## Developing 24 | 25 | ```sh 26 | # Install OSRM and its dependencies 27 | brew install osrm-backend 28 | # OR `brew install --HEAD deliveroo/osrm/osrm-backend` for Deliveroo's patched version 29 | 30 | # Update/initialise the libosrmc submodule 31 | git submodule update --init 32 | 33 | # Build library 34 | cargo build 35 | ``` 36 | 37 | ## Testing 38 | 39 | ```sh 40 | # Follow steps in `Developing` section 41 | 42 | # Install additional dependencies 43 | brew install wget docker 44 | 45 | # Download/process the required maps 46 | ./prepare-test-data.sh 47 | 48 | # Run tests 49 | cargo test 50 | ``` 51 | -------------------------------------------------------------------------------- /src/route.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::*; 2 | use crate::Coordinate; 3 | 4 | pub struct Parameters { 5 | pub handle: osrmc_sys::osrmc_route_params_t, 6 | } 7 | 8 | impl_drop!(Parameters, osrmc_sys::osrmc_route_params_destruct); 9 | 10 | impl Parameters { 11 | pub fn new() -> Result { 12 | let handle = call_with_error!(osrmc_route_params_construct())?; 13 | Ok(Parameters { handle }) 14 | } 15 | 16 | pub fn add_coordinate(&mut self, coordinate: &Coordinate) -> Result<()> { 17 | call_with_error!(osrmc_params_add_coordinate( 18 | self.handle as osrmc_sys::osrmc_params_t, 19 | coordinate.longitude, 20 | coordinate.latitude 21 | ))?; 22 | Ok(()) 23 | } 24 | } 25 | 26 | pub struct Response { 27 | handle: osrmc_sys::osrmc_route_response_t, 28 | } 29 | 30 | impl_drop!(Response, osrmc_sys::osrmc_route_response_destruct); 31 | 32 | impl From for Response { 33 | fn from(handle: osrmc_sys::osrmc_route_response_t) -> Self { 34 | Response { handle } 35 | } 36 | } 37 | 38 | impl Response { 39 | pub fn distance(&self) -> Result { 40 | let result = call_with_error!(osrmc_route_response_distance(self.handle))?; 41 | Ok(result) 42 | } 43 | 44 | pub fn duration(&self) -> Result { 45 | let result = call_with_error!(osrmc_route_response_duration(self.handle))?; 46 | Ok(result) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /osrmc-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn compile_libosrmc() { 5 | env::set_var( 6 | "CXXFLAGS", 7 | env::var("CXXFLAGS").unwrap_or_default() + " -std=c++14", 8 | ); 9 | 10 | let mut build = cc::Build::new(); 11 | build 12 | .cpp(true) 13 | .include("src/libosrmc/libosrmc") 14 | .file("src/libosrmc/libosrmc/osrmc.cc"); 15 | 16 | let libosrm = pkg_config::Config::new() 17 | .probe("libosrm") 18 | .expect("Could not call pkg-config for libosrm"); 19 | println!("pkgconfig: {:?}", libosrm); 20 | for include in libosrm.include_paths { 21 | build.include(include); 22 | } 23 | for link_path in libosrm.link_paths { 24 | if let Some(path) = link_path.to_str() { 25 | println!("cargo:rustc-link-search=native={}", path); 26 | } 27 | } 28 | 29 | build.compile("libosrmc"); 30 | } 31 | 32 | fn generate_libosrmc_bindings() { 33 | let bindings = bindgen::Builder::default() 34 | .header("src/wrapper.h") 35 | .generate() 36 | .expect("Unable to generate bindings for libosrmc"); 37 | 38 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 39 | bindings 40 | .write_to_file(out_path.join("bindings.rs")) 41 | .expect("Couldn't write bindings for libosrmc"); 42 | } 43 | 44 | fn main() { 45 | compile_libosrmc(); 46 | generate_libosrmc_bindings(); 47 | 48 | println!("cargo:rustc-link-lib=boost_system"); 49 | println!("cargo:rustc-link-lib=boost_filesystem"); 50 | println!("cargo:rustc-link-lib=boost_iostreams"); 51 | // The homebrew `osrm-backend` package specifies tbb@2020 as a dependency which is keg only and 52 | // therefore not symlinked to a directory where the linker can find it. `rustc-link-search` is a 53 | // hack which tells rustc to add the TBB location to the linker search path. Note that the 54 | // DYLD_LIBRARY_PATH environment variable can no longer be used on MacOS due to System Integrity 55 | // Protection. `/usr/local` is the Homebrew install path on Intel and `/opt/homebrew` is for 56 | // Apple Silicon. 57 | println!("cargo:rustc-link-search=/usr/local/opt/tbb@2020/lib"); 58 | println!("cargo:rustc-link-search=/opt/homebrew/opt/tbb@2020/lib"); 59 | println!("cargo:rustc-link-search=/opt/homebrew/opt/boost/lib"); 60 | println!("cargo:rustc-link-lib=tbb"); 61 | 62 | // Boost library names differ on macOS. 63 | if env::var("TARGET").unwrap().contains("apple") { 64 | println!("cargo:rustc-link-lib=boost_thread-mt"); 65 | } else { 66 | println!("cargo:rustc-link-lib=boost_thread"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::error; 3 | use std::ffi::{self, CStr}; 4 | use std::fmt::{self, Display}; 5 | use std::result::Result as StdResult; 6 | 7 | struct OsrmcError { 8 | handle: osrmc_sys::osrmc_error_t, 9 | } 10 | 11 | impl_drop!(OsrmcError, osrmc_sys::osrmc_error_destruct); 12 | 13 | impl OsrmcError { 14 | fn code(&self) -> Cow<'_, str> { 15 | unsafe { 16 | let ptr = osrmc_sys::osrmc_error_code(self.handle); 17 | CStr::from_ptr(ptr).to_string_lossy() 18 | } 19 | } 20 | 21 | fn message(&self) -> Cow<'_, str> { 22 | unsafe { 23 | let ptr = osrmc_sys::osrmc_error_message(self.handle); 24 | CStr::from_ptr(ptr).to_string_lossy() 25 | } 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug, PartialEq)] 30 | pub enum ErrorKind { 31 | Message(String), 32 | Osrmc { code: String, message: String }, 33 | NoRoute, 34 | InvalidCoordinate, 35 | FfiNul(ffi::NulError), 36 | } 37 | 38 | impl Display for ErrorKind { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> { 40 | match self { 41 | ErrorKind::Message(inner) => Display::fmt(inner, f), 42 | ErrorKind::Osrmc { code, message } => write!(f, "Osrmc: {}: {}", code, message), 43 | ErrorKind::NoRoute => write!(f, "Impossible route between points"), 44 | ErrorKind::InvalidCoordinate => write!(f, "Invalid coordinate value"), 45 | ErrorKind::FfiNul(inner) => Display::fmt(inner, f), 46 | } 47 | } 48 | } 49 | 50 | #[derive(Debug, PartialEq)] 51 | pub struct Error { 52 | kind: ErrorKind, 53 | } 54 | 55 | impl Error { 56 | pub fn kind(&self) -> ErrorKind { 57 | self.kind.clone() 58 | } 59 | } 60 | 61 | impl Display for Error { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> { 63 | write!(f, "osrm-rs: {}", self.kind) 64 | } 65 | } 66 | 67 | impl error::Error for Error {} 68 | 69 | impl From for Error { 70 | fn from(handle: osrmc_sys::osrmc_error_t) -> Error { 71 | let error = OsrmcError { handle }; 72 | let code = error.code().into_owned(); 73 | let message = error.message().into_owned(); 74 | let kind = match code.as_ref() { 75 | "NoRoute" => ErrorKind::NoRoute, 76 | "InvalidValue" => ErrorKind::InvalidCoordinate, 77 | _ => ErrorKind::Osrmc { code, message }, 78 | }; 79 | Error { kind } 80 | } 81 | } 82 | 83 | impl From for Error { 84 | fn from(other: ffi::NulError) -> Error { 85 | Error { 86 | kind: ErrorKind::FfiNul(other), 87 | } 88 | } 89 | } 90 | 91 | impl From for Error { 92 | fn from(other: String) -> Error { 93 | Error { 94 | kind: ErrorKind::Message(other), 95 | } 96 | } 97 | } 98 | 99 | impl From<&str> for Error { 100 | fn from(other: &str) -> Error { 101 | Error { 102 | kind: ErrorKind::Message(other.into()), 103 | } 104 | } 105 | } 106 | 107 | pub type Result = StdResult; 108 | -------------------------------------------------------------------------------- /src/table.rs: -------------------------------------------------------------------------------- 1 | use crate::{errors::*, Coordinate}; 2 | 3 | struct Annotations { 4 | handle: osrmc_sys::osrmc_table_annotations_t, 5 | } 6 | 7 | impl_drop!(Annotations, osrmc_sys::osrmc_table_annotations_destruct); 8 | 9 | impl Annotations { 10 | fn new(include_distance: bool) -> Result { 11 | let handle = call_with_error!(osrmc_table_annotations_construct())?; 12 | let mut annotations = Annotations { handle }; 13 | if include_distance { 14 | annotations.set_distance()?; 15 | } 16 | Ok(annotations) 17 | } 18 | 19 | fn set_distance(&mut self) -> Result<()> { 20 | call_with_error!(osrmc_table_annotations_enable_distance(self.handle, true))?; 21 | Ok(()) 22 | } 23 | } 24 | 25 | pub struct Parameters { 26 | pub handle: osrmc_sys::osrmc_table_params_t, 27 | num_coords: usize, 28 | } 29 | 30 | impl_drop!(Parameters, osrmc_sys::osrmc_table_params_destruct); 31 | 32 | impl Parameters { 33 | pub fn new(include_distance: bool) -> Result { 34 | let handle = call_with_error!(osrmc_table_params_construct())?; 35 | 36 | let annotations = Annotations::new(include_distance)?; 37 | call_with_error!(osrmc_table_params_set_annotations( 38 | handle, 39 | annotations.handle 40 | ))?; 41 | 42 | Ok(Parameters { 43 | handle, 44 | num_coords: 0, 45 | }) 46 | } 47 | 48 | fn add_coordinate(&mut self, coordinate: &Coordinate) -> Result { 49 | call_with_error!(osrmc_params_add_coordinate( 50 | self.handle as osrmc_sys::osrmc_params_t, 51 | coordinate.longitude, 52 | coordinate.latitude 53 | ))?; 54 | let index = self.num_coords; 55 | self.num_coords += 1; 56 | Ok(index) 57 | } 58 | 59 | pub fn add_source(&mut self, coordinate: &Coordinate) -> Result<()> { 60 | let index = self.add_coordinate(coordinate)?; 61 | call_with_error!(osrmc_table_params_add_source(self.handle, index as u64))?; 62 | Ok(()) 63 | } 64 | 65 | pub fn add_destination(&mut self, coordinate: &Coordinate) -> Result<()> { 66 | let index = self.add_coordinate(coordinate)?; 67 | call_with_error!(osrmc_table_params_add_destination( 68 | self.handle, 69 | index as u64 70 | ))?; 71 | Ok(()) 72 | } 73 | } 74 | 75 | pub struct Response { 76 | pub(crate) include_distance: bool, 77 | pub(crate) handle: osrmc_sys::osrmc_table_response_t, 78 | } 79 | 80 | impl_drop!(Response, osrmc_sys::osrmc_table_response_destruct); 81 | 82 | impl Response { 83 | pub fn get_duration(&self, from: usize, to: usize) -> Result { 84 | let result = call_with_error!(osrmc_table_response_duration( 85 | self.handle, 86 | from as u64, 87 | to as u64 88 | ))?; 89 | Ok(result) 90 | } 91 | 92 | pub fn get_distance(&self, from: usize, to: usize) -> Result { 93 | if !self.include_distance { 94 | return Err("get_distance() called on table response without distance".into()); 95 | } 96 | 97 | let result = call_with_error!(osrmc_table_response_distance( 98 | self.handle, 99 | from as u64, 100 | to as u64 101 | ))?; 102 | Ok(result) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::ffi::CString; 4 | use std::os::unix::ffi::OsStrExt; 5 | use std::path::Path; 6 | 7 | #[macro_use] 8 | mod macros; 9 | 10 | mod errors; 11 | mod route; 12 | mod table; 13 | 14 | pub use self::errors::{Error, ErrorKind, Result}; 15 | pub use self::table::Response as TableResponse; 16 | 17 | #[derive(Clone, Debug, PartialEq)] 18 | pub struct Coordinate { 19 | pub latitude: f32, 20 | pub longitude: f32, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct RouteResponse { 25 | pub duration: f32, 26 | pub distance: f32, 27 | } 28 | 29 | struct Config { 30 | handle: osrmc_sys::osrmc_config_t, 31 | } 32 | 33 | impl_drop!(Config, osrmc_sys::osrmc_config_destruct); 34 | 35 | impl Config { 36 | fn new>(path: P) -> Result { 37 | let path = path.as_ref().as_os_str().as_bytes(); 38 | let cstring = CString::new(path)?; 39 | let handle = call_with_error!(osrmc_config_construct(cstring.as_ptr()))?; 40 | Ok(Config { handle }) 41 | } 42 | } 43 | 44 | pub struct Osrm { 45 | handle: osrmc_sys::osrmc_osrm_t, 46 | } 47 | 48 | impl_drop!(Osrm, osrmc_sys::osrmc_osrm_destruct); 49 | 50 | // This is just a thin wrapper around the OSRM C++ class, which is thread-safe. 51 | unsafe impl Send for Osrm {} 52 | unsafe impl Sync for Osrm {} 53 | 54 | impl Osrm { 55 | pub fn new>(path: P) -> Result { 56 | let config = Config::new(path)?; 57 | let handle = call_with_error!(osrmc_osrm_construct(config.handle))?; 58 | Ok(Osrm { handle }) 59 | } 60 | 61 | pub fn table( 62 | &self, 63 | sources: &[Coordinate], 64 | destinations: &[Coordinate], 65 | ) -> Result { 66 | if sources.is_empty() || destinations.is_empty() { 67 | return Err("sources/destinations can not be empty".into()); 68 | } 69 | 70 | let include_distance = true; 71 | let mut params = table::Parameters::new(include_distance)?; 72 | for source in sources { 73 | params.add_source(source)?; 74 | } 75 | for destination in destinations { 76 | params.add_destination(destination)?; 77 | } 78 | 79 | let handle = call_with_error!(osrmc_table(self.handle, params.handle))?; 80 | Ok(TableResponse { 81 | include_distance, 82 | handle, 83 | }) 84 | } 85 | 86 | pub fn route(&self, from: &Coordinate, to: &Coordinate) -> Result { 87 | let mut params = route::Parameters::new()?; 88 | params.add_coordinate(from)?; 89 | params.add_coordinate(to)?; 90 | 91 | let handle = call_with_error!(osrmc_route(self.handle, params.handle))?; 92 | let response = route::Response::from(handle); 93 | 94 | Ok(RouteResponse { 95 | duration: response.duration()?, 96 | distance: response.distance()?, 97 | }) 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use std::path::Path; 104 | 105 | use super::*; 106 | use crate::errors::ErrorKind; 107 | 108 | const OSRM_FILE: &str = "./test-data/gcc-states-latest.osrm"; 109 | 110 | const COORDINATE_A: Coordinate = Coordinate { 111 | latitude: 24.447_618, 112 | longitude: 54.371_037, 113 | }; 114 | const COORDINATE_B: Coordinate = Coordinate { 115 | latitude: 24.454_87, 116 | longitude: 54.391_076, 117 | }; 118 | const COORDINATE_C: Coordinate = Coordinate { 119 | latitude: 24.454_979, 120 | longitude: 54.376_52, 121 | }; 122 | 123 | const COORDINATE_BROKEN_A: Coordinate = Coordinate { 124 | latitude: 25.071_65, 125 | longitude: 55.402_115, 126 | }; 127 | const COORDINATE_BROKEN_B: Coordinate = Coordinate { 128 | latitude: 25.086_226, 129 | longitude: 55.385_334, 130 | }; 131 | 132 | const COORDINATE_INVALID: Coordinate = Coordinate { 133 | latitude: -190.0, 134 | longitude: -190.0, 135 | }; 136 | 137 | fn load_osrm() -> Result { 138 | if !Path::new(OSRM_FILE).exists() { 139 | return Err(format!( 140 | "Couldn't load {}. Has `./prepare-test-data.sh` been run?", 141 | OSRM_FILE 142 | ) 143 | .into()); 144 | } 145 | 146 | let osrm = Osrm::new(OSRM_FILE)?; 147 | Ok(osrm) 148 | } 149 | 150 | #[test] 151 | fn test_table() -> Result<()> { 152 | let osrm = load_osrm()?; 153 | let result = osrm.table(&[COORDINATE_A, COORDINATE_B], &[COORDINATE_C])?; 154 | 155 | assert_ne!(result.get_duration(0, 0)?, 0.0); 156 | assert_ne!(result.get_duration(1, 0)?, 0.0); 157 | assert_ne!(result.get_duration(0, 0)?, result.get_duration(1, 0)?); 158 | 159 | assert_ne!(result.get_distance(0, 0)?, 0.0); 160 | assert_ne!(result.get_distance(1, 0)?, 0.0); 161 | assert_ne!(result.get_distance(0, 0)?, result.get_distance(1, 0)?); 162 | 163 | Ok(()) 164 | } 165 | 166 | #[test] 167 | fn test_table_no_parameters() -> Result<()> { 168 | let osrm = load_osrm()?; 169 | let result = osrm.table(&[], &[]); 170 | assert!(result.is_err()); 171 | Ok(()) 172 | } 173 | 174 | #[test] 175 | fn test_route() -> Result<()> { 176 | let osrm = load_osrm()?; 177 | 178 | let result1 = osrm.route(&COORDINATE_A, &COORDINATE_B)?; 179 | 180 | assert_ne!(result1.duration, 0.0); 181 | assert_ne!(result1.distance, 0.0); 182 | 183 | let result2 = osrm.route(&COORDINATE_A, &COORDINATE_C)?; 184 | 185 | assert_ne!(result2.duration, 0.0); 186 | assert_ne!(result2.distance, 0.0); 187 | 188 | assert_ne!(result1.duration, result2.duration); 189 | assert_ne!(result1.distance, result2.distance); 190 | 191 | Ok(()) 192 | } 193 | 194 | #[test] 195 | fn test_unroutable() -> Result<()> { 196 | let osrm = load_osrm()?; 197 | 198 | let result = osrm.route(&COORDINATE_BROKEN_A, &COORDINATE_BROKEN_B); 199 | assert_eq!(result.err().unwrap().kind(), ErrorKind::NoRoute); 200 | 201 | Ok(()) 202 | } 203 | 204 | #[test] 205 | fn test_unroutable_table() -> Result<()> { 206 | let osrm = load_osrm()?; 207 | 208 | let resp = osrm.table(&[COORDINATE_BROKEN_A], &[COORDINATE_BROKEN_B])?; 209 | 210 | let result = resp.get_duration(0, 0); 211 | assert_eq!(result.err().unwrap().kind(), ErrorKind::NoRoute); 212 | 213 | let result = resp.get_distance(0, 0); 214 | assert_eq!(result.err().unwrap().kind(), ErrorKind::NoRoute); 215 | 216 | Ok(()) 217 | } 218 | 219 | #[test] 220 | fn test_invalid_coordinate() -> Result<()> { 221 | let osrm = load_osrm()?; 222 | 223 | let result = osrm.route(&COORDINATE_A, &COORDINATE_INVALID); 224 | assert_eq!(result.err().unwrap().kind(), ErrorKind::InvalidCoordinate); 225 | 226 | Ok(()) 227 | } 228 | } 229 | --------------------------------------------------------------------------------