use std::f64::consts::{FRAC_PI_2, PI};
use crate::r1;
use crate::s1;
use crate::s2::{LatLng, Point, Rect};
#[derive(Debug)]
pub struct LatLngRectBounder {
a: Point,
a_latlng: LatLng,
bound: Rect,
}
impl LatLngRectBounder {
pub fn new() -> Self {
LatLngRectBounder {
a: Point::from_coords(0.0, 0.0, 0.0),
a_latlng: LatLng::from_radians(0.0, 0.0),
bound: Rect::empty(),
}
}
pub fn add_point(&mut self, b: Point) {
self.add_internal(b, LatLng::from_point(b));
}
pub fn add_latlng(&mut self, b_latlng: LatLng) {
self.add_internal(b_latlng.to_point(), b_latlng);
}
fn add_internal(&mut self, b: Point, b_latlng: LatLng) {
if self.bound.is_empty() {
self.bound = self.bound.add_point(b_latlng);
} else {
let n = (self.a.0 - b.0).cross(self.a.0 + b.0);
let n_norm = n.norm();
if n_norm < 1.91346e-15 {
if self.a.0.dot(b.0) < 0.0 {
self.bound = Rect::full();
} else {
let pair = Rect::new(
r1::Interval::from_point_pair(
self.a_latlng.lat.radians(),
b_latlng.lat.radians(),
),
s1::Interval::from_point_pair(
self.a_latlng.lng.radians(),
b_latlng.lng.radians(),
),
);
self.bound = self.bound.union(pair);
}
} else {
let mut lng_ab = s1::Interval::from_point_pair(
self.a_latlng.lng.radians(),
b_latlng.lng.radians(),
);
if lng_ab.length() >= PI - 2.0 * f64::EPSILON {
lng_ab = s1::Interval::full();
}
let mut lat_ab = r1::Interval::from_point_pair(
self.a_latlng.lat.radians(),
b_latlng.lat.radians(),
);
let z_axis = crate::r3::Vector {
x: 0.0,
y: 0.0,
z: 1.0,
};
let m = n.cross(z_axis);
let m_a = m.dot(self.a.0);
let m_b = m.dot(b.0);
let m_error = 6.06638e-16 * n_norm + 6.83174e-31;
if m_a * m_b < 0.0 || m_a.abs() <= m_error || m_b.abs() <= m_error {
let max_lat = (n.x.hypot(n.y)).atan2(n.z.abs()) + 3.0 * f64::EPSILON;
let max_lat = max_lat.min(FRAC_PI_2);
let lat_budget_z = 0.5 * (self.a.0 - b.0).norm() * max_lat.sin();
let lat_budget =
2.0 * ((1.0 + 4.0 * f64::EPSILON) * lat_budget_z).min(1.0).asin();
let max_delta = 0.5 * (lat_budget - lat_ab.length()) + f64::EPSILON;
if m_a <= m_error && m_b >= -m_error {
lat_ab = r1::Interval::new(lat_ab.lo, max_lat.min(lat_ab.hi + max_delta));
}
if m_b <= m_error && m_a >= -m_error {
lat_ab =
r1::Interval::new((-max_lat).max(lat_ab.lo - max_delta), lat_ab.hi);
}
}
self.bound = self.bound.union(Rect::new(lat_ab, lng_ab));
}
}
self.a = b;
self.a_latlng = b_latlng;
}
pub fn get_bound(&self) -> Rect {
let expansion = LatLng::from_radians(2.0 * f64::EPSILON, 0.0);
self.bound.expanded(expansion).polar_closure()
}
pub fn expand_for_subregions(bound: Rect) -> Rect {
bound.expand_for_subregions()
}
pub fn max_error_for_tests() -> LatLng {
LatLng::from_radians(10.0 * f64::EPSILON, f64::EPSILON)
}
}
impl Default for LatLngRectBounder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::s1::Angle;
use crate::s2::text_format;
#[test]
fn test_empty() {
let bounder = LatLngRectBounder::new();
assert!(bounder.get_bound().is_empty());
}
#[test]
fn test_single_point() {
let mut bounder = LatLngRectBounder::new();
bounder.add_point(text_format::parse_point("0:0"));
let bound = bounder.get_bound();
assert!(!bound.is_empty());
assert!(bound.contains_point(text_format::parse_point("0:0")));
}
#[test]
fn test_two_points_equator() {
let mut bounder = LatLngRectBounder::new();
bounder.add_point(text_format::parse_point("0:0"));
bounder.add_point(text_format::parse_point("0:90"));
let bound = bounder.get_bound();
let max_err = LatLngRectBounder::max_error_for_tests();
assert!(bound.lat.lo >= -max_err.lat.radians());
assert!(bound.lat.hi <= max_err.lat.radians());
assert!(bound.lng.lo <= max_err.lng.radians());
assert!((bound.lng.hi - FRAC_PI_2).abs() <= max_err.lng.radians());
}
#[test]
fn test_edge_through_pole() {
let mut bounder = LatLngRectBounder::new();
bounder.add_point(text_format::parse_point("0:0"));
bounder.add_point(text_format::parse_point("0:180"));
let bound = bounder.get_bound();
assert!(bound.lat.hi >= FRAC_PI_2 - 1e-14);
}
#[test]
fn test_antipodal_points() {
let mut bounder = LatLngRectBounder::new();
bounder.add_point(text_format::parse_point("0:0"));
bounder.add_point(Point::from_coords(-1.0, 0.0, 0.0));
let bound = bounder.get_bound();
assert!(bound.is_full());
}
#[test]
fn test_loop_around_equator() {
let mut bounder = LatLngRectBounder::new();
let vertices = text_format::parse_points("0:0, 0:90, 0:180, 0:-90");
for v in &vertices {
bounder.add_point(*v);
}
bounder.add_point(vertices[0]);
let bound = bounder.get_bound();
let max_err = LatLngRectBounder::max_error_for_tests();
assert!(bound.lat.lo >= -max_err.lat.radians());
assert!(bound.lat.hi <= max_err.lat.radians());
assert!(bound.lng.is_full());
}
#[test]
fn test_triangle() {
let mut bounder = LatLngRectBounder::new();
let vertices = text_format::parse_points("10:0, 10:60, 10:120");
for v in &vertices {
bounder.add_point(*v);
}
bounder.add_point(vertices[0]);
let bound = bounder.get_bound();
let max_err = LatLngRectBounder::max_error_for_tests();
assert!(bound.lat.lo < Angle::from_degrees(10.0).radians() + max_err.lat.radians());
assert!(bound.lat.hi > Angle::from_degrees(10.0).radians() - max_err.lat.radians());
}
#[test]
fn test_expand_for_subregions() {
let bound = Rect::from_lat_lng(LatLng::from_degrees(10.0, 20.0));
let expanded = LatLngRectBounder::expand_for_subregions(bound);
assert!(expanded.contains(bound));
}
}