#![expect(
clippy::cast_sign_loss,
reason = "EdgeId (i32) used as Vec indices in shape utilities"
)]
#![expect(
clippy::cast_possible_truncation,
reason = "EdgeId/ShapeId (i32) <-> usize and num_edges (usize->i32)"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "usize -> i32 for EdgeId/ShapeId — always in range"
)]
use std::ops::ControlFlow;
use crate::s2::CellId;
use crate::s2::Point;
use crate::s2::builder::S2Error;
use crate::s2::builder::S2ErrorCode;
use crate::s2::crossing_edge_query::{CrossingEdgeQuery, CrossingType};
use crate::s2::edge_crosser::EdgeCrosser;
use crate::s2::edge_crossings::Crossing;
use crate::s2::padded_cell::PaddedCell;
use crate::s2::predicates;
use crate::s2::shape::{Dimension, Edge, Shape, ShapeEdge, ShapeEdgeId, ShapeId};
use crate::s2::shape_index::{ShapeIndex, ShapeIndexCell, ShapeIndexIterator};
use crate::s2::wedge_relations::{self, WedgeRel};
fn append_shape_edges(index: &ShapeIndex, cell: &ShapeIndexCell, edges: &mut Vec<ShapeEdge>) {
for clipped in &cell.shapes {
let shape_id = clipped.shape_id;
let Some(shape) = index.shape(shape_id) else {
continue;
};
for &edge_id in &clipped.edges {
let edge = shape.edge(edge_id as usize);
edges.push(ShapeEdge::new(ShapeEdgeId::new(shape_id, edge_id), edge));
}
}
}
fn get_shape_edges(index: &ShapeIndex, cell: &ShapeIndexCell, edges: &mut Vec<ShapeEdge>) {
edges.clear();
append_shape_edges(index, cell, edges);
}
fn get_shape_edges_multi(
index: &ShapeIndex,
cells: &[&ShapeIndexCell],
edges: &mut Vec<ShapeEdge>,
) {
edges.clear();
for cell in cells {
append_shape_edges(index, cell, edges);
}
}
fn visit_crossings_vec(
shape_edges: &[ShapeEdge],
cross_type: CrossingType,
need_adjacent: bool,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
let min_crossing_sign = if cross_type == CrossingType::Interior {
1
} else {
0
};
let num_edges = shape_edges.len();
for i in 0..num_edges.saturating_sub(1) {
let a = &shape_edges[i];
let mut j = i + 1;
if !need_adjacent && j < num_edges && a.edge.v1 == shape_edges[j].edge.v0 {
j += 1;
if j >= num_edges {
break;
}
}
let mut crosser = EdgeCrosser::new(a.edge.v0, a.edge.v1);
while j < num_edges {
let b = &shape_edges[j];
crosser.restart_at(b.edge.v0);
let sign = crosser.chain_crossing_sign(b.edge.v1);
let sign_val = match sign {
Crossing::Cross => 1,
Crossing::MaybeCross => 0,
Crossing::DoNotCross => -1,
};
if sign_val >= min_crossing_sign {
visitor(a, b, sign_val == 1)?;
}
j += 1;
}
}
ControlFlow::Continue(())
}
fn visit_crossings_internal(
index: &ShapeIndex,
cross_type: CrossingType,
need_adjacent: bool,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
let mut shape_edges = Vec::new();
let mut it = index.iter();
while !it.done() {
if let Some(cell) = it.index_cell() {
get_shape_edges(index, cell, &mut shape_edges);
visit_crossings_vec(&shape_edges, cross_type, need_adjacent, visitor)?;
}
it.next();
}
ControlFlow::Continue(())
}
pub fn visit_crossing_edge_pairs(
index: &ShapeIndex,
cross_type: CrossingType,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
let need_adjacent = cross_type == CrossingType::All;
visit_crossings_internal(index, cross_type, need_adjacent, visitor)
}
struct IndexCrosser<'a> {
a_index: &'a ShapeIndex,
b_index: &'a ShapeIndex,
min_crossing_sign: i32,
swapped: bool,
b_query: CrossingEdgeQuery<'a>,
a_shape_edges: Vec<ShapeEdge>,
b_shape_edges: Vec<ShapeEdge>,
}
impl<'a> IndexCrosser<'a> {
fn new(
a_index: &'a ShapeIndex,
b_index: &'a ShapeIndex,
cross_type: CrossingType,
swapped: bool,
) -> Self {
IndexCrosser {
a_index,
b_index,
min_crossing_sign: if cross_type == CrossingType::Interior {
1
} else {
0
},
swapped,
b_query: CrossingEdgeQuery::new(b_index),
a_shape_edges: Vec::new(),
b_shape_edges: Vec::new(),
}
}
fn visit_edge_pair(
&self,
a: &ShapeEdge,
b: &ShapeEdge,
is_interior: bool,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
if self.swapped {
visitor(b, a, is_interior)
} else {
visitor(a, b, is_interior)
}
}
fn visit_edge_cell_crossings(
&mut self,
a: &ShapeEdge,
b_cell: &ShapeIndexCell,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
get_shape_edges(self.b_index, b_cell, &mut self.b_shape_edges);
let a_copy = *a;
let mut crosser = EdgeCrosser::new(a_copy.edge.v0, a_copy.edge.v1);
for b in &self.b_shape_edges {
crosser.restart_at(b.edge.v0);
let sign = crosser.chain_crossing_sign(b.edge.v1);
let sign_val = match sign {
Crossing::Cross => 1,
Crossing::MaybeCross => 0,
Crossing::DoNotCross => -1,
};
if sign_val >= self.min_crossing_sign {
self.visit_edge_pair(&a_copy, b, sign_val == 1, visitor)?;
}
}
ControlFlow::Continue(())
}
fn visit_subcell_crossings(
&mut self,
a_cell: &ShapeIndexCell,
b_id: CellId,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
self.a_shape_edges.clear();
append_shape_edges(self.a_index, a_cell, &mut self.a_shape_edges);
let a_edges: Vec<ShapeEdge> = self.a_shape_edges.clone();
let mut b_root = PaddedCell::from_cell_id(b_id, 0.0);
for a in &a_edges {
let cells = self.b_query.get_cells(a.edge.v0, a.edge.v1, &mut b_root);
for cell in cells {
self.visit_edge_cell_crossings(a, cell, visitor)?;
}
}
ControlFlow::Continue(())
}
fn visit_edges_edges_crossings(
&self,
a_edges: &[ShapeEdge],
b_edges: &[ShapeEdge],
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
for a in a_edges {
let mut crosser = EdgeCrosser::new(a.edge.v0, a.edge.v1);
for b in b_edges {
crosser.restart_at(b.edge.v0);
let sign = crosser.chain_crossing_sign(b.edge.v1);
let sign_val = match sign {
Crossing::Cross => 1,
Crossing::MaybeCross => 0,
Crossing::DoNotCross => -1,
};
if sign_val >= self.min_crossing_sign {
self.visit_edge_pair(a, b, sign_val == 1, visitor)?;
}
}
}
ControlFlow::Continue(())
}
fn visit_cell_cell_crossings(
&mut self,
a_cell: &ShapeIndexCell,
b_cell: &ShapeIndexCell,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
get_shape_edges(self.a_index, a_cell, &mut self.a_shape_edges);
get_shape_edges(self.b_index, b_cell, &mut self.b_shape_edges);
let a_edges: Vec<ShapeEdge> = self.a_shape_edges.clone();
let b_edges: Vec<ShapeEdge> = self.b_shape_edges.clone();
self.visit_edges_edges_crossings(&a_edges, &b_edges, visitor)
}
fn visit_crossings(
&mut self,
ai: &mut ShapeIndexIterator<'_>,
bi: &mut ShapeIndexIterator<'_>,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
debug_assert!(ai.cell_id().contains(bi.cell_id()));
let ai_cell = ai.index_cell();
if ai_cell.is_none_or(|c| c.num_edges() == 0) {
seek_beyond(bi, ai.cell_id());
} else if let Some(a_cell) = ai_cell {
const EDGE_QUERY_MIN_EDGES: usize = 23;
let mut b_edges_count = 0;
let mut b_cells: Vec<&ShapeIndexCell> = Vec::new();
loop {
if let Some(b_cell) = bi.index_cell() {
let cell_edges = b_cell.num_edges();
if cell_edges > 0 {
b_edges_count += cell_edges;
if b_edges_count >= EDGE_QUERY_MIN_EDGES {
let ai_id = ai.cell_id();
self.visit_subcell_crossings(a_cell, ai_id, visitor)?;
seek_beyond(bi, ai_id);
ai.next();
return ControlFlow::Continue(());
}
b_cells.push(b_cell);
}
}
bi.next();
if bi.done() || bi.cell_id() > ai.cell_id().range_max() {
break;
}
}
if !b_cells.is_empty() {
get_shape_edges(self.a_index, a_cell, &mut self.a_shape_edges);
get_shape_edges_multi(self.b_index, &b_cells, &mut self.b_shape_edges);
let a_edges: Vec<ShapeEdge> = self.a_shape_edges.clone();
let b_edges: Vec<ShapeEdge> = self.b_shape_edges.clone();
self.visit_edges_edges_crossings(&a_edges, &b_edges, visitor)?;
}
}
ai.next();
ControlFlow::Continue(())
}
}
fn seek_beyond(it: &mut ShapeIndexIterator<'_>, target: CellId) {
it.seek(target.range_max().next());
}
pub fn visit_crossing_edge_pairs_ab(
a_index: &ShapeIndex,
b_index: &ShapeIndex,
cross_type: CrossingType,
visitor: &mut dyn FnMut(&ShapeEdge, &ShapeEdge, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
let mut ai = a_index.iter();
let mut bi = b_index.iter();
let mut ab = IndexCrosser::new(a_index, b_index, cross_type, false);
let mut ba = IndexCrosser::new(b_index, a_index, cross_type, true);
while !ai.done() || !bi.done() {
if ai.done() {
break;
}
if bi.done() {
break;
}
let ai_range_max = ai.cell_id().range_max();
let bi_range_min = bi.cell_id().range_min();
let bi_range_max = bi.cell_id().range_max();
let ai_range_min = ai.cell_id().range_min();
if ai_range_max < bi_range_min {
ai.seek(bi.cell_id().range_min());
} else if bi_range_max < ai_range_min {
bi.seek(ai.cell_id().range_min());
} else {
let ai_lsb = ai.cell_id().lsb();
let bi_lsb = bi.cell_id().lsb();
match ai_lsb.cmp(&bi_lsb) {
std::cmp::Ordering::Greater => {
ab.visit_crossings(&mut ai, &mut bi, visitor)?;
}
std::cmp::Ordering::Less => {
ba.visit_crossings(&mut bi, &mut ai, visitor)?;
}
std::cmp::Ordering::Equal => {
let a_cell = ai.index_cell();
let b_cell = bi.index_cell();
if let (Some(ac), Some(bc)) = (a_cell, b_cell)
&& ac.num_edges() > 0
&& bc.num_edges() > 0
{
ab.visit_cell_cell_crossings(ac, bc, visitor)?;
}
ai.next();
bi.next();
}
}
}
}
ControlFlow::Continue(())
}
fn init_loop_error(code: S2ErrorCode, chain_id: usize, is_polygon: bool, msg: String) -> S2Error {
if is_polygon {
S2Error {
code,
message: format!("Loop {chain_id}: {msg}"),
}
} else {
S2Error { code, message: msg }
}
}
fn find_crossing_error(
shape: &dyn Shape,
a: &ShapeEdge,
b: &ShapeEdge,
is_interior: bool,
error: &mut S2Error,
) -> bool {
let is_polygon = shape.num_chains() > 1;
let ap = shape.chain_position(a.id.edge_id as usize);
let bp = shape.chain_position(b.id.edge_id as usize);
if is_interior {
if ap.chain_id == bp.chain_id {
let msg = format!("Edge {} crosses edge {}", ap.offset, bp.offset);
*error = init_loop_error(
S2ErrorCode::LoopSelfIntersection,
ap.chain_id,
is_polygon,
msg,
);
} else {
*error = S2Error {
code: S2ErrorCode::PolygonLoopsCross,
message: format!(
"Loop {} edge {} crosses loop {} edge {}",
ap.chain_id, ap.offset, bp.chain_id, bp.offset
),
};
}
return true;
}
if a.edge.v1 != b.edge.v1 {
return false;
}
if ap.chain_id == bp.chain_id {
let msg = format!(
"Edge {} has duplicate vertex with edge {}",
ap.offset, bp.offset
);
*error = init_loop_error(S2ErrorCode::DuplicateVertices, ap.chain_id, is_polygon, msg);
return true;
}
let a_len = shape.chain(ap.chain_id).length;
let b_len = shape.chain(bp.chain_id).length;
let a_next = if ap.offset + 1 == a_len {
0
} else {
ap.offset + 1
};
let b_next = if bp.offset + 1 == b_len {
0
} else {
bp.offset + 1
};
let a2 = shape.chain_edge(ap.chain_id, a_next).v1;
let b2 = shape.chain_edge(bp.chain_id, b_next).v1;
if a.edge.v0 == b.edge.v0 || a.edge.v0 == b2 {
*error = S2Error {
code: S2ErrorCode::PolygonLoopsShareEdge,
message: format!(
"Loop {} edge {} has duplicate near loop {} edge {}",
ap.chain_id, ap.offset, bp.chain_id, bp.offset
),
};
return true;
}
if wedge_relations::wedge_relation(a.edge.v0, a.edge.v1, a2, b.edge.v0, b2)
== WedgeRel::ProperlyOverlaps
&& wedge_relations::wedge_relation(a.edge.v0, a.edge.v1, a2, b2, b.edge.v0)
== WedgeRel::ProperlyOverlaps
{
*error = S2Error {
code: S2ErrorCode::PolygonLoopsCross,
message: format!(
"Loop {} edge {} crosses loop {} edge {}",
ap.chain_id, ap.offset, bp.chain_id, bp.offset
),
};
return true;
}
false
}
pub fn find_self_intersection(index: &ShapeIndex) -> Option<S2Error> {
if index.num_shape_ids() == 0 {
return None;
}
debug_assert_eq!(1, index.num_shape_ids());
let mut crossing_pairs: Vec<(ShapeEdge, ShapeEdge, bool)> = Vec::new();
let _ = visit_crossings_internal(
index,
CrossingType::All,
false, &mut |a: &ShapeEdge, b: &ShapeEdge, is_interior: bool| {
crossing_pairs.push((*a, *b, is_interior));
ControlFlow::Continue(())
},
);
let shape = index.shape(0)?;
let mut error = S2Error::ok();
for (a, b, is_interior) in &crossing_pairs {
if find_crossing_error(shape, a, b, *is_interior, &mut error) {
return Some(error);
}
}
None
}
pub fn contains_brute_force(shape: &dyn Shape, point: Point) -> bool {
if shape.dimension() < Dimension::Polygon {
return false;
}
let rp = shape.reference_point();
let mut inside = rp.contained;
let mut crosser = EdgeCrosser::new(rp.point, point);
for e in 0..shape.num_edges() {
let edge = shape.edge(e);
inside ^= crosser.edge_or_vertex_crossing(edge.v0, edge.v1);
}
inside
}
pub fn shape_to_points(shape: &dyn Shape) -> Result<Vec<Point>, S2Error> {
if shape.dimension() != Dimension::Point {
return Err(S2Error::new(
S2ErrorCode::InvalidArgument,
"shape_to_points requires a dimension-0 (point) shape",
));
}
let mut points = Vec::with_capacity(shape.num_edges());
for i in 0..shape.num_edges() {
points.push(shape.edge(i).v0);
}
Ok(points)
}
pub fn shape_to_polyline_vertices(shape: &dyn Shape) -> Vec<Point> {
debug_assert_eq!(shape.dimension(), Dimension::Polyline);
debug_assert_eq!(shape.num_chains(), 1);
crate::s2::shape_measures::get_chain_vertices(shape, 0)
}
pub fn shape_to_loop_vertices(shape: &dyn Shape) -> Vec<Vec<Point>> {
debug_assert_eq!(shape.dimension(), Dimension::Polygon);
let mut loops = Vec::with_capacity(shape.num_chains());
for i in 0..shape.num_chains() {
loops.push(crate::s2::shape_measures::get_chain_vertices(shape, i));
}
loops
}
pub fn count_edges(index: &ShapeIndex) -> usize {
index.num_edges()
}
pub fn count_edges_up_to(index: &ShapeIndex, max_edges: usize) -> usize {
let mut total = 0;
for id in 0..index.num_shape_ids() {
if let Some(shape) = index.shape(id as i32) {
total += shape.num_edges();
if total >= max_edges {
return total;
}
}
}
total
}
pub fn count_vertices_shape(shape: &dyn Shape) -> usize {
match shape.dimension() {
Dimension::Point => shape.num_chains(),
Dimension::Polyline => shape.num_edges() + shape.num_chains(),
Dimension::Polygon => shape.num_edges(),
}
}
pub fn count_vertices_index(index: &ShapeIndex) -> usize {
let mut total = 0;
for id in 0..index.num_shape_ids() {
if let Some(shape) = index.shape(id as i32) {
total += count_vertices_shape(shape);
}
}
total
}
pub fn next_edge_wrap(shape: &dyn Shape, edge_id: usize) -> Option<usize> {
debug_assert!(edge_id < shape.num_edges());
let chainpos = shape.chain_position(edge_id);
let chaininfo = shape.chain(chainpos.chain_id);
let offset = chainpos.offset;
match shape.dimension() {
Dimension::Polygon => {
let new_offset = (offset + 1) % chaininfo.length;
Some(chaininfo.start + new_offset)
}
Dimension::Polyline => {
if offset == chaininfo.length - 1 {
let curr = shape.chain_edge(chainpos.chain_id, offset);
let next = shape.chain_edge(chainpos.chain_id, 0);
if curr.v1 == next.v0 {
Some(chaininfo.start)
} else {
None
}
} else {
Some(chaininfo.start + offset + 1)
}
}
Dimension::Point => None,
}
}
pub fn prev_edge_wrap(shape: &dyn Shape, edge_id: usize) -> Option<usize> {
debug_assert!(edge_id < shape.num_edges());
let chainpos = shape.chain_position(edge_id);
let chaininfo = shape.chain(chainpos.chain_id);
let offset = chainpos.offset;
match shape.dimension() {
Dimension::Polygon => {
let new_offset = if offset == 0 {
chaininfo.length - 1
} else {
offset - 1
};
Some(chaininfo.start + new_offset)
}
Dimension::Polyline => {
if offset == 0 {
let end = chaininfo.length - 1;
let curr = shape.chain_edge(chainpos.chain_id, 0);
let prev = shape.chain_edge(chainpos.chain_id, end);
if prev.v1 == curr.v0 {
Some(chaininfo.start + end)
} else {
None
}
} else {
Some(chaininfo.start + offset - 1)
}
}
Dimension::Point => None,
}
}
pub fn sort_edges_ccw(origin: Point, first: Edge, data: &mut [Edge]) {
debug_assert!(first.v0 == origin || first.v1 == origin);
let first_vertex = if first.v0 == origin {
first.v1
} else {
first.v0
};
debug_assert!(first_vertex != origin);
data.sort_unstable_by(|a, b| {
debug_assert!(a.v0 == origin || a.v1 == origin);
debug_assert!(b.v0 == origin || b.v1 == origin);
if a == b {
return std::cmp::Ordering::Equal;
}
if *a == b.reversed() {
return if a.v0 == origin {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
};
}
if *a == first {
return std::cmp::Ordering::Less;
}
if *b == first {
return std::cmp::Ordering::Greater;
}
let apnt = if a.v0 == origin { a.v1 } else { a.v0 };
let bpnt = if b.v0 == origin { b.v1 } else { b.v0 };
if predicates::ordered_ccw(first_vertex, apnt, bpnt, origin) {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
});
}
#[derive(Debug)]
pub struct EdgeIterator<'a> {
index: &'a ShapeIndex,
shape_id: i32,
num_edges: i32,
edge_id: i32,
}
impl<'a> EdgeIterator<'a> {
pub fn new(index: &'a ShapeIndex) -> Self {
let mut it = EdgeIterator {
index,
shape_id: -1,
num_edges: 0,
edge_id: -1,
};
it.advance();
it
}
pub fn shape_id(&self) -> i32 {
self.shape_id
}
pub fn edge_id(&self) -> i32 {
self.edge_id
}
pub fn shape_edge_id(&self) -> ShapeEdgeId {
ShapeEdgeId::new(self.shape_id, self.edge_id)
}
pub fn edge(&self) -> Edge {
debug_assert!(!self.done());
match self.index.shape(self.shape_id) {
Some(shape) => shape.edge(self.edge_id as usize),
None => Edge {
v0: Point::default(),
v1: Point::default(),
},
}
}
pub fn done(&self) -> bool {
self.shape_id >= self.index.num_shape_ids() as i32
}
pub fn advance(&mut self) {
self.edge_id += 1;
while self.edge_id >= self.num_edges {
self.shape_id += 1;
if self.shape_id >= self.index.num_shape_ids() as i32 {
break;
}
self.num_edges = match self.index.shape(self.shape_id) {
Some(shape) => shape.num_edges() as i32,
None => 0,
};
self.edge_id = 0;
}
}
}
impl Iterator for EdgeIterator<'_> {
type Item = (ShapeEdgeId, Edge);
fn next(&mut self) -> Option<Self::Item> {
if self.done() {
return None;
}
let id = self.shape_edge_id();
let edge = self.edge();
self.advance();
Some((id, edge))
}
}
pub fn build_polygon_boundaries(components: &[Vec<&dyn Shape>]) -> Vec<Vec<usize>> {
if components.is_empty() {
return Vec::new();
}
let mut all_shapes: Vec<(usize, usize, &dyn Shape)> = Vec::new();
for (ci, component) in components.iter().enumerate() {
for (li, shape) in component.iter().enumerate() {
all_shapes.push((ci, li, *shape));
}
}
let origin = Point::origin();
let mut indexed_global_ids: Vec<usize> = Vec::new();
let mut indexed_component_ids: Vec<usize> = Vec::new();
let mut outer_loops: Vec<usize> = Vec::new();
let mut index = ShapeIndex::new();
let mut global_id = 0usize;
for (ci, component) in components.iter().enumerate() {
let mut found_outer = false;
for shape in component {
if component.len() > 1 && !contains_brute_force(*shape, origin) {
let mut verts = Vec::new();
for e_id in 0..shape.num_edges() {
let e = shape.edge(e_id);
verts.push(e.v0);
}
let lax_loop = crate::s2::lax_loop::LaxLoop::new(verts);
index.add(Box::new(lax_loop));
indexed_global_ids.push(global_id);
indexed_component_ids.push(ci);
} else if !found_outer {
outer_loops.push(global_id);
found_outer = true;
} else {
outer_loops.push(global_id);
}
global_id += 1;
}
if !found_outer {
if let Some(last) = indexed_global_ids.last() {
outer_loops.push(*last);
}
}
}
index.build();
let mut contains_query = crate::s2::contains_point_query::ContainsPointQuery::new(
&index,
crate::s2::contains_point_query::VertexModel::SemiOpen,
);
let mut ancestors: Vec<Vec<ShapeId>> = Vec::new(); for &outer_gid in &outer_loops {
let (_, _, shape) = all_shapes[outer_gid];
if shape.num_edges() > 0 {
let v0 = shape.edge(0).v0;
let ids = contains_query.containing_shape_ids(v0);
ancestors.push(ids);
} else {
ancestors.push(Vec::new());
}
}
use std::collections::HashMap;
let mut children: HashMap<ShapeId, Vec<usize>> = HashMap::new();
for (i, &outer_gid) in outer_loops.iter().enumerate() {
let depth = ancestors[i].len();
let mut ancestor_shape_id = ShapeId(-1);
if depth > 0 {
for &candidate_shape_id in &ancestors[i] {
let cand_comp_id = indexed_component_ids[candidate_shape_id.as_usize()];
#[expect(
clippy::expect_used,
reason = "invariant: every component has an outer loop"
)]
let outer_idx = outer_loops
.iter()
.position(|&gid| all_shapes[gid].0 == cand_comp_id)
.expect("outer loop must exist for component");
if ancestors[outer_idx].len() == depth - 1 {
ancestor_shape_id = candidate_shape_id;
break;
}
}
}
children
.entry(ancestor_shape_id)
.or_default()
.push(outer_gid);
}
let num_indexed = indexed_global_ids.len();
let mut polygons: Vec<Vec<usize>> = Vec::with_capacity(num_indexed + 1);
for (i, &gid) in indexed_global_ids.iter().enumerate() {
let mut polygon = Vec::new();
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "index fits in i32"
)]
let key = ShapeId(i as i32);
if let Some(kids) = children.get(&key) {
polygon.extend(kids.iter().copied());
}
polygon.push(gid);
polygons.push(polygon);
}
let top = children.get(&ShapeId(-1)).cloned().unwrap_or_default();
polygons.push(top);
polygons
}
#[cfg(test)]
#[path = "shape_util_tests.rs"]
mod shape_util_tests;
#[cfg(test)]
mod tests {
use super::*;
use crate::s2::shape::{Chain, ChainPosition, Edge, ReferencePoint};
use crate::s2::{LatLng, Point};
fn p(lat: f64, lng: f64) -> Point {
LatLng::from_degrees(lat, lng).to_point()
}
#[derive(Debug)]
struct TestPolyline {
vertices: Vec<Point>,
}
impl Shape for TestPolyline {
fn num_edges(&self) -> usize {
self.vertices.len().saturating_sub(1)
}
fn edge(&self, id: usize) -> Edge {
Edge::new(self.vertices[id], self.vertices[id + 1])
}
fn reference_point(&self) -> ReferencePoint {
ReferencePoint::default()
}
fn num_chains(&self) -> usize {
1
}
fn chain(&self, _chain_id: usize) -> Chain {
Chain::new(0, self.num_edges())
}
fn chain_edge(&self, _chain_id: usize, offset: usize) -> Edge {
self.edge(offset)
}
fn chain_position(&self, edge_id: usize) -> ChainPosition {
ChainPosition::new(0, edge_id)
}
fn dimension(&self) -> Dimension {
Dimension::Polyline
}
}
#[test]
fn test_no_crossings_single_index() {
let mut index = ShapeIndex::new();
index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, 0.0), p(0.0, 10.0)],
}));
index.add(Box::new(TestPolyline {
vertices: vec![p(10.0, 0.0), p(10.0, 10.0)],
}));
index.build();
let mut count = 0;
let _ = visit_crossing_edge_pairs(&index, CrossingType::Interior, &mut |_, _, _| {
count += 1;
ControlFlow::Continue(())
});
assert_eq!(count, 0);
}
#[test]
fn test_interior_crossing_single_index() {
let mut index = ShapeIndex::new();
index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, -10.0), p(0.0, 10.0)],
}));
index.add(Box::new(TestPolyline {
vertices: vec![p(-10.0, 0.0), p(10.0, 0.0)],
}));
index.build();
let mut interior_count = 0;
let _ =
visit_crossing_edge_pairs(&index, CrossingType::Interior, &mut |_, _, is_interior| {
if is_interior {
interior_count += 1;
}
ControlFlow::Continue(())
});
assert!(
interior_count >= 1,
"Expected at least one interior crossing"
);
}
#[test]
fn test_dual_index_crossing() {
let mut a_index = ShapeIndex::new();
a_index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, -10.0), p(0.0, 10.0)],
}));
a_index.build();
let mut b_index = ShapeIndex::new();
b_index.add(Box::new(TestPolyline {
vertices: vec![p(-10.0, 0.0), p(10.0, 0.0)],
}));
b_index.build();
let mut crossings = Vec::new();
let _ = visit_crossing_edge_pairs_ab(
&a_index,
&b_index,
CrossingType::Interior,
&mut |a, b, is_interior| {
crossings.push((a.id, b.id, is_interior));
ControlFlow::Continue(())
},
);
assert!(
!crossings.is_empty(),
"Expected at least one crossing between two indexes"
);
}
#[test]
fn test_early_termination() {
let mut index = ShapeIndex::new();
index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, -10.0), p(0.0, 10.0)],
}));
index.add(Box::new(TestPolyline {
vertices: vec![p(-10.0, 0.0), p(10.0, 0.0)],
}));
index.build();
let result = visit_crossing_edge_pairs(&index, CrossingType::Interior, &mut |_, _, _| {
ControlFlow::Break(())
});
assert!(
result.is_break(),
"Should return Break when visitor returns Break"
);
}
#[test]
fn test_empty_indexes() {
let a_index = ShapeIndex::new();
let b_index = ShapeIndex::new();
let result =
visit_crossing_edge_pairs_ab(&a_index, &b_index, CrossingType::All, &mut |_, _, _| {
ControlFlow::Continue(())
});
assert!(result.is_continue(), "Empty indexes should return Continue");
}
#[test]
fn test_count_vertices_points() {
let index = crate::s2::text_format::make_index("0:0 | 1:1 | 2:2 # #");
let shape = index.shape(0).unwrap();
assert_eq!(shape.dimension(), Dimension::Point);
assert_eq!(count_vertices_shape(shape), 3);
}
#[test]
fn test_count_vertices_polyline() {
let shape = crate::s2::text_format::make_lax_polyline("0:0, 1:0, 2:0, 3:0");
assert_eq!(shape.dimension(), Dimension::Polyline);
assert_eq!(count_vertices_shape(&shape), 4);
}
#[test]
fn test_count_vertices_polygon() {
let shape = crate::s2::text_format::make_lax_polygon("0:0, 0:1, 1:0");
assert_eq!(shape.dimension(), Dimension::Polygon);
assert_eq!(count_vertices_shape(&shape), 3);
}
#[test]
fn test_count_vertices_index() {
let mut index = ShapeIndex::new();
index.add(Box::new(crate::s2::text_format::make_lax_polygon(
"0:0, 0:1, 1:1, 1:0",
)));
index.add(Box::new(crate::s2::text_format::make_lax_polyline(
"0:0, 1:0, 2:0",
)));
index.build();
assert_eq!(count_vertices_index(&index), 4 + 3);
}
#[test]
fn test_next_edge_wrap_polygon() {
let shape = crate::s2::text_format::make_lax_polygon("0:0, 0:1, 1:0");
assert_eq!(next_edge_wrap(&shape, 0), Some(1));
assert_eq!(next_edge_wrap(&shape, 1), Some(2));
assert_eq!(next_edge_wrap(&shape, 2), Some(0)); }
#[test]
fn test_prev_edge_wrap_polygon() {
let shape = crate::s2::text_format::make_lax_polygon("0:0, 0:1, 1:0");
assert_eq!(prev_edge_wrap(&shape, 0), Some(2)); assert_eq!(prev_edge_wrap(&shape, 1), Some(0));
assert_eq!(prev_edge_wrap(&shape, 2), Some(1));
}
#[test]
fn test_next_edge_wrap_open_polyline() {
let shape = crate::s2::text_format::make_lax_polyline("0:0, 1:0, 2:0, 3:0");
assert_eq!(next_edge_wrap(&shape, 0), Some(1));
assert_eq!(next_edge_wrap(&shape, 1), Some(2));
assert_eq!(next_edge_wrap(&shape, 2), None); }
#[test]
fn test_prev_edge_wrap_open_polyline() {
let shape = crate::s2::text_format::make_lax_polyline("0:0, 1:0, 2:0, 3:0");
assert_eq!(prev_edge_wrap(&shape, 0), None); assert_eq!(prev_edge_wrap(&shape, 1), Some(0));
assert_eq!(prev_edge_wrap(&shape, 2), Some(1));
}
#[test]
fn test_edge_iterator_empty_index() {
let index = ShapeIndex::new();
let it = EdgeIterator::new(&index);
assert!(it.done());
}
#[test]
fn test_edge_iterator_counts_all_edges() {
let mut index = ShapeIndex::new();
index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, 0.0), p(1.0, 0.0), p(2.0, 0.0)],
}));
index.add(Box::new(TestPolyline {
vertices: vec![p(0.0, 0.0), p(0.0, 1.0)],
}));
index.build();
let mut count = 0;
let mut it = EdgeIterator::new(&index);
while !it.done() {
count += 1;
it.advance();
}
assert_eq!(count, 3);
}
#[test]
fn test_shape_to_points() {
let index = crate::s2::text_format::make_index("0:0 | 1:1 | 2:2 # #");
let shape = index.shape(0).unwrap();
let points = shape_to_points(shape).unwrap();
assert_eq!(points.len(), 3);
}
#[test]
fn test_shape_to_polyline_vertices() {
let shape = crate::s2::text_format::make_lax_polyline("0:0, 1:0, 2:0");
let verts = shape_to_polyline_vertices(&shape);
assert_eq!(verts.len(), 3); }
#[test]
fn test_shape_to_loop_vertices() {
let shape = crate::s2::text_format::make_lax_polygon("0:0, 0:1, 1:0; 2:2, 2:3, 3:2");
let loops = shape_to_loop_vertices(&shape);
assert_eq!(loops.len(), 2);
assert_eq!(loops[0].len(), 3);
assert_eq!(loops[1].len(), 3);
}
#[test]
fn test_shape_edge_id_both_fields_equal_is_equal() {
assert_eq!(ShapeEdgeId::new(10, 20), ShapeEdgeId::new(10, 20));
}
#[test]
fn test_shape_edge_id_shape_id_unequal_is_unequal() {
assert_ne!(ShapeEdgeId::new(11, 20), ShapeEdgeId::new(10, 20));
}
#[test]
fn test_shape_edge_id_edge_id_unequal_is_unequal() {
assert_ne!(ShapeEdgeId::new(10, 21), ShapeEdgeId::new(10, 20));
}
#[test]
#[expect(clippy::nonminimal_bool, reason = "testing operator directly")]
fn test_shape_edge_id_less_than_is_lexicographic_shape_id_first() {
assert!(!(ShapeEdgeId::new(10, 20) < ShapeEdgeId::new(10, 20)));
assert!(ShapeEdgeId::new(10, 20) < ShapeEdgeId::new(11, 20));
assert!(ShapeEdgeId::new(10, 20) < ShapeEdgeId::new(10, 21));
}
#[test]
#[expect(clippy::nonminimal_bool, reason = "testing operator directly")]
fn test_shape_edge_id_less_eq_is_lexicographic_shape_id_first() {
assert!(!(ShapeEdgeId::new(10, 20) <= ShapeEdgeId::new(9, 20)));
assert!(ShapeEdgeId::new(10, 20) <= ShapeEdgeId::new(10, 20));
assert!(!(ShapeEdgeId::new(10, 20) <= ShapeEdgeId::new(10, 19)));
}
#[test]
#[expect(clippy::nonminimal_bool, reason = "testing operator directly")]
fn test_shape_edge_id_greater_than_is_lexicographic_shape_id_first() {
assert!(!(ShapeEdgeId::new(10, 20) > ShapeEdgeId::new(10, 20)));
assert!(ShapeEdgeId::new(10, 20) > ShapeEdgeId::new(9, 20));
assert!(ShapeEdgeId::new(10, 20) > ShapeEdgeId::new(10, 19));
}
#[test]
#[expect(clippy::nonminimal_bool, reason = "testing operator directly")]
fn test_shape_edge_id_greater_eq_is_lexicographic_shape_id_first() {
assert!(!(ShapeEdgeId::new(10, 20) >= ShapeEdgeId::new(11, 20)));
assert!(ShapeEdgeId::new(10, 20) >= ShapeEdgeId::new(10, 20));
assert!(!(ShapeEdgeId::new(10, 20) >= ShapeEdgeId::new(10, 21)));
}
#[test]
fn test_next_prev_edge_point_does_not_wrap() {
let index = crate::s2::text_format::make_index("1:1 | 2:2 # #");
let shape = index.shape(0).unwrap();
assert_eq!(prev_edge_wrap(shape, 0), None);
assert_eq!(next_edge_wrap(shape, 0), None);
assert_eq!(prev_edge_wrap(shape, 1), None);
assert_eq!(next_edge_wrap(shape, 1), None);
}
#[test]
fn test_next_prev_edge_closed_polyline_wraps() {
let index = crate::s2::text_format::make_index("# 0:0, 1:1, 0:2, -1:1, 0:0 #");
let shape = index.shape(0).unwrap();
assert_eq!(prev_edge_wrap(shape, 0), Some(3));
assert_eq!(next_edge_wrap(shape, 0), Some(1));
assert_eq!(prev_edge_wrap(shape, 3), Some(2));
assert_eq!(next_edge_wrap(shape, 3), Some(0));
}
fn get_edges_direct(index: &ShapeIndex) -> Vec<Edge> {
let mut result = Vec::new();
for id in 0..index.num_shape_ids() {
if let Some(shape) = index.shape(id as i32) {
for j in 0..shape.num_edges() {
result.push(shape.edge(j));
}
}
}
result
}
fn verify_edge_iterator(index: &ShapeIndex) {
let expected = get_edges_direct(index);
let mut actual = Vec::new();
let mut shape_id: i32 = -1;
let mut edge_id: i32 = -1;
let mut it = EdgeIterator::new(index);
while !it.done() {
if it.shape_id() != shape_id {
shape_id = it.shape_id();
edge_id = 0;
}
assert!(
actual.len() < expected.len(),
"too many edges from iterator"
);
assert_eq!(it.edge(), expected[actual.len()]);
assert_eq!(it.edge_id(), edge_id);
assert_eq!(it.shape_edge_id(), ShapeEdgeId::new(shape_id, edge_id));
actual.push(it.edge());
edge_id += 1;
it.advance();
}
assert_eq!(actual.len(), expected.len());
}
#[test]
fn test_edge_iterator_empty() {
let index = crate::s2::text_format::make_index("# #");
verify_edge_iterator(&index);
}
#[test]
fn test_edge_iterator_points() {
let index = crate::s2::text_format::make_index("0:0 | 1:1 # #");
verify_edge_iterator(&index);
}
#[test]
fn test_edge_iterator_lines() {
let index = crate::s2::text_format::make_index("# 0:0, 10:10 | 5:5, 5:10 | 1:2, 2:1 #");
verify_edge_iterator(&index);
}
#[test]
fn test_edge_iterator_polygons() {
let index =
crate::s2::text_format::make_index("# # 10:10, 10:0, 0:0; -10:-10, -10:0, 0:0, 0:-10");
verify_edge_iterator(&index);
}
#[test]
fn test_edge_iterator_collection() {
let index = crate::s2::text_format::make_index(
"1:1 | 7:2 # 1:1, 2:2, 3:3 | 2:2, 1:7 # 10:10, 10:0, 0:0; 20:20, 20:10, 10:10 | 15:15, 15:0, 0:0",
);
verify_edge_iterator(&index);
}
#[test]
fn test_count_edges_up_to_stops_early() {
let index = crate::s2::text_format::make_index(
"0:0 | 0:1 | 0:2 | 0:3 | 0:4 # 1:0, 1:1 | 1:2, 1:3 | 1:4, 1:5, 1:6 #",
);
assert_eq!(index.num_shape_ids(), 4);
assert_eq!(index.shape(0).unwrap().num_edges(), 5);
assert_eq!(index.shape(1).unwrap().num_edges(), 1);
assert_eq!(index.shape(2).unwrap().num_edges(), 1);
assert_eq!(index.shape(3).unwrap().num_edges(), 2);
assert_eq!(count_edges(&index), 9);
assert_eq!(count_edges_up_to(&index, 1), 5); assert_eq!(count_edges_up_to(&index, 5), 5); assert_eq!(count_edges_up_to(&index, 6), 6); assert_eq!(count_edges_up_to(&index, 8), 9); }
#[test]
fn test_count_vertices_counts_correctly() {
let index = crate::s2::text_format::make_index("1:1 | 2:2 | 3:3 # #");
assert_eq!(count_vertices_index(&index), 3);
let index = crate::s2::text_format::make_index("1:1 | 2:2 # 3:3, 4:4, 5:5 #");
assert_eq!(count_vertices_index(&index), 5);
let index =
crate::s2::text_format::make_index("1:1 | 2:2 # 3:3, 4:4, 5:5 # 6:6, 7:7, 8:8, 9:9");
assert_eq!(count_vertices_index(&index), 9);
let index = crate::s2::text_format::make_index("# 3:3, 3:3, 3:3 #");
assert_eq!(count_vertices_index(&index), 3);
let index = crate::s2::text_format::make_index("# # 4:4, 4:4, 4:4, 4:4");
assert_eq!(count_vertices_index(&index), 4);
}
#[test]
fn test_contains_brute_force_no_interior() {
let polyline = crate::s2::text_format::make_lax_polyline("0:0, 0:1, 1:-1, -1:-1, -1e-9:1");
let point = crate::s2::text_format::parse_point("0:0");
assert!(!contains_brute_force(&polyline, point));
}
#[test]
fn test_contains_brute_force_contains_reference_point() {
let polygon = crate::s2::text_format::make_lax_polygon("0:0, 0:1, 1:-1, -1:-1, -1e-9:1");
let r = polygon.reference_point();
assert_eq!(r.contained, contains_brute_force(&polygon, r.point));
}
#[test]
fn test_contains_brute_force_consistent_with_s2loop() {
use crate::s1::Angle;
use crate::s2::Loop;
use crate::s2::region::Region;
let center = crate::s2::text_format::parse_point("89:-179");
let loop_ = Loop::make_regular(center, Angle::from_degrees(10.0), 100);
for i in 0..loop_.num_vertices() {
let v = loop_.vertex(i);
assert_eq!(
loop_.contains_point(&v),
contains_brute_force(&loop_, v),
"mismatch at vertex {i}"
);
}
}
#[test]
fn test_get_reference_point_empty_polygon() {
let polygon = crate::s2::text_format::make_lax_polygon("");
assert!(!polygon.reference_point().contained);
}
#[test]
fn test_get_reference_point_full_polygon() {
let polygon = crate::s2::text_format::make_lax_polygon("full");
assert!(polygon.reference_point().contained);
}
#[test]
fn test_get_reference_point_degenerate_loops() {
use crate::s2::lax_polygon::LaxPolygon;
let loops = vec![
crate::s2::text_format::parse_points("1:1, 1:2, 2:2, 1:2, 1:3, 1:2, 1:1"),
crate::s2::text_format::parse_points("0:0, 0:3, 0:6, 0:9, 0:6, 0:3, 0:0"),
crate::s2::text_format::parse_points("5:5, 6:6"),
];
let shape = LaxPolygon::from_loops_owned(loops);
assert!(!shape.reference_point().contained);
}
#[test]
fn test_get_reference_point_inverted_loops() {
use crate::s2::lax_polygon::LaxPolygon;
let loops = vec![
crate::s2::text_format::parse_points("1:2, 1:1, 2:2"),
crate::s2::text_format::parse_points("3:4, 3:3, 4:4"),
];
let shape = LaxPolygon::from_loops_owned(loops);
assert!(contains_brute_force(&shape, Point::origin()));
}
fn get_crossings(
index: &ShapeIndex,
crossing_type: CrossingType,
) -> Vec<(ShapeEdgeId, ShapeEdgeId)> {
let mut pairs = Vec::new();
let _ = visit_crossing_edge_pairs(index, crossing_type, &mut |a, b, _is_interior| {
pairs.push((a.id, b.id));
ControlFlow::Continue(())
});
pairs.sort_unstable();
pairs.dedup();
pairs
}
fn get_crossings_ab(
index_a: &ShapeIndex,
index_b: &ShapeIndex,
crossing_type: CrossingType,
) -> Vec<(ShapeEdgeId, ShapeEdgeId)> {
let mut pairs = Vec::new();
let _ = visit_crossing_edge_pairs_ab(
index_a,
index_b,
crossing_type,
&mut |a, b, _is_interior| {
pairs.push((a.id, b.id));
ControlFlow::Continue(())
},
);
pairs.sort_unstable();
pairs.dedup();
pairs
}
fn crossing_meets_threshold(crossing: Crossing, crossing_type: CrossingType) -> bool {
match crossing_type {
CrossingType::All => crossing != Crossing::DoNotCross,
CrossingType::Interior => crossing == Crossing::Cross,
}
}
fn get_crossings_brute_force(
index: &ShapeIndex,
crossing_type: CrossingType,
) -> Vec<(ShapeEdgeId, ShapeEdgeId)> {
let mut result = Vec::new();
let mut a_it = EdgeIterator::new(index);
while !a_it.done() {
let a = a_it.edge();
let a_id = a_it.shape_edge_id();
let mut b_it = EdgeIterator::new(index);
while !b_it.done() && b_it.shape_edge_id() <= a_id {
b_it.advance();
}
while !b_it.done() {
let b = b_it.edge();
let sign = crate::s2::edge_crossings::crossing_sign(a.v0, a.v1, b.v0, b.v1);
if crossing_meets_threshold(sign, crossing_type) {
result.push((a_id, b_it.shape_edge_id()));
}
b_it.advance();
}
a_it.advance();
}
result
}
fn get_crossings_brute_force_ab(
index_a: &ShapeIndex,
index_b: &ShapeIndex,
crossing_type: CrossingType,
) -> Vec<(ShapeEdgeId, ShapeEdgeId)> {
let mut result = Vec::new();
let mut a_it = EdgeIterator::new(index_a);
while !a_it.done() {
let a = a_it.edge();
let mut b_it = EdgeIterator::new(index_b);
while !b_it.done() {
let b = b_it.edge();
let sign = crate::s2::edge_crossings::crossing_sign(a.v0, a.v1, b.v0, b.v1);
if crossing_meets_threshold(sign, crossing_type) {
result.push((a_it.shape_edge_id(), b_it.shape_edge_id()));
}
b_it.advance();
}
a_it.advance();
}
result
}
fn test_crossing_edge_pairs(index: &ShapeIndex, ct: CrossingType, expected: usize) {
let brute = get_crossings_brute_force(index, ct);
let actual = get_crossings(index, ct);
assert_eq!(expected, brute.len(), "brute force count mismatch");
assert_eq!(expected, actual.len(), "optimized count mismatch");
assert_eq!(brute, actual);
}
fn test_crossing_edge_pairs_ab(
a: &ShapeIndex,
b: &ShapeIndex,
ct: CrossingType,
expected: usize,
) {
let brute = get_crossings_brute_force_ab(a, b, ct);
let actual = get_crossings_ab(a, b, ct);
assert_eq!(expected, brute.len(), "brute force count mismatch (ab)");
assert_eq!(expected, actual.len(), "optimized count mismatch (ab)");
assert_eq!(brute, actual);
}
#[test]
fn test_crossing_no_intersections_one_index() {
let index = ShapeIndex::new();
test_crossing_edge_pairs(&index, CrossingType::All, 0);
test_crossing_edge_pairs(&index, CrossingType::Interior, 0);
}
#[test]
fn test_crossing_no_intersections_two_indexes() {
let a = ShapeIndex::new();
let b = ShapeIndex::new();
test_crossing_edge_pairs_ab(&a, &b, CrossingType::All, 0);
test_crossing_edge_pairs_ab(&a, &b, CrossingType::Interior, 0);
}
#[test]
fn test_crossing_edge_grid_one_index() {
use crate::s2::edge_vector_shape::EdgeVectorShape;
let grid_size = 10;
let epsilon = 1e-10;
let mut shape = EdgeVectorShape::new();
for i in 0..=grid_size {
let e = if i == 0 || i == grid_size {
0.0
} else {
epsilon
};
let i_f = f64::from(i);
shape.add(
LatLng::from_degrees(-e, i_f).to_point(),
LatLng::from_degrees(f64::from(grid_size) + e, i_f).to_point(),
);
shape.add(
LatLng::from_degrees(i_f, -e).to_point(),
LatLng::from_degrees(i_f, f64::from(grid_size) + e).to_point(),
);
}
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
test_crossing_edge_pairs(&index, CrossingType::All, 112);
test_crossing_edge_pairs(&index, CrossingType::Interior, 108);
}
#[test]
fn test_crossing_edge_grid_two_indexes() {
use crate::s2::edge_vector_shape::EdgeVectorShape;
let grid_size = 10;
let epsilon = 1e-10;
let mut shape_a = EdgeVectorShape::new();
let mut shape_b = EdgeVectorShape::new();
for i in 0..=grid_size {
let e = if i == 0 || i == grid_size {
0.0
} else {
epsilon
};
let i_f = f64::from(i);
shape_a.add(
LatLng::from_degrees(-e, i_f).to_point(),
LatLng::from_degrees(f64::from(grid_size) + e, i_f).to_point(),
);
shape_b.add(
LatLng::from_degrees(i_f, -e).to_point(),
LatLng::from_degrees(i_f, f64::from(grid_size) + e).to_point(),
);
}
let mut index_a = ShapeIndex::new();
index_a.add(Box::new(shape_a));
index_a.build();
let mut index_b = ShapeIndex::new();
index_b.add(Box::new(shape_b));
index_b.build();
test_crossing_edge_pairs_ab(&index_a, &index_b, CrossingType::All, 112);
test_crossing_edge_pairs_ab(&index_a, &index_b, CrossingType::Interior, 108);
}
#[test]
fn test_find_self_intersection_basic() {
fn test_has_crossing(polygon_str: &str, has_crossing: bool) {
use crate::s2::text_format::make_polygon;
let polygon = make_polygon(polygon_str);
let mut index = ShapeIndex::new();
index.add(Box::new(polygon));
index.build();
let result = find_self_intersection(&index);
assert_eq!(
result.is_some(),
has_crossing,
"polygon: {polygon_str}, expected crossing={has_crossing}, got={result:?}"
);
}
test_has_crossing("0:0, 0:1, 0:2, 1:2, 1:1, 1:0", false);
test_has_crossing("0:0, 0:1, 0:2, 1:2, 0:1, 1:0", true); test_has_crossing("0:0, 1:1, 0:1; 0:0, 1:1, 1:0", true); test_has_crossing("0:0, 1:1, 0:1; 1:1, 0:0, 1:0", true); test_has_crossing("0:0, 0:2, 2:2, 2:0; 1:1, 0:2, 3:1, 2:0", true); }
#[test]
fn test_point_vector_shape_to_points() {
let points = crate::s2::text_format::parse_points("11:11, 10:0, 5:5");
let index = crate::s2::text_format::make_index("11:11 | 10:0 | 5:5 # #");
let shape = index.shape(0).unwrap();
let extracted = shape_to_points(shape).unwrap();
assert_eq!(extracted.len(), 3);
for (i, pt) in extracted.iter().enumerate() {
assert_eq!(*pt, points[i], "point mismatch at index {i}");
}
}
#[test]
fn test_line_to_polyline_vertices() {
let points = crate::s2::text_format::parse_points("11:11, 10:0, 5:5");
let shape = crate::s2::text_format::make_lax_polyline("11:11, 10:0, 5:5");
let verts = shape_to_polyline_vertices(&shape);
assert_eq!(verts.len(), 3);
for (i, v) in verts.iter().enumerate() {
assert_eq!(*v, points[i], "vertex mismatch at index {i}");
}
}
#[test]
fn test_closed_line_to_polyline_vertices() {
let points = crate::s2::text_format::parse_points("0:0, 0:10, 10:10, 0:0");
let shape = crate::s2::text_format::make_lax_polyline("0:0, 0:10, 10:10, 0:0");
let verts = shape_to_polyline_vertices(&shape);
assert_eq!(verts.len(), 4);
for (i, v) in verts.iter().enumerate() {
assert_eq!(*v, points[i], "vertex mismatch at index {i}");
}
}
#[test]
fn test_polygon_to_loop_vertices_with_hole() {
let shape =
crate::s2::text_format::make_lax_polygon("0:0, 0:10, 10:10, 10:0; 4:4, 6:4, 6:6, 4:6");
let loops = shape_to_loop_vertices(&shape);
assert_eq!(loops.len(), 2);
assert_eq!(loops[0].len(), 4);
assert_eq!(loops[1].len(), 4);
}
#[test]
fn test_polygon_to_loop_vertices_multi() {
let shape = crate::s2::text_format::make_lax_polygon("0:0, 0:2, 2:2, 2:0; 0:4, 0:6, 3:6");
let loops = shape_to_loop_vertices(&shape);
assert_eq!(loops.len(), 2);
assert_eq!(loops[0].len(), 4);
assert_eq!(loops[1].len(), 3);
}
#[test]
fn test_polygon_to_loop_vertices_two_holes() {
let shape = crate::s2::text_format::make_lax_polygon(
"0:0, 0:10, 10:10, 10:0; 1:1, 3:3, 1:3; 2:6, 4:7, 2:8",
);
let loops = shape_to_loop_vertices(&shape);
assert_eq!(loops.len(), 3);
assert_eq!(loops[0].len(), 4);
assert_eq!(loops[1].len(), 3);
assert_eq!(loops[2].len(), 3);
}
#[test]
fn test_polygon_to_loop_vertices_full() {
let shape = crate::s2::text_format::make_lax_polygon("full");
assert!(shape.reference_point().contained);
let _loops = shape_to_loop_vertices(&shape);
assert_eq!(shape.num_chains(), 1);
assert_eq!(shape.num_edges(), 0);
}
fn make_test_lax_loop(vertex_str: &str) -> crate::s2::lax_loop::LaxLoop {
let vertices = crate::s2::text_format::parse_points(vertex_str);
crate::s2::lax_loop::LaxLoop::new(vertices)
}
#[test]
fn test_build_polygon_boundaries_no_components() {
let components: Vec<Vec<&dyn Shape>> = vec![];
let faces = build_polygon_boundaries(&components);
assert_eq!(0, faces.len());
}
#[test]
fn test_build_polygon_boundaries_one_loop() {
let a0 = make_test_lax_loop("0:0, 1:0, 0:1"); let a1 = make_test_lax_loop("0:0, 0:1, 1:0");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0, &a1]];
let faces = build_polygon_boundaries(&components);
assert_eq!(2, faces.len());
}
#[test]
fn test_build_polygon_boundaries_two_loops_same_component() {
let a0 = make_test_lax_loop("0:0, 1:0, 0:1"); let a1 = make_test_lax_loop("0:0, 0:1, 1:0");
let a2 = make_test_lax_loop("1:0, 0:1, 1:1");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0, &a1, &a2]];
let faces = build_polygon_boundaries(&components);
assert_eq!(3, faces.len());
}
#[test]
fn test_build_polygon_boundaries_two_loops_different_components() {
let a0 = make_test_lax_loop("0:0, 1:0, 0:1");
let a1 = make_test_lax_loop("0:0, 0:1, 1:0");
let b0 = make_test_lax_loop("0:2, 1:2, 0:3");
let b1 = make_test_lax_loop("0:2, 0:3, 1:2");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0, &a1], vec![&b0, &b1]];
let faces = build_polygon_boundaries(&components);
assert_eq!(3, faces.len());
}
#[test]
fn test_build_polygon_boundaries_one_degenerate_loop() {
let a0 = make_test_lax_loop("0:0, 1:0, 0:0");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0]];
let faces = build_polygon_boundaries(&components);
assert_eq!(1, faces.len());
}
#[test]
fn test_build_polygon_boundaries_two_degenerate_loops() {
let a0 = make_test_lax_loop("0:0, 1:0, 0:0");
let b0 = make_test_lax_loop("2:0, 3:0, 2:0");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0], vec![&b0]];
let faces = build_polygon_boundaries(&components);
assert_eq!(1, faces.len());
assert_eq!(2, faces[0].len());
}
#[test]
fn test_build_polygon_boundaries_two_nested_loops() {
let a0 = make_test_lax_loop("0:0, 3:0, 0:3"); let a1 = make_test_lax_loop("0:0, 0:3, 3:0");
let b0 = make_test_lax_loop("1:1, 2:0, 0:2"); let b1 = make_test_lax_loop("1:1, 0:2, 2:0");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0, &a1], vec![&b0, &b1]];
let faces = build_polygon_boundaries(&components);
assert_eq!(3, faces.len());
let face_with_a1 = faces.iter().find(|f| f.len() == 2 && f.contains(&1));
assert!(face_with_a1.is_some(), "should have a face with a1 and b0");
}
#[test]
fn test_build_polygon_boundaries_complex_test1() {
let a0 = make_test_lax_loop("0:0, 25:0, 50:0, 50:25, 50:50, 25:50, 0:50, 0:50");
let a1 = make_test_lax_loop("0:0, 0:25, 25:25, 25:0");
let a2 = make_test_lax_loop("0:25, 0:50, 25:50, 25:25");
let a3 = make_test_lax_loop("25:0, 25:25, 50:25, 50:0");
let a4 = make_test_lax_loop("25:25, 25:50, 50:50, 50:25");
let b0 = make_test_lax_loop("0:-10, 10:-10");
let components: Vec<Vec<&dyn Shape>> = vec![vec![&a0, &a1, &a2, &a3, &a4], vec![&b0]];
let faces = build_polygon_boundaries(&components);
assert_eq!(5, faces.len());
let top_face = faces.last().unwrap();
assert_eq!(2, top_face.len(), "top face should have a0 and b0");
}
}