rsc_osrm 0.1.56

rust wrapper for osrm, folk from TehGoat/rs_osrm
use crate::general::c_string_to_option_string;
use crate::general::c_string_to_string;
use crate::general::Approach;
use crate::general::CGeneralOptions;
use crate::general::COsrmRouteLeg;
use crate::general::Coordinate;
use crate::general::GeneralOptions;
use crate::general::RouteLeg;
use crate::route::AnnotationsType;
use crate::route::GeometriesType;
use crate::route::OverviewType;
use crate::Boolean;
use crate::Osrm;
use crate::Status;
use core::ffi::c_void;
use std::os::raw::c_char;
use std::os::raw::c_double;
use std::{ffi::CStr, os::raw::c_float, os::raw::c_int, slice};

#[link(name = "c_osrm")]
extern "C" {
    fn match_result_destroy(result: *mut CMatchResult);

    fn osrm_match(
        osrm: *mut c_void,
        request: *mut CMatchRequest,
        result: *mut *mut CMatchResult,
    ) -> Status;
}

#[repr(C)]
#[derive(Clone, Debug)]
pub(crate) struct CMatchWaypoint {
    hint: *const c_char,
    distance: c_double,
    name: *const c_char,
    location: [c_double; 2],
    matchings_index: c_int,
    waypoint_index: c_int,
    alternatives_count: c_int,
    snap_failed: c_int,
}

#[derive(Debug)]
pub struct MatchWaypoint {
    pub hint: Option<String>,
    pub distance: f64,
    pub name: String,
    pub location: [f64; 2],
    pub matchings_index: i32,
    pub waypoint_index: i32,
    pub alternatives_count: i32,
}

impl MatchWaypoint {
    pub(crate) fn new(c_match: &CMatchWaypoint) -> MatchWaypoint {
        MatchWaypoint {
            hint: c_string_to_option_string(c_match.hint),
            distance: c_match.distance,
            name: c_string_to_string(c_match.name),
            location: c_match.location,
            matchings_index: c_match.matchings_index,
            waypoint_index: c_match.waypoint_index,
            alternatives_count: c_match.alternatives_count,
        }
    }
}

#[repr(C)]
#[derive(Clone, Debug)]
pub(crate) struct CMatchRoute {
    pub(crate) duration: c_double,
    pub(crate) distance: c_double,
    pub(crate) weight_name: *const c_char,
    pub(crate) weight: c_double,
    pub(crate) geometry: *const c_char,
    pub(crate) legs: *const COsrmRouteLeg,
    pub(crate) number_of_legs: c_int,
    pub(crate) confidence: c_float,
}

#[derive(Debug)]
pub struct MatchRoute {
    pub duration: f64,
    pub distance: f64,
    pub weight_name: Option<String>,
    pub weight: f64,
    pub geometry: Option<String>,
    pub legs: Vec<RouteLeg>,
    pub confidence: f32,
}

impl MatchRoute {
    // todo: Need a way to use the type defined in c directly or do type check between rust type and c type statically.
    //  because if they have some difference then it crash at runtime
    pub(crate) fn new(c_route: &CMatchRoute) -> MatchRoute {
        let mut weight_name: Option<String> = None;
        if c_route.weight_name != std::ptr::null() {
            weight_name = Option::from(c_string_to_string(c_route.weight_name));
        }

        let mut geometry: Option<String> = None;
        if c_route.geometry != std::ptr::null() {
            geometry = Option::from(c_string_to_string(c_route.geometry));
        }

        let mut legs: Vec<RouteLeg> = Vec::new();

        if c_route.legs != std::ptr::null_mut() {
            let legs_vec = unsafe {
                slice::from_raw_parts(c_route.legs, c_route.number_of_legs as usize).to_vec()
            };

            for leg in legs_vec {
                legs.push(leg.to_route_leg());
            }
        }

        MatchRoute {
            duration: c_route.duration,
            distance: c_route.distance,
            weight_name,
            weight: c_route.weight,
            geometry,
            legs,
            confidence: c_route.confidence,
        }
    }
}

#[repr(C)]
#[derive(Clone)]
pub enum Annotations {
    NONE = 0,
    DURATION = 1,
    DISTANCE = 2,
    ALL = 3,
}

#[repr(C)]
#[derive(Clone)]
pub enum FallbackCoordinate {
    INPUT = 0,
    SNAPPED = 1,
}

#[repr(C)]
#[derive(Clone)]
pub enum Gap {
    Split = 0,
    Ignore = 1,
}

#[repr(C)]
struct CMatchRequest {
    general_options: CGeneralOptions,
    steps: Boolean,
    geometries: GeometriesType,
    annotations: Boolean,
    annotations_type: AnnotationsType,
    overview: OverviewType,
    timestamps: *const c_int,
    gaps: Gap,
    tidy: Boolean,
    waypoints: *const c_int,
    number_of_waypoints: c_int,
}

impl CMatchRequest {
    fn new(request: &mut MatchRequest) -> CMatchRequest {
        let mut c_request = CMatchRequest {
            general_options: CGeneralOptions::new(&mut request.general_options),
            steps: Boolean::from(request.steps),
            geometries: request.geometries.clone(),
            annotations: Boolean::from(request.annotations),
            annotations_type: request.annotations_type.clone(),
            overview: request.overview.clone(),
            timestamps: std::ptr::null(),
            gaps: request.gaps.clone(),
            tidy: Boolean::from(request.tidy),
            waypoints: std::ptr::null(),
            number_of_waypoints: 0,
        };

        match &request.timestamps {
            Some(timestamps) => {
                c_request.timestamps = timestamps.as_ptr();
            }
            None => {}
        }

        match &request.waypoints {
            Some(waypoints) => {
                c_request.waypoints = waypoints.as_ptr();
                c_request.number_of_waypoints = waypoints.len() as c_int;
            }
            None => {}
        }

        c_request
    }
}

pub struct MatchRequest {
    general_options: GeneralOptions,
    steps: bool,
    geometries: GeometriesType,
    annotations: bool,
    annotations_type: AnnotationsType,
    overview: OverviewType,
    timestamps: Option<Vec<i32>>,
    gaps: Gap,
    tidy: bool,
    waypoints: Option<Vec<i32>>,
}

impl MatchRequest {
    pub fn new(coordinates: &Vec<Coordinate>) -> MatchRequest {
        MatchRequest {
            general_options: GeneralOptions::new(coordinates),
            steps: false,
            geometries: GeometriesType::Polyline,
            annotations: false,
            annotations_type: AnnotationsType::None,
            overview: OverviewType::Simplified,
            timestamps: None,
            gaps: Gap::Split,
            tidy: false,
            waypoints: None,
        }
    }

    pub fn geometries(&mut self, val: GeometriesType) -> &mut MatchRequest {
        self.geometries = val;
        self
    }

    pub fn overview(&mut self, val: OverviewType) -> &mut MatchRequest {
        self.overview = val;
        self
    }

    pub fn approach(&mut self, approaches: Option<Vec<Option<Approach>>>) -> &mut MatchRequest {
        self.general_options.approach = approaches;
        self
    }

    pub fn exclude(&mut self, exclude: Option<Vec<String>>) -> &mut MatchRequest {
        self.general_options.exclude(&exclude);
        self
    }

    pub fn skip_waypoints(&mut self, val: bool) -> &mut MatchRequest {
        self.general_options.skip_waypoints(val);
        self
    }

    pub fn waypoints(&mut self, val: &Option<Vec<i32>>) -> &mut MatchRequest {
        self.waypoints = val.clone();
        self
    }

    pub fn steps(&mut self, val: bool) -> &mut MatchRequest {
        self.steps = val;
        self
    }

    pub fn radiuses(&mut self, val: &Option<Vec<Option<f64>>>) -> &mut MatchRequest {
        self.general_options.radiuses(val);
        self
    }

    pub fn timestamps(&mut self, val: &Option<Vec<i32>>) -> &mut MatchRequest {
        self.timestamps = val.clone();
        self
    }

    pub fn annotations(
        &mut self,
        annotations: bool,
        annotations_type: AnnotationsType,
    ) -> &mut MatchRequest {
        self.annotations = annotations;
        self.annotations_type = annotations_type;
        self
    }

    fn validate(&mut self) -> Result<(), String> {
        self.general_options.validate()
    }

    pub fn run(&mut self, osrm: &Osrm) -> (Status, MatchResult) {
        if let Err(msg) = self.validate() {
            return (
                Status::Error,
                MatchResult {
                    code: Some("Err".to_owned()),
                    message: Some(msg),
                    tracepoints: vec![],
                    matchings: vec![],
                },
            );
        }
        unsafe {
            let mut result: *mut CMatchResult = std::ptr::null_mut();
            let result_ptr: *mut *mut CMatchResult = &mut result;

            let status = osrm_match(
                *osrm.config,
                &mut CMatchRequest::new(self) as *mut CMatchRequest,
                result_ptr,
            );

            let converted_result = MatchResult::new(&(*result));

            match_result_destroy(result);

            (status, converted_result)
        }
    }
}

#[repr(C)]
#[derive(Debug)]
struct CMatchResult {
    code: *const c_char,
    message: *const c_char,
    waypoints: *const CMatchWaypoint,
    number_of_waypoints: c_int,
    routes: *const CMatchRoute,
    number_of_routes: c_int,
}

#[derive(Debug)]
pub struct MatchResult {
    pub code: Option<String>,
    pub message: Option<String>,
    pub tracepoints: Vec<Option<MatchWaypoint>>,
    pub matchings: Vec<MatchRoute>,
}

impl MatchResult {
    fn new(c_reasult: &CMatchResult) -> MatchResult {
        let mut code: Option<String> = None;
        if c_reasult.code != std::ptr::null_mut() {
            let c_code_buf: *const c_char = c_reasult.code;
            let c_code_str: &CStr = unsafe { CStr::from_ptr(c_code_buf) };
            let code_str_slice: &str = c_code_str.to_str().unwrap();
            code = Option::from(code_str_slice.to_owned());
        }

        let mut message: Option<String> = None;
        if c_reasult.message != std::ptr::null_mut() {
            let c_message_buf: *const c_char = c_reasult.message;
            let c_message_str: &CStr = unsafe { CStr::from_ptr(c_message_buf) };
            let message_str_slice: &str = c_message_str.to_str().unwrap();
            message = Option::from(message_str_slice.to_owned());
        }

        let mut waypoints: Vec<Option<MatchWaypoint>> = Vec::new();
        if c_reasult.waypoints != std::ptr::null_mut() {
            let waypoints_vec = unsafe {
                slice::from_raw_parts(c_reasult.waypoints, c_reasult.number_of_waypoints as usize)
                    .to_vec()
            };

            for waypoint in &waypoints_vec {
                match waypoint.snap_failed {
                    0 => {
                        waypoints.push(Some(MatchWaypoint::new(waypoint)));
                    }
                    _ => {
                        waypoints.push(None);
                    }
                }
            }
        }

        let mut routes: Vec<MatchRoute> = Vec::new();
        if c_reasult.routes != std::ptr::null_mut() {
            let routes_vec = unsafe {
                slice::from_raw_parts(c_reasult.routes, c_reasult.number_of_routes as usize)
                    .to_vec()
            };

            for route in routes_vec {
                routes.push(MatchRoute::new(&route));
            }
        }

        MatchResult {
            code,
            message,
            tracepoints: waypoints,
            matchings: routes,
        }
    }
}