-
Notifications
You must be signed in to change notification settings - Fork 8
/
ffi.rs
142 lines (132 loc) · 4.27 KB
/
ffi.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::polylabel;
use geo::{GeoFloat, LineString, Point, Polygon};
use libc::{c_double, c_void, size_t};
use std::f64;
use std::slice;
/// Wrapper for a void pointer to a sequence of [`Array`](struct.Array.html)s, and the sequence length. Used for FFI.
///
/// Each sequence entry represents an inner Polygon ring.
#[repr(C)]
#[derive(Copy, Clone)]
pub struct WrapperArray {
pub data: *const Array,
pub len: size_t,
}
/// Wrapper for a void pointer to a sequence of 2-element arrays representing points, and the sequence length. Used for FFI.
///
/// Used for the outer Polygon shell. `data` is a `Vec<[c_double; 2]>`.
#[repr(C)]
pub struct Array {
pub data: *const c_void,
pub len: size_t,
}
/// FFI struct for returned optimum Polygon label position
#[repr(C)]
pub struct Position {
pub x_pos: c_double,
pub y_pos: c_double,
}
// convert a Polylabel result Point into values that can be sent across the FFI boundary
impl<T> From<Point<T>> for Position
where
T: GeoFloat,
{
fn from(point: Point<T>) -> Position {
Position {
x_pos: point.x().to_f64().unwrap() as c_double,
y_pos: point.y().to_f64().unwrap() as c_double,
}
}
}
fn reconstitute(arr: &Array) -> Vec<[f64; 2]> {
unsafe { slice::from_raw_parts(arr.data as *mut [f64; 2], arr.len).to_vec() }
}
fn reconstitute2(arr: WrapperArray) -> Vec<Vec<[f64; 2]>> {
let arrays = unsafe { slice::from_raw_parts(arr.data as *mut Array, arr.len) };
arrays.iter().map(reconstitute).collect()
}
/// FFI access to the [`polylabel`](fn.polylabel.html) function
///
/// Accepts three arguments:
///
/// - an exterior ring representing a Polygon shell or closed LineString
/// - zero or more interior rings representing Polygon holes
/// - a tolerance `c_double`.
/// If an error occurs while attempting to calculate the label position, the resulting point coordinates
/// will be `NaN, NaN`.
#[no_mangle]
pub extern "C" fn polylabel_ffi(
outer: Array,
inners: WrapperArray,
tolerance: c_double,
) -> Position {
let exterior: LineString<_> = unsafe {
slice::from_raw_parts(outer.data as *mut [c_double; 2], outer.len)
.to_vec()
.into()
};
let interior: Vec<Vec<[f64; 2]>> = reconstitute2(inners);
let ls_int: Vec<LineString<c_double>> = interior.into_iter().map(|vec| vec.into()).collect();
let poly = Polygon::new(exterior, ls_int);
polylabel(&poly, &tolerance)
.unwrap_or_else(|_| Point::new(f64::NAN, f64::NAN))
.into()
}
#[cfg(test)]
mod tests {
use crate::ffi::{polylabel_ffi, reconstitute2, Array, WrapperArray};
use geo::Point;
use libc::{c_void, size_t};
use std::mem;
// Only used for testing
fn gen_array(v: Vec<[f64; 2]>) -> Array {
let array = Array {
data: v.as_ptr() as *const c_void,
len: v.len() as size_t,
};
mem::forget(v);
array
}
// only used for testing
fn gen_wrapperarray(v: Vec<Vec<[f64; 2]>>) -> WrapperArray {
let converted: Vec<Array> = v.into_iter().map(gen_array).collect();
let array2 = WrapperArray {
data: converted.as_ptr(),
len: converted.len() as size_t,
};
mem::forget(converted);
array2
}
#[test]
fn test_array() {
let i_a = vec![[0.5, 0.5], [1.0, 1.0], [1.5, 0.5]];
let i_b = vec![[0.55, 0.55], [0.8, 0.8], [1.2, 0.55]];
let inners = vec![i_a, i_b];
let array = gen_wrapperarray(inners);
let rec_inners = reconstitute2(array);
assert_eq!(rec_inners[0][2], [1.5, 0.5])
}
#[test]
fn test_ffi() {
let ext_vec = vec![
[4.0, 1.0],
[5.0, 2.0],
[5.0, 3.0],
[4.0, 4.0],
[3.0, 4.0],
[2.0, 3.0],
[2.0, 2.0],
[3.0, 1.0],
[4.0, 1.0],
];
let int_vec = vec![
vec![[3.5, 3.5], [4.4, 2.0], [2.6, 2.0], [3.5, 3.5]],
vec![[4.0, 3.0], [4.0, 3.2], [4.5, 3.2], [4.0, 3.0]],
];
let outer = gen_array(ext_vec);
let inners = gen_wrapperarray(int_vec);
let res = polylabel_ffi(outer, inners, 0.1);
let res_point = Point::new(res.x_pos, res.y_pos);
assert_eq!(res_point, Point::new(3.125, 2.875));
}
}