use crate::{LinestringError, linestring_2d};
use rustc_hash::FxHashSet;
use std::{cmp::Ordering, collections::BTreeMap, convert::identity};
use vector_traits::{
approx,
approx::{AbsDiffEq, UlpsEq},
num_traits::float::FloatCore,
prelude::*,
};
#[derive(Clone, Copy)]
struct SiteEventKey<T: GenericVector2> {
pos: T,
}
impl<T: GenericVector2> SiteEventKey<T> {
#[inline(always)]
fn new(pos: T) -> Self {
Self { pos }
}
}
impl<T: GenericVector2> Ord for SiteEventKey<T> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl<T: GenericVector2> PartialOrd for SiteEventKey<T>
where
T::Scalar: UlpsEq,
{
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if approx::ulps_eq!(&self.pos.y(), &other.pos.y()) {
if approx::ulps_eq!(&self.pos.x(), &other.pos.x()) {
Some(Ordering::Equal)
} else {
self.pos.x().partial_cmp(&other.pos.x())
}
} else {
self.pos.y().partial_cmp(&other.pos.y())
}
}
}
impl<T: GenericVector2> Eq for SiteEventKey<T> {}
impl<T: GenericVector2> PartialEq for SiteEventKey<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.pos.is_ulps_eq(
other.pos,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
)
}
}
pub(crate) struct MinMax<T: GenericVector2> {
best_left: Option<T::Scalar>,
slope: MinMaxSlope<T>,
best_right: Option<T::Scalar>,
}
impl<T: GenericVector2> MinMax<T>
where
T::Scalar: UlpsEq,
{
pub(crate) fn new() -> Self {
Self {
best_left: None,
best_right: None,
slope: MinMaxSlope::new(),
}
}
pub(crate) fn update(
&mut self,
pivot_x: T::Scalar,
candidate_x: T::Scalar,
candidate_slope: T::Scalar,
candidate_index: usize,
) {
if candidate_x < pivot_x {
if let Some(current_min) = self.best_left {
if approx::ulps_eq!(¤t_min, &candidate_x) {
self.slope
.update_left(false, candidate_slope, candidate_index);
} else if current_min < candidate_x {
self.slope
.update_left(true, candidate_slope, candidate_index);
self.best_left = Some(candidate_x);
} else {
}
} else {
self.best_left = Some(candidate_x);
self.slope
.update_left(false, candidate_slope, candidate_index);
}
} else if candidate_x > pivot_x {
if let Some(current_max) = self.best_right {
if approx::ulps_eq!(¤t_max, &candidate_x) {
self.slope
.update_right(false, candidate_slope, candidate_index);
} else if current_max > candidate_x {
self.slope
.update_right(true, candidate_slope, candidate_index);
self.best_right = Some(candidate_x);
} else {
}
} else {
self.best_right = Some(candidate_x);
self.slope
.update_right(false, candidate_slope, candidate_index);
}
}
}
pub(crate) fn clear(&mut self) {
self.best_left = None;
self.best_right = None;
self.slope.clear();
}
}
struct MinMaxSlope<T: GenericVector2> {
best_left: Option<T::Scalar>, candidates_left: Vec<usize>,
best_right: Option<T::Scalar>, candidates_right: Vec<usize>,
}
impl<T: GenericVector2> MinMaxSlope<T>
where
T::Scalar: UlpsEq,
{
fn new() -> Self {
Self {
best_left: None,
candidates_left: Vec::<usize>::new(),
best_right: None,
candidates_right: Vec::<usize>::new(),
}
}
fn update_both(&mut self, candidate_index: usize, lines: &[linestring_2d::Line2<T>]) {
let line = lines[candidate_index];
let candidate_slope = if approx::ulps_eq!(&line.end.y(), &line.start.y()) {
T::Scalar::INFINITY
} else {
(line.end.x() - line.start.x()) / (line.end.y() - line.start.y())
};
self.update_left(false, candidate_slope, candidate_index);
self.update_right(false, candidate_slope, candidate_index);
}
fn update_left(&mut self, clear: bool, candidate_slope: T::Scalar, candidate_index: usize) {
if clear {
self.candidates_left.clear();
self.best_left = None;
}
if let Some(current_slope) = self.best_left {
if approx::ulps_eq!(¤t_slope, &candidate_slope) {
self.candidates_left.push(candidate_index);
} else if candidate_slope < current_slope {
self.candidates_left.clear();
self.candidates_left.push(candidate_index);
self.best_left = Some(candidate_slope);
} else {
}
} else {
self.best_left = Some(candidate_slope);
self.candidates_left.push(candidate_index);
}
}
fn update_right(&mut self, clear: bool, candidate_slope: T::Scalar, candidate_index: usize) {
if clear {
self.candidates_right.clear();
self.best_right = None;
}
if let Some(current_slope) = self.best_right {
if approx::ulps_eq!(¤t_slope, &candidate_slope) {
self.candidates_right.push(candidate_index);
} else if candidate_slope > current_slope {
self.candidates_right.clear();
self.candidates_right.push(candidate_index);
self.best_right = Some(candidate_slope);
} else {
}
} else {
self.best_right = Some(candidate_slope);
self.candidates_right.push(candidate_index);
}
}
fn clear(&mut self) {
self.best_left = None;
self.candidates_left.clear();
self.best_right = None;
self.candidates_right.clear();
}
}
struct SiteEvent {
drop: Option<Vec<usize>>,
add: Option<Vec<usize>>,
intersection: Option<Vec<usize>>,
}
impl SiteEvent {
pub(crate) fn with_intersection(i: &[usize]) -> Self {
Self {
drop: None,
add: None,
intersection: Some(i.to_vec()),
}
}
pub(crate) fn with_drop(l: &[usize]) -> Self {
Self {
drop: Some(l.to_vec()),
add: None,
intersection: None,
}
}
pub(crate) fn with_add(l: &[usize]) -> Self {
Self {
drop: None,
add: Some(l.to_vec()),
intersection: None,
}
}
#[allow(dead_code)]
pub fn get_intersections(&self) -> &Option<Vec<usize>> {
&self.intersection
}
}
fn sweepline_intersection<T: GenericVector2>(
sweepline: T,
other: &linestring_2d::Line2<T>,
) -> Option<(T::Scalar, T::Scalar)> {
let y1 = other.start.y();
let y2 = other.end.y();
let x1 = other.start.x();
let x2 = other.end.x();
if approx::ulps_eq!(&y1, &y2) {
return (sweepline.x() < x2).then_some((x2, T::Scalar::ZERO));
}
if approx::ulps_eq!(&x1, &x2) {
return Some((x1, T::Scalar::INFINITY));
}
let slope = (y2 - y1) / (x2 - x1);
let d = y1 - slope * x1;
Some(((sweepline.y() - d) / slope, slope))
}
pub struct IntersectionData<T: GenericVector2> {
sweepline_pos: T,
stop_at_first_intersection: bool,
pub ignore_end_point_intersections: bool,
site_events: Option<BTreeMap<SiteEventKey<T>, SiteEvent>>,
lines: Vec<linestring_2d::Line2<T>>,
}
impl<T: GenericVector2> Default for IntersectionData<T> {
fn default() -> Self {
Self {
sweepline_pos: T::new_2d(-T::Scalar::max_value(), -T::Scalar::max_value()),
stop_at_first_intersection: false,
ignore_end_point_intersections: false,
site_events: Some(BTreeMap::new()),
lines: Vec::<linestring_2d::Line2<T>>::new(),
}
}
}
impl<T: GenericVector2> IntersectionData<T> {
pub fn get_sweepline_pos(&self) -> &T {
&self.sweepline_pos
}
pub fn get_lines(&self) -> &Vec<linestring_2d::Line2<T>> {
&self.lines
}
pub fn with_stop_at_first_intersection(mut self, value: bool) -> Result<Self, LinestringError> {
self.stop_at_first_intersection = value;
Ok(self)
}
pub fn with_ignore_end_point_intersections(
mut self,
value: bool,
) -> Result<Self, LinestringError> {
self.ignore_end_point_intersections = value;
Ok(self)
}
pub fn with_lines<IT>(mut self, input_iter: IT) -> Result<Self, LinestringError>
where
IT: Iterator<Item = linestring_2d::Line2<T>>,
{
let mut site_events = self.site_events.take().ok_or_else(|| {
LinestringError::InternalError("with_lines() could not take 'site_events'".to_string())
})?;
for (index, mut aline) in input_iter.enumerate() {
if !(aline.start.x().is_finite()
&& aline.start.y().is_finite()
&& aline.end.x().is_finite()
&& aline.end.y().is_finite())
{
return Err(LinestringError::InvalidData(
"Some of the points are infinite".to_string(),
));
}
if !(SiteEventKey::new(aline.start)).lt(&(SiteEventKey::new(aline.end))) {
std::mem::swap(&mut aline.start, &mut aline.end);
};
self.lines.push(aline);
let key_start = SiteEventKey::new(aline.start);
let key_end = SiteEventKey::new(aline.end);
if let Some(event) = site_events.get_mut(&key_start) {
let mut lower = event.add.take().map_or(Vec::<usize>::new(), identity);
lower.push(index);
event.add = Some(lower);
} else {
let event = SiteEvent::with_add(&[index]);
let _ = site_events.insert(key_start, event);
}
if let Some(event) = site_events.get_mut(&key_end) {
let mut upper = event.drop.take().map_or(Vec::<usize>::new(), identity);
upper.push(index);
event.drop = Some(upper);
} else {
let event = SiteEvent::with_drop(&[index]);
let _ = site_events.insert(key_end, event);
}
}
self.site_events = Some(site_events);
Ok(self)
}
#[inline]
fn add_intersection_event(
&self,
site_events: &mut BTreeMap<SiteEventKey<T>, SiteEvent>,
position: &SiteEventKey<T>,
intersecting_lines: &[usize],
) {
if let Some(event) = site_events.get_mut(position) {
let mut intersections_added = 0;
for new_intersection in intersecting_lines.iter() {
let i_line = self.lines[*new_intersection];
if position.pos.is_ulps_eq(
i_line.start,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
) || position.pos.is_ulps_eq(
i_line.end,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
) {
continue;
}
if let Some(prev_intersections) = &mut event.intersection {
prev_intersections.push(*new_intersection);
} else {
let new_vec = vec![*new_intersection];
event.intersection = Some(new_vec);
}
intersections_added += 1;
}
if intersections_added > 0 {
let mut intersections = event.intersection.take().unwrap();
intersections.sort_unstable();
intersections.dedup();
event.intersection = Some(intersections);
}
} else {
let event = SiteEvent::with_intersection(intersecting_lines);
let _ = site_events.insert(*position, event);
}
}
#[allow(clippy::type_complexity)]
pub fn compute(
mut self,
) -> Result<impl ExactSizeIterator<Item = (T, Vec<usize>)>, LinestringError> {
let mut active_lines = FxHashSet::<usize>::default();
let mut site_events = self.site_events.take().ok_or_else(|| {
LinestringError::InternalError("compute() could not take 'site_events'".to_string())
})?;
let mut result: BTreeMap<SiteEventKey<T>, Vec<usize>> = BTreeMap::default();
let mut neighbour_priority = MinMax::new();
let mut connected_priority = MinMaxSlope::new();
loop {
if let Some((key, event)) = {
site_events.pop_first()
} {
self.handle_event(
&key,
&event,
&mut active_lines,
&mut neighbour_priority,
&mut connected_priority,
&mut site_events,
&mut result,
)?;
if !result.is_empty() && self.stop_at_first_intersection {
break;
}
} else {
self.sweepline_pos = T::new_2d(T::Scalar::max_value(), T::Scalar::max_value());
break;
}
}
Ok(result.into_iter().map(|x| (x.0.pos, x.1)))
}
#[allow(clippy::too_many_arguments)]
fn handle_event(
&mut self,
key: &SiteEventKey<T>,
event: &SiteEvent,
active_lines: &mut FxHashSet<usize>,
neighbour_priority: &mut MinMax<T>,
connected_priority: &mut MinMaxSlope<T>,
site_events: &mut BTreeMap<SiteEventKey<T>, SiteEvent>,
result: &mut BTreeMap<SiteEventKey<T>, Vec<usize>>,
) -> Result<(), LinestringError> {
self.sweepline_pos = key.pos;
let removed_active_lines = event.drop.iter().flatten().count();
let added_active_lines = event.add.iter().flatten().count();
let intersections_found = event.intersection.iter().flatten().count();
if self.ignore_end_point_intersections {
if intersections_found > 0 {
if removed_active_lines > 0 {
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.drop.iter().flatten(),
);
}
if added_active_lines > 0 {
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.add.iter().flatten(),
);
}
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.intersection.iter().flatten(),
);
}
} else if removed_active_lines + added_active_lines + intersections_found > 1 {
if removed_active_lines > 0 {
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.drop.iter().flatten(),
);
}
if added_active_lines > 0 {
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.add.iter().flatten(),
);
}
if intersections_found > 0 {
self.report_intersections_to_result(
result,
self.sweepline_pos,
event.intersection.iter().flatten(),
);
}
}
for line_index in event.drop.iter().flatten() {
let _ = active_lines.remove(line_index);
}
neighbour_priority.clear();
'active_lines: for line_index in active_lines.iter() {
for i in event.intersection.iter().flatten() {
if i == line_index {
continue 'active_lines;
}
}
if let Some((intersection_x, intersection_slope)) =
sweepline_intersection(self.sweepline_pos, self.lines.get(*line_index).unwrap())
{
neighbour_priority.update(
self.sweepline_pos.x(),
intersection_x,
intersection_slope,
*line_index,
);
}
}
for l in event.add.iter().flatten() {
let _ = active_lines.insert(*l);
}
if intersections_found + added_active_lines == 0 {
if !neighbour_priority.slope.candidates_left.is_empty()
&& !neighbour_priority.slope.candidates_right.is_empty()
{
self.find_new_events(
&neighbour_priority.slope.candidates_left,
&neighbour_priority.slope.candidates_right,
site_events,
)?;
}
} else {
connected_priority.clear();
for l in event.add.iter().flatten() {
connected_priority.update_both(*l, &self.lines);
}
for l in event.intersection.iter().flatten() {
connected_priority.update_both(*l, &self.lines);
}
if !neighbour_priority.slope.candidates_left.is_empty() {
self.find_new_events(
&neighbour_priority.slope.candidates_left,
&connected_priority.candidates_left,
site_events,
)?;
}
if !neighbour_priority.slope.candidates_right.is_empty() {
self.find_new_events(
&neighbour_priority.slope.candidates_right,
&connected_priority.candidates_right,
site_events,
)?;
}
}
Ok(())
}
fn find_new_events(
&mut self,
left: &[usize],
right: &[usize],
site_events: &mut BTreeMap<SiteEventKey<T>, SiteEvent>,
) -> Result<(), LinestringError> {
for left_i in left.iter() {
for right_i in right.iter() {
let left_l = self.lines[*left_i];
let right_l = self.lines[*right_i];
if left_l.end.is_ulps_eq(
right_l.end,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
) {
continue;
}
if let Some(intersection_p) = left_l.intersection_point(right_l) {
let intersection_p = intersection_p.single();
if !intersection_p.x().is_finite() || !intersection_p.y().is_finite() {
return Err(LinestringError::InvalidData(format!(
"Input data has intersection at invalid position x:{:?}, y:{:?}",
intersection_p.x(),
intersection_p.y()
)));
}
if intersection_p.y() >= self.sweepline_pos.y()
&& !(intersection_p.y() == self.sweepline_pos.y()
&& intersection_p.x() < self.sweepline_pos.x())
&& !intersection_p.is_ulps_eq(
self.sweepline_pos,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
)
{
self.add_intersection_event(
site_events,
&SiteEventKey {
pos: intersection_p,
},
&[*left_i, *right_i],
)
}
}
}
}
Ok(())
}
fn report_intersections_to_result<'a, I: Iterator<Item = &'a usize>>(
&mut self,
result: &mut BTreeMap<SiteEventKey<T>, Vec<usize>>,
pos: T,
intersecting_lines: I,
) {
let key = SiteEventKey { pos };
let value = if let Some(value) = result.get_mut(&key) {
value
} else {
let _ = result.insert(key, Vec::default());
result.get_mut(&key).unwrap()
};
for line in intersecting_lines {
value.push(*line);
}
value.sort_unstable();
}
}