use geonative_core::{Coord, LineString, Polygon};
pub fn distance(a: Coord, b: Coord) -> f64 {
let dx = b.x - a.x;
let dy = b.y - a.y;
(dx * dx + dy * dy).sqrt()
}
pub fn length(ls: &LineString) -> f64 {
if ls.coords.len() < 2 {
return 0.0;
}
let mut total = 0.0;
for w in ls.coords.windows(2) {
total += distance(w[0], w[1]);
}
total
}
pub fn signed_area(ring: &[Coord]) -> f64 {
if ring.len() < 3 {
return 0.0;
}
let mut sum = 0.0;
let n = ring.len();
for i in 0..n {
let j = (i + 1) % n;
sum += ring[i].x * ring[j].y - ring[j].x * ring[i].y;
}
sum * 0.5
}
pub fn ring_area(ring: &[Coord]) -> f64 {
signed_area(ring).abs()
}
pub fn area(p: &Polygon) -> f64 {
let exterior = ring_area(&p.exterior.coords);
let holes: f64 = p.holes.iter().map(|h| ring_area(&h.coords)).sum();
(exterior - holes).max(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn xy(x: f64, y: f64) -> Coord {
Coord {
x,
y,
z: None,
m: None,
}
}
#[test]
fn distance_matches_pythagoras() {
assert!((distance(xy(0.0, 0.0), xy(3.0, 4.0)) - 5.0).abs() < 1e-12);
assert_eq!(distance(xy(1.0, 1.0), xy(1.0, 1.0)), 0.0);
}
#[test]
fn length_of_two_segment_line() {
let ls = LineString::new(vec![xy(0.0, 0.0), xy(3.0, 0.0), xy(3.0, 4.0)]);
assert!((length(&ls) - 7.0).abs() < 1e-12);
}
#[test]
fn length_of_short_input_is_zero() {
assert_eq!(length(&LineString::default()), 0.0);
assert_eq!(length(&LineString::new(vec![xy(1.0, 1.0)])), 0.0);
}
#[test]
fn signed_area_unit_square_ccw_is_one() {
let ring = vec![
xy(0.0, 0.0),
xy(1.0, 0.0),
xy(1.0, 1.0),
xy(0.0, 1.0),
xy(0.0, 0.0),
];
assert!((signed_area(&ring) - 1.0).abs() < 1e-12);
}
#[test]
fn signed_area_unit_square_cw_is_minus_one() {
let mut ring = vec![
xy(0.0, 0.0),
xy(1.0, 0.0),
xy(1.0, 1.0),
xy(0.0, 1.0),
xy(0.0, 0.0),
];
ring.reverse();
assert!((signed_area(&ring) - (-1.0)).abs() < 1e-12);
}
#[test]
fn ring_area_is_unsigned() {
let ring = vec![
xy(0.0, 0.0),
xy(1.0, 0.0),
xy(1.0, 1.0),
xy(0.0, 1.0),
xy(0.0, 0.0),
];
assert!((ring_area(&ring) - 1.0).abs() < 1e-12);
let mut reversed = ring.clone();
reversed.reverse();
assert!((ring_area(&reversed) - 1.0).abs() < 1e-12);
}
#[test]
fn polygon_area_subtracts_holes() {
let p = Polygon::new(
LineString::new(vec![
xy(0.0, 0.0),
xy(10.0, 0.0),
xy(10.0, 10.0),
xy(0.0, 10.0),
xy(0.0, 0.0),
]),
vec![LineString::new(vec![
xy(2.0, 2.0),
xy(4.0, 2.0),
xy(4.0, 4.0),
xy(2.0, 4.0),
xy(2.0, 2.0),
])],
);
assert!((area(&p) - 96.0).abs() < 1e-12);
}
#[test]
fn polygon_with_multiple_holes_sums_them() {
let p = Polygon::new(
LineString::new(vec![
xy(0.0, 0.0),
xy(10.0, 0.0),
xy(10.0, 10.0),
xy(0.0, 10.0),
xy(0.0, 0.0),
]),
vec![
LineString::new(vec![
xy(1.0, 1.0),
xy(2.0, 1.0),
xy(2.0, 2.0),
xy(1.0, 2.0),
xy(1.0, 1.0),
]),
LineString::new(vec![
xy(5.0, 5.0),
xy(8.0, 5.0),
xy(8.0, 8.0),
xy(5.0, 8.0),
xy(5.0, 5.0),
]),
],
);
assert!((area(&p) - 90.0).abs() < 1e-12);
}
#[test]
fn degenerate_ring_zero_area() {
assert_eq!(signed_area(&[]), 0.0);
assert_eq!(signed_area(&[xy(0.0, 0.0)]), 0.0);
assert_eq!(signed_area(&[xy(0.0, 0.0), xy(1.0, 1.0)]), 0.0);
}
}