use alloc::vec;
use alloc::vec::Vec;
use crate::position::Geodetic;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Containment {
Inside,
OnBoundary,
Outside,
}
const MAX_BUCKETS: usize = 1024;
const MAX_COPIES: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Xy {
x: f64,
y: f64,
}
impl From<Xy> for robust::Coord<f64> {
#[inline]
fn from(p: Xy) -> Self {
robust::Coord { x: p.x, y: p.y }
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Ring {
edges: Vec<(Xy, Xy)>,
single: Option<Xy>,
y0: f64,
y1: f64,
dy: f64,
buckets: Vec<Vec<u32>>,
}
impl Ring {
fn new(points: impl IntoIterator<Item = Xy>) -> Ring {
let points: Vec<Xy> = points.into_iter().collect();
let mut edges: Vec<(Xy, Xy)> = points.windows(2).map(|w| (w[0], w[1])).collect();
if let (Some(&first), Some(&last)) = (points.first(), points.last()) {
if points.len() > 1 && first != last {
edges.push((last, first));
}
}
let single = (points.len() == 1).then(|| points[0]);
let (mut y0, mut y1) = (f64::INFINITY, f64::NEG_INFINITY);
for (s, e) in &edges {
y0 = y0.min(s.y.min(e.y));
y1 = y1.max(s.y.max(e.y));
}
let mut ring = Ring {
edges,
single,
y0,
y1,
dy: 1.0,
buckets: Vec::new(),
};
let mut n = ring.edges.len().clamp(1, MAX_BUCKETS);
loop {
ring.dy = if y1 > y0 { (y1 - y0) / n as f64 } else { 1.0 };
ring.buckets = vec![Vec::new(); n];
let mut copies = 0;
for (i, (s, e)) in ring.edges.iter().enumerate() {
let (lo, hi) = (ring.bucket(s.y.min(e.y)), ring.bucket(s.y.max(e.y)));
copies += hi - lo + 1;
for b in &mut ring.buckets[lo..=hi] {
b.push(i as u32);
}
}
if n == 1 || copies <= MAX_COPIES * ring.edges.len() {
return ring;
}
n /= 2;
}
}
#[inline]
fn bucket(&self, y: f64) -> usize {
(((y - self.y0) / self.dy) as usize).min(self.buckets.len() - 1)
}
#[inline]
fn containment(&self, c: Xy) -> Containment {
if self.edges.is_empty() {
return match self.single {
Some(p) if p == c => Containment::OnBoundary,
_ => Containment::Outside,
};
}
if !(c.y >= self.y0 && c.y <= self.y1) {
return Containment::Outside;
}
let mut winding = 0i32;
for &i in &self.buckets[self.bucket(c.y)] {
let (s, e) = self.edges[i as usize];
if s.y <= c.y {
if e.y >= c.y {
let o = robust::orient2d(s.into(), e.into(), c.into());
if o > 0.0 && e.y != c.y {
winding += 1;
} else if o == 0.0 && between(c.x, s.x, e.x) {
return Containment::OnBoundary;
}
}
} else if e.y <= c.y {
let o = robust::orient2d(s.into(), e.into(), c.into());
if o < 0.0 {
winding -= 1;
} else if o == 0.0 && between(c.x, s.x, e.x) {
return Containment::OnBoundary;
}
}
}
if winding == 0 {
Containment::Outside
} else {
Containment::Inside
}
}
}
#[inline]
fn between(v: f64, a: f64, b: f64) -> bool {
if a < b {
v >= a && v <= b
} else {
v >= b && v <= a
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Footprint {
polygons: Vec<(Ring, Vec<Ring>)>,
bounds: Option<(Xy, Xy)>,
}
impl Footprint {
pub fn polygon(exterior: &[Geodetic], holes: &[&[Geodetic]]) -> Self {
Footprint::multi_polygon(&[(exterior, holes)])
}
pub fn multi_polygon(polygons: &[(&[Geodetic], &[&[Geodetic]])]) -> Self {
let xy = |ring: &[Geodetic]| {
ring.iter()
.map(|g| Xy {
x: g.lon_deg,
y: g.lat_deg,
})
.collect::<Vec<_>>()
};
Footprint::from_rings(
polygons
.iter()
.map(|(exterior, holes)| (xy(exterior), holes.iter().map(|h| xy(h)).collect())),
)
}
fn from_rings(polygons: impl IntoIterator<Item = (Vec<Xy>, Vec<Vec<Xy>>)>) -> Self {
let mut bounds: Option<(Xy, Xy)> = None;
let polygons = polygons
.into_iter()
.filter(|(exterior, _)| !exterior.is_empty())
.map(|(exterior, holes)| {
for p in &exterior {
bounds = Some(match bounds {
None => (*p, *p),
Some((lo, hi)) => (
Xy {
x: lo.x.min(p.x),
y: lo.y.min(p.y),
},
Xy {
x: hi.x.max(p.x),
y: hi.y.max(p.y),
},
),
});
}
(Ring::new(exterior), holes.into_iter().map(Ring::new).collect())
})
.collect();
Footprint { polygons, bounds }
}
#[inline]
pub fn containment(&self, point: Geodetic) -> Containment {
let c = Xy {
x: point.lon_deg,
y: point.lat_deg,
};
match self.bounds {
Some((lo, hi)) if c.x >= lo.x && c.x <= hi.x && c.y >= lo.y && c.y <= hi.y => {}
_ => return Containment::Outside,
}
let (mut inside, mut boundaries) = (false, 0usize);
for (exterior, holes) in &self.polygons {
match exterior.containment(c) {
Containment::Outside => {}
Containment::OnBoundary => boundaries += 1,
Containment::Inside => {
let mut in_polygon = true;
for hole in holes {
match hole.containment(c) {
Containment::Outside => {}
Containment::OnBoundary => {
boundaries += 1;
in_polygon = false;
break;
}
Containment::Inside => {
in_polygon = false;
break;
}
}
}
inside |= in_polygon;
}
}
}
if boundaries % 2 == 1 {
Containment::OnBoundary
} else if inside {
Containment::Inside
} else {
Containment::Outside
}
}
pub fn bounds(&self) -> Option<(Geodetic, Geodetic)> {
self.bounds.map(|(lo, hi)| {
(
Geodetic::from_lon_lat(lo.x, lo.y, 0.0),
Geodetic::from_lon_lat(hi.x, hi.y, 0.0),
)
})
}
}
#[cfg(feature = "geo-types")]
mod geo_types_impls {
use super::{Footprint, Xy};
use alloc::vec::Vec;
fn ring(ls: &geo_types::LineString<f64>) -> Vec<Xy> {
ls.0.iter().map(|c| Xy { x: c.x, y: c.y }).collect()
}
fn polygon(p: &geo_types::Polygon<f64>) -> (Vec<Xy>, Vec<Vec<Xy>>) {
(ring(p.exterior()), p.interiors().iter().map(ring).collect())
}
impl From<&geo_types::Polygon<f64>> for super::Footprint {
fn from(p: &geo_types::Polygon<f64>) -> Self {
Footprint::from_rings([polygon(p)])
}
}
impl From<&geo_types::MultiPolygon<f64>> for super::Footprint {
fn from(m: &geo_types::MultiPolygon<f64>) -> Self {
Footprint::from_rings(m.0.iter().map(polygon))
}
}
}