#![doc(
html_logo_url = "https://cdn.rawgit.com/urschrei/polyline-ffi/master/line.svg",
html_root_url = "https://docs.rs/polyline-ffi/"
)]
#![deny(
clippy::cast_slice_from_raw_parts,
clippy::cast_slice_different_sizes,
clippy::invalid_null_ptr_usage,
clippy::ptr_as_ptr,
clippy::transmute_ptr_to_ref
)]
use polyline::{decode_polyline, encode_coordinates};
use std::ffi::{CStr, CString};
use std::slice;
use std::{f64, ptr};
use geo_types::{CoordFloat, LineString};
use libc::c_char;
#[allow(dead_code)]
enum Precision {
Zero,
One,
Two,
Three,
Four,
Five,
Six,
}
fn get_precision(input: u32) -> Option<u32> {
match input {
5 => Some(Precision::Five as u32),
6 => Some(Precision::Six as u32),
_ => None,
}
}
#[repr(C)]
pub struct ExternalArray {
pub data: *const libc::c_void,
pub len: libc::size_t,
}
#[repr(C)]
pub struct InternalArray {
pub data: *mut libc::c_void,
pub len: libc::size_t,
}
impl Drop for InternalArray {
fn drop(&mut self) {
if self.data.is_null() {
return;
}
unsafe {
let p = ptr::slice_from_raw_parts_mut(self.data.cast::<[f64; 2]>(), self.len);
drop(Box::from_raw(p));
};
}
}
impl<T> From<LineString<T>> for InternalArray
where
T: CoordFloat,
{
fn from(sl: LineString<T>) -> Self {
let v: Vec<[T; 2]> = sl.0.iter().map(|p| [p.x, p.y]).collect();
let boxed = v.into_boxed_slice();
let blen = boxed.len();
let rawp = Box::into_raw(boxed);
InternalArray {
data: rawp.cast::<libc::c_void>(),
len: blen as libc::size_t,
}
}
}
impl From<InternalArray> for LineString<f64> {
fn from(arr: InternalArray) -> Self {
unsafe {
let p = ptr::slice_from_raw_parts_mut(arr.data.cast::<[f64; 2]>(), arr.len);
let v = Box::from_raw(p).to_vec();
v.into()
}
}
}
impl From<Vec<[f64; 2]>> for InternalArray {
fn from(v: Vec<[f64; 2]>) -> Self {
let boxed = v.into_boxed_slice();
let blen = boxed.len();
let rawp = Box::into_raw(boxed);
InternalArray {
data: rawp.cast::<libc::c_void>(),
len: blen as libc::size_t,
}
}
}
impl From<Vec<[f64; 2]>> for ExternalArray {
fn from(v: Vec<[f64; 2]>) -> Self {
let boxed = v.into_boxed_slice();
let blen = boxed.len();
let rawp = Box::into_raw(boxed);
ExternalArray {
data: rawp.cast::<libc::c_void>(),
len: blen as libc::size_t,
}
}
}
impl From<ExternalArray> for LineString<f64> {
fn from(arr: ExternalArray) -> Self {
unsafe {
let v = slice::from_raw_parts(arr.data as *mut [f64; 2], arr.len).to_vec();
v.into()
}
}
}
fn arr_from_string(incoming: &str, precision: u32) -> InternalArray {
let result: InternalArray = if get_precision(precision).is_some() {
match decode_polyline(incoming, precision) {
Ok(res) => res.into(),
Err(_) => vec![[f64::NAN, f64::NAN]].into(),
}
} else {
vec![[f64::NAN, f64::NAN]].into()
};
result
}
fn string_from_arr(incoming: ExternalArray, precision: u32) -> String {
let inc: LineString<_> = incoming.into();
if get_precision(precision).is_some() {
match encode_coordinates(Into::<LineString<_>>::into(inc), precision) {
Ok(res) => res,
Err(res) => res,
}
} else {
"Bad precision parameter supplied".to_string()
}
}
#[no_mangle]
pub unsafe extern "C" fn decode_polyline_ffi(pl: *const c_char, precision: u32) -> InternalArray {
let s = CStr::from_ptr(pl).to_str();
if let Ok(unwrapped) = s {
arr_from_string(unwrapped, precision)
} else {
vec![[f64::NAN, f64::NAN]].into()
}
}
#[no_mangle]
pub extern "C" fn encode_coordinates_ffi(coords: ExternalArray, precision: u32) -> *mut c_char {
let s: String = string_from_arr(coords, precision);
match CString::new(s) {
Ok(res) => res.into_raw(),
Err(_) => CString::new("Couldn't decode Polyline".to_string())
.unwrap()
.into_raw(),
}
}
#[no_mangle]
pub extern "C" fn drop_float_array(_: InternalArray) {}
#[no_mangle]
pub unsafe extern "C" fn drop_cstring(p: *mut c_char) {
drop(CString::from_raw(p));
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn test_drop_empty_float_array() {
let original: LineString<_> = vec![[2.0, 1.0], [4.0, 3.0]].into();
let mut arr: InternalArray = original.into();
arr.data = ptr::null_mut();
drop_float_array(arr);
}
#[test]
fn test_coordinate_conversion() {
let input = vec![[2.0, 1.0], [4.0, 3.0]];
let output = "_ibE_seK_seK_seK";
let input_arr: ExternalArray = input.into();
let transformed: String = super::string_from_arr(input_arr, 5);
assert_eq!(transformed, output);
}
#[test]
fn test_string_conversion() {
let input = "_ibE_seK_seK_seK";
let output = vec![[2.0, 1.0], [4.0, 3.0]];
let transformed: InternalArray = super::arr_from_string(input, 5);
let v = unsafe {
slice::from_raw_parts(transformed.data as *mut [f64; 2], transformed.len).to_vec()
};
let ls: LineString<_> = v.into();
assert_eq!(ls, output.into());
}
#[test]
#[should_panic]
fn test_bad_string_conversion() {
let input = "_p~iF~ps|U_u🗑lLnnqC_mqNvxq`@";
let output = vec![[1.0, 2.0], [3.0, 4.0]];
let transformed: InternalArray = super::arr_from_string(input, 5);
let v = unsafe {
slice::from_raw_parts(transformed.data as *mut [f64; 2], transformed.len).to_vec()
};
let ls: LineString<_> = v.into();
assert_eq!(ls, output.into());
}
#[test]
fn test_long_vec() {
use std::clone::Clone;
let arr = include!("../test_fixtures/berlin.rs");
let s = include!("../test_fixtures/berlin_decoded.rs");
for _ in 0..9999 {
let a = arr.clone();
let n = 5;
let input_ls: ExternalArray = a.into();
let transformed: String = super::string_from_arr(input_ls, n);
assert_eq!(transformed, s);
}
}
}