use crate::geometry::*;
use crate::{CoordFloat, EQUATORIAL_EARTH_RADIUS};
pub trait ChamberlainDuquetteArea<T>
where
T: CoordFloat,
{
fn chamberlain_duquette_signed_area(&self) -> T;
fn chamberlain_duquette_unsigned_area(&self) -> T;
}
impl<T> ChamberlainDuquetteArea<T> for Polygon<T>
where
T: CoordFloat,
{
fn chamberlain_duquette_signed_area(&self) -> T {
self.interiors()
.iter()
.fold(ring_area(self.exterior()), |total, next| {
total - ring_area(next)
})
}
fn chamberlain_duquette_unsigned_area(&self) -> T {
self.chamberlain_duquette_signed_area().abs()
}
}
fn ring_area<T>(coords: &LineString<T>) -> T
where
T: CoordFloat,
{
let mut total = T::zero();
let coords_len = coords.0.len();
if coords_len > 2 {
for i in 0..coords_len {
let (lower_index, middle_index, upper_index) = if i == coords_len - 2 {
(coords_len - 2, coords_len - 1, 0)
} else if i == coords_len - 1 {
(coords_len - 1, 0, 1)
} else {
(i, i + 1, i + 2)
};
let p1 = coords[lower_index];
let p2 = coords[middle_index];
let p3 = coords[upper_index];
total = total + (p3.x.to_radians() - p1.x.to_radians()) * p2.y.to_radians().sin();
}
total = total
* T::from(EQUATORIAL_EARTH_RADIUS).unwrap()
* T::from(EQUATORIAL_EARTH_RADIUS).unwrap()
/ T::from(-2).unwrap();
}
total
}
macro_rules! zero_impl {
($type:ident) => {
impl<T> ChamberlainDuquetteArea<T> for $type<T>
where
T: CoordFloat,
{
fn chamberlain_duquette_signed_area(&self) -> T {
T::zero()
}
fn chamberlain_duquette_unsigned_area(&self) -> T {
T::zero()
}
}
};
}
macro_rules! to_polygon_impl {
($type:ident) => {
impl<T> ChamberlainDuquetteArea<T> for $type<T>
where
T: CoordFloat,
{
fn chamberlain_duquette_signed_area(&self) -> T {
self.to_polygon().chamberlain_duquette_signed_area()
}
fn chamberlain_duquette_unsigned_area(&self) -> T {
self.to_polygon().chamberlain_duquette_unsigned_area()
}
}
};
}
macro_rules! sum_impl {
($type:ident) => {
impl<T> ChamberlainDuquetteArea<T> for $type<T>
where
T: CoordFloat,
{
fn chamberlain_duquette_signed_area(&self) -> T {
self.iter().fold(T::zero(), |total, next| {
total + next.chamberlain_duquette_signed_area()
})
}
fn chamberlain_duquette_unsigned_area(&self) -> T {
self.iter().fold(T::zero(), |total, next| {
total + next.chamberlain_duquette_unsigned_area()
})
}
}
};
}
zero_impl!(Point);
zero_impl!(Line);
zero_impl!(LineString);
zero_impl!(MultiPoint);
zero_impl!(MultiLineString);
to_polygon_impl!(Rect);
to_polygon_impl!(Triangle);
sum_impl!(GeometryCollection);
sum_impl!(MultiPolygon);
impl<T> ChamberlainDuquetteArea<T> for Geometry<T>
where
T: CoordFloat,
{
crate::geometry_delegate_impl! {
fn chamberlain_duquette_signed_area(&self) -> T;
fn chamberlain_duquette_unsigned_area(&self) -> T;
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::polygon;
#[test]
fn test_negative() {
let polygon = polygon![
(x: 125., y: -15.),
(x: 144., y: -15.),
(x: 154., y: -27.),
(x: 148., y: -39.),
(x: 130., y: -33.),
(x: 117., y: -37.),
(x: 113., y: -22.),
(x: 125., y: -15.),
];
assert_relative_eq!(
-7766240997209.013,
polygon.chamberlain_duquette_signed_area()
);
}
#[test]
fn test_positive() {
let polygon = polygon![
(x: 125., y: -15.),
(x: 113., y: -22.),
(x: 117., y: -37.),
(x: 130., y: -33.),
(x: 148., y: -39.),
(x: 154., y: -27.),
(x: 144., y: -15.),
(x: 125., y: -15.),
];
assert_relative_eq!(
7766240997209.013,
polygon.chamberlain_duquette_signed_area()
);
}
#[test]
fn test_holes() {
let poly = polygon![
exterior: [
(x: 0., y: 0.),
(x: 10., y: 0.),
(x: 10., y: 10.),
(x: 0., y: 10.),
(x: 0., y: 0.)
],
interiors: [
[
(x: 1., y: 1.),
(x: 2., y: 1.),
(x: 2., y: 2.),
(x: 1., y: 2.),
(x: 1., y: 1.),
],
[
(x: 5., y: 5.),
(x: 6., y: 5.),
(x: 6., y: 6.),
(x: 5., y: 6.),
(x: 5., y: 5.)
],
],
];
assert_relative_eq!(1208198651182.4727, poly.chamberlain_duquette_signed_area());
}
}