use crate::geomath::ang_diff;
use crate::geomath::ang_normalize;
use crate::Geodesic;
use crate::geodesic_capability as caps;
const POLYGONAREA_MASK: u64 =
caps::LATITUDE | caps::LONGITUDE | caps::DISTANCE | caps::AREA | caps::LONG_UNROLL;
#[cfg(feature = "accurate")]
use accurate::traits::*;
#[derive(Debug, Default, Copy, Clone)]
pub enum Winding {
Clockwise,
#[default]
CounterClockwise,
}
#[derive(Debug, Clone)]
pub struct PolygonArea<'a> {
geoid: &'a Geodesic,
winding: Winding,
num: usize,
#[cfg(not(feature = "accurate"))]
areasum: f64,
#[cfg(feature = "accurate")]
areasum: accurate::sum::Sum2<f64>,
#[cfg(not(feature = "accurate"))]
perimetersum: f64,
#[cfg(feature = "accurate")]
perimetersum: accurate::sum::Sum2<f64>,
crossings: i64,
initial_lat: f64,
initial_lon: f64,
latest_lat: f64,
latest_lon: f64,
}
impl<'a> PolygonArea<'a> {
pub fn new(geoid: &'a Geodesic, winding: Winding) -> PolygonArea {
PolygonArea {
geoid,
winding,
num: 0,
#[cfg(not(feature = "accurate"))]
areasum: 0.0,
#[cfg(feature = "accurate")]
areasum: accurate::sum::Sum2::zero(),
#[cfg(not(feature = "accurate"))]
perimetersum: 0.0,
#[cfg(feature = "accurate")]
perimetersum: accurate::sum::Sum2::zero(),
crossings: 0,
initial_lat: 0.0,
initial_lon: 0.0,
latest_lat: 0.0,
latest_lon: 0.0,
}
}
pub fn add_point(&mut self, lat: f64, lon: f64) {
if self.num == 0 {
self.initial_lat = lat;
self.initial_lon = lon;
} else {
#[allow(non_snake_case)]
let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) = self
.geoid
._gen_inverse(self.latest_lat, self.latest_lon, lat, lon, POLYGONAREA_MASK);
self.perimetersum += s12;
self.areasum += S12;
self.crossings += PolygonArea::transit(self.latest_lon, lon);
}
self.latest_lat = lat;
self.latest_lon = lon;
self.num += 1;
}
pub fn add_edge(&mut self, azimuth: f64, distance: f64) {
if self.num == 0 {
panic!("PolygonArea::add_edge: No points added yet");
}
#[allow(non_snake_case)]
let (_a12, lat, lon, _azi2, _s12, _m12, _M12, _M21, S12) = self.geoid._gen_direct(
self.latest_lat,
self.latest_lon,
azimuth,
false,
distance,
POLYGONAREA_MASK,
);
self.perimetersum += distance;
self.areasum += S12;
self.crossings += PolygonArea::transitdirect(self.latest_lon, lon);
self.latest_lat = lat;
self.latest_lon = lon;
self.num += 1;
}
pub fn compute(mut self, sign: bool) -> (f64, f64, usize) {
#[allow(non_snake_case)]
let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) =
self.geoid._gen_inverse(
self.latest_lat,
self.latest_lon,
self.initial_lat,
self.initial_lon,
POLYGONAREA_MASK,
);
self.perimetersum += s12;
self.areasum += S12;
let perimetersum;
let areasum;
#[cfg(not(feature = "accurate"))]
{
perimetersum = self.perimetersum;
areasum = self.areasum;
}
#[cfg(feature = "accurate")]
{
perimetersum = self.perimetersum.sum();
areasum = self.areasum.sum();
}
self.crossings += PolygonArea::transit(self.latest_lon, self.initial_lon);
let areasum = self.reduce_area(areasum, sign);
(perimetersum, areasum, self.num)
}
pub fn test_point(&self, lat: f64, lon: f64, sign: bool) -> (f64, f64, usize) {
let mut pa = self.clone();
pa.add_point(lat, lon);
pa.compute(sign)
}
pub fn test_edge(&self, azimuth: f64, distance: f64, sign: bool) -> (f64, f64, usize) {
let mut pa = self.clone();
pa.add_edge(azimuth, distance);
pa.compute(sign)
}
fn transit(lon1: f64, lon2: f64) -> i64 {
let (lon12, _lon12s) = ang_diff(lon1, lon2);
let lon1 = ang_normalize(lon1);
let lon2 = ang_normalize(lon2);
if lon12 > 0.0 && ((lon1 < 0.0 && lon2 >= 0.0) || (lon1 > 0.0 && lon2 == 0.0)) {
1
} else if lon12 < 0.0 && lon1 >= 0.0 && lon2 < 0.0 {
-1
} else {
0
}
}
fn transitdirect(lon1: f64, lon2: f64) -> i64 {
let lon1 = lon1 % 720.0;
let lon2 = lon2 % 720.0;
let a = if 0.0 <= lon2 && lon2 < 360.0 { 0 } else { 1 };
let b = if 0.0 <= lon1 && lon1 < 360.0 { 0 } else { 1 };
a - b
}
fn reduce_area(&self, area: f64, signed: bool) -> f64 {
let geoid_area = self.geoid.area(); let mut area = area % geoid_area;
if self.crossings % 2 != 0 {
if area < 0.0 {
area += geoid_area / 2.0;
} else {
area -= geoid_area / 2.0;
}
}
area = match self.winding {
Winding::Clockwise => area,
Winding::CounterClockwise => -area,
};
if signed {
if area > geoid_area / 2.0 {
area -= geoid_area;
} else if area <= -geoid_area / 2.0 {
area += geoid_area;
}
} else {
if area < 0.0 {
area += geoid_area;
}
}
area
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Geodesic;
use approx::assert_relative_eq;
#[test]
fn test_simple_polygonarea() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(0.0, 0.0);
pa.add_point(0.0, 1.0);
pa.add_point(1.0, 1.0);
pa.add_point(1.0, 0.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3);
assert_relative_eq!(area, 12308778361.469, epsilon = 1.0e-3);
let mut pa = PolygonArea::new(&geoid, Winding::Clockwise);
pa.add_point(0.0, 0.0);
pa.add_point(0.0, 1.0);
pa.add_point(1.0, 1.0);
pa.add_point(1.0, 0.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3);
assert_relative_eq!(area, -12308778361.469, epsilon = 1.0e-3);
}
#[test]
fn test_planimeter0() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(89.0, 0.0);
pa.add_point(89.0, 90.0);
pa.add_point(89.0, 180.0);
pa.add_point(89.0, 270.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4);
assert_relative_eq!(area, 24952305678.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(-89.0, 0.0);
pa.add_point(-89.0, 90.0);
pa.add_point(-89.0, 180.0);
pa.add_point(-89.0, 270.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4);
assert_relative_eq!(area, -24952305678.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(0.0, -1.0);
pa.add_point(-1.0, 0.0);
pa.add_point(0.0, 1.0);
pa.add_point(1.0, 0.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 627598.2731, epsilon = 1.0e-4);
assert_relative_eq!(area, 24619419146.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(90.0, 0.0);
pa.add_point(0.0, 0.0);
pa.add_point(0.0, 90.0);
let (perimeter, area, _count) = pa.compute(true);
assert_relative_eq!(perimeter, 30022685.0, epsilon = 1.0);
assert_relative_eq!(area, 63758202715511.0, epsilon = 1.0);
}
#[test]
fn test_planimeter5() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(89.0, 0.1);
pa.add_point(89.0, 90.1);
pa.add_point(89.0, -179.9);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 539297.0, epsilon = 1.0);
assert_relative_eq!(area, 12476152838.5, epsilon = 1.0);
}
#[test]
fn test_planimeter6() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(9.0, -0.00000000000001);
pa.add_point(9.0, 180.0);
pa.add_point(9.0, 0.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(9.0, 0.00000000000001);
pa.add_point(9.0, 0.0);
pa.add_point(9.0, 180.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(9.0, 0.00000000000001);
pa.add_point(9.0, 180.0);
pa.add_point(9.0, 0.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0, epsilon = 1.0);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(9.0, -0.00000000000001);
pa.add_point(9.0, 0.0);
pa.add_point(9.0, 180.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0, epsilon = 1.0);
}
#[test]
fn test_planimeter12() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(66.562222222, 0.0);
pa.add_point(66.562222222, 180.0);
pa.add_point(66.562222222, 360.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0);
}
#[test]
fn test_planimeter12r() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(66.562222222, -0.0);
pa.add_point(66.562222222, -180.0);
pa.add_point(66.562222222, -360.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0);
assert_relative_eq!(area, 0.0);
}
#[test]
fn test_planimeter13() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(89.0, -360.0);
pa.add_point(89.0, -240.0);
pa.add_point(89.0, -120.0);
pa.add_point(89.0, 0.0);
pa.add_point(89.0, 120.0);
pa.add_point(89.0, 240.0);
let (perimeter, area, _) = pa.compute(true);
assert_relative_eq!(perimeter, 1160741.0, epsilon = 1.0);
assert_relative_eq!(area, 32415230256.0, epsilon = 1.0);
}
#[test]
fn test_planimeter15() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(2.0, 1.0);
pa.add_point(1.0, 2.0);
pa.add_point(3.0, 3.0);
let (_, area, _) = pa.compute(true);
assert_relative_eq!(area, 18454562325.45119, epsilon = 1.0e-4);
let mut pa = PolygonArea::new(&geoid, Winding::Clockwise);
pa.add_point(2.0, 1.0);
pa.add_point(1.0, 2.0);
pa.add_point(3.0, 3.0);
let (_, area, _) = pa.compute(true);
assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4);
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(1.0, 2.0);
pa.add_point(2.0, 1.0);
pa.add_point(3.0, 3.0);
let (_, area, _) = pa.compute(true);
assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4);
}
#[test]
fn test_planimeter19() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
let (perimeter, area, _) = pa.clone().compute(true);
assert_relative_eq!(perimeter, 0.0);
assert_relative_eq!(area, 0.0);
let (perimeter, area, _) = pa.test_point(1.0, 1.0, true);
assert_relative_eq!(perimeter, 0.0);
assert_relative_eq!(area, 0.0);
let result = std::panic::catch_unwind(|| {
let (_, _, _) = pa.test_edge(90.0, 1000.0, true);
});
assert!(result.is_err());
pa.add_point(1.0, 1.0);
let (perimeter, area, _) = pa.clone().compute(true);
assert_relative_eq!(perimeter, 0.0);
assert_relative_eq!(area, 0.0);
}
#[test]
fn test_planimeter21() {
let g = Geodesic::wgs84();
let lat = 45.0;
let azi = 39.2144607176828184218;
let s = 8420705.40957178156285;
let r = 39433884866571.4277; let a0 = g.area(); let mut pa_clockwise = PolygonArea::new(&g, Winding::Clockwise);
let mut pa_counter = PolygonArea::new(&g, Winding::CounterClockwise);
pa_clockwise.add_point(lat, 60.0);
pa_clockwise.add_point(lat, 180.0);
pa_clockwise.add_point(lat, -60.0);
pa_clockwise.add_point(lat, 60.0);
pa_clockwise.add_point(lat, 180.0);
pa_clockwise.add_point(lat, -60.0);
pa_counter.add_point(lat, 60.0);
pa_counter.add_point(lat, 180.0);
pa_counter.add_point(lat, -60.0);
pa_counter.add_point(lat, 60.0);
pa_counter.add_point(lat, 180.0);
pa_counter.add_point(lat, -60.0);
for i in 3..=4 {
pa_clockwise.add_point(lat, 60.0);
pa_clockwise.add_point(lat, 180.0);
pa_counter.add_point(lat, 60.0);
pa_counter.add_point(lat, 180.0);
let (_, area, _) = pa_counter.test_point(lat, -60.0, true);
assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_counter.test_point(lat, -60.0, false);
assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_clockwise.test_point(lat, -60.0, true);
assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_clockwise.test_point(lat, -60.0, false);
assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5);
let (_, area, _) = pa_counter.test_edge(azi, s, true);
assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_counter.test_edge(azi, s, false);
assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_clockwise.test_edge(azi, s, true);
assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5);
let (_, area, _) = pa_clockwise.test_edge(azi, s, false);
assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5);
pa_clockwise.add_point(lat, -60.0);
pa_counter.add_point(lat, -60.0);
let (_, area, _) = pa_counter.clone().compute(true);
assert_relative_eq!(area, r * i as f64, epsilon = 0.5);
let (_, area, _) = pa_counter.clone().compute(false);
assert_relative_eq!(area, r * i as f64, epsilon = 0.5);
let (_, area, _) = pa_clockwise.clone().compute(true);
assert_relative_eq!(area, -(r * i as f64), epsilon = 0.5);
let (_, area, _) = pa_clockwise.clone().compute(false);
assert_relative_eq!(area, -(r * i as f64) + a0, epsilon = 0.5);
}
}
#[test]
fn test_planimeter29() {
let geoid = Geodesic::wgs84();
let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
pa.add_point(0.0, 0.0);
pa.add_edge(90.0, 1000.0);
pa.add_edge(0.0, 1000.0);
pa.add_edge(-90.0, 1000.0);
let (_, area, _) = pa.compute(true);
assert_relative_eq!(area, 1000000.0, epsilon = 0.01);
}
}