use super::*;
use crate::{CellIndex, LatLng, Resolution};
use float_eq::assert_float_eq;
use std::f64::consts::FRAC_PI_2;
const EPSILON_RADS2: f64 = 1e-14; const EPSILON_KM2: f64 = 1e-6; const EPSILON_M2: f64 = 1e0; const EARTH_RADS2: f64 = 4. * PI; const EARTH_KM2: f64 = 510065621.7240886; const EARTH_M2: f64 = 510065621724088.6;
#[test]
fn triangle_basic() {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(0., FRAC_PI_2).unwrap(),
];
let expected = FRAC_PI_2;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn triangle_reversed() {
let ring = &[
LatLng::from_radians(0., FRAC_PI_2).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
];
let expected = 7. * FRAC_PI_2;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn slice() {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(-FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., FRAC_PI_2).unwrap(),
];
let expected = PI;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn slice_reversed() {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., FRAC_PI_2).unwrap(),
LatLng::from_radians(-FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
];
let expected = 3. * PI;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn hemisphere_east() {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(-FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., PI).unwrap(),
];
let expected = 2. * PI;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn hemisphere_north() {
let ring = &[
LatLng::from_radians(0., -PI).unwrap(),
LatLng::from_radians(0., -FRAC_PI_2).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(0., FRAC_PI_2).unwrap(),
];
let expected = 2. * PI;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn percentage_slice() {
let mut t = 0.;
while t <= 1.2 {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., -FRAC_PI_2).unwrap(),
LatLng::from_radians(0., t * PI - FRAC_PI_2).unwrap(),
];
let result = linear_ring_area(ring);
if t < 0.99 {
let expected = t * PI;
assert_float_eq!(result, expected, abs <= 1e-13);
} else if t > 1.01 {
let expected = (2. + t) * PI;
assert_float_eq!(result, expected, abs <= 1e-13);
}
t += 0.01;
}
}
#[test]
fn percentage_slice_large() {
let t = 1.2;
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., -FRAC_PI_2).unwrap(),
LatLng::from_radians(0., 0.).unwrap(),
LatLng::from_radians(0., t * PI - FRAC_PI_2).unwrap(),
];
let expected = t * PI;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn invalid_ring_line() {
let ring = &[
LatLng::from_radians(FRAC_PI_2, 0.).unwrap(),
LatLng::from_radians(0., -FRAC_PI_2).unwrap(),
];
let expected = 0.;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn invalid_ring_point() {
let ring = &[LatLng::from_radians(0., 0.).unwrap()];
let expected = 0.;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
#[test]
fn invalid_ring_empty() {
let ring: &[LatLng] = &[];
let expected = 0.;
let result = linear_ring_area(ring);
assert_float_eq!(result, expected, abs <= EPSILON_RADS2);
}
macro_rules! area_earth_test {
($name:ident, $resolution:literal, $area_fn:ident, $expected:ident, $epsilon: ident) => {
#[test]
fn $name() {
let resolution =
Resolution::try_from($resolution).expect("index resolution");
let area: f64 = CellIndex::base_cells()
.flat_map(|index| {
index.children(resolution).map(|child| child.$area_fn())
})
.fold(FloatAdder::default(), |mut adder, area| {
adder += area;
adder
})
.into();
assert_float_eq!(area, $expected, abs <= $epsilon);
}
};
}
area_earth_test!(earth_rads2_res0, 0, area_rads2, EARTH_RADS2, EPSILON_RADS2);
area_earth_test!(earth_rads2_res1, 1, area_rads2, EARTH_RADS2, EPSILON_RADS2);
area_earth_test!(earth_rads2_res2, 2, area_rads2, EARTH_RADS2, EPSILON_RADS2);
area_earth_test!(earth_rads2_res3, 3, area_rads2, EARTH_RADS2, EPSILON_RADS2);
area_earth_test!(earth_rads2_res4, 4, area_rads2, EARTH_RADS2, EPSILON_RADS2);
area_earth_test!(earth_km2_res0, 0, area_km2, EARTH_KM2, EPSILON_KM2);
area_earth_test!(earth_km2_res1, 1, area_km2, EARTH_KM2, EPSILON_KM2);
area_earth_test!(earth_km2_res2, 2, area_km2, EARTH_KM2, EPSILON_KM2);
area_earth_test!(earth_km2_res3, 3, area_km2, EARTH_KM2, EPSILON_KM2);
area_earth_test!(earth_km2_res4, 4, area_km2, EARTH_KM2, EPSILON_KM2);
area_earth_test!(earth_m2_res0, 0, area_m2, EARTH_M2, EPSILON_M2);
area_earth_test!(earth_m2_res1, 1, area_m2, EARTH_M2, EPSILON_M2);
area_earth_test!(earth_m2_res2, 2, area_m2, EARTH_M2, EPSILON_M2);
area_earth_test!(earth_m2_res3, 3, area_m2, EARTH_M2, EPSILON_M2);
area_earth_test!(earth_m2_res4, 4, area_m2, EARTH_M2, EPSILON_M2);