use crate::geometry::{RingIntersection, RingIntersectionLookup};
use alloc::{
collections::{BTreeMap, BTreeSet},
rc::Rc,
vec,
vec::Vec,
};
use core::{
cell::RefCell,
cmp::Ordering,
f64::consts::{PI, TAU},
mem::take,
};
use libm::atan2;
use s2json::{BBox, FullXY, Point};
#[derive(Debug)]
pub struct PolyPath<P: FullXY> {
pub id: usize,
pub outer: Option<Vec<P>>,
pub old_outers: Vec<BBox>,
pub holes: Vec<Vec<P>>,
pub polys_consumed: BTreeSet<usize>,
pub bbox: BBox,
}
pub type PolyPathRef<P> = Rc<RefCell<PolyPath<P>>>;
impl<P: FullXY> PolyPath<P> {
pub fn new_ref(
ring: Vec<P>,
polys_consumed: BTreeSet<usize>,
is_outer: bool,
bbox: Option<BBox>,
) -> PolyPathRef<P> {
Rc::new(RefCell::new(PolyPath::new(ring, polys_consumed, is_outer, bbox)))
}
pub fn new(
ring: Vec<P>,
polys_consumed: BTreeSet<usize>,
is_outer: bool,
bbox: Option<BBox>,
) -> Self {
let bbox = bbox.unwrap_or(BBox::from_linestring(&ring));
let mut outer = None;
let mut holes = vec![];
if is_outer {
outer = Some(ring);
} else {
holes.push(ring);
}
PolyPath { id: 0, outer, old_outers: vec![], holes, polys_consumed, bbox }
}
pub fn get_path(&mut self) -> Option<Vec<Vec<P>>> {
self.outer.as_ref()?;
let outer = self.outer.as_mut().unwrap();
if outer.len() < 4 {
return None;
}
let mut res = vec![take(outer)];
for hole in self.holes.iter_mut() {
if hole.len() < 4 {
continue;
}
res.push(take(hole));
}
Some(res)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NextRingChunk<P: FullXY> {
pub int_point: Point,
pub chunk: RingChunkRef<P>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RingChunk<P: FullXY> {
pub visted: bool,
pub poly_index: usize,
pub ring_index: usize,
pub bbox: BBox,
pub mid: Vec<P>,
pub from: Point,
pub to: Point,
pub next: Option<NextRingChunk<P>>,
pub from_angle: f64,
pub to_angle: f64,
}
pub type RingChunkRef<P> = Rc<RefCell<RingChunk<P>>>;
impl<P: FullXY> RingChunk<P> {
#[allow(clippy::too_many_arguments)]
pub fn new(
poly_index: usize,
ring_index: usize,
bbox: BBox,
mid: Vec<P>,
from: Point,
to: Point,
from_angle: f64,
to_angle: f64,
) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(RingChunk {
visted: false,
poly_index,
ring_index,
bbox,
mid,
from,
to,
next: None,
from_angle,
to_angle,
}))
}
pub fn equal_chunk(&self, other: &RingChunkRef<P>) -> bool {
let other = &other.borrow();
(self.ring_index > 0) == (other.ring_index > 0)
&& self.from == other.from
&& self.to == other.to
&& self.mid == other.mid
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IntersectionPoint<P: FullXY> {
pub point: Point,
pub from: Vec<RingChunkRef<P>>,
pub to: Vec<RingChunkRef<P>>,
}
pub type IntersectionPointRef<P> = Rc<RefCell<IntersectionPoint<P>>>;
impl<P: FullXY> IntersectionPoint<P> {
pub fn new(point: Point) -> IntersectionPointRef<P> {
Rc::new(RefCell::new(IntersectionPoint { point, from: vec![], to: vec![] }))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct InterPointLookup<P: FullXY> {
pub lookup: BTreeMap<Point, IntersectionPointRef<P>>,
}
impl<P: FullXY> Default for InterPointLookup<P> {
fn default() -> Self {
Self { lookup: BTreeMap::new() }
}
}
impl<P: FullXY> InterPointLookup<P> {
pub fn get(&mut self, point: Point) -> IntersectionPointRef<P> {
self.lookup.entry(point).or_insert_with(|| IntersectionPoint::new(point)).clone()
}
#[allow(clippy::too_many_arguments)]
pub fn link_ints(
&mut self,
poly_index: usize,
ring_index: usize,
from: Point,
to: Point,
mid: Vec<P>,
from_angle: Option<f64>,
to_angle: Option<f64>,
) -> RingChunkRef<P> {
let bbox = BBox::from_linestring(&mid).merge(&BBox::from_linestring(&[from, to]));
let from_angle =
from_angle.unwrap_or(angle(&mid.last().map(Point::from).unwrap_or(from), &to));
let to_angle =
to_angle.unwrap_or(angle(&mid.first().map(Point::from).unwrap_or(to), &from));
let chunk =
RingChunk::new(poly_index, ring_index, bbox, mid, from, to, from_angle, to_angle);
self.get(from).borrow_mut().to.push(chunk.clone());
self.get(to).borrow_mut().from.push(chunk.clone());
chunk
}
}
#[allow(clippy::type_complexity)]
pub fn build_paths_and_chunks<P: FullXY>(
vector_polygons: &[Vec<Vec<P>>],
ring_intersect_lookup: &mut RingIntersectionLookup,
) -> (
Vec<PolyPathRef<P>>,
BTreeMap<usize, PolyPathRef<P>>,
Vec<RingChunkRef<P>>,
InterPointLookup<P>,
Vec<BBox>,
) {
let mut paths: Vec<PolyPathRef<P>> = vec![];
let mut path_lookup: BTreeMap<usize, PolyPathRef<P>> = BTreeMap::new();
let mut outer_ring_bboxes: Vec<BBox> = vec![Default::default(); vector_polygons.len()];
let mut chunks: Vec<RingChunkRef<P>> = vec![];
let mut int_lookup = InterPointLookup::<P>::default();
for (p_i, poly) in vector_polygons.iter().enumerate() {
for (r_i, ring) in poly.iter().enumerate() {
let intersections = ring_intersect_lookup.get_mut(&p_i).and_then(|r| r.get_mut(&r_i));
if intersections.is_none() || intersections.as_ref().unwrap().is_empty() {
if let Some(existing_path) = path_lookup.get(&p_i) {
let existing_path = &mut existing_path.borrow_mut();
if r_i == 0 {
existing_path.bbox.merge_in_place(&BBox::from_linestring(ring));
existing_path.outer = Some(ring.clone());
outer_ring_bboxes[p_i] = existing_path.bbox;
} else {
existing_path.holes.push(ring.clone());
}
} else {
let path =
PolyPath::new_ref(ring.clone(), BTreeSet::from([p_i]), r_i == 0, None);
if r_i == 0 {
outer_ring_bboxes[p_i] = path.borrow().bbox;
}
path_lookup.insert(p_i, path.clone());
paths.push(path);
}
continue;
}
if r_i == 0 {
outer_ring_bboxes[p_i] = BBox::from_linestring(ring);
}
let intersections: Vec<&RingIntersection> =
intersections.as_ref().unwrap().iter().filter(|i| i.t != 0.).collect();
let mut curr_index = 0;
let mut int_index = 0;
while curr_index < ring.len() - 1 {
if let Some(cur_inter) = intersections.get(int_index) {
if curr_index != cur_inter.from {
let start = curr_index;
while curr_index != cur_inter.from {
curr_index += 1;
}
let mid = ring[start + 1..curr_index].to_vec();
let from = &ring[start];
let to = &ring[curr_index];
let chunk =
int_lookup.link_ints(p_i, r_i, from.into(), to.into(), mid, None, None);
chunks.push(chunk);
}
let mut from: Point = (&ring[curr_index]).into();
let mut cur_int = Some(cur_inter);
while let Some(c_i) = cur_int
&& c_i.from == curr_index
{
if from != *c_i.point.borrow() {
chunks.push(int_lookup.link_ints(
p_i,
r_i,
from,
*c_i.point.borrow(),
vec![],
Some(invert_angle(c_i.t_angle)),
Some(c_i.t_angle),
));
}
int_index += 1;
from = *c_i.point.borrow();
cur_int = intersections.get(int_index);
}
let next: Point = (&ring[curr_index + 1]).into();
if from != next {
let ang = intersections[int_index - 1].t_angle;
chunks.push(int_lookup.link_ints(
p_i,
r_i,
from,
next,
vec![],
Some(invert_angle(ang)),
Some(ang),
));
}
} else {
chunks.push(int_lookup.link_ints(
p_i,
r_i,
(&ring[curr_index]).into(),
(&ring[curr_index + 1]).into(),
vec![],
None,
None,
));
}
curr_index += 1;
}
}
}
chunks.sort_by(|a, b| {
let BBox { left: a_left, bottom: a_bottom, .. } = a.borrow().bbox;
let BBox { left: b_left, bottom: b_bottom, .. } = b.borrow().bbox;
a_left
.partial_cmp(&b_left)
.unwrap_or(Ordering::Equal)
.then(a_bottom.partial_cmp(&b_bottom).unwrap_or(Ordering::Equal))
});
(paths, path_lookup, chunks, int_lookup, outer_ring_bboxes)
}
#[derive(Debug)]
struct IntPair<P: FullXY> {
from: RingChunkRef<P>,
to: RingChunkRef<P>,
angle: f64,
}
pub fn merge_intersection_pairs<P: FullXY>(intersection: &IntersectionPointRef<P>) {
let IntersectionPoint { from, to, point, .. } = &mut *intersection.borrow_mut();
let int_point = *point;
if from.is_empty() || to.is_empty() {
return;
}
if from.len() == 1 && to.len() == 1 {
from[0].borrow_mut().next = Some(NextRingChunk { chunk: to[0].clone(), int_point });
return;
}
let mut froms: Vec<RingChunkRef<P>> = vec![];
for c in from {
if c.borrow().visted {
continue;
}
if !froms.iter().any(|r| r.borrow().equal_chunk(c)) {
froms.push(c.clone());
} else {
c.borrow_mut().visted = true;
}
}
let mut tos: Vec<RingChunkRef<P>> = vec![];
for c in to {
if c.borrow().visted {
continue;
}
if !tos.iter().any(|r| r.borrow().equal_chunk(c)) {
tos.push(c.clone());
} else {
c.borrow_mut().visted = true;
}
}
let mut pairs: Vec<IntPair<P>> = vec![];
for f in froms.iter() {
for t in tos.iter() {
let mut angle = t.borrow().to_angle - f.borrow().from_angle;
angle = if angle < 0. { angle + TAU } else { angle };
pairs.push(IntPair { from: f.clone(), to: t.clone(), angle });
}
}
pairs.sort_by(|a, b| a.angle.partial_cmp(&b.angle).unwrap_or(Ordering::Equal));
for IntPair { from, to, .. } in &pairs {
let from = &mut from.borrow_mut();
if from.visted || to.borrow().visted {
continue;
}
from.next = Some(NextRingChunk { chunk: to.clone(), int_point });
from.visted = true;
to.borrow_mut().visted = true;
}
for f in froms {
f.borrow_mut().visted = false;
}
for t in tos {
t.borrow_mut().visted = false;
}
}
fn angle<P: FullXY, Q: FullXY>(a: &P, b: &Q) -> f64 {
atan2(a.y() - b.y(), a.x() - b.x())
}
fn invert_angle(angle: f64) -> f64 {
if angle >= 0. { angle - PI } else { angle + PI }
}