use geo_types::Coord;
use crate::algorithm::cg_algorithms_dd::orientation_index_coordinate;
#[allow(dead_code)]
pub(crate) const CLOCKWISE: i32 = -1;
pub(crate) const COUNTERCLOCKWISE: i32 = 1;
pub(crate) const COLLINEAR: i32 = 0;
#[allow(dead_code)]
pub(crate) const RIGHT: i32 = CLOCKWISE;
pub(crate) const LEFT: i32 = COUNTERCLOCKWISE;
#[allow(dead_code)]
pub(crate) const STRAIGHT: i32 = COLLINEAR;
pub(crate) fn index(p1: Coord<f64>, p2: Coord<f64>, q: Coord<f64>) -> i32 {
orientation_index_coordinate(p1, p2, q)
}
pub(crate) fn is_ccw_coordinates(ring: &[Coord<f64>]) -> bool {
is_ccw_coordinate_sequence(ring)
}
pub(crate) fn is_ccw_coordinate_sequence(ring: &[Coord<f64>]) -> bool {
let n_pts = ring.len() - 1;
if n_pts < 3 {
return false;
}
let mut up_hi_pt = ring[0];
let mut prev_y = up_hi_pt.y;
let mut up_low_pt = None;
let mut i_up_hi = 0;
for i in 1..=n_pts {
let py = ring[i].y;
if py > prev_y && py >= up_hi_pt.y {
up_hi_pt = ring[i];
i_up_hi = i;
up_low_pt = Some(ring[i - 1]);
}
prev_y = py;
}
if i_up_hi == 0 {
return false;
}
let mut i_down_low = i_up_hi;
loop {
i_down_low = (i_down_low + 1) % n_pts;
if i_down_low == i_up_hi || ring[i_down_low].y != up_hi_pt.y {
break;
}
}
let down_low_pt = ring[i_down_low];
let i_down_hi = if i_down_low > 0 {
i_down_low - 1
} else {
n_pts - 1
};
let down_hi_pt = ring[i_down_hi];
if up_hi_pt == down_hi_pt {
let Some(up_low_pt) = up_low_pt else {
return false;
};
if up_low_pt == up_hi_pt || down_low_pt == up_hi_pt || up_low_pt == down_low_pt {
return false;
}
index(up_low_pt, up_hi_pt, down_low_pt) == COUNTERCLOCKWISE
} else {
let del_x = down_hi_pt.x - up_hi_pt.x;
del_x < 0.0
}
}
#[cfg(test)]
mod tests {
use super::{
CLOCKWISE, COLLINEAR, COUNTERCLOCKWISE, LEFT, RIGHT, STRAIGHT, index, is_ccw_coordinates,
};
use geo_types::Coord;
fn ring(pts: &[(f64, f64)]) -> Vec<Coord<f64>> {
pts.iter().map(|&(x, y)| Coord { x, y }).collect()
}
#[test]
fn names_the_jts_constants() {
assert_eq!(CLOCKWISE, -1);
assert_eq!(COUNTERCLOCKWISE, 1);
assert_eq!(COLLINEAR, 0);
}
#[test]
fn classifies_a_counter_clockwise_ring() {
let r = ring(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)]);
assert!(is_ccw_coordinates(&r));
}
#[test]
fn classifies_a_clockwise_ring() {
let r = ring(&[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]);
assert!(!is_ccw_coordinates(&r));
}
#[test]
fn returns_false_for_a_ring_with_too_few_points() {
let r = ring(&[(0.0, 0.0), (1.0, 1.0), (0.0, 0.0)]);
assert!(!is_ccw_coordinates(&r));
}
#[test]
fn returns_false_for_a_flat_ring() {
let r = ring(&[(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (1.0, 0.0), (0.0, 0.0)]);
assert!(!is_ccw_coordinates(&r));
}
#[test]
fn handles_a_ring_with_a_flat_top() {
let r = ring(&[
(0.0, 0.0),
(2.0, 0.0),
(2.0, 1.0),
(1.0, 1.0),
(0.0, 1.0),
(0.0, 0.0),
]);
assert!(is_ccw_coordinates(&r));
}
#[test]
fn handles_repeated_points_along_the_top() {
let r = ring(&[
(0.0, 0.0),
(2.0, 0.0),
(2.0, 1.0),
(2.0, 1.0),
(0.0, 1.0),
(0.0, 0.0),
]);
assert!(is_ccw_coordinates(&r));
}
#[test]
fn delegates_the_pointed_cap_case_to_the_robust_index() {
let o = Coord { x: 0.0, y: 0.0 };
let e = Coord { x: 1.0, y: 0.0 };
assert_eq!(index(o, e, Coord { x: 0.0, y: 1.0 }), COUNTERCLOCKWISE);
assert_eq!(index(o, e, Coord { x: 0.0, y: -1.0 }), CLOCKWISE);
assert_eq!(index(o, e, Coord { x: 2.0, y: 0.0 }), COLLINEAR);
}
#[test]
fn names_each_orientation_twice_as_jts_does() {
assert_eq!(RIGHT, CLOCKWISE);
assert_eq!(LEFT, COUNTERCLOCKWISE);
assert_eq!(STRAIGHT, COLLINEAR);
}
}