use std::{cmp::Ordering, f64::consts::PI};
use crate::{
numbers::{eq_zero, gte, lte},
Angle, LatLong, Vec3,
};
use super::MinorArc;
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub struct Rectangle {
lat: LatitudeInterval,
lng: LongitudeInterval,
}
impl Rectangle {
pub const EMPTY: Rectangle = Self {
lat: LatitudeInterval::EMPTY,
lng: LongitudeInterval::EMPTY,
};
pub const FULL: Rectangle = Self {
lat: LatitudeInterval::FULL,
lng: LongitudeInterval::FULL,
};
pub fn from_minor_arc(ma: MinorArc) -> Self {
let lls = LatLong::from_nvector(ma.start());
let lle = LatLong::from_nvector(ma.end());
Self {
lat: LatitudeInterval::from_minor_arc(ma, lls, lle),
lng: LongitudeInterval::from_minor_arc(lls, lle),
}
}
pub fn from_nesw(north: Angle, east: Angle, south: Angle, west: Angle) -> Self {
Self {
lat: LatitudeInterval::new(south, north),
lng: LongitudeInterval::new(west, east),
}
}
pub fn cmp_by_latitude(&self, o: Self) -> Ordering {
let a = self.lat.lo.as_radians() + self.lat.hi.as_radians();
let b = o.lat.lo.as_radians() + o.lat.hi.as_radians();
if a < b {
Ordering::Less
} else if a > b {
Ordering::Greater
} else {
Ordering::Equal
}
}
pub fn cmp_by_longitude(&self, o: Self) -> Ordering {
let a = self.lng.lo.as_radians() + self.lng.hi.as_radians();
let b = o.lng.lo.as_radians() + o.lng.hi.as_radians();
if a < b {
Ordering::Less
} else if a > b {
Ordering::Greater
} else {
Ordering::Equal
}
}
pub fn contains_point(&self, p: LatLong) -> bool {
self.lat.contains_lat(p.latitude()) && self.lng.contains_lng(p.longitude())
}
pub fn contains_rectangle(&self, r: Rectangle) -> bool {
self.lat.contains_int(r.lat) && self.lng.contains_int(r.lng)
}
pub fn is_full(&self) -> bool {
self.is_latitude_full() && self.is_longitude_full()
}
pub fn is_latitude_full(&self) -> bool {
self.lat.is_full()
}
pub fn is_longitude_full(&self) -> bool {
self.lng.is_full()
}
pub fn is_empty(&self) -> bool {
self.is_latitude_empty() && self.is_longitude_empty()
}
pub fn is_latitude_empty(&self) -> bool {
self.lat.is_empty()
}
pub fn is_longitude_empty(&self) -> bool {
self.lng.is_empty()
}
pub fn north_east(&self) -> LatLong {
LatLong::new(self.lat.hi, self.lng.hi)
}
pub fn south_west(&self) -> LatLong {
LatLong::new(self.lat.lo, self.lng.lo)
}
pub fn expand(&self, amount: Angle) -> Self {
let lat = self.lat.expand(amount);
let lng = self.lng.expand(amount);
if lat.is_empty() || lng.is_empty() {
Self::EMPTY
} else {
Self {
lat: lat.intersection(LatitudeInterval::FULL),
lng,
}
}
}
pub fn expand_to_north_pole(&self) -> Self {
Self {
lat: LatitudeInterval::new(self.lat.lo, Angle::QUARTER_CIRCLE),
lng: LongitudeInterval::FULL,
}
}
pub fn expand_to_south_pole(&self) -> Self {
Self {
lat: LatitudeInterval::new(-Angle::QUARTER_CIRCLE, self.lat.hi),
lng: LongitudeInterval::FULL,
}
}
pub fn polar_closure(&self) -> Self {
if self.lat.lo == -Angle::QUARTER_CIRCLE || self.lat.hi == Angle::QUARTER_CIRCLE {
Self {
lat: self.lat,
lng: LongitudeInterval::FULL,
}
} else {
*self
}
}
pub fn union(&self, o: Self) -> Self {
Rectangle {
lat: self.lat.union(o.lat),
lng: self.lng.union(o.lng),
}
}
pub fn from_union(all: &[Rectangle]) -> Self {
let mut res = Self::EMPTY;
for r in all {
res.lat.mut_union(r.lat);
res.lng.mut_union(r.lng);
}
res
}
}
#[derive(PartialEq, Clone, Copy, Debug, Default)]
struct LatitudeInterval {
lo: Angle,
hi: Angle,
}
impl LatitudeInterval {
const EMPTY: LatitudeInterval = Self {
lo: Angle::QUARTER_CIRCLE,
hi: Angle::ZERO,
};
const FULL: LatitudeInterval = Self {
lo: Angle::NEG_QUARTER_CIRCLE,
hi: Angle::QUARTER_CIRCLE,
};
fn new(lo: Angle, hi: Angle) -> Self {
Self { lo, hi }
}
fn from_minor_arc(ma: MinorArc, lls: LatLong, lle: LatLong) -> Self {
let n = ma.normal();
let m = Vec3::new(n.y(), -n.x(), 0.0);
let ms = m.dot_prod(ma.start().as_vec3());
let me = m.dot_prod(ma.end().as_vec3());
let s_lat = lls.latitude();
let e_lat = lle.latitude();
let (mut lo, mut hi) = if s_lat > e_lat {
(e_lat, s_lat)
} else {
(s_lat, e_lat)
};
if ms * me < 0.0 || eq_zero(ms) || eq_zero(me) {
let max =
Angle::from_radians((n.x() * n.x() + n.y() * n.y()).sqrt().atan2(n.z().abs()));
if lte(ms, 0.0) && gte(me, 0.0) {
hi = max;
}
if lte(me, 0.0) && gte(ms, 0.0) {
lo = -max;
}
}
Self::new(lo, hi)
}
fn contains_lat(&self, latitude: Angle) -> bool {
latitude >= self.lo && latitude <= self.hi
}
fn contains_int(&self, o: Self) -> bool {
if o.is_empty() {
true
} else {
o.lo >= self.lo && o.hi <= self.hi
}
}
fn expand(&self, amount: Angle) -> Self {
if self.is_empty() {
*self
} else {
Self {
lo: self.lo - amount,
hi: self.hi + amount,
}
}
}
fn is_empty(&self) -> bool {
self.lo > self.hi
}
fn is_full(&self) -> bool {
self.lo == -Angle::QUARTER_CIRCLE && self.hi == Angle::QUARTER_CIRCLE
}
fn intersection(&self, o: Self) -> Self {
let lo = if self.lo >= o.lo { self.lo } else { o.lo };
let hi = if self.hi <= o.hi { self.hi } else { o.hi };
Self { lo, hi }
}
fn union(&self, o: Self) -> Self {
let mut r = *self;
r.mut_union(o);
r
}
fn mut_union(&mut self, o: Self) {
if self.is_empty() {
self.lo = o.lo;
self.hi = o.hi;
} else if o.is_empty() {
} else {
if self.lo > o.lo {
self.lo = o.lo;
}
if self.hi < o.hi {
self.hi = o.hi;
}
}
}
}
#[derive(PartialEq, Clone, Copy, Debug, Default)]
struct LongitudeInterval {
lo: Angle,
hi: Angle,
}
impl LongitudeInterval {
const EMPTY: LongitudeInterval = Self {
lo: Angle::HALF_CIRCLE,
hi: Angle::NEG_HALF_CIRCLE,
};
const FULL: LongitudeInterval = Self {
lo: Angle::NEG_HALF_CIRCLE,
hi: Angle::HALF_CIRCLE,
};
fn new(lo: Angle, hi: Angle) -> Self {
Self { lo, hi }
}
fn from_minor_arc(lls: LatLong, lle: LatLong) -> Self {
let start = Self::normalised_longitude(lls.longitude());
let end = Self::normalised_longitude(lle.longitude());
if Self::positive_distance(start, end) <= Angle::HALF_CIRCLE {
Self::new(start, end)
} else {
Self::new(end, start)
}
}
fn normalised_longitude(longitude: Angle) -> Angle {
if longitude == Angle::NEG_HALF_CIRCLE {
Angle::HALF_CIRCLE
} else {
longitude
}
}
fn positive_distance(a: Angle, b: Angle) -> Angle {
let d = b - a;
if d >= Angle::ZERO {
d
} else {
(b + Angle::HALF_CIRCLE) - (a - Angle::HALF_CIRCLE)
}
}
fn expand(&self, amount: Angle) -> Self {
if amount > Angle::ZERO {
if self.is_empty() {
return *self;
}
if self.len() + 2.0 * amount + 2.0 * Angle::DBL_EPSILON >= Angle::FULL_CIRCLE {
return Self::FULL;
}
} else {
if self.is_full() {
return *self;
}
if self.len() + 2.0 * amount - 2.0 * Angle::DBL_EPSILON <= Angle::ZERO {
return Self::EMPTY;
}
}
let mut lo = (self.lo - amount).as_radians() % (2.0 * PI);
let hi = (self.hi + amount).as_radians() % (2.0 * PI);
if lo < -PI {
lo = PI;
}
Self {
lo: Angle::from_radians(lo),
hi: Angle::from_radians(hi),
}
}
fn contains_lng(&self, longitude: Angle) -> bool {
let lng = Self::normalised_longitude(longitude);
if self.is_inverted() {
(lng >= self.lo || lng <= self.hi) && !self.is_empty()
} else {
lng >= self.lo && lng <= self.hi
}
}
fn contains_int(&self, o: Self) -> bool {
if self.is_inverted() {
if o.is_inverted() {
return o.lo >= self.lo && o.hi <= self.hi;
}
return (o.lo >= self.lo || o.hi <= self.hi) && !self.is_empty();
}
if o.is_inverted() {
return self.is_full() || o.is_empty();
}
o.lo >= self.lo && o.hi <= self.hi
}
fn is_full(&self) -> bool {
self.lo == Angle::NEG_HALF_CIRCLE && self.hi == Angle::HALF_CIRCLE
}
fn is_empty(&self) -> bool {
self.lo == Angle::HALF_CIRCLE && self.hi == Angle::NEG_HALF_CIRCLE
}
fn is_inverted(&self) -> bool {
self.lo > self.hi
}
fn union(&self, o: Self) -> Self {
let mut r = *self;
r.mut_union(o);
r
}
fn len(&self) -> Angle {
let mut len = self.hi - self.lo;
if len >= Angle::ZERO {
len
} else {
len = len + Angle::FULL_CIRCLE;
if len > Angle::ZERO {
len
} else {
Angle::from_radians(-1.0)
}
}
}
fn mut_union(&mut self, o: Self) {
if o.is_empty() {
} else if self.contains_lng(o.lo) {
if self.contains_lng(o.hi) {
if self.contains_int(o) {
} else {
self.lo = Angle::NEG_HALF_CIRCLE;
self.hi = Angle::HALF_CIRCLE;
}
} else {
self.hi = o.hi;
}
} else if self.contains_lng(o.hi) {
self.lo = o.lo;
} else if self.is_empty() || o.contains_lng(self.lo) {
self.lo = o.lo;
self.hi = o.hi;
} else {
let dlo = Self::positive_distance(o.hi, self.lo);
let dhi = Self::positive_distance(self.hi, o.lo);
if dlo < dhi {
self.lo = o.lo;
} else {
self.hi = o.hi;
}
}
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use crate::{spherical::MinorArc, Angle, LatLong, NVector};
use super::Rectangle;
#[test]
fn cmp_by_latitude_eq() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(20.0),
Angle::ZERO,
Angle::from_degrees(10.0),
);
assert_eq!(Ordering::Equal, a.cmp_by_latitude(b));
assert_eq!(Ordering::Equal, b.cmp_by_latitude(a));
}
#[test]
fn cmp_by_latitude_northernmost_in_north_hemisphere() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::ZERO,
Angle::from_degrees(10.0),
);
assert_eq!(Ordering::Greater, a.cmp_by_latitude(b));
assert_eq!(Ordering::Less, b.cmp_by_latitude(a));
}
#[test]
fn cmp_by_latitude_northernmost_in_south_hemisphere() {
let a = Rectangle::from_nesw(
Angle::from_degrees(-10.0),
Angle::from_degrees(30.0),
Angle::from_degrees(-30.0),
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(-20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(-30.0),
Angle::from_degrees(10.0),
);
assert_eq!(Ordering::Greater, a.cmp_by_latitude(b));
assert_eq!(Ordering::Less, b.cmp_by_latitude(a));
}
#[test]
fn cmp_by_longitude_eq() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(30.0),
Angle::from_degrees(10.0),
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::from_degrees(20.0),
Angle::ZERO,
);
assert_eq!(Ordering::Equal, a.cmp_by_longitude(b));
assert_eq!(Ordering::Equal, b.cmp_by_longitude(a));
}
#[test]
fn cmp_by_longitude_easternmost_longitude_is_negative() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(-20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(-40.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(-30.0),
Angle::from_degrees(20.0),
Angle::from_degrees(-40.0),
);
assert_eq!(Ordering::Greater, a.cmp_by_longitude(b));
assert_eq!(Ordering::Less, b.cmp_by_longitude(a));
}
#[test]
fn cmp_by_longitude_easternmost_longitude_is_positive() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(30.0),
Angle::from_degrees(10.0),
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::ZERO,
);
assert_eq!(Ordering::Greater, a.cmp_by_longitude(b));
assert_eq!(Ordering::Less, b.cmp_by_longitude(a));
}
#[test]
fn contains_point_east() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
assert!(a.contains_point(LatLong::from_degrees(10.0, 30.0)));
}
#[test]
fn contains_point_north() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
assert!(a.contains_point(LatLong::from_degrees(30.0, 20.0)));
}
#[test]
fn contains_point_north_pole() {
let a = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let np: LatLong = LatLong::from_degrees(90.0, 160.0);
assert!(!a.contains_point(np));
assert!(a.polar_closure().contains_point(np));
}
#[test]
fn contains_point_south() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
assert!(a.contains_point(LatLong::from_degrees(0.0, 20.0)));
}
#[test]
fn contains_south_pole() {
let a = Rectangle::from_nesw(
Angle::ZERO,
Angle::from_degrees(30.0),
Angle::from_degrees(-90.0),
Angle::ZERO,
);
let sp = LatLong::from_degrees(-90.0, 50.0);
assert!(!a.contains_point(sp));
assert!(a.polar_closure().contains_point(sp));
}
#[test]
fn contains_point_west() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
assert!(a.contains_point(LatLong::from_degrees(10.0, 0.0)));
}
#[test]
fn contains_point_date_line() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(-170.0),
Angle::ZERO,
Angle::from_degrees(170.0),
);
assert!(a.contains_point(LatLong::from_degrees(10.0, 180.0)));
assert!(a.contains_point(LatLong::from_degrees(10.0, -180.0)));
}
#[test]
fn contains_point_empty() {
assert!(!Rectangle::EMPTY.contains_point(LatLong::from_degrees(0.0, 0.0)));
}
#[test]
fn contains_point_empty_longitude_interval() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(-180.0),
Angle::ZERO,
Angle::from_degrees(180.0),
);
assert!(!a.contains_point(LatLong::from_degrees(10.0, 180.0)));
}
#[test]
fn contains_point_inverted() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
Angle::from_degrees(30.0),
);
assert!(a.contains_point(LatLong::from_degrees(10.0, 40.0)));
assert!(!a.contains_point(LatLong::from_degrees(40.0, 40.0)));
assert!(!a.contains_point(LatLong::from_degrees(-1.0, 40.0)));
assert!(!a.contains_point(LatLong::from_degrees(10.0, 10.0)));
}
#[test]
fn contains_point_nominal() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
assert!(a.contains_point(LatLong::from_degrees(10.0, 10.0)));
assert!(!a.contains_point(LatLong::from_degrees(40.0, 10.0)));
assert!(!a.contains_point(LatLong::from_degrees(-1.0, 10.0)));
assert!(!a.contains_point(LatLong::from_degrees(10.0, 40.0)));
assert!(!a.contains_point(LatLong::from_degrees(10.0, -10.0)));
}
#[test]
fn contains_rectangle() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
assert_contains_rect(a, b, true);
assert_contains_rect(b, a, false);
}
#[test]
fn contains_rectangle_empty() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
assert_contains_rect(a, Rectangle::EMPTY, true);
assert_contains_rect(Rectangle::EMPTY, a, false);
assert_contains_rect(Rectangle::EMPTY, Rectangle::EMPTY, true);
}
#[test]
fn contains_rectangle_intersecting() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::from_degrees(15.0),
Angle::from_degrees(15.0),
);
assert_contains_rect(a, b, false);
assert_contains_rect(b, a, false);
}
#[test]
fn contains_rectangle_non_verlapping() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(40.0),
Angle::from_degrees(45.0),
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
);
assert_contains_rect(a, b, false);
assert_contains_rect(b, a, false);
}
#[test]
fn contains_rectangle_not_by_ongitude() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(40.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
assert_contains_rect(a, b, false);
assert_contains_rect(b, a, false);
}
#[test]
fn contains_rectangle_other_longitude_empty() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::ZERO,
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(-180.0),
Angle::from_degrees(10.0),
Angle::from_degrees(180.0),
);
assert_contains_rect(a, b, true);
}
#[test]
fn contains_rectangle_other_longitude_inverted_does_not_contain() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::ZERO,
Angle::from_degrees(20.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(30.0),
Angle::from_degrees(10.0),
Angle::from_degrees(40.0),
);
assert_contains_rect(a, b, false);
}
#[test]
fn contains_rectangle_other_longitude_inverted_this_full() {
let a = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(180.0),
Angle::ZERO,
Angle::from_degrees(-180.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(30.0),
Angle::from_degrees(10.0),
Angle::from_degrees(40.0),
);
assert_contains_rect(a, b, true);
}
fn assert_contains_rect(a: Rectangle, b: Rectangle, expected: bool) {
assert_eq!(expected, a.contains_rectangle(b));
assert_eq!(expected, a.union(b) == a);
}
#[test]
fn from_minor_arc_from_north_pole() {
let ma = MinorArc::new(
NVector::from_lat_long_degrees(90.0, 0.0),
NVector::from_lat_long_degrees(45.0, 45.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(45.0),
Angle::from_degrees(45.0),
Angle::ZERO,
);
assert_rect_eq_d7(expected, actual);
}
#[test]
fn from_minor_arc_from_south_pole() {
let ma = MinorArc::new(
NVector::from_lat_long_degrees(-90.0, 0.0),
NVector::from_lat_long_degrees(-45.0, 45.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(-45.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-90.0),
Angle::ZERO,
);
assert_rect_eq_d7(expected, actual);
}
#[test]
fn from_minor_arc_iso_latitude_north() {
let ma = MinorArc::new(
NVector::from_lat_long_degrees(45.0, 0.0),
NVector::from_lat_long_degrees(45.0, 10.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(45.1092215),
Angle::from_degrees(10.0),
Angle::from_degrees(45.0),
Angle::ZERO,
);
assert_rect_eq_d7(expected, actual);
}
#[test]
fn from_minor_arc_iso_latitude_south() {
let ma: MinorArc = MinorArc::new(
NVector::from_lat_long_degrees(-45.0, 0.0),
NVector::from_lat_long_degrees(-45.0, 10.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(-45.0),
Angle::from_degrees(10.0),
Angle::from_degrees(-45.1092215),
Angle::ZERO,
);
assert_rect_eq_d7(expected, actual);
}
#[test]
fn from_minor_arc_iso_longitude() {
let ma = MinorArc::new(
NVector::from_lat_long_degrees(0.0, 0.0),
NVector::from_lat_long_degrees(10.0, 0.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::ZERO,
Angle::ZERO,
Angle::ZERO,
);
assert_rect_eq_d7(expected, actual);
for lat in -900..900 {
let lat_f = lat as f64;
let p = LatLong::from_degrees(lat_f / 10.0, 0.0);
if lat >= 0 && lat <= 100 {
assert!(actual.contains_point(p));
} else {
assert!(!actual.contains_point(p));
}
}
}
#[test]
fn from_minor_arc_smallest_longitude_interval() {
let ma = MinorArc::new(
NVector::from_lat_long_degrees(45.0, 0.0),
NVector::from_lat_long_degrees(46.0, -10.0),
);
let actual = Rectangle::from_minor_arc(ma);
let expected = Rectangle::from_nesw(
Angle::from_degrees(46.0),
Angle::ZERO,
Angle::from_degrees(45.0),
Angle::from_degrees(-10.0),
);
assert_rect_eq_d7(expected, actual);
assert!(actual.contains_point(LatLong::from_degrees(45.5, -5.0)));
assert!(!actual.contains_point(LatLong::from_degrees(45.5, 1.0)));
}
fn assert_rect_eq_d7(e: Rectangle, a: Rectangle) {
assert_eq!(e.north_east(), a.north_east().round_d7());
assert_eq!(e.south_west(), a.south_west().round_d7());
}
#[test]
fn from_nesw_longitude_spans_180() {
check_nesw(false, ll(0, 0), 10, -170, -10, 170);
check_nesw(true, ll(0, 180), 10, -170, -10, 170);
check_nesw(true, ll(0, -175), 10, -170, -10, 170);
check_nesw(true, ll(0, 175), 10, -170, -10, 170);
check_nesw(false, ll(0, -165), 10, -170, -10, 170);
check_nesw(false, ll(0, 165), 10, -170, -10, 170);
}
#[test]
fn from_nesw_nominal_inside() {
check_nesw(true, ll(0, 0), 10, 10, -10, -10);
check_nesw(true, ll(10, 0), 10, 10, -10, -10);
check_nesw(true, ll(-10, 0), 10, 10, -10, -10);
check_nesw(true, ll(0, 10), 10, 10, -10, -10);
check_nesw(true, ll(0, -10), 10, 10, -10, -10);
}
#[test]
fn from_nesw_nominal_outside() {
check_nesw(false, ll(20, 0), 10, 10, -10, -10);
check_nesw(false, ll(-20, 0), 10, 10, -10, -10);
check_nesw(false, ll(0, 20), 10, 10, -10, -10);
check_nesw(false, ll(0, -20), 10, 10, -10, -10);
check_nesw(false, ll(90, 0), 10, 10, -10, -10);
check_nesw(false, ll(-90, 0), 10, 10, -10, -10);
}
#[test]
fn from_nesw_north_pole() {
check_nesw(true, ll(90, 45), 90, 50, 80, 40);
check_nesw(true, ll(85, 45), 90, 50, 80, 40);
check_nesw(false, ll(85, 35), 90, 50, 80, 40);
check_nesw(false, ll(76, 0), 90, 50, 80, 40);
}
#[test]
fn from_nesw_over_both_hemispheres() {
check_nesw(true, ll(0, 0), 10, 170, -10, -170);
check_nesw(false, ll(0, 180), 10, 170, -10, -170);
}
#[test]
fn from_nesw_reversed_latitudes() {
check_nesw(false, ll(0, 0), -10, 10, 10, -10);
}
#[test]
fn from_nesw_south_pole() {
check_nesw(true, ll(-90, 45), -80, 50, -90, 40);
check_nesw(true, ll(-85, 45), -80, 50, -90, 40);
check_nesw(false, ll(-85, 35), -80, 50, -90, 40);
check_nesw(false, ll(-76, 0), -80, 50, -90, 40);
}
#[test]
fn from_nesw_zero_rectangle() {
check_nesw(true, ll(10, 10), 10, 10, 10, 10);
check_nesw(false, ll(10, 11), 10, 10, 10, 10);
check_nesw(false, ll(11, 10), 10, 10, 10, 10);
}
fn check_nesw(expected: bool, test_point: LatLong, n: i64, e: i64, s: i64, w: i64) {
let rect = Rectangle::from_nesw(
Angle::from_degrees(n as f64),
Angle::from_degrees(e as f64),
Angle::from_degrees(s as f64),
Angle::from_degrees(w as f64),
);
let actual = rect.contains_point(test_point);
assert_eq!(expected, actual);
}
#[test]
fn polar_closure_no_pole() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
assert_eq!(a, a.polar_closure());
assert!(!a.is_longitude_full());
}
#[test]
fn polar_closure_north_pole() {
let actual = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(50.0),
Angle::from_degrees(80.0),
Angle::from_degrees(40.0),
)
.polar_closure();
assert!(actual.is_longitude_full());
assert!(actual.contains_point(ll(90, 0)));
assert!(actual.contains_point(ll(85, 45)));
assert!(actual.contains_point(ll(85, 35)));
assert!(!actual.contains_point(ll(76, 0)));
}
#[test]
fn polar_closure_south_pole() {
let actual = Rectangle::from_nesw(
Angle::from_degrees(-80.0),
Angle::from_degrees(50.0),
Angle::from_degrees(-90.0),
Angle::from_degrees(40.0),
)
.polar_closure();
assert!(actual.is_longitude_full());
assert!(actual.contains_point(ll(-90, 0)));
assert!(actual.contains_point(ll(-85, 45)));
assert!(actual.contains_point(ll(-85, 35)));
assert!(!actual.contains_point(ll(-76, 0)));
}
#[test]
fn union_both_empty() {
assert_eq!(Rectangle::EMPTY, Rectangle::EMPTY.union(Rectangle::EMPTY));
}
#[test]
fn union_non_overlapping() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(40.0),
Angle::from_degrees(45.0),
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
);
let union = a.union(b);
assert!(union.contains_rectangle(a));
assert!(union.contains_rectangle(b));
let p = ll(25, 25);
assert!(!a.contains_point(p));
assert!(!b.contains_point(p));
assert!(union.contains_point(p));
}
#[test]
fn union_one_empty() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
assert_eq!(a, a.union(Rectangle::EMPTY));
assert_eq!(a, Rectangle::EMPTY.union(a));
}
#[test]
fn overlapping() {
let a = Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
);
let b = Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::from_degrees(15.0),
Angle::from_degrees(15.0),
);
let union: Rectangle = a.union(b);
assert!(union.contains_rectangle(a));
assert!(union.contains_rectangle(b));
let pa = ll(14, 14);
assert!(a.contains_point(pa));
assert!(!b.contains_point(pa));
assert!(union.contains_point(pa));
let pb = ll(25, 25);
assert!(!a.contains_point(pb));
assert!(b.contains_point(pb));
assert!(union.contains_point(pb));
}
#[test]
fn from_union_all_empty() {
let all = vec![Rectangle::EMPTY, Rectangle::EMPTY, Rectangle::EMPTY];
let union = Rectangle::from_union(&all);
assert_eq!(Rectangle::EMPTY, union);
}
#[test]
fn from_union_all_overlapping() {
let all = vec![
Rectangle::from_nesw(
Angle::from_degrees(20.0),
Angle::from_degrees(20.0),
Angle::from_degrees(10.0),
Angle::from_degrees(10.0),
),
Rectangle::from_nesw(
Angle::from_degrees(30.0),
Angle::from_degrees(30.0),
Angle::from_degrees(15.0),
Angle::from_degrees(15.0),
),
Rectangle::from_nesw(
Angle::from_degrees(40.0),
Angle::from_degrees(40.0),
Angle::from_degrees(5.0),
Angle::from_degrees(5.0),
),
];
let union = Rectangle::from_union(&all);
for r in all.iter() {
assert!(union.contains_rectangle(*r));
}
}
#[test]
fn is_longitude_full() {
assert!(Rectangle::from_nesw(
Angle::ZERO,
Angle::from_degrees(180.0),
Angle::ZERO,
Angle::from_degrees(-180.0)
)
.is_longitude_full());
assert!(!Rectangle::from_nesw(
Angle::ZERO,
Angle::from_degrees(179.0),
Angle::ZERO,
Angle::from_degrees(-180.0)
)
.is_longitude_full());
assert!(!Rectangle::from_nesw(
Angle::ZERO,
Angle::from_degrees(180.0),
Angle::ZERO,
Angle::from_degrees(-179.0)
)
.is_longitude_full());
}
#[test]
fn expand_to_north_pole() {
let r = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-10.0),
Angle::from_degrees(10.0),
);
let expanded = r.expand_to_north_pole();
assert!(expanded.is_longitude_full());
assert!(expanded.contains_point(ll(90, 0)));
assert!(expanded.contains_point(ll(-10, 0)));
}
#[test]
fn expand_to_south_pole() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-10.0),
Angle::from_degrees(10.0),
);
let expanded = r.expand_to_south_pole();
assert!(expanded.is_longitude_full());
assert!(expanded.contains_point(ll(-90, 0)));
assert!(expanded.contains_point(ll(10, 0)));
}
#[test]
fn expand_nominal() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-10.0),
Angle::from_degrees(10.0),
);
let expanded = r.expand(Angle::from_degrees(1.0));
let e = Rectangle::from_nesw(
Angle::from_degrees(11.0),
Angle::from_degrees(46.0),
Angle::from_degrees(-11.0),
Angle::from_degrees(9.0),
);
assert_eq!(e, expanded);
}
#[test]
fn expand_full_lat_remains_full() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-90.0),
Angle::from_degrees(10.0),
);
let expanded = r.expand(Angle::from_degrees(1.0));
let e = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(46.0),
Angle::from_degrees(-90.0),
Angle::from_degrees(9.0),
);
assert_eq!(e, expanded);
}
#[test]
fn expand_full_lat_no_longer_full() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(90.0),
Angle::from_degrees(45.0),
Angle::from_degrees(-90.0),
Angle::from_degrees(10.0),
);
let expanded = r.expand(Angle::from_degrees(-1.0));
let e = Rectangle::from_nesw(
Angle::from_degrees(89.0),
Angle::from_degrees(44.0),
Angle::from_degrees(-89.0),
Angle::from_degrees(11.0),
);
assert_eq!(e, expanded);
}
#[test]
fn expand_full_lng_positive_amount() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::from_degrees(180.0),
Angle::from_degrees(-10.0),
Angle::from_degrees(-180.0),
);
let expanded = r.expand(Angle::from_degrees(1.0));
let e = Rectangle::from_nesw(
Angle::from_degrees(11.0),
Angle::from_degrees(180.0),
Angle::from_degrees(-11.0),
Angle::from_degrees(-180.0),
);
assert_eq!(e, expanded);
}
#[test]
fn expand_full_lng_negative_amount() {
let r: Rectangle = Rectangle::from_nesw(
Angle::from_degrees(10.0),
Angle::from_degrees(180.0),
Angle::from_degrees(-10.0),
Angle::from_degrees(-180.0),
);
let expanded = r.expand(Angle::from_degrees(-1.0));
let e = Rectangle::from_nesw(
Angle::from_degrees(9.0),
Angle::from_degrees(180.0),
Angle::from_degrees(-9.0),
Angle::from_degrees(-180.0),
);
assert_eq!(e, expanded);
}
fn ll(lat: i64, lng: i64) -> LatLong {
LatLong::from_degrees(lat as f64, lng as f64)
}
}