#![expect(
clippy::cast_sign_loss,
reason = "EdgeId/ShapeId/VertexId/Label (i32) used as Vec indices — mirrors C++ S2Builder API"
)]
#![expect(
clippy::cast_possible_truncation,
reason = "EdgeId/ShapeId/InputEdgeId (i32) <-> usize — mirrors C++ S2Builder"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "usize -> i32 for EdgeId/ShapeId — mirrors C++ S2Builder"
)]
pub mod closed_set_normalizer;
pub mod find_polygon_degeneracies;
pub(crate) mod get_snapped_winding_delta;
pub mod graph;
pub(crate) mod graph_shape;
pub mod id_set_lexicon;
pub mod indexed_layers;
pub mod lax_polygon_layer;
pub mod lax_polyline_layer;
pub mod layer;
pub mod point_vector_layer;
pub mod polygon_layer;
pub mod polyline_layer;
pub mod polyline_vector_layer;
pub mod snap;
use crate::s1;
use crate::s1::ChordAngle;
use crate::s2::closest_point_query::{self, ClosestPointQuery, EdgeTarget, PointTarget};
use crate::s2::edge_crossings;
use crate::s2::edge_distances;
use crate::s2::point_index::S2PointIndex;
use crate::s2::polyline::Polyline;
use crate::s2::predicates;
use crate::s2::shape::Shape;
use crate::s2::{CellId, Loop, Point, Polygon};
use graph::{Graph, GraphOptions};
use id_set_lexicon::{EMPTY_SET_ID, IdSetLexicon};
use layer::Layer;
use snap::SnapFunction;
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct S2Error {
pub code: S2ErrorCode,
pub message: String,
}
impl S2Error {
pub const OK: S2Error = S2Error {
code: S2ErrorCode::Ok,
message: String::new(),
};
pub fn ok() -> Self {
S2Error {
code: S2ErrorCode::Ok,
message: String::new(),
}
}
pub fn new(code: S2ErrorCode, message: impl Into<String>) -> Self {
S2Error {
code,
message: message.into(),
}
}
pub fn is_ok(&self) -> bool {
self.code == S2ErrorCode::Ok
}
}
impl std::fmt::Display for S2Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.message.is_empty() {
write!(f, "{:?}", self.code)
} else {
write!(f, "{:?}: {}", self.code, self.message)
}
}
}
impl std::error::Error for S2Error {}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum S2ErrorCode {
#[default]
Ok,
Unknown,
Unimplemented,
OutOfRange,
InvalidArgument,
FailedPrecondition,
Internal,
DataLoss,
ResourceExhausted,
Cancelled,
NotUnitLength,
DuplicateVertices,
AntipodalVertices,
NotContinuous,
InvalidVertex,
LoopNotEnoughVertices,
LoopSelfIntersection,
PolygonLoopsShareEdge,
PolygonLoopsCross,
PolygonEmptyLoop,
PolygonExcessFullLoop,
PolygonInconsistentLoopOrientations,
PolygonInvalidLoopDepth,
PolygonInvalidLoopNesting,
InvalidDimension,
SplitInterior,
OverlappingGeometry,
BuilderSnapRadiusTooSmall,
BuilderMissingExpectedSiblingEdges,
BuilderUnexpectedDegenerateEdge,
BuilderEdgesDoNotFormLoops,
BuilderEdgesDoNotFormPolyline,
BuilderIsFullPredicateNotSpecified,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InputEdgeId(pub i32);
impl InputEdgeId {
pub const fn as_i32(self) -> i32 {
self.0
}
#[expect(clippy::cast_sign_loss, reason = "guarded by assert")]
pub const fn as_usize(self) -> usize {
assert!(self.0 >= 0, "InputEdgeId must be non-negative for indexing");
self.0 as usize
}
}
impl std::fmt::Display for InputEdgeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<i32> for InputEdgeId {
fn from(v: i32) -> Self {
InputEdgeId(v)
}
}
impl From<InputEdgeId> for i32 {
fn from(v: InputEdgeId) -> Self {
v.0
}
}
impl std::ops::Add<i32> for InputEdgeId {
type Output = InputEdgeId;
fn add(self, rhs: i32) -> Self {
InputEdgeId(self.0 + rhs)
}
}
impl std::ops::Sub<i32> for InputEdgeId {
type Output = InputEdgeId;
fn sub(self, rhs: i32) -> Self {
InputEdgeId(self.0 - rhs)
}
}
impl std::ops::Sub<InputEdgeId> for InputEdgeId {
type Output = i32;
fn sub(self, rhs: InputEdgeId) -> i32 {
self.0 - rhs.0
}
}
impl PartialEq<i32> for InputEdgeId {
fn eq(&self, other: &i32) -> bool {
self.0 == *other
}
}
impl PartialOrd<i32> for InputEdgeId {
fn partial_cmp(&self, other: &i32) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
pub type InputEdgeIdSetId = i32;
pub type Label = i32;
pub type LabelSetId = i32;
#[derive(Debug)]
pub struct Options {
pub snap_function: Box<dyn SnapFunction>,
pub split_crossing_edges: bool,
pub intersection_tolerance: s1::Angle,
pub simplify_edge_chains: bool,
pub idempotent: bool,
pub memory_tracker:
Option<std::sync::Arc<std::sync::Mutex<super::memory_tracker::S2MemoryTracker>>>,
}
impl Default for Options {
fn default() -> Self {
Options {
snap_function: Box::new(snap::IdentitySnapFunction::new(s1::Angle::default())),
split_crossing_edges: false,
intersection_tolerance: s1::Angle::default(),
simplify_edge_chains: false,
idempotent: true,
memory_tracker: None,
}
}
}
impl Options {
pub fn new(snap_function: Box<dyn SnapFunction>) -> Self {
Options {
snap_function,
..Default::default()
}
}
pub fn edge_snap_radius(&self) -> s1::Angle {
s1::Angle::from_radians(
self.snap_function.snap_radius().radians() + self.intersection_tolerance.radians(),
)
}
pub fn max_edge_deviation(&self) -> s1::Angle {
s1::Angle::from_radians(MAX_EDGE_DEVIATION_RATIO * self.edge_snap_radius().radians())
}
}
const MAX_EDGE_DEVIATION_RATIO: f64 = 1.1;
fn is_valid_for_index(p: &Point) -> bool {
let v = &p.0;
v.x.is_finite() && v.y.is_finite() && v.z.is_finite() && v.norm2() > 0.0
}
fn round_up_chord_angle(angle: s1::Angle) -> ChordAngle {
let ca = ChordAngle::from_angle(angle);
let error = 1.5 * f64::EPSILON * ca.length2();
ChordAngle::from_length2(ca.length2() + error)
}
fn add_point_to_point_error(ca: ChordAngle) -> ChordAngle {
let error = 4.5 * f64::EPSILON * ca.length2() + 16.0 * f64::EPSILON * f64::EPSILON;
ChordAngle::from_length2(ca.length2() + error)
}
#[derive(Clone, Copy, Debug)]
struct InputEdge {
first: i32,
second: i32,
}
#[expect(clippy::struct_excessive_bools, reason = "matches C++ structure")]
pub struct S2Builder {
options: Options,
layers: Vec<Box<dyn Layer>>,
layer_options: Vec<GraphOptions>,
layer_begins: Vec<i32>,
layer_is_full_polygon_predicates: Vec<Option<layer::IsFullPolygonPredicate>>,
input_vertices: Vec<Point>,
input_edges: Vec<InputEdge>,
label_set_ids: Vec<LabelSetId>,
label_set_lexicon: IdSetLexicon,
label_set: Vec<Label>,
forced_vertices: Vec<Point>,
edge_crossing_vertices: std::collections::HashMap<usize, Vec<usize>>,
snapped: bool,
site_snap_radius_ca: ChordAngle,
edge_snap_radius_ca: ChordAngle,
edge_site_query_radius_ca: ChordAngle,
max_adjacent_site_separation_ca: ChordAngle,
min_edge_length_to_split_ca: ChordAngle,
edge_snap_radius_sin2: f64,
min_edge_site_separation_ca: ChordAngle,
check_all_site_crossings: bool,
snapping_needed: bool,
snapping_requested: bool,
sites: Vec<Point>,
num_forced_sites: usize,
edge_sites: Vec<Vec<usize>>,
}
impl std::fmt::Debug for S2Builder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("S2Builder")
.field("options", &self.options)
.field("layers", &self.layers)
.field("layer_options", &self.layer_options)
.field("layer_begins", &self.layer_begins)
.field("snapped", &self.snapped)
.finish_non_exhaustive()
}
}
impl S2Builder {
pub fn new(options: Options) -> Self {
S2Builder {
options,
layers: Vec::new(),
layer_options: Vec::new(),
layer_begins: Vec::new(),
layer_is_full_polygon_predicates: Vec::new(),
input_vertices: Vec::new(),
input_edges: Vec::new(),
label_set_ids: Vec::new(),
label_set_lexicon: IdSetLexicon::new(),
label_set: Vec::new(),
forced_vertices: Vec::new(),
edge_crossing_vertices: std::collections::HashMap::new(),
snapped: true, site_snap_radius_ca: ChordAngle::ZERO,
edge_snap_radius_ca: ChordAngle::ZERO,
edge_site_query_radius_ca: ChordAngle::ZERO,
max_adjacent_site_separation_ca: ChordAngle::ZERO,
min_edge_length_to_split_ca: ChordAngle::INFINITY,
edge_snap_radius_sin2: 0.0,
min_edge_site_separation_ca: ChordAngle::ZERO,
check_all_site_crossings: false,
snapping_needed: false,
snapping_requested: false,
sites: Vec::new(),
num_forced_sites: 0,
edge_sites: Vec::new(),
}
}
pub fn options(&self) -> &Options {
&self.options
}
pub fn num_input_edges(&self) -> i32 {
self.input_edges.len() as i32
}
pub fn input_edge(&self, input_edge_id: impl Into<InputEdgeId>) -> (Point, Point) {
let ie = &self.input_edges[input_edge_id.into().as_usize()];
(
self.input_vertices[ie.first as usize],
self.input_vertices[ie.second as usize],
)
}
pub fn is_full_polygon_unspecified() -> layer::IsFullPolygonPredicate {
std::sync::Arc::new(|_graph: &Graph| -> Result<bool, S2Error> {
Err(S2Error::new(
S2ErrorCode::BuilderIsFullPredicateNotSpecified,
"A degenerate polygon was found, but no predicate was specified \
to determine whether the polygon is empty or full. Call \
S2Builder::add_is_full_polygon_predicate() to fix this problem.",
))
})
}
pub fn is_full_polygon(is_full: bool) -> layer::IsFullPolygonPredicate {
std::sync::Arc::new(move |_graph: &Graph| -> Result<bool, S2Error> { Ok(is_full) })
}
pub fn reset(&mut self) {
self.input_vertices.clear();
self.input_edges.clear();
self.layers.clear();
self.layer_options.clear();
self.layer_begins.clear();
self.layer_is_full_polygon_predicates.clear();
self.label_set_ids.clear();
self.label_set_lexicon = IdSetLexicon::new();
self.label_set.clear();
self.forced_vertices.clear();
self.snapped = false;
self.snapping_needed = false;
self.sites.clear();
self.num_forced_sites = 0;
self.edge_sites.clear();
self.edge_crossing_vertices.clear();
}
pub fn start_layer(&mut self, layer: Box<dyn Layer>) {
self.layer_begins.push(self.input_edges.len() as i32);
self.layer_options.push(layer.graph_options());
self.layer_is_full_polygon_predicates.push(None);
self.layers.push(layer);
}
pub fn add_is_full_polygon_predicate(&mut self, predicate: layer::IsFullPolygonPredicate) {
if let Some(last) = self.layer_is_full_polygon_predicates.last_mut() {
*last = Some(predicate);
}
}
pub fn add_point(&mut self, v: Point) {
self.add_edge(v, v);
}
pub fn add_edge(&mut self, v0: Point, v1: Point) {
debug_assert!(
!self.layers.is_empty(),
"Call start_layer before adding any edges"
);
let first = self.add_vertex(v0);
let second = self.add_vertex(v1);
let label_set_id = self.label_set_lexicon.add_set(&self.label_set);
self.label_set_ids.push(label_set_id);
self.input_edges.push(InputEdge { first, second });
}
pub fn add_polyline(&mut self, polyline: &Polyline) {
let n = polyline.num_vertices();
if n >= 2 {
for i in 0..n - 1 {
self.add_edge(polyline.vertex(i), polyline.vertex(i + 1));
}
}
}
pub fn add_loop(&mut self, loop_: &Loop) {
if loop_.is_empty_or_full() {
return;
}
let n = loop_.num_vertices();
for i in 0..n {
self.add_edge(loop_.oriented_vertex(i), loop_.oriented_vertex(i + 1));
}
}
pub fn add_loop_from_points(&mut self, vertices: &[Point]) {
let n = vertices.len();
for i in 0..n {
self.add_edge(vertices[i], vertices[(i + 1) % n]);
}
}
pub fn add_polyline_from_points(&mut self, vertices: &[Point]) {
if vertices.len() >= 2 {
for i in 0..vertices.len() - 1 {
self.add_edge(vertices[i], vertices[i + 1]);
}
}
}
pub fn add_polygon(&mut self, polygon: &Polygon) {
for i in 0..polygon.num_loops() {
self.add_loop(polygon.loop_at(i));
}
}
pub fn add_shape(&mut self, shape: &dyn Shape) {
for i in 0..shape.num_edges() {
let e = shape.edge(i);
self.add_edge(e.v0, e.v1);
}
}
pub fn add_intersection(&mut self, vertex: Point) {
debug_assert!(self.options.intersection_tolerance.radians() > 0.0);
self.snapped = false; self.add_vertex(vertex);
}
pub fn add_intersection_for_edges(&mut self, vertex: Point, edge_a: usize, edge_b: usize) {
debug_assert!(self.options.intersection_tolerance.radians() > 0.0);
self.snapped = false;
let vert_idx = self.input_vertices.len();
self.input_vertices.push(vertex);
self.edge_crossing_vertices
.entry(edge_a)
.or_default()
.push(vert_idx);
self.edge_crossing_vertices
.entry(edge_b)
.or_default()
.push(vert_idx);
}
pub fn add_intersection_for_edge(&mut self, vertex: Point, edge_idx: usize) {
debug_assert!(self.options.intersection_tolerance.radians() > 0.0);
self.snapped = false;
let vert_idx = self.input_vertices.len();
self.input_vertices.push(vertex);
self.edge_crossing_vertices
.entry(edge_idx)
.or_default()
.push(vert_idx);
}
pub fn force_vertex(&mut self, v: Point) {
self.forced_vertices.push(v);
}
pub fn set_label(&mut self, label: Label) {
debug_assert!(label >= 0);
self.label_set.clear();
self.label_set.push(label);
}
pub fn push_label(&mut self, label: Label) {
debug_assert!(label >= 0);
self.label_set.push(label);
}
pub fn pop_label(&mut self) {
self.label_set.pop();
}
pub fn clear_labels(&mut self) {
self.label_set.clear();
}
pub fn build(&mut self) -> Result<Vec<Box<dyn Layer>>, S2Error> {
if self.layers.is_empty() {
self.reset();
return Ok(Vec::new());
}
self.layer_begins.push(self.input_edges.len() as i32);
if self.options.split_crossing_edges {
let ie = edge_crossings::intersection_error();
if self.options.intersection_tolerance.radians() < ie.radians() {
self.options.intersection_tolerance = ie;
}
}
self.init_snap_constants();
if self.options.split_crossing_edges {
self.add_edge_crossings();
}
self.tally_memory(
(self.input_vertices.capacity() * size_of::<Point>()
+ self.input_edges.capacity() * size_of::<InputEdge>()) as i64,
);
if !self.tracker_ok() {
return Err(self.tracker_error());
}
self.choose_sites_internal();
self.tally_memory((self.sites.capacity() * size_of::<Point>()) as i64);
if !self.tracker_ok() {
return Err(self.tracker_error());
}
let site_map = self.assign_vertices_to_sites_internal();
if self.snapping_requested && !self.options.idempotent {
self.snapping_needed = true;
}
if self.snapping_requested && !self.snapped {
self.snapping_needed = true;
}
if self.snapping_requested && !self.snapping_needed {
for (i, v) in self.input_vertices.iter().enumerate() {
let site_id = site_map[i] as usize;
if self.sites[site_id] != *v {
self.snapping_needed = true;
break;
}
}
}
if self.snapping_requested {
self.collect_site_edges();
let edge_sites_bytes: usize = self
.edge_sites
.iter()
.map(|v| v.capacity() * size_of::<usize>())
.sum();
self.tally_memory(edge_sites_bytes as i64);
if !self.tracker_ok() {
return Err(self.tracker_error());
}
if self.snapping_needed {
self.add_extra_sites(&site_map);
}
}
let mut error = S2Error::ok();
let result = self.build_layers_internal(&site_map, &mut error);
let built_layers = std::mem::take(&mut self.layers);
self.reset();
match result {
Err(e) => Err(e),
Ok(()) if !error.is_ok() => Err(error),
Ok(()) => Ok(built_layers),
}
}
fn init_snap_constants(&mut self) {
let snap_fn = &*self.options.snap_function;
let snap_radius = snap_fn.snap_radius();
debug_assert!(snap_radius <= snap::MAX_SNAP_RADIUS);
self.site_snap_radius_ca = ChordAngle::from_angle(snap_radius);
let edge_snap_radius = self.options.edge_snap_radius();
self.edge_snap_radius_ca = round_up_chord_angle(edge_snap_radius);
self.snapping_requested = edge_snap_radius.radians() > 0.0;
let max_edge_deviation = self.options.max_edge_deviation();
self.edge_site_query_radius_ca = ChordAngle::from_angle(s1::Angle::from_radians(
max_edge_deviation.radians() + snap_fn.min_edge_vertex_separation().radians(),
));
if self.snapping_requested {
let esr = edge_snap_radius.radians();
let med = max_edge_deviation.radians();
self.min_edge_length_to_split_ca =
ChordAngle::from_radians(2.0 * (esr.sin() / med.sin()).acos());
} else {
self.min_edge_length_to_split_ca = ChordAngle::INFINITY;
}
self.check_all_site_crossings = max_edge_deviation.radians()
> edge_snap_radius.radians() + snap_fn.min_edge_vertex_separation().radians();
if self.options.intersection_tolerance.radians() <= 0.0 {
debug_assert!(!self.check_all_site_crossings);
}
self.min_edge_site_separation_ca =
ChordAngle::from_angle(snap_fn.min_edge_vertex_separation());
self.max_adjacent_site_separation_ca = add_point_to_point_error(round_up_chord_angle(
s1::Angle::from_radians(2.0 * edge_snap_radius.radians()),
));
let d = edge_snap_radius.radians().sin();
self.edge_snap_radius_sin2 = d * d;
self.edge_snap_radius_sin2 +=
((9.5 * d + 2.5 + 2.0 * 3.0_f64.sqrt()) * d + 9.0 * f64::EPSILON) * f64::EPSILON;
self.snapping_needed = false;
}
fn tally_memory(&self, delta_bytes: i64) -> bool {
if let Some(ref tracker) = self.options.memory_tracker {
return crate::s2::memory_tracker::lock_tracker(tracker).tally(delta_bytes);
}
true
}
fn tracker_ok(&self) -> bool {
match self.options.memory_tracker {
Some(ref tracker) => crate::s2::memory_tracker::lock_tracker(tracker).ok(),
None => true,
}
}
fn tracker_error(&self) -> S2Error {
self.options
.memory_tracker
.as_ref()
.map_or_else(S2Error::ok, |t| {
crate::s2::memory_tracker::lock_tracker(t).error().clone()
})
}
fn is_forced(&self, site_id: usize) -> bool {
site_id < self.num_forced_sites
}
fn snap_site(&self, p: Point) -> Point {
self.options.snap_function.snap_point(p)
}
fn choose_sites_internal(&mut self) {
let snap_fn = &*self.options.snap_function;
let snap_radius = snap_fn.snap_radius();
self.sites.clear();
self.num_forced_sites = 0;
if snap_radius.radians() <= 0.0 {
for v in &self.forced_vertices {
let snapped = snap_fn.snap_point(*v);
if !self.sites.contains(&snapped) {
self.sites.push(snapped);
}
}
self.num_forced_sites = self.sites.len();
for v in &self.input_vertices {
let snapped = snap_fn.snap_point(*v);
if snapped != *v && !self.snapping_needed {
self.snapping_needed = true;
}
if !self.sites.contains(&snapped) {
self.sites.push(snapped);
}
}
} else {
let mut site_index: S2PointIndex<usize> = S2PointIndex::new();
let min_sep_ca = ChordAngle::from_angle(snap_fn.min_vertex_separation());
let mut forced: Vec<Point> = self
.forced_vertices
.iter()
.map(|v| snap_fn.snap_point(*v))
.collect();
forced.sort_unstable_by(|a, b| {
a.0.x
.total_cmp(&b.0.x)
.then_with(|| a.0.y.total_cmp(&b.0.y))
.then_with(|| a.0.z.total_cmp(&b.0.z))
});
forced.dedup();
for site in &forced {
site_index.add(*site, self.sites.len());
self.sites.push(*site);
}
self.num_forced_sites = self.sites.len();
let sorted = self.sort_input_vertices();
let query_opts = closest_point_query::Options {
max_distance: min_sep_ca.successor(),
..Default::default()
};
for &idx in &sorted {
let vertex = self.input_vertices[idx];
let site = snap_fn.snap_point(vertex);
self.snapping_needed = self.snapping_needed || site != vertex;
let mut add_site = true;
if self.site_snap_radius_ca == ChordAngle::ZERO {
add_site = self.sites.is_empty() || site != self.sites[self.sites.len() - 1];
} else {
let query = ClosestPointQuery::new(&site_index, query_opts);
let mut target = PointTarget::new(site);
let results = query.find_closest_points(&mut target);
for r in &results {
if predicates::compare_distance(site, r.point, min_sep_ca) <= 0 {
add_site = false;
self.snapping_needed = self.snapping_needed || site != r.point;
}
}
}
if add_site {
site_index.add(site, self.sites.len());
self.sites.push(site);
}
}
}
}
fn sort_input_vertices(&self) -> Vec<usize> {
let mut sorted: Vec<usize> = (0..self.input_vertices.len()).collect();
sorted.sort_unstable_by(|&a, &b| {
let pa = &self.input_vertices[a];
let pb = &self.input_vertices[b];
let ca = if pa.0.x.is_finite() && pa.0.y.is_finite() && pa.0.z.is_finite() {
CellId::from_point(pa)
} else {
CellId::sentinel()
};
let cb = if pb.0.x.is_finite() && pb.0.y.is_finite() && pb.0.z.is_finite() {
CellId::from_point(pb)
} else {
CellId::sentinel()
};
ca.0.cmp(&cb.0)
.then_with(|| pa.0.x.total_cmp(&pb.0.x))
.then_with(|| pa.0.y.total_cmp(&pb.0.y))
.then_with(|| pa.0.z.total_cmp(&pb.0.z))
.then_with(|| a.cmp(&b))
});
sorted
}
fn assign_vertices_to_sites_internal(&self) -> Vec<i32> {
if self.sites.len() <= 50 {
return self.assign_vertices_linear();
}
let mut site_index: S2PointIndex<usize> = S2PointIndex::new();
for (sid, site) in self.sites.iter().enumerate() {
if !is_valid_for_index(site) {
continue;
}
site_index.add(*site, sid);
}
let snap_fn = &*self.options.snap_function;
let query_opts = closest_point_query::Options {
max_results: 1,
..Default::default()
};
self.input_vertices
.iter()
.map(|v| {
let snapped = snap_fn.snap_point(*v);
if !is_valid_for_index(&snapped) {
return self.find_nearest_site_linear(snapped);
}
let query = ClosestPointQuery::new(&site_index, query_opts);
let mut target = PointTarget::new(snapped);
let result = query.find_closest_point(&mut target);
if result.is_empty() {
self.find_nearest_site_linear(snapped)
} else {
result.data as i32
}
})
.collect()
}
fn assign_vertices_linear(&self) -> Vec<i32> {
let snap_fn = &*self.options.snap_function;
self.input_vertices
.iter()
.map(|v| {
let snapped = snap_fn.snap_point(*v);
self.find_nearest_site_linear(snapped)
})
.collect()
}
fn find_nearest_site_linear(&self, point: Point) -> i32 {
let mut best_idx = 0i32;
let mut best_dist = f64::MAX;
for (i, site) in self.sites.iter().enumerate() {
let d = site.distance(point).radians();
if d < best_dist - 1e-14 {
best_idx = i as i32;
best_dist = d;
}
}
best_idx
}
fn collect_site_edges(&mut self) {
let mut site_index: S2PointIndex<usize> = S2PointIndex::new();
for (sid, site) in self.sites.iter().enumerate() {
if is_valid_for_index(site) {
site_index.add(*site, sid);
}
}
let query_opts = closest_point_query::Options {
max_distance: self.edge_site_query_radius_ca.successor(),
..Default::default()
};
let num_edges = self.input_edges.len();
self.edge_sites = Vec::with_capacity(num_edges);
for e in 0..num_edges {
let ie = &self.input_edges[e];
let v0 = self.input_vertices[ie.first as usize];
let v1 = self.input_vertices[ie.second as usize];
let query = ClosestPointQuery::new(&site_index, query_opts);
let mut target = EdgeTarget::new(v0, v1);
let results = query.find_closest_points(&mut target);
let mut nearby: Vec<usize> = Vec::with_capacity(results.len());
for r in &results {
let sid = r.data;
nearby.push(sid);
if !self.snapping_needed
&& r.point != v0
&& r.point != v1
&& predicates::compare_edge_distance(
r.point,
v0,
v1,
self.min_edge_site_separation_ca,
) < 0
{
self.snapping_needed = true;
}
}
let sites = &self.sites;
nearby.sort_unstable_by(|&a, &b| {
predicates::compare_distances(v0, sites[a], sites[b]).cmp(&0)
});
self.edge_sites.push(nearby);
}
}
fn snap_edge(&self, e: usize, site_map: &[i32], chain: &mut Vec<usize>) {
chain.clear();
let ie = &self.input_edges[e];
if !self.snapping_needed {
chain.push(site_map[ie.first as usize] as usize);
chain.push(site_map[ie.second as usize] as usize);
return;
}
let x = self.input_vertices[ie.first as usize];
let y = self.input_vertices[ie.second as usize];
let candidates = &self.edge_sites[e];
for &site_id in candidates {
let c = self.sites[site_id];
if predicates::compare_edge_distance(c, x, y, self.edge_snap_radius_ca) > 0 {
continue;
}
let mut add_site_c = true;
while !chain.is_empty() {
let b = self.sites[chain[chain.len() - 1]];
let bc = b.chord_angle(c);
if bc >= self.max_adjacent_site_separation_ca {
break;
}
let result =
predicates::get_voronoi_site_exclusion(b, c, x, y, self.edge_snap_radius_ca);
match result {
predicates::Excluded::First => {
chain.pop();
continue;
}
predicates::Excluded::Second => {
add_site_c = false;
break;
}
predicates::Excluded::Neither => {}
predicates::Excluded::Uncertain => {
debug_assert!(false, "Unexpected Uncertain exclusion result");
}
}
if chain.len() < 2 {
break;
}
let a = self.sites[chain[chain.len() - 2]];
let ac = a.chord_angle(c);
if ac >= self.max_adjacent_site_separation_ca {
break;
}
let xyb = predicates::robust_sign(x, y, b);
if predicates::robust_sign(a, b, c) == xyb {
break;
}
if predicates::edge_circumcenter_sign(x, y, a, b, c) != xyb as i32 {
break;
}
chain.pop();
}
if add_site_c {
chain.push(site_id);
}
}
debug_assert!(!chain.is_empty());
}
fn add_extra_site(
&mut self,
new_site: Point,
edges_to_resnap: &mut std::collections::HashSet<usize>,
) {
if !self.sites.is_empty() {
debug_assert_ne!(new_site, self.sites[self.sites.len() - 1]);
}
let site_id = self.sites.len();
self.sites.push(new_site);
for e in 0..self.input_edges.len() {
let ie = &self.input_edges[e];
let v0 = self.input_vertices[ie.first as usize];
let v1 = self.input_vertices[ie.second as usize];
if edge_distances::is_distance_less(
new_site,
v0,
v1,
self.edge_site_query_radius_ca.successor(),
) {
edges_to_resnap.insert(e);
let sites = &self.sites;
let pos = self.edge_sites[e]
.binary_search_by(|&sid| {
predicates::compare_distances(v0, sites[sid], new_site).cmp(&0)
})
.unwrap_or_else(|x| x);
self.edge_sites[e].insert(pos, site_id);
}
}
}
fn get_separation_site(
&self,
site_to_avoid: Point,
v0: Point,
v1: Point,
input_edge_id: usize,
) -> Point {
let ie = &self.input_edges[input_edge_id];
let x = self.input_vertices[ie.first as usize];
let y = self.input_vertices[ie.second as usize];
let n = x.point_cross(y);
let xy_dir = y.0 - x.0;
let mut new_site = edge_distances::project(site_to_avoid, x, y);
let gap_min = self.get_coverage_endpoint(v0, n);
let gap_max = self.get_coverage_endpoint(v1, -n);
if (new_site.0 - gap_min.0).dot(xy_dir) < 0.0 {
new_site = gap_min;
} else if (gap_max.0 - new_site.0).dot(xy_dir) < 0.0 {
new_site = gap_max;
}
let new_site = self.snap_site(new_site);
debug_assert_ne!(v0, new_site);
debug_assert_ne!(v1, new_site);
new_site
}
fn get_coverage_endpoint(&self, p: Point, n: Point) -> Point {
let n2 = n.0.norm2();
let n_dot_p = n.0.dot(p.0);
let nxp = n.0.cross(p.0);
let nxp_xn = n2 * p.0 - n_dot_p * n.0;
let om = (1.0 - self.edge_snap_radius_sin2).sqrt() * nxp_xn;
let mr2 = self.edge_snap_radius_sin2 * n2 - n_dot_p * n_dot_p;
let mr = mr2.max(0.0).sqrt() * nxp;
Point((om + mr).normalize())
}
fn add_extra_sites(&mut self, site_map: &[i32]) {
let mut edges_to_resnap = std::collections::HashSet::new();
let mut chain: Vec<usize> = Vec::new();
for e in 0..self.input_edges.len() {
self.snap_edge(e, site_map, &mut chain);
self.maybe_add_extra_sites(e, &chain, &mut edges_to_resnap);
}
while !edges_to_resnap.is_empty() {
let edges_to_snap: Vec<usize> = edges_to_resnap.drain().collect();
for e in edges_to_snap {
self.snap_edge(e, site_map, &mut chain);
self.maybe_add_extra_sites(e, &chain, &mut edges_to_resnap);
}
}
}
fn maybe_add_extra_sites(
&mut self,
edge_id: usize,
chain: &[usize],
edges_to_resnap: &mut std::collections::HashSet<usize>,
) {
if chain.is_empty() {
return;
}
let ie = &self.input_edges[edge_id];
let a0 = self.input_vertices[ie.first as usize];
let a1 = self.input_vertices[ie.second as usize];
let max_edge_deviation = self.options.max_edge_deviation();
let nearby_sites = self.edge_sites[edge_id].clone();
let mut i = 0usize; let mut j = 0usize;
while j < nearby_sites.len() {
let id = nearby_sites[j];
if id == chain[i] {
i += 1;
if i >= chain.len() {
break;
}
let v0 = self.sites[chain[i - 1]];
let v1 = self.sites[chain[i]];
if v0.chord_angle(v1) >= self.min_edge_length_to_split_ca
&& !edge_distances::is_edge_b_near_edge_a(a0, a1, v0, v1, max_edge_deviation)
{
let mid = (edge_distances::project(v0, a0, a1).0
+ edge_distances::project(v1, a0, a1).0)
.normalize();
let new_site = self.get_separation_site(Point(mid), v0, v1, edge_id);
self.add_extra_site(new_site, edges_to_resnap);
return;
}
} else {
if i == 0 {
j += 1;
continue;
}
let site_to_avoid = self.sites[id];
let v0 = self.sites[chain[i - 1]];
let v1 = self.sites[chain[i]];
let mut add_separation_site = false;
if !self.is_forced(id)
&& self.min_edge_site_separation_ca > ChordAngle::ZERO
&& predicates::compare_edge_distance(
site_to_avoid,
v0,
v1,
self.min_edge_site_separation_ca,
) < 0
{
add_separation_site = true;
}
if !add_separation_site
&& (self.is_forced(id) || self.check_all_site_crossings)
&& predicates::robust_sign(a0, a1, site_to_avoid)
!= predicates::robust_sign(v0, v1, site_to_avoid)
&& predicates::compare_edge_directions(a0, a1, a0, site_to_avoid) > 0
&& predicates::compare_edge_directions(a0, a1, site_to_avoid, a1) > 0
&& predicates::compare_edge_directions(a0, a1, v0, site_to_avoid) > 0
&& predicates::compare_edge_directions(a0, a1, site_to_avoid, v1) > 0
{
add_separation_site = true;
}
if add_separation_site {
let new_site = self.get_separation_site(site_to_avoid, v0, v1, edge_id);
debug_assert_ne!(site_to_avoid, new_site);
self.add_extra_site(new_site, edges_to_resnap);
while j + 1 < nearby_sites.len() && nearby_sites[j + 1] != chain[i] {
j += 1;
}
}
}
j += 1;
}
}
fn add_vertex(&mut self, v: Point) -> i32 {
let idx = self.input_vertices.len() as i32;
self.input_vertices.push(v);
idx
}
fn add_edge_crossings(&mut self) {
let mut new_vertices: Vec<(Point, usize, usize)> = Vec::new();
let num_edges = self.input_edges.len();
for i in 0..num_edges {
let ie_i = self.input_edges[i];
let a0 = self.input_vertices[ie_i.first as usize];
let a1 = self.input_vertices[ie_i.second as usize];
for j in (i + 1)..num_edges {
let ie_j = self.input_edges[j];
let b0 = self.input_vertices[ie_j.first as usize];
let b1 = self.input_vertices[ie_j.second as usize];
if edge_crossings::crossing_sign(a0, a1, b0, b1) == edge_crossings::Crossing::Cross
{
new_vertices.push((edge_crossings::intersection(a0, a1, b0, b1), i, j));
}
}
}
let base = self.input_vertices.len();
for (idx, (pt, edge_i, edge_j)) in new_vertices.iter().enumerate() {
self.input_vertices.push(*pt);
let vert_idx = base + idx;
self.edge_crossing_vertices
.entry(*edge_i)
.or_default()
.push(vert_idx);
self.edge_crossing_vertices
.entry(*edge_j)
.or_default()
.push(vert_idx);
}
}
fn build_layers_internal(
&mut self,
site_map: &[i32],
error: &mut S2Error,
) -> Result<(), S2Error> {
let num_layers = self.layers.len();
let use_snap_edge = self.snapping_requested && !self.edge_sites.is_empty();
let simplify = self.snapping_requested && self.options.simplify_edge_chains;
let mut layer_edges: Vec<Vec<graph::Edge>> = Vec::with_capacity(num_layers);
let mut layer_input_edge_ids: Vec<Vec<InputEdgeIdSetId>> = Vec::with_capacity(num_layers);
let mut input_edge_id_set_lexicon = IdSetLexicon::new();
let mut site_vertices: Vec<Vec<i32>> = if simplify {
vec![Vec::new(); self.sites.len()]
} else {
Vec::new()
};
let mut chain: Vec<usize> = Vec::new();
for li in 0..num_layers {
let edge_begin = self.layer_begins[li] as usize;
let edge_end = self.layer_begins[li + 1] as usize;
let discard_degenerate =
self.layer_options[li].degenerate_edges == graph::DegenerateEdges::Discard;
let undirected = self.layer_options[li].edge_type == graph::EdgeType::Undirected;
let capacity = edge_end - edge_begin;
let edge_count = if undirected { capacity * 2 } else { capacity };
let mut edges: Vec<graph::Edge> = Vec::with_capacity(edge_count);
let mut edge_id_set_ids: Vec<InputEdgeIdSetId> = Vec::with_capacity(edge_count);
for edge_idx in edge_begin..edge_end {
let id_set = input_edge_id_set_lexicon.add_set(&[edge_idx as i32]);
if use_snap_edge {
self.snap_edge(edge_idx, site_map, &mut chain);
if simplify && !chain.is_empty() {
let ie = &self.input_edges[edge_idx];
Self::maybe_add_input_vertex(ie.first, chain[0], &mut site_vertices);
if chain.len() > 1 {
Self::maybe_add_input_vertex(
ie.second,
chain[chain.len() - 1],
&mut site_vertices,
);
}
}
if chain.is_empty() {
continue;
}
if chain.len() == 1 {
if discard_degenerate {
continue;
}
let s = VertexId(chain[0] as i32);
edges.push((s, s));
edge_id_set_ids.push(id_set);
if undirected {
edges.push((s, s));
edge_id_set_ids.push(EMPTY_SET_ID);
}
} else {
for k in 0..chain.len() - 1 {
edges.push((VertexId(chain[k] as i32), VertexId(chain[k + 1] as i32)));
edge_id_set_ids.push(id_set);
if undirected {
edges.push((
VertexId(chain[k + 1] as i32),
VertexId(chain[k] as i32),
));
edge_id_set_ids.push(EMPTY_SET_ID);
}
}
}
} else {
let ie = &self.input_edges[edge_idx];
let v0 = VertexId(site_map[ie.first as usize]);
let v1 = VertexId(site_map[ie.second as usize]);
edges.push((v0, v1));
edge_id_set_ids.push(id_set);
if undirected {
edges.push((v1, v0));
edge_id_set_ids.push(EMPTY_SET_ID);
}
}
}
layer_edges.push(edges);
layer_input_edge_ids.push(edge_id_set_ids);
}
if simplify {
simplify_edge_chains(
self,
&site_vertices,
&mut layer_edges,
&mut layer_input_edge_ids,
&mut input_edge_id_set_lexicon,
);
}
const MIN_LAYERS_FOR_VERTEX_FILTERING: usize = 10;
let mut layer_vertices: Vec<Vec<Point>> = Vec::new();
if num_layers >= MIN_LAYERS_FOR_VERTEX_FILTERING {
let allow = self
.layer_options
.iter()
.all(|opts| opts.allow_vertex_filtering);
if allow {
layer_vertices.reserve(num_layers);
for edges in &mut layer_edges {
let filtered = Graph::filter_vertices(&self.sites, edges);
layer_vertices.push(filtered);
}
}
}
for li in 0..num_layers {
let is_full_pred = self.layer_is_full_polygon_predicates[li].take();
let edges = std::mem::take(&mut layer_edges[li]);
let ie_set_ids = std::mem::take(&mut layer_input_edge_ids[li]);
let vertices = if layer_vertices.is_empty() {
self.sites.clone()
} else {
std::mem::take(&mut layer_vertices[li])
};
let graph = Graph::new(
self.layer_options[li].clone(),
vertices,
edges,
ie_set_ids,
input_edge_id_set_lexicon.clone(),
self.label_set_ids.clone(),
self.label_set_lexicon.clone(),
is_full_pred,
);
self.layers[li].build(&graph, error);
if !error.is_ok() {
return Err(error.clone());
}
}
Ok(())
}
fn maybe_add_input_vertex(
input_vertex_id: i32,
site_id: usize,
site_vertices: &mut [Vec<i32>],
) {
if site_vertices.is_empty() {
return;
}
let verts = &mut site_vertices[site_id];
if verts.is_empty() || verts[verts.len() - 1] != input_vertex_id {
verts.push(input_vertex_id);
}
}
}
use crate::s2::polyline_simplifier::PolylineSimplifier;
use graph::{EdgeId, VertexId};
fn simplify_edge_chains(
builder: &S2Builder,
site_vertices: &[Vec<i32>],
layer_edges: &mut [Vec<graph::Edge>],
layer_input_edge_ids: &mut [Vec<InputEdgeIdSetId>],
input_edge_id_set_lexicon: &mut IdSetLexicon,
) {
if builder.layers.is_empty() {
return;
}
let (merged_edges, merged_input_edge_ids, merged_edge_layers) =
merge_layer_edges(layer_edges, layer_input_edge_ids);
for edges in layer_edges.iter_mut() {
edges.clear();
}
for ids in layer_input_edge_ids.iter_mut() {
ids.clear();
}
let graph_options = GraphOptions::new(
graph::EdgeType::Directed,
graph::DegenerateEdges::Keep,
graph::DuplicateEdges::Keep,
graph::SiblingPairs::Keep,
);
let g = Graph::from_raw_parts(
graph_options,
builder.sites.clone(),
merged_edges,
merged_input_edge_ids,
input_edge_id_set_lexicon.clone(),
Vec::new(), IdSetLexicon::new(),
None,
);
let mut simplifier = EdgeChainSimplifier::new(builder, &g, &merged_edge_layers, site_vertices);
simplifier.run();
for i in 0..simplifier.new_edges.len() {
let layer = simplifier.new_edge_layers[i];
layer_edges[layer].push(simplifier.new_edges[i]);
layer_input_edge_ids[layer].push(simplifier.new_input_edge_ids[i]);
}
*input_edge_id_set_lexicon = simplifier.input_edge_id_set_lexicon.clone();
}
fn merge_layer_edges(
layer_edges: &[Vec<graph::Edge>],
layer_input_edge_ids: &[Vec<InputEdgeIdSetId>],
) -> (Vec<graph::Edge>, Vec<InputEdgeIdSetId>, Vec<usize>) {
let mut order: Vec<(usize, usize)> = Vec::new();
for (i, edges) in layer_edges.iter().enumerate() {
for e in 0..edges.len() {
order.push((i, e));
}
}
order.sort_unstable_by(|&(li_a, ei_a), &(li_b, ei_b)| {
let a = layer_edges[li_a][ei_a];
let b = layer_edges[li_b][ei_b];
a.0.cmp(&b.0)
.then(a.1.cmp(&b.1))
.then(li_a.cmp(&li_b))
.then(ei_a.cmp(&ei_b))
});
let mut edges = Vec::with_capacity(order.len());
let mut input_edge_ids = Vec::with_capacity(order.len());
let mut edge_layers = Vec::with_capacity(order.len());
for &(li, ei) in &order {
edges.push(layer_edges[li][ei]);
input_edge_ids.push(layer_input_edge_ids[li][ei]);
edge_layers.push(li);
}
(edges, input_edge_ids, edge_layers)
}
struct EdgeChainSimplifier<'a> {
builder: &'a S2Builder,
g: &'a Graph,
in_map: graph::VertexInMap,
out_map: graph::VertexOutMap,
edge_layers: &'a [usize],
site_vertices: &'a [Vec<i32>],
is_interior: Vec<bool>,
used: Vec<bool>,
new_edges: Vec<graph::Edge>,
new_input_edge_ids: Vec<InputEdgeIdSetId>,
new_edge_layers: Vec<usize>,
input_edge_id_set_lexicon: IdSetLexicon,
}
impl<'a> EdgeChainSimplifier<'a> {
fn new(
builder: &'a S2Builder,
g: &'a Graph,
edge_layers: &'a [usize],
site_vertices: &'a [Vec<i32>],
) -> Self {
let in_map = graph::VertexInMap::new(g);
let out_map = graph::VertexOutMap::new(g);
let num_vertices = g.num_vertices().as_usize();
let num_edges = g.num_edges().as_usize();
EdgeChainSimplifier {
builder,
g,
in_map,
out_map,
edge_layers,
site_vertices,
is_interior: vec![false; num_vertices],
used: vec![false; num_edges],
new_edges: Vec::with_capacity(num_edges),
new_input_edge_ids: Vec::with_capacity(num_edges),
new_edge_layers: Vec::with_capacity(num_edges),
input_edge_id_set_lexicon: g.input_edge_id_set_lexicon().clone(),
}
}
fn run(&mut self) {
for v in (0..self.g.num_vertices().0).map(VertexId) {
self.is_interior[v.as_usize()] = self.is_interior_vertex(v);
}
for e in (0..self.g.num_edges().0).map(EdgeId) {
if self.used[e.as_usize()] {
continue;
}
let edge = self.g.edge(e);
if self.is_interior[edge.0.as_usize()] {
continue;
}
if self.is_interior[edge.1.as_usize()] {
self.simplify_chain(edge.0, edge.1);
} else {
self.output_edge(e);
}
}
for e in (0..self.g.num_edges().0).map(EdgeId) {
if self.used[e.as_usize()] {
continue;
}
let edge = self.g.edge(e);
if edge.0 == edge.1 {
self.output_edge(e);
} else {
self.simplify_chain(edge.0, edge.1);
}
}
}
fn output_edge(&mut self, e: EdgeId) {
self.new_edges.push(self.g.edge(e));
self.new_input_edge_ids.push(self.g.input_edge_id_set_id(e));
self.new_edge_layers.push(self.edge_layers[e.as_usize()]);
self.used[e.as_usize()] = true;
}
fn graph_edge_layer(&self, e: EdgeId) -> usize {
self.edge_layers[e.as_usize()]
}
fn input_edge_layer(&self, id: InputEdgeId) -> usize {
debug_assert!(id >= 0);
let pos = self
.builder
.layer_begins
.iter()
.position(|&b| b > id.0)
.unwrap_or(self.builder.layer_begins.len());
if pos > 0 { pos - 1 } else { 0 }
}
fn is_interior_vertex(&self, v: VertexId) -> bool {
if self.out_map.degree(v) == 0 {
return false;
}
if self.out_map.degree(v) != self.in_map.degree(v) {
return false;
}
if self.builder.is_forced(v.as_usize()) {
return false;
}
let mut edges: Vec<EdgeId> = Vec::new();
for &e in self.out_map.edge_ids(v) {
edges.push(e);
}
for &e in self.in_map.edge_ids(v) {
edges.push(e);
}
edges.sort_by_key(|&e| self.graph_edge_layer(e));
let mut too_many = false;
let mut v1 = VertexId(-1);
let mut v2 = VertexId(-1);
let mut i = 0;
while i < edges.len() {
let layer = self.graph_edge_layer(edges[i]);
let mut n0 = 0i32;
let mut n1 = 0i32;
let mut n2 = 0i32;
let mut excess_out = 0i32;
while i < edges.len() && self.graph_edge_layer(edges[i]) == layer {
let edge = self.g.edge(edges[i]);
if edge.0 == v {
excess_out += 1;
Self::tally_vertex(
v,
edge.1,
&mut v1,
&mut v2,
&mut n0,
&mut n1,
&mut n2,
&mut too_many,
);
}
if edge.1 == v {
excess_out -= 1;
Self::tally_vertex(
v,
edge.0,
&mut v1,
&mut v2,
&mut n0,
&mut n1,
&mut n2,
&mut too_many,
);
}
i += 1;
}
if too_many || excess_out != 0 || n1 != n2 || (n0 > 0 && n1 == 0) {
return false;
}
}
true
}
fn tally_vertex(
v0: VertexId,
v: VertexId,
v1: &mut VertexId,
v2: &mut VertexId,
n0: &mut i32,
n1: &mut i32,
n2: &mut i32,
too_many: &mut bool,
) {
if v == v0 {
*n0 += 1;
} else {
if *v1 < 0 {
*v1 = v;
}
if *v1 == v {
*n1 += 1;
} else {
if *v2 < 0 {
*v2 = v;
}
if *v2 == v {
*n2 += 1;
} else {
*too_many = true;
}
}
}
}
fn simplify_chain(&mut self, mut v0: VertexId, mut v1: VertexId) {
let mut chain: Vec<VertexId> = Vec::new();
let mut used_vertices = std::collections::HashSet::new();
let mut simplifier = PolylineSimplifier::new();
let vstart = v0;
let mut done;
loop {
chain.push(v0);
used_vertices.insert(v0);
simplifier.init(self.g.vertex(v0));
let can_simplify = self.avoid_sites(v0, v0, v1, &mut used_vertices, &mut simplifier);
loop {
chain.push(v1);
used_vertices.insert(v1);
done = !self.is_interior[v1.as_usize()] || v1 == vstart;
if done {
break;
}
let vprev = v0;
v0 = v1;
v1 = self.follow_chain(vprev, v0);
let target_ok = can_simplify && self.target_input_vertices(v0, &mut simplifier);
let avoid_ok = target_ok
&& self.avoid_sites(chain[0], v0, v1, &mut used_vertices, &mut simplifier);
let extend_ok = avoid_ok && simplifier.extend(self.g.vertex(v1));
if !extend_ok {
break;
}
}
if chain.len() == 2 {
self.output_all_edges(chain[0], chain[1]);
} else {
self.merge_chain(&chain);
}
chain.clear();
used_vertices.clear();
if done {
break;
}
}
}
fn follow_chain(&self, v0: VertexId, v1: VertexId) -> VertexId {
debug_assert!(self.is_interior[v1.as_usize()]);
for &e in self.out_map.edge_ids(v1) {
let v = self.g.edge(e).1;
if v != v0 && v != v1 {
return v;
}
}
unreachable!("Could not find next edge in edge chain");
}
fn output_all_edges(&mut self, v0: VertexId, v1: VertexId) {
let edges = self.g.edges();
let fwd: Vec<EdgeId> = self.out_map.edge_ids_between(v0, v1, edges).to_vec();
let rev: Vec<EdgeId> = self.out_map.edge_ids_between(v1, v0, edges).to_vec();
for e in fwd {
self.output_edge(e);
}
for e in rev {
self.output_edge(e);
}
}
fn target_input_vertices(&self, v: VertexId, simplifier: &mut PolylineSimplifier) -> bool {
let verts = &self.site_vertices[v.as_usize()];
for &input_id in verts {
let p = self.builder.input_vertices[input_id as usize];
if !simplifier.target_disc(p, self.builder.edge_snap_radius_ca) {
return false;
}
}
true
}
fn avoid_sites(
&self,
v0: VertexId,
v1: VertexId,
v2: VertexId,
used_vertices: &mut std::collections::HashSet<VertexId>,
simplifier: &mut PolylineSimplifier,
) -> bool {
let p0 = self.g.vertex(v0);
let p1 = self.g.vertex(v1);
let p2 = self.g.vertex(v2);
let r1 = p0.chord_angle(p1);
let r2 = p0.chord_angle(p2);
if r2 < r1 {
return false;
}
if r2 >= self.builder.min_edge_length_to_split_ca {
return false;
}
let edges = self.g.edges();
let mut best: i32 = -1;
for &e in self.out_map.edge_ids_between(v1, v2, edges) {
for id in self.g.input_edge_ids(e) {
if best < 0
|| self.builder.edge_sites[id as usize].len()
< self.builder.edge_sites[best as usize].len()
{
best = id;
}
}
}
for &e in self.out_map.edge_ids_between(v2, v1, edges) {
for id in self.g.input_edge_ids(e) {
if best < 0
|| self.builder.edge_sites[id as usize].len()
< self.builder.edge_sites[best as usize].len()
{
best = id;
}
}
}
if best < 0 {
return true; }
for &site_id in &self.builder.edge_sites[best as usize] {
let v = VertexId(site_id as i32);
let p = self.g.vertex(v);
let r = p0.chord_angle(p);
if r >= r2 {
continue;
}
if !used_vertices.insert(v) {
continue;
}
let disc_on_left = if v1 == v0 {
predicates::robust_sign(p1, p2, p) == predicates::Direction::CounterClockwise
} else {
predicates::ordered_ccw(p0, p2, p, p1)
};
if !simplifier.avoid_disc(p, self.builder.min_edge_site_separation_ca, disc_on_left) {
return false;
}
}
true
}
fn merge_chain(&mut self, vertices: &[VertexId]) {
let edges_slice = self.g.edges();
let mut merged_input_ids: Vec<Vec<i32>> = Vec::new();
let mut degenerate_ids: Vec<i32> = Vec::new();
#[expect(unused_assignments, reason = "mirrors C++ control flow")]
let mut num_out = 0usize;
for i in 1..vertices.len() {
let v0 = vertices[i - 1];
let v1 = vertices[i];
let out_edges = self.out_map.edge_ids_between(v0, v1, edges_slice);
let in_edges = self.out_map.edge_ids_between(v1, v0, edges_slice);
if i == 1 {
num_out = out_edges.len();
merged_input_ids.resize(num_out + in_edges.len(), Vec::new());
for ids in &mut merged_input_ids {
ids.reserve(vertices.len() - 1);
}
} else {
debug_assert!(self.is_interior[v0.as_usize()]);
for &e in self.out_map.edge_ids_between(v0, v0, edges_slice) {
for id in self.g.input_edge_ids(e) {
degenerate_ids.push(id);
}
self.used[e.as_usize()] = true;
}
}
let mut j = 0;
for &e in out_edges {
for id in self.g.input_edge_ids(e) {
merged_input_ids[j].push(id);
}
self.used[e.as_usize()] = true;
j += 1;
}
for &e in in_edges {
for id in self.g.input_edge_ids(e) {
merged_input_ids[j].push(id);
}
self.used[e.as_usize()] = true;
j += 1;
}
debug_assert_eq!(merged_input_ids.len(), j);
}
if !degenerate_ids.is_empty() {
degenerate_ids.sort_unstable();
self.assign_degenerate_edges(°enerate_ids, &mut merged_input_ids);
}
let v0 = vertices[0];
let v1 = vertices[1];
let vb = vertices[vertices.len() - 1];
for &e in self.out_map.edge_ids_between(v0, v1, edges_slice) {
self.new_edges.push((v0, vb));
self.new_edge_layers.push(self.graph_edge_layer(e));
}
for &e in self.out_map.edge_ids_between(v1, v0, edges_slice) {
self.new_edges.push((vb, v0));
self.new_edge_layers.push(self.graph_edge_layer(e));
}
for ids in &merged_input_ids {
self.new_input_edge_ids
.push(self.input_edge_id_set_lexicon.add_set(ids));
}
}
fn assign_degenerate_edges(&self, degenerate_ids: &[i32], merged_ids: &mut [Vec<i32>]) {
for ids in merged_ids.iter_mut() {
ids.sort_unstable();
}
let mut order: Vec<usize> = (0..merged_ids.len())
.filter(|&i| !merged_ids[i].is_empty())
.collect();
order.sort_by_key(|&i| merged_ids[i][0]);
for °enerate_id in degenerate_ids {
let layer = self.input_edge_layer(InputEdgeId(degenerate_id));
let pos = order.partition_point(|&i| merged_ids[i][0] <= degenerate_id);
let target = if pos > 0 {
let prev_idx = order[pos - 1];
let layer_begin = self.builder.layer_begins[layer];
if merged_ids[prev_idx][0] >= layer_begin {
order[pos - 1]
} else if pos < order.len() {
order[pos]
} else {
order[pos - 1]
}
} else {
order[0]
};
merged_ids[target].push(degenerate_id);
}
}
}
#[cfg(test)]
#[expect(
clippy::field_reassign_with_default,
reason = "clearer than a single struct literal with many fields"
)]
mod tests {
use super::*;
use crate::s2::CellId;
use crate::s2::LatLng;
use crate::s2::edge_crossings;
use crate::s2::edge_distances;
use crate::s2::polyline::Polyline;
use crate::s2::text_format;
use point_vector_layer::S2PointVectorLayer;
use polygon_layer::S2PolygonLayer;
use polyline_vector_layer::S2PolylineVectorLayer;
use quickcheck_macros::quickcheck;
use snap::{IdentitySnapFunction, IntLatLngSnapFunction, S2CellIdSnapFunction};
use std::cell::RefCell;
use std::rc::Rc;
#[test]
fn test_s2error_display() {
let err = S2Error::ok();
assert!(err.is_ok());
assert_eq!(err.code, S2ErrorCode::Ok);
let err = S2Error::new(
S2ErrorCode::BuilderSnapRadiusTooSmall,
"snap radius too small",
);
assert!(!err.is_ok());
assert!(format!("{err}").contains("snap radius too small"));
}
#[test]
fn test_options_default() {
let opts = Options::default();
assert!(!opts.split_crossing_edges);
assert!(!opts.simplify_edge_chains);
assert!(opts.idempotent);
assert_eq!(opts.snap_function.snap_radius().radians(), 0.0);
assert_eq!(opts.intersection_tolerance.radians(), 0.0);
}
#[test]
fn test_options_edge_snap_radius_with_intersection_tolerance() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
intersection_tolerance: s1::Angle::from_degrees(0.5),
..Options::default()
};
let esr = opts.edge_snap_radius();
assert!(
(esr.degrees() - 1.5).abs() < 1e-10,
"edge_snap_radius should be snap_radius + intersection_tolerance"
);
}
#[test]
fn test_num_input_edges_and_input_edge() {
let mut builder = S2Builder::new(Options::default());
let output = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
let p2 = LatLng::from_degrees(2.0, 0.0).to_point();
assert_eq!(builder.num_input_edges(), 0);
builder.add_edge(p0, p1);
assert_eq!(builder.num_input_edges(), 1);
let (e0, e1) = builder.input_edge(0);
assert_eq!(e0, p0);
assert_eq!(e1, p1);
builder.add_edge(p1, p2);
assert_eq!(builder.num_input_edges(), 2);
let (e0, e1) = builder.input_edge(1);
assert_eq!(e0, p1);
assert_eq!(e1, p2);
}
#[test]
fn test_options_accessor() {
let snap_fn = S2CellIdSnapFunction::new(10);
let expected_radius = snap_fn.snap_radius();
let opts = Options::new(Box::new(snap_fn));
let builder = S2Builder::new(opts);
assert_eq!(
builder.options().snap_function.snap_radius().radians(),
expected_radius.radians()
);
}
#[test]
fn test_is_full_polygon_predicate() {
let pred_true = S2Builder::is_full_polygon(true);
let pred_false = S2Builder::is_full_polygon(false);
let opts = GraphOptions::new(
graph::EdgeType::Directed,
graph::DegenerateEdges::Discard,
graph::DuplicateEdges::Merge,
graph::SiblingPairs::Discard,
);
let g = Graph::from_raw_parts(
opts,
vec![],
vec![],
vec![],
IdSetLexicon::new(),
vec![],
IdSetLexicon::new(),
None,
);
assert!(pred_true(&g).unwrap());
assert!(!pred_false(&g).unwrap());
}
#[test]
fn test_is_full_polygon_unspecified() {
let pred = S2Builder::is_full_polygon_unspecified();
let opts = GraphOptions::new(
graph::EdgeType::Directed,
graph::DegenerateEdges::Discard,
graph::DuplicateEdges::Merge,
graph::SiblingPairs::Discard,
);
let g = Graph::from_raw_parts(
opts,
vec![],
vec![],
vec![],
IdSetLexicon::new(),
vec![],
IdSetLexicon::new(),
None,
);
let result = pred(&g);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, S2ErrorCode::BuilderIsFullPredicateNotSpecified);
}
#[test]
fn test_reset_clears_state() {
let mut builder = S2Builder::new(Options::default());
let output = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
builder.add_edge(p0, p1);
assert_eq!(builder.num_input_edges(), 1);
builder.reset();
assert_eq!(builder.num_input_edges(), 0);
}
#[test]
fn test_build_auto_resets() {
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
builder.add_edge(p0, p1);
builder.build().expect("build failed");
assert_eq!(builder.num_input_edges(), 0);
let output2 = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output2,
))));
let p2 = LatLng::from_degrees(2.0, 0.0).to_point();
builder.add_edge(p0, p2);
builder.build().expect("second build failed");
let result = output2.borrow();
assert_eq!(result.len(), 1);
assert_eq!(result[0].num_vertices(), 2);
}
#[test]
fn test_identity_snap_polygon_passthrough() {
let input = text_format::make_polygon("0:0, 0:5, 5:5, 5:0");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
assert_eq!(result.num_vertices(), input.num_vertices());
for i in 0..input.loop_at(0).num_vertices() {
let v_in = input.loop_at(0).vertex(i);
let v_out = result.loop_at(0).vertex(i);
assert_eq!(v_in, v_out, "vertex {i} differs: {v_in:?} vs {v_out:?}");
}
}
#[test]
fn test_identity_snap_polygon_with_hole() {
let input = text_format::make_polygon("0:0, 0:5, 5:5, 5:0; 1:1, 1:4, 4:4, 4:1");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 2);
assert_eq!(result.loop_at(0).num_vertices(), 4);
assert_eq!(result.loop_at(1).num_vertices(), 4);
}
#[test]
fn test_cell_id_snapping_polygon() {
let level = S2CellIdSnapFunction::level_for_max_snap_radius(s1::Angle::from_degrees(1.0));
let snap_fn = S2CellIdSnapFunction::new(level);
let opts = Options::new(Box::new(snap_fn));
let input = text_format::make_polygon("2:2, 3:4, 2:6, 4:5, 6:6, 5:4, 6:2, 4:3");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
let loop_ = result.loop_at(0);
for i in 0..loop_.num_vertices() {
let v = loop_.vertex(i);
let cell_center = CellId::from_point(&v).parent_at_level(level).to_point();
assert_eq!(
v, cell_center,
"vertex {i} is not a cell center at level {level}"
);
}
}
#[test]
fn test_int_latlng_snapping_polygon() {
let snap_fn = IntLatLngSnapFunction::new(0); let opts = Options::new(Box::new(snap_fn));
let input = text_format::make_polygon(
"2.01:2.09, 3.24:4.49, 1.78:6.25, 3.51:5.49, \
6.11:6.11, 5.22:3.88, 5.55:2.49, 4.49:2.51",
);
let expected = text_format::make_polygon("2:2, 3:4, 2:6, 4:5, 6:6, 5:4, 6:2, 4:3");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
let out_loop = result.loop_at(0);
let exp_loop = expected.loop_at(0);
assert_eq!(out_loop.num_vertices(), exp_loop.num_vertices());
for i in 0..out_loop.num_vertices() {
let v_out = out_loop.vertex(i);
let v_exp = exp_loop.vertex(i);
let dist = v_out.distance(v_exp).radians();
assert!(
dist < 1e-10,
"vertex {i}: distance {dist:.2e} between output and expected"
);
}
}
#[test]
fn test_identity_snap_polyline() {
let input = text_format::make_polyline("0:0, 1:1, 2:0, 3:1");
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
assert_eq!(result[0].num_vertices(), 4);
}
#[test]
fn test_point_vector_layer() {
let points = text_format::parse_points("0:0, 1:1, 2:2, 3:3");
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(&output))));
for &p in &points {
builder.add_point(p);
}
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 4);
}
#[test]
fn test_cell_id_snapping_points() {
let snap_fn = S2CellIdSnapFunction::new(10);
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(&output))));
let p = LatLng::from_degrees(47.6, -122.3).to_point();
builder.add_point(p);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
let cell_center = CellId::from_point(&result[0])
.parent_at_level(10)
.to_point();
assert_eq!(result[0], cell_center);
}
#[test]
fn test_add_edge_polyline() {
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
let p2 = LatLng::from_degrees(2.0, 0.0).to_point();
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(p0, p1);
builder.add_edge(p1, p2);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
assert_eq!(result[0].num_vertices(), 3);
}
#[test]
fn test_add_loop_polygon() {
let loop_ = text_format::make_loop("0:0, 0:10, 10:10, 10:0");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_loop(&loop_);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
assert_eq!(result.loop_at(0).num_vertices(), loop_.num_vertices());
}
#[test]
fn test_label_tracking() {
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
let p2 = LatLng::from_degrees(2.0, 0.0).to_point();
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.set_label(42);
builder.add_edge(p0, p1);
builder.set_label(99);
builder.add_edge(p1, p2);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
}
#[test]
fn test_force_vertex() {
let snap_fn = IdentitySnapFunction::new(s1::Angle::from_degrees(0.5));
let opts = Options::new(Box::new(snap_fn));
let forced = LatLng::from_degrees(0.0, 0.0).to_point();
let p0 = LatLng::from_degrees(0.1, 0.1).to_point();
let p1 = LatLng::from_degrees(1.0, 1.0).to_point();
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.force_vertex(forced);
builder.add_edge(p0, p1);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
let v0 = result[0].vertex(0);
let dist = v0.distance(forced).radians();
assert!(
dist < 1e-10,
"expected first vertex to snap to forced vertex, dist={dist:.2e}"
);
}
#[test]
fn test_int_latlng_e7_polyline() {
let snap_fn = IntLatLngSnapFunction::new(7); let opts = Options::new(Box::new(snap_fn));
let input = text_format::make_polyline("47.1234567:-122.9876543, 47.2345678:-122.8765432");
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
for i in 0..result[0].num_vertices() {
let ll = LatLng::from_point(result[0].vertex(i));
let lat_e7 = (ll.lat.degrees() * 1e7).round();
let lng_e7 = (ll.lng.degrees() * 1e7).round();
let lat_back = lat_e7 / 1e7;
let lng_back = lng_e7 / 1e7;
assert!(
(ll.lat.degrees() - lat_back).abs() < 1e-12,
"lat not E7-snapped: {}",
ll.lat.degrees()
);
assert!(
(ll.lng.degrees() - lng_back).abs() < 1e-12,
"lng not E7-snapped: {}",
ll.lng.degrees()
);
}
}
#[test]
fn test_multi_layer() {
let points_out = Rc::new(RefCell::new(Vec::new()));
let polylines_out = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(
&points_out,
))));
let p = LatLng::from_degrees(1.0, 2.0).to_point();
builder.add_point(p);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&polylines_out,
))));
let line = text_format::make_polyline("10:10, 20:20");
builder.add_polyline(&line);
builder.build().expect("build failed");
let points = points_out.borrow();
let polylines = polylines_out.borrow();
assert_eq!(points.len(), 1);
assert_eq!(polylines.len(), 1);
}
#[test]
fn test_empty_builder() {
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.build().expect("build failed");
let result = output.borrow();
assert!(result.is_empty_polygon());
}
#[test]
fn test_triangle_polygon() {
let input = text_format::make_polygon("0:0, 0:1, 1:0");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
assert_eq!(result.loop_at(0).num_vertices(), 3);
}
fn make_test_point(x: i32, y: i32, z: i32) -> Option<Point> {
let (xf, yf, zf) = (f64::from(x), f64::from(y), f64::from(z));
let norm = (xf * xf + yf * yf + zf * zf).sqrt();
if norm < 1e-10 {
return None;
}
Some(Point::from_coords(xf / norm, yf / norm, zf / norm))
}
#[quickcheck]
fn prop_identity_preserves_edge(x0: i32, y0: i32, z0: i32, x1: i32, y1: i32, z1: i32) -> bool {
let (p0, p1) = match (make_test_point(x0, y0, z0), make_test_point(x1, y1, z1)) {
(Some(a), Some(b)) if a != b && a != -b => (a, b),
_ => return true,
};
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(p0, p1);
if builder.build().is_err() {
return true; }
let result = output.borrow();
if result.len() != 1 {
return false;
}
result[0].num_vertices() == 2 && result[0].vertex(0) == p0 && result[0].vertex(1) == p1
}
#[quickcheck]
fn prop_cell_id_snap_polyline_vertices_are_cell_centers(
x0: i32,
y0: i32,
z0: i32,
x1: i32,
y1: i32,
z1: i32,
level: u8,
) -> bool {
let (p0, p1) = match (make_test_point(x0, y0, z0), make_test_point(x1, y1, z1)) {
(Some(a), Some(b)) if a != b && a != -b => (a, b),
_ => return true,
};
let level = level % 21;
let snap_fn = S2CellIdSnapFunction::new(level);
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(p0, p1);
if builder.build().is_err() {
return true;
}
let result = output.borrow();
for polyline in result.iter() {
for i in 0..polyline.num_vertices() {
let v = polyline.vertex(i);
let expected = CellId::from_point(&v).parent_at_level(level).to_point();
if v != expected {
return false;
}
}
}
true
}
#[quickcheck]
fn prop_int_latlng_snap_polyline_coords_integral(
x0: i32,
y0: i32,
z0: i32,
x1: i32,
y1: i32,
z1: i32,
exp: u8,
) -> bool {
let (p0, p1) = match (make_test_point(x0, y0, z0), make_test_point(x1, y1, z1)) {
(Some(a), Some(b)) if a != b && a != -b => (a, b),
_ => return true,
};
let exp = i32::from(exp) % 8;
let snap_fn = IntLatLngSnapFunction::new(exp);
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(p0, p1);
if builder.build().is_err() {
return true;
}
let power = 10_f64.powi(exp);
let result = output.borrow();
for polyline in result.iter() {
for i in 0..polyline.num_vertices() {
let ll = LatLng::from_point(polyline.vertex(i));
let lat_int = (ll.lat.degrees() * power).round();
let lng_int = (ll.lng.degrees() * power).round();
if (ll.lat.degrees() - lat_int / power).abs() > 1e-10 {
return false;
}
if (ll.lng.degrees() - lng_int / power).abs() > 1e-10 {
return false;
}
}
}
true
}
#[quickcheck]
fn prop_identity_point_count_preserved(coords: Vec<(i32, i32, i32)>) -> bool {
let points: Vec<Point> = coords
.iter()
.filter_map(|&(x, y, z)| make_test_point(x, y, z))
.collect();
let mut unique = points.clone();
unique.dedup();
if unique.is_empty() {
return true;
}
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(&output))));
for &p in &unique {
builder.add_point(p);
}
if builder.build().is_err() {
return true;
}
let result = output.borrow();
result.len() <= unique.len()
}
#[quickcheck]
fn prop_empty_input_never_panics(level: u8) -> bool {
let level = level % 31;
let snap_fn = S2CellIdSnapFunction::new(level);
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
drop(builder.build());
true }
#[quickcheck]
fn prop_cell_id_snap_output_within_radius(x0: i32, y0: i32, z0: i32, level: u8) -> bool {
let Some(p) = make_test_point(x0, y0, z0) else {
return true;
};
let level = level % 21;
let snap_fn = S2CellIdSnapFunction::new(level);
let snap_radius = snap_fn.snap_radius();
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(&output))));
builder.add_point(p);
if builder.build().is_err() {
return true;
}
let result = output.borrow();
if result.len() != 1 {
return true; }
let dist = p.distance(result[0]);
dist.radians() <= snap_radius.radians() + 1e-15
}
#[test]
fn test_add_empty_loop_skipped() {
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
let empty_loop = Loop::empty();
builder.add_loop(&empty_loop);
assert_eq!(builder.num_input_edges(), 0);
builder.build().expect("build failed");
let result = output.borrow();
assert!(result.is_empty_polygon());
}
#[test]
fn test_add_full_loop_skipped() {
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
let full_loop = Loop::full();
builder.add_loop(&full_loop);
assert_eq!(builder.num_input_edges(), 0);
builder.build().expect("build failed");
}
#[test]
fn test_add_loop_from_points() {
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(0.0, 10.0).to_point();
let p2 = LatLng::from_degrees(10.0, 0.0).to_point();
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_loop_from_points(&[p0, p1, p2]);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
assert_eq!(result.loop_at(0).num_vertices(), 3);
}
#[test]
fn test_add_polyline_from_points() {
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(1.0, 0.0).to_point();
let p2 = LatLng::from_degrees(2.0, 0.0).to_point();
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline_from_points(&[p0, p1, p2]);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
assert_eq!(result[0].num_vertices(), 3);
}
#[test]
fn test_split_crossing_edges() {
let a0 = LatLng::from_degrees(0.0, 0.0).to_point();
let a1 = LatLng::from_degrees(10.0, 10.0).to_point();
let b0 = LatLng::from_degrees(0.0, 10.0).to_point();
let b1 = LatLng::from_degrees(10.0, 0.0).to_point();
let opts = Options {
split_crossing_edges: true,
..Options::default()
};
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(a0, a1);
builder.add_edge(b0, b1);
builder.build().expect("build failed");
let result = output.borrow();
let total_vertices: usize = result.iter().map(Polyline::num_vertices).sum();
assert!(
total_vertices >= 4,
"expected crossing to create shared vertex, got {total_vertices} vertices across {} polylines",
result.len()
);
}
#[quickcheck]
fn prop_build_deterministic(x0: i32, y0: i32, z0: i32, x1: i32, y1: i32, z1: i32) -> bool {
let (p0, p1) = match (make_test_point(x0, y0, z0), make_test_point(x1, y1, z1)) {
(Some(a), Some(b)) if a != b && a != -b => (a, b),
_ => return true,
};
let build = || {
let output = Rc::new(RefCell::new(Vec::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_edge(p0, p1);
drop(builder.build());
output.borrow().clone()
};
let r1 = build();
let r2 = build();
if r1.len() != r2.len() {
return false;
}
for (a, b) in r1.iter().zip(r2.iter()) {
if !a.equal(b) {
return false;
}
}
true
}
fn test_snapping_with_forced_vertices(
input_str: &str,
snap_radius: s1::Angle,
vertices_str: &str,
expected_str: &str,
) {
let mut builder = S2Builder::new(Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
..Options::default()
});
let vertices = text_format::parse_points(vertices_str);
for v in &vertices {
builder.force_vertex(*v);
}
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline(input_str));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1, "expected 1 polyline, got {}", result.len());
let actual = text_format::polyline_to_string(&result[0]);
assert_eq!(actual, expected_str, "polyline mismatch");
}
#[test]
fn test_nearby_vertices_snapped_with_zero_snap_radius_edge_splitting() {
let opts = Options {
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let layer_options = polyline_vector_layer::Options {
polyline_type: graph::PolylineType::Walk,
..Default::default()
};
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_options,
)));
builder.add_polyline(&text_format::make_polyline("0:180, 0:3"));
builder.add_polyline(&text_format::make_polyline("90:180, 0:179.9999999999999"));
builder.add_polyline(&text_format::make_polyline("10:10, 1e-15:10"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(
result.len(),
3,
"expected 3 polylines, got {}",
result.len()
);
assert_eq!(
text_format::polyline_to_string(&result[0]),
"0:180, 0:180, 1e-15:10, 0:3"
);
assert_eq!(text_format::polyline_to_string(&result[1]), "90:180, 0:180");
assert_eq!(
text_format::polyline_to_string(&result[2]),
"10:10, 1e-15:10"
);
}
#[test]
fn test_nearby_intersection_snapped_with_zero_snap_radius() {
let opts = Options {
intersection_tolerance: edge_crossings::intersection_error(),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("0:0, 0:10"));
builder.add_intersection(text_format::parse_point("1e-16:5"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1, "expected 1 polyline");
assert_eq!(
text_format::polyline_to_string(&result[0]),
"0:0, 1e-16:5, 0:10"
);
}
#[test]
fn test_max_edge_deviation() {
let ie = edge_crossings::intersection_error();
let opts_with_tol = Options {
split_crossing_edges: true,
idempotent: false,
intersection_tolerance: ie,
..Options::default()
};
assert_eq!(opts_with_tol.edge_snap_radius().radians(), ie.radians());
let max_deviation = opts_with_tol.max_edge_deviation();
let mut num_effective = 0;
let num_iters = 50;
for iter in 0..num_iters {
let mut builder = S2Builder::new(Options {
split_crossing_edges: true,
idempotent: false,
..Options::default()
});
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
let theta = f64::from(iter) * 2.0 * std::f64::consts::PI / f64::from(num_iters);
let phi = f64::from(iter) * 1.234;
let a = Point::from_coords(theta.cos() * phi.cos(), theta.sin() * phi.cos(), phi.sin())
.normalize();
let mut b: Point = (-a.0).into();
b = Point::from_coords(
b.x() + 5e-16 * f64::from(iter * 7).sin(),
b.y() + 5e-16 * f64::from(iter * 13).cos(),
b.z() + 5e-16 * f64::from(iter * 17).sin(),
)
.normalize();
let c = Point::from_coords(
a.x() + 5e-16 * f64::from(iter * 3).cos(),
a.y() + 5e-16 * f64::from(iter * 5).sin(),
a.z() + 5e-16 * f64::from(iter * 11).cos(),
)
.normalize();
if b == Point::from(-a.0) || c == a {
continue;
}
builder.add_edge(a, b);
builder.force_vertex(c);
builder.build().expect("build failed");
let result = output.borrow();
assert!(!result.is_empty(), "no output polylines");
let polyline = &result[0];
let n = polyline.num_vertices();
assert_eq!(polyline.vertex(0), a, "first vertex should be a");
assert_eq!(polyline.vertex(n - 1), b, "last vertex should be b");
for i in 0..n - 1 {
assert!(
edge_distances::is_edge_b_near_edge_a(
a,
b,
polyline.vertex(i),
polyline.vertex(i + 1),
max_deviation,
),
"Iteration {iter}: snapped edge deviates too far from original"
);
}
if n > 2 {
num_effective += 1;
}
}
assert!(
num_effective * 5 >= num_iters,
"only {num_effective}/{num_iters} effective tests"
);
}
#[test]
fn test_topology_preserved_with_zero_snap_radius_edge_splitting() {
let opts = Options {
split_crossing_edges: true,
idempotent: false,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let layer_options = polyline_vector_layer::Options {
polyline_type: graph::PolylineType::Walk,
..Default::default()
};
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_options,
)));
let k_edge_snap_rad_deg = edge_crossings::intersection_error().degrees();
let a = LatLng::from_degrees(0.0, -1.0).to_point();
let b = LatLng::from_degrees(0.0, 46.0).to_point();
let x = LatLng::from_degrees(0.99 * k_edge_snap_rad_deg, 0.0).to_point();
let y = LatLng::from_degrees(0.99 * k_edge_snap_rad_deg, 45.0).to_point();
let c = LatLng::from_degrees(1.03 * k_edge_snap_rad_deg, 22.5).to_point();
let d = LatLng::from_degrees(10.0, 22.5).to_point();
builder.add_edge(a, b);
builder.force_vertex(x);
builder.force_vertex(y);
builder.add_edge(c, d);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(
result.len(),
2,
"expected 2 polylines, got {}",
result.len()
);
assert!(
result[0].num_vertices() >= 4,
"snapped edge should have >= 4 vertices, got {}",
result[0].num_vertices()
);
if result[0].num_vertices() >= 3 {
let crossing = edge_crossings::crossing_sign(
result[0].vertex(1),
result[0].vertex(2),
result[1].vertex(0),
result[1].vertex(1),
);
assert!(
crossing != edge_crossings::Crossing::Cross,
"snapped edge should not cross vertex C"
);
}
}
#[test]
fn test_topology_preserved_with_forced_vertices() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(
edge_crossings::intersection_error(),
)),
idempotent: false,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let layer_options = polyline_vector_layer::Options {
polyline_type: graph::PolylineType::Walk,
..Default::default()
};
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_options,
)));
let k_edge_snap_rad_deg = edge_crossings::intersection_error().degrees();
let a = LatLng::from_degrees(0.0, -1.0).to_point();
let b = LatLng::from_degrees(0.0, 46.0).to_point();
let x = LatLng::from_degrees(0.99 * k_edge_snap_rad_deg, 0.0).to_point();
let y = LatLng::from_degrees(0.99 * k_edge_snap_rad_deg, 45.0).to_point();
let c = LatLng::from_degrees(1.03 * k_edge_snap_rad_deg, 22.5).to_point();
let d = LatLng::from_degrees(10.0, 22.5).to_point();
builder.add_edge(a, b);
builder.force_vertex(x);
builder.force_vertex(y);
builder.force_vertex(c);
builder.add_edge(c, d);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(
result.len(),
2,
"expected 2 polylines, got {}",
result.len()
);
assert!(
result[0].num_vertices() >= 4,
"snapped edge should have >= 4 vertices, got {}",
result[0].num_vertices()
);
if result[0].num_vertices() >= 3 {
let crossing = edge_crossings::crossing_sign(
result[0].vertex(1),
result[0].vertex(2),
result[1].vertex(0),
result[1].vertex(1),
);
assert!(
crossing != edge_crossings::Crossing::Cross,
"snapped edge should not cross vertex C"
);
}
}
#[test]
fn test_adjacent_coverage_intervals_span_more_than_90_degrees() {
test_snapping_with_forced_vertices(
"0:0, 0:80",
s1::Angle::from_degrees(60.0),
"0:0, 0:70",
"0:0, 0:70",
);
test_snapping_with_forced_vertices(
"0:0, 0:80",
s1::Angle::from_degrees(60.0),
"0:0, 0:90",
"0:0, 0:90",
);
test_snapping_with_forced_vertices(
"0:0, 0:80",
s1::Angle::from_degrees(60.0),
"0:0, 0:110",
"0:0, 0:110",
);
test_snapping_with_forced_vertices(
"0:10, 0:170",
s1::Angle::from_degrees(50.0),
"47:0, 49:180",
"47:0, 0:90, 49:180",
);
test_snapping_with_forced_vertices(
"0:10, 0:170",
s1::Angle::from_degrees(70.0),
"0:-20, 0:-160",
"0:-20, 0:90, 0:-160",
);
test_snapping_with_forced_vertices(
"0:0.1, 0:179.9",
s1::Angle::from_degrees(70.0),
"0:-69.8, 0:-110.2",
"0:-69.8, 0:90, 0:-110.2",
);
}
#[test]
fn test_voronoi_site_exclusion_bug1() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(64.83))),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("29.40:173.03, -18.02:-5.83"));
builder.force_vertex(text_format::parse_point("25.84:131.46"));
builder.force_vertex(text_format::parse_point("-29.23:-166.58"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
assert_eq!(
text_format::polyline_to_string(&result[0]),
"25.84:131.46, -18.02:-5.83"
);
}
#[test]
fn test_voronoi_site_exclusion_bug2() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(67.75))),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("47.06:-175.17, -47.59:10.57"));
builder.force_vertex(text_format::parse_point("36.36:47.63"));
builder.force_vertex(text_format::parse_point("-28.34:-72.46"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1);
let n = result[0].num_vertices();
assert!(n >= 2, "expected at least 2 vertices, got {n}");
let expected_start = text_format::parse_point("47.06:-175.17");
assert_eq!(result[0].vertex(0), expected_start);
}
#[test]
fn test_min_edge_vertex_separation() {
let input = text_format::make_polygon(
"0:0, 0:1, 1:.9, 2:.8, 3:.7, 4:.6, 5:.5, 6:.4, 7:.3, 8:.2, 9:.1, 10:0",
);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(0.5))),
..Options::default()
};
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert!(
result.num_loops() >= 1,
"expected at least 1 loop, got {}",
result.num_loops()
);
let nv = result.num_vertices();
assert!((4..=10).contains(&nv), "expected 4-10 vertices, got {nv}");
}
#[test]
fn test_separation_sites_regression_bug() {
let opts = Options {
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let layer_options = polyline_vector_layer::Options {
polyline_type: graph::PolylineType::Walk,
..Default::default()
};
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_options,
)));
let input_polylines: Vec<Vec<Point>> = vec![
vec![
Point::from_coords(
0.99482894039096326,
0.087057485575229562,
0.05231035811301657,
),
Point::from_coords(
0.19008255728509718,
0.016634125542513145,
0.98162718344766398,
),
],
vec![
Point::from_coords(
0.99802098666373784,
0.052325259429907504,
0.034873735164620751,
),
Point::from_coords(
0.99585181570926085,
0.087146997393412709,
0.026164135641767797,
),
Point::from_coords(
0.99939172130835197,
6.9770704216017258e-20,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
1.7442676054004314e-202,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
2.4185105853059967e-57,
0.034873878194564757,
),
Point::from_coords(0.99939091697091686, 0.0, 0.034896920724182809),
Point::from_coords(
0.99543519482327569,
0.088840224357046416,
0.034873879097925588,
),
],
vec![
Point::from_coords(
-0.86549861898490243,
0.49969586065415578,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
1.542605867912342e-181,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
1.5426058679123417e-281,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
1.5426058504696658e-231,
0.034873878194564757,
),
Point::from_coords(
0.19080899537654492,
3.3302452117433465e-113,
0.98162718344766398,
),
],
vec![
Point::from_coords(
0.99802098660295513,
0.052325259426720727,
0.034873736908888363,
),
Point::from_coords(
0.99558688908226523,
0.08712381366290145,
0.034873878194564757,
),
Point::from_coords(
0.99939172130835197,
1.0221039496805218e-23,
0.034873878194564757,
),
Point::from_coords(
0.99939172127682907,
3.4885352106908273e-20,
0.034873879097925602,
),
Point::from_coords(
0.99391473614090387,
0.10448593114531293,
0.03487387954694085,
),
],
];
for polyline in &input_polylines {
for i in 0..polyline.len() - 1 {
builder.add_edge(polyline[i], polyline[i + 1]);
}
}
builder.build().expect("build failed");
}
#[test]
fn test_add_shape() {
let input = text_format::make_polygon("0:0, 0:5, 5:5, 5:0; 1:1, 1:4, 4:4, 4:1");
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_shape(&input);
builder.build().expect("build failed");
let result = output.borrow();
let result_str = text_format::polygon_to_string(&result);
let input_str = text_format::polygon_to_string(&input);
assert_eq!(result_str, input_str, "add_shape polygon mismatch");
}
#[test]
fn test_max_snap_radius_can_snap_at_level_0() {
assert!(
S2CellIdSnapFunction::min_snap_radius_for_level(0) <= snap::MAX_SNAP_RADIUS,
"min_snap_radius_for_level(0) should be <= MAX_SNAP_RADIUS"
);
}
#[test]
fn test_push_pop_label() {
let mut builder = S2Builder::new(Options::default());
builder.push_label(1);
builder.pop_label();
}
#[test]
fn test_nan_vertices() {
use crate::s2::lax_polygon::LaxPolygon;
let nan_point = Point::from_coords(f64::NAN, f64::NAN, f64::NAN);
let loop_verts = vec![nan_point, nan_point, nan_point];
let lax = LaxPolygon::from_loops(&[&loop_verts]);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let output = Rc::new(RefCell::new(LaxPolygon::empty()));
let mut builder = S2Builder::new(Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_radians(1e-15))),
..Options::default()
});
builder.start_layer(Box::new(lax_polygon_layer::LaxPolygonLayer::new_legacy(
Rc::clone(&output),
)));
builder.add_shape(&lax);
let build_result = builder.build();
(build_result, output.borrow().num_loops())
}));
match result {
Err(_) => {} Ok((build_result, num_loops)) => {
assert!(build_result.is_err() || num_loops == 0);
}
}
}
#[test]
fn test_hausdorff_distance_bug() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(70.0))),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
let lax = text_format::make_lax_polygon("35:17; -40:88, 68:-161, 48:-156, -45:-10, -40:88");
builder.add_shape(&lax);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
}
#[test]
fn test_idempotency_snaps_inadequately_separated_vertices() {
use polyline_layer::S2PolylineLayer;
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output))));
builder.add_polyline(&text_format::make_polyline("0:0, 0:0.9, 0:2"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(
text_format::polyline_to_string(&result),
"0:0, 0:2",
"inadequately separated vertices should be snapped"
);
}
#[test]
fn test_idempotency_snaps_identical_vertices_with_zero_snap_radius() {
let mut builder = S2Builder::new(Options::default());
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polyline(&text_format::make_polyline("0:1, 1:0"));
builder.add_polyline(&text_format::make_polyline("0:0, 0:1"));
builder.add_edge(
text_format::parse_point("0:1"),
text_format::parse_point("0:1"),
);
builder.add_polyline(&text_format::make_polyline("1:0, 0:0"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(text_format::polygon_to_string(&result), "0:0, 0:1, 1:0",);
}
#[test]
fn test_idempotency_snaps_identical_vertices_with_zero_snap_radius_edge_splitting() {
let opts = Options {
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polyline(&text_format::make_polyline("0:1, 1:0"));
builder.add_polyline(&text_format::make_polyline("0:0, 0:1"));
builder.add_edge(
text_format::parse_point("0:1"),
text_format::parse_point("0:1"),
);
builder.add_polyline(&text_format::make_polyline("1:0, 0:0"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(text_format::polygon_to_string(&result), "0:0, 0:1, 1:0",);
}
#[test]
fn test_idempotency_snaps_unsnapped_vertices() {
use polyline_layer::S2PolylineLayer;
let snap_function = IntLatLngSnapFunction::new(0);
assert!(snap_function.snap_radius() >= s1::Angle::from_degrees(0.7));
assert!(snap_function.min_vertex_separation() <= s1::Angle::from_degrees(0.35));
let a = LatLng::from_degrees(0.0, 0.0).to_point();
let b = LatLng::from_degrees(0.01, 0.6).to_point();
assert!(CellId::from_point(&a) < CellId::from_point(&b));
let mut builder = S2Builder::new(Options::new(Box::new(IntLatLngSnapFunction::new(0))));
let output1 = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output1))));
builder.add_polyline(&Polyline::new(vec![a, b]));
builder.build().expect("build failed");
assert_eq!(
text_format::polyline_to_string(&output1.borrow()),
"0:0, 0:1",
);
let c = LatLng::from_degrees(0.01, 0.4).to_point();
let d = LatLng::from_degrees(0.0, 1.0).to_point();
assert!(CellId::from_point(&c) < CellId::from_point(&d));
let mut builder = S2Builder::new(Options::new(Box::new(IntLatLngSnapFunction::new(0))));
let output2 = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output2))));
builder.add_polyline(&Polyline::new(vec![c, d]));
builder.build().expect("build failed");
assert_eq!(
text_format::polyline_to_string(&output2.borrow()),
"0:0, 0:1",
);
}
#[test]
fn test_idempotency_snaps_edges_with_tiny_snap_radius() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(
edge_crossings::intersection_error(),
)),
..Options::default()
};
let layer_options = polyline_vector_layer::Options {
duplicate_edges: graph::DuplicateEdges::Merge,
..Default::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_options,
)));
builder.add_polyline(&text_format::make_polyline("0:0, 0:10"));
builder.add_polyline(&text_format::make_polyline("0:5, 0:7"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 1, "expected 1 polyline");
assert_eq!(
text_format::polyline_to_string(&result[0]),
"0:0, 0:5, 0:7, 0:10",
);
}
#[test]
fn test_idempotency_does_not_snap_adequately_separated_edges() {
let opts = Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
idempotent: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output1 = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output1))));
builder.add_polygon(&text_format::make_polygon("1.49:0, 0:2, 0.49:3"));
builder.build().expect("build failed");
let expected = "1:0, 0:2, 0:3";
assert_eq!(text_format::polygon_to_string(&output1.borrow()), expected,);
let mut builder = S2Builder::new(Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
idempotent: true,
..Options::default()
});
let output2 = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output2))));
builder.add_polygon(&output1.borrow());
builder.build().expect("build failed");
assert_eq!(text_format::polygon_to_string(&output2.borrow()), expected,);
}
#[test]
fn test_self_intersecting_polyline() {
use polyline_layer::S2PolylineLayer;
let opts = Options {
snap_function: Box::new(IntLatLngSnapFunction::new(1)),
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output))));
builder.add_polyline(&text_format::make_polyline("3:1, 1:3, 1:1, 3:3"));
builder.build().expect("build failed");
let expected = text_format::make_polyline("3:1, 2:2, 1:3, 1:1, 2:2, 3:3");
assert!(
output.borrow().equal(&expected),
"expected: {}, got: {}",
text_format::polyline_to_string(&expected),
text_format::polyline_to_string(&output.borrow()),
);
}
#[test]
fn test_self_intersecting_polygon() {
let opts = Options {
snap_function: Box::new(IntLatLngSnapFunction::new(1)),
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::with_options_legacy(
Rc::clone(&output),
polygon_layer::Options {
edge_type: graph::EdgeType::Undirected,
..Default::default()
},
)));
builder.add_polyline(&text_format::make_polyline("3:1, 1:3, 1:1, 3:3, 3:1"));
builder.build().expect("build failed");
let result = output.borrow();
assert!(
result.num_loops() >= 2,
"expected at least 2 loops, got {}",
result.num_loops(),
);
assert!(result.validate().is_ok(), "output should be valid");
}
#[test]
fn test_old_s2_polygon_builder_bug() {
use crate::s2::earth;
let input = text_format::make_polygon(
"32.2983095:72.3416582, 32.2986281:72.3423059, \
32.2985238:72.3423743, 32.2987176:72.3427807, \
32.2988174:72.3427056, 32.2991269:72.3433480, \
32.2991881:72.3433077, 32.2990668:72.3430462, \
32.2991745:72.3429778, 32.2995078:72.3436725, \
32.2996075:72.3436269, 32.2985465:72.3413832, \
32.2984558:72.3414530, 32.2988015:72.3421839, \
32.2991552:72.3429416, 32.2990498:72.3430073, \
32.2983764:72.3416059",
);
assert!(input.validate().is_ok());
let snap_radius = earth::meters_to_angle(20.0 / 0.866);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert!(result.validate().is_ok(), "output polygon should be valid");
assert!(
result.num_loops() >= 1,
"expected at least 1 loop, got {}",
result.num_loops(),
);
}
#[test]
fn test_incorrect_separation_site_bug() {
use polyline_layer::S2PolylineLayer;
let opts = Options {
idempotent: false,
split_crossing_edges: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output))));
builder.add_edge(
Point::from_coords(-0.50094438964076704, -0.86547947317509455, 0.0),
Point::from_coords(1.0, 1.7786363250284876e-322, 4.7729929394856611e-65),
);
builder.force_vertex(Point::from_coords(1.0, 0.0, -4.7729929394856611e-65));
builder.force_vertex(Point::from_coords(
1.0,
2.2603503297237029e-320,
4.7729929394856619e-65,
));
builder.build().expect("build failed");
}
#[test]
fn test_snapping_tiny_loop_regression() {
use crate::s2::convex_hull_query::ConvexHullQuery;
let mut query = ConvexHullQuery::new();
query.add_point(LatLng::from_degrees(4.56, 1.23).to_point());
let loop_ = query.convex_hull();
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_radians(1e-15))),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let points = Rc::new(RefCell::new(Vec::new()));
let polylines = Rc::new(RefCell::new(Vec::new()));
let polygon_out = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(&points))));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&polylines,
))));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(
&polygon_out,
))));
builder.add_loop(&loop_);
builder.add_is_full_polygon_predicate(S2Builder::is_full_polygon(false));
builder.build().expect("build failed");
assert_eq!(points.borrow().len(), 0);
assert_eq!(polylines.borrow().len(), 0);
assert!(
polygon_out.borrow().num_loops() <= 1,
"expected 0 or 1 loops, got {}",
polygon_out.borrow().num_loops(),
);
}
#[test]
fn test_simple_vertex_merging() {
let snap_radius = s1::Angle::from_degrees(0.5);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
let input = text_format::make_polygon(
"0:0, 0.2:0.2, 0.1:0.2, 0.1:0.9, 0:1, 0.1:1.1, 0.9:1, 1:1, 1:0.9",
);
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
let expected = text_format::make_polygon("0:0, 0:1, 1:0.9");
assert!(
expected.boundary_approx_eq(&result, snap_radius),
"expected: {}, got: {}",
text_format::polygon_to_string(&expected),
text_format::polygon_to_string(&result),
);
}
#[test]
fn test_snapping_does_not_rotate_vertices() {
let input = text_format::make_polygon(
"49.9305505:-124.8345463, 49.9307448:-124.8299657, \
49.9332101:-124.8301996, 49.9331224:-124.8341368; \
49.9311087:-124.8327042, 49.9318176:-124.8312621, \
49.9318866:-124.8334451",
);
let snap_fn = S2CellIdSnapFunction::new(crate::s2::coords::MAX_CELL_LEVEL);
let snap_radius = snap_fn.snap_radius();
let opts = Options::new(Box::new(snap_fn));
let output1 = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output1))));
builder.add_polygon(&input);
builder.build().expect("build failed");
assert!(
input.boundary_approx_eq(&output1.borrow(), snap_radius),
"first pass not approx equal to input",
);
let snap_fn2 = S2CellIdSnapFunction::new(crate::s2::coords::MAX_CELL_LEVEL);
let opts2 = Options::new(Box::new(snap_fn2));
let output2 = Rc::new(RefCell::new(Polygon::empty()));
let mut builder2 = S2Builder::new(opts2);
builder2.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output2))));
builder2.add_polygon(&output1.borrow());
builder2.build().expect("build failed");
assert!(
output1
.borrow()
.boundary_approx_eq(&output2.borrow(), s1::Angle::from_radians(1e-15),),
"second pass should equal first pass:\npass1: {}\npass2: {}",
text_format::polygon_to_string(&output1.borrow()),
text_format::polygon_to_string(&output2.borrow()),
);
}
#[test]
fn test_tie_breaking_is_consistent() {
use polyline_layer::S2PolylineLayer;
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(2.0))),
idempotent: false,
..Options::default()
};
let mut builder = S2Builder::new(opts);
builder.force_vertex(LatLng::from_degrees(1.0, 0.0).to_point());
builder.force_vertex(LatLng::from_degrees(-1.0, 0.0).to_point());
let output1 = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output1))));
builder.add_polyline(&text_format::make_polyline("0:-5, 0:5"));
let output2 = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output2))));
builder.add_polyline(&text_format::make_polyline("0:5, 0:-5"));
builder.build().expect("build failed");
let r1 = output1.borrow();
let r2 = output2.borrow();
assert_eq!(r1.num_vertices(), 3, "forward polyline should have 3 verts");
assert_eq!(r2.num_vertices(), 3, "reverse polyline should have 3 verts");
for i in 0..3 {
assert_eq!(
r1.vertex(i),
r2.vertex(2 - i),
"vertex {i}: reversed polylines should have reversed vertices",
);
}
}
#[test]
fn test_s2_cell_id_snapping_at_all_levels() {
let input = text_format::make_polygon("0:0, 0:2, 2:0; 0:0, 0:-2, -2:-2, -2:0");
for level in 0..=crate::s2::coords::MAX_CELL_LEVEL {
let snap_fn = S2CellIdSnapFunction::new(level);
let _snap_radius = snap_fn.snap_radius();
let opts = Options::new(Box::new(snap_fn));
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder
.build()
.unwrap_or_else(|e| panic!("build failed at level {level}: {e}"));
let result = output.borrow();
assert!(
result.validate().is_ok(),
"invalid polygon at level {level}",
);
if !result.is_empty_polygon() {
assert!(
result.num_loops() >= 1,
"level {level}: expected at least 1 loop"
);
}
}
}
#[test]
fn test_vertices_move_less_than_snap_radius() {
let snap_radius = s1::Angle::from_degrees(1.0);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
let center = Point::from_coords(1.0, 0.0, 0.0).normalize();
let radius = s1::Angle::from_degrees(20.0);
let n = 1000;
let loop_ = make_regular_loop(center, radius, n);
let input = Polygon::from_loops(vec![loop_]);
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
let nv = result.loop_at(0).num_vertices();
assert!(
(80..=120).contains(&nv),
"expected 80-120 output vertices, got {nv}",
);
let input_loop = input.loop_at(0);
let output_loop = result.loop_at(0);
for i in 0..output_loop.num_vertices() {
let v = output_loop.vertex(i);
let mut min_dist = f64::MAX;
for j in 0..input_loop.num_vertices() {
let d = v.distance(input_loop.vertex(j)).radians();
if d < min_dist {
min_dist = d;
}
}
assert!(
min_dist <= snap_radius.radians() + 1e-10,
"output vertex {i} is {min_dist:.6e} rad from nearest input vertex, snap_radius = {:.6e}",
snap_radius.radians(),
);
}
}
#[test]
fn test_input_edge_id_assignment() {
test_input_edge_ids(
&["0:0, 0:1, 0:2"],
&[("0:0, 0:1", &[0]), ("0:1, 0:2", &[1])],
GraphOptions::default(),
);
}
#[test]
fn test_undirected_siblings_dont_have_input_edge_ids() {
let mut graph_options = GraphOptions::default();
graph_options.edge_type = graph::EdgeType::Undirected;
test_input_edge_ids(
&["0:0, 0:1, 0:2"],
&[
("0:0, 0:1", &[0]),
("0:1, 0:2", &[1]),
("0:1, 0:0", &[]),
("0:2, 0:1", &[]),
],
graph_options,
);
}
#[test]
fn test_created_siblings_dont_have_input_edge_ids() {
let mut graph_options = GraphOptions::default();
graph_options.sibling_pairs = graph::SiblingPairs::Create;
test_input_edge_ids(
&["0:0, 0:1, 0:2"],
&[
("0:0, 0:1", &[0]),
("0:1, 0:2", &[1]),
("0:1, 0:0", &[]),
("0:2, 0:1", &[]),
],
graph_options,
);
}
#[test]
fn test_edge_merging_directed() {
let mut graph_options = GraphOptions::default();
graph_options.duplicate_edges = graph::DuplicateEdges::Merge;
test_input_edge_ids(
&["0:0, 0:1", "0:0, 0:1"],
&[("0:0, 0:1", &[0, 1])],
graph_options,
);
}
#[test]
fn test_edge_merging_undirected() {
let mut graph_options = GraphOptions::default();
graph_options.duplicate_edges = graph::DuplicateEdges::Merge;
graph_options.sibling_pairs = graph::SiblingPairs::Keep;
test_input_edge_ids(
&["0:0, 0:1, 0:2", "0:0, 0:1", "0:2, 0:1"],
&[
("0:0, 0:1", &[0, 2]),
("0:1, 0:2", &[1]),
("0:2, 0:1", &[3]),
],
graph_options,
);
}
#[test]
fn test_high_precision_predicates() {
use polyline_layer::S2PolylineLayer;
let vertices = vec![
Point::from_coords(
-0.1053119128423491,
-0.80522217121852213,
0.58354661852470235,
),
Point::from_coords(
-0.10531192039134209,
-0.80522217309706012,
0.58354661457019508,
),
Point::from_coords(
-0.10531192039116592,
-0.80522217309701472,
0.58354661457028933,
),
];
let input = Polyline::new(vertices);
let snap_radius = edge_crossings::intersection_merge_radius();
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
idempotent: false,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output))));
builder.force_vertex(Point::from_coords(
-0.10531192039134191,
-0.80522217309705857,
0.58354661457019719,
));
builder.add_polyline(&input);
builder.build().expect("build failed");
}
#[test]
fn test_high_precision_stress_test() {
let snap_radius = edge_crossings::intersection_merge_radius();
let num_iters = 2000; let mut non_degenerate = 0;
for iter in 0..num_iters {
let v1 = choose_point(iter);
let v0_dir = choose_point(iter * 37 + 1);
let d0_raw = (iter as f64 * 0.7654321 + 0.5) % 1.0;
let d0 = 10f64.powf(-16.0 + 16.0 * d0_raw); let v0 =
edge_distances::interpolate_at_distance(s1::Angle::from_radians(d0), v1, v0_dir);
let d2 = 0.5 * d0 * 10f64.powf(-16.0 * ((iter as f64 * 0.4321).fract()).powi(2));
let v2_base =
edge_distances::interpolate_at_distance(s1::Angle::from_radians(d2), v1, v0_dir);
let v2 = perturb_point(v2_base, 2.0 * snap_radius.radians(), iter * 3 + 7);
let (v0, v2) = if iter % 2 == 0 { (v0, v2) } else { (v2, v0) };
let d3 = if iter % 3 == 0 {
snap_radius.radians() * 0.75
} else {
snap_radius.radians()
};
let v3_base = if iter % 5 == 0 {
v1
} else if iter % 5 == 1 {
v2
} else {
edge_distances::interpolate((iter as f64 * 0.3456 + 0.1).fract(), v1, v2)
};
let v3 = perturb_point(v3_base, d3, iter * 11 + 3);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
idempotent: false,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.force_vertex(v3);
builder.add_edge(v0, v1);
builder.add_edge(v1, v2);
builder.add_edge(v2, v0);
if builder.build().is_err() {
continue;
}
let result = output.borrow();
if !result.is_empty_polygon() {
assert_eq!(
result.num_loops(),
1,
"iter {iter}: expected 0 or 1 loops, got {}",
result.num_loops(),
);
if result.num_loops() == 1 {
assert!(result.validate().is_ok(), "iter {iter}: invalid polygon");
non_degenerate += 1;
}
}
}
assert!(
non_degenerate >= num_iters / 10,
"only {non_degenerate}/{num_iters} non-degenerate",
);
}
#[test]
fn test_self_intersection_stress_test() {
let num_iters = 10; for iter in 0..num_iters {
let cap_center = choose_point(iter * 101);
let cap_radius = 10f64.powf(-14.0 + 12.0 * ((iter as f64 * 0.234).fract()));
let cap_radius_angle = s1::Angle::from_radians(cap_radius);
let mut opts = Options {
split_crossing_edges: true,
..Options::default()
};
if iter % 2 == 0 {
let min_exp = IntLatLngSnapFunction::exponent_for_max_snap_radius(cap_radius_angle);
let exponent = min_exp.min(10);
opts.snap_function = Box::new(IntLatLngSnapFunction::new(exponent));
}
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::with_options_legacy(
Rc::clone(&output),
polygon_layer::Options {
edge_type: graph::EdgeType::Undirected,
..Default::default()
},
)));
let num_vertices = 50;
let mut vertices = Vec::with_capacity(num_vertices);
for j in 0..num_vertices {
vertices.push(perturb_point(cap_center, cap_radius, iter * 1000 + j));
}
vertices.push(vertices[0]); let input = Polyline::new(vertices);
builder.add_polyline(&input);
builder.build().unwrap_or_else(|e| {
panic!("iter {iter}: build failed: {e}");
});
let result = output.borrow();
assert!(
result.validate().is_ok(),
"iter {iter}: invalid output polygon",
);
}
}
fn test_polyline_layers(
input_strs: &[&str],
expected_strs: &[&str],
edge_type: graph::EdgeType,
builder_opts: Options,
) {
assert_eq!(input_strs.len(), expected_strs.len());
let mut builder = S2Builder::new(builder_opts);
let mut outputs: Vec<Rc<RefCell<Polyline>>> = Vec::new();
for input_str in input_strs {
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
outputs.push(Rc::clone(&output));
let opts = polyline_layer::Options {
edge_type,
..polyline_layer::Options::default()
};
builder.start_layer(Box::new(
polyline_layer::S2PolylineLayer::with_options_legacy(output, opts),
));
builder.add_polyline(&text_format::make_polyline(input_str));
}
builder
.build()
.unwrap_or_else(|e| panic!("build failed: {e}"));
for (i, (output, expected_str)) in outputs.iter().zip(expected_strs).enumerate() {
let expected = text_format::make_polyline(expected_str);
assert_eq!(
text_format::polyline_to_string(&expected),
text_format::polyline_to_string(&output.borrow()),
"polyline {i}: edge_type={edge_type:?}"
);
}
}
fn test_polyline_layers_both_edge_types(
input_strs: &[&str],
expected_strs: &[&str],
make_opts: impl Fn() -> Options,
) {
test_polyline_layers(
input_strs,
expected_strs,
graph::EdgeType::Directed,
make_opts(),
);
test_polyline_layers(
input_strs,
expected_strs,
graph::EdgeType::Undirected,
make_opts(),
);
}
fn test_polyline_vector_with_builder_options(
input_strs: &[&str],
expected_strs: &[&str],
layer_opts: polyline_vector_layer::Options,
builder_opts: Options,
) {
let mut builder = S2Builder::new(builder_opts);
let output = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::with_options_legacy(
Rc::clone(&output),
layer_opts,
)));
for input_str in input_strs {
builder.add_polyline(&text_format::make_polyline(input_str));
}
builder
.build()
.unwrap_or_else(|e| panic!("build failed: {e}"));
let result = output.borrow();
let mut output_strs: Vec<String> =
result.iter().map(text_format::polyline_to_string).collect();
output_strs.sort();
let mut expected_sorted: Vec<String> = expected_strs
.iter()
.map(|s| text_format::polyline_to_string(&text_format::make_polyline(s)))
.collect();
expected_sorted.sort();
assert_eq!(expected_sorted, output_strs);
}
fn test_input_edge_ids_with_builder_options(
input_strs: &[&str],
expected: &[(&str, &[i32])],
graph_options: GraphOptions,
builder_options: Options,
) {
let expected_vec: Vec<(String, Vec<i32>)> = expected
.iter()
.map(|(s, ids)| (s.to_string(), ids.to_vec()))
.collect();
let mut builder = S2Builder::new(builder_options);
builder.start_layer(Box::new(InputEdgeIdCheckingLayer::new(
expected_vec,
graph_options,
)));
for input_str in input_strs {
builder.add_polyline(&text_format::make_polyline(input_str));
}
builder.build().expect("build failed");
}
#[test]
fn test_simplify_one_edge() {
test_polyline_layers_both_edge_types(
&["0:0, 1:0.5, 2:-0.5, 3:0.5, 4:-0.5, 5:0"],
&["0:0, 5:0"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_nearly_antipodal() {
test_polyline_layers_both_edge_types(
&["0:180, 0:1e-09, 32:32"],
&["0:180, 0:1e-09, 32:32"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_two_layers() {
test_polyline_layers_both_edge_types(
&["-2:-1, -1:0, 1:0, 2:1", "1:-2, 0:-1, 0:1, -1:2"],
&["-2:-1, 0:0, 2:1", "1:-2, 0:0, -1:2"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(0.5))),
split_crossing_edges: true,
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_one_loop() {
for edge_type in [graph::EdgeType::Directed, graph::EdgeType::Undirected] {
let snap_radius = s1::Angle::from_degrees(1.0);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
simplify_edge_chains: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::with_options_legacy(
Rc::clone(&output),
polygon_layer::Options {
edge_type,
..Default::default()
},
)));
let input_loop = make_regular_loop(
Point::from_coords(1.0, 0.0, 0.0),
s1::Angle::from_degrees(20.0),
1000,
);
let input = Polygon::from_loops(vec![input_loop]);
builder.add_polygon(&input);
builder.build().unwrap();
let result = output.borrow();
assert_eq!(result.num_loops(), 1, "edge_type={edge_type:?}");
let nv = result.loop_at(0).num_vertices();
assert!(
nv >= 10,
"edge_type={edge_type:?}: expected >=10 vertices, got {nv}"
);
assert!(
nv <= 12,
"edge_type={edge_type:?}: expected <=12 vertices, got {nv}"
);
assert!(
result.boundary_near(&input, snap_radius),
"edge_type={edge_type:?}: output not boundary_near input"
);
}
}
#[test]
fn test_simplify_opposite_directions() {
test_polyline_layers_both_edge_types(
&[
"-4:0.83, -3:0.46, -2:0.2, -1:0.05, 0:0, 1:0.5, 2:0.2, 3:0.46, 4:0.83",
"4:0.83, 3:0.46, 2:0.2, 1:0.05, 0:0, -1:0.5, -2:0.2, -3:0.46, -4:0.83",
],
&["-4:0.83, -2:0.2, 4:0.83", "4:0.83, -2:0.2, -4:0.83"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(0.5))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_keeps_edge_vertex_separation() {
test_polyline_layers_both_edge_types(
&["0:-10, 0.99:0, 0:10", "-5:-5, -0.2:0, -5:5"],
&["0:-10, 0.99:0, 0:10", "-5:-5, -0.2:0, -5:5"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_backtracking_edge_chain() {
test_polyline_layers_both_edge_types(
&["0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 4:0, 3:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0"],
&["0:0, 2:0, 5:0, 2:0, 5:0, 7:0"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(0.5))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_avoids_backtracking_vertices() {
test_polyline_layers_both_edge_types(
&["0:0, 1:0.1, 1:2", "0:1.05, -10:1.05"],
&["0:0, 1:0.1, 1:2", "0:1.05, -10:1.05"],
|| Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(1.0))),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_limits_edge_deviation() {
test_polyline_layers_both_edge_types(
&["-30.49:-29.51, 29.51:30.49"],
&["-30:-30, -1:1, 30:30"],
|| Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_preserves_topology() {
let num_loops = 20;
let num_vertices_per_loop = 1000;
let base_radius = s1::Angle::from_degrees(5.0);
let snap_radius = s1::Angle::from_degrees(0.1);
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(snap_radius)),
simplify_edge_chains: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let center = Point::from_coords(1.0, 0.0, 0.0);
let mut inputs = Vec::new();
let mut outputs: Vec<Rc<RefCell<Polygon>>> = Vec::new();
for j in 0..num_loops {
let radius_rad = base_radius.radians()
+ 0.7 * (j * j) as f64 / num_loops as f64 * snap_radius.radians();
let radius = s1::Angle::from_radians(radius_rad);
let input_loop = make_regular_loop(center, radius, num_vertices_per_loop);
let input = Polygon::from_loops(vec![input_loop]);
let output = Rc::new(RefCell::new(Polygon::empty()));
outputs.push(Rc::clone(&output));
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(output)));
builder.add_polygon(&input);
inputs.push(input);
}
builder.build().unwrap();
for j in 0..num_loops {
assert!(
outputs[j].borrow().boundary_near(&inputs[j], snap_radius),
"loop {j} not boundary_near input"
);
if j > 0 {
assert!(
outputs[j]
.borrow()
.contains_polygon(&outputs[j - 1].borrow()),
"loop {j} does not contain loop {}",
j - 1
);
}
}
}
#[test]
fn test_simplify_removes_sibling_pairs() {
test_polyline_vector_with_builder_options(
&["0:0, 0:10", "0:10, 0.6:5, 0:0"],
&["0:0, 0:10, 1:5, 0:0"],
polyline_vector_layer::Options {
sibling_pairs: graph::SiblingPairs::Discard,
..Default::default()
},
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
..Options::default()
},
);
test_polyline_vector_with_builder_options(
&["0:0, 0:10", "0:10, 0.6:5, 0:0"],
&[],
polyline_vector_layer::Options {
sibling_pairs: graph::SiblingPairs::Discard,
..Default::default()
},
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_merges_duplicate_edges() {
test_polyline_vector_with_builder_options(
&["0:0, 0:10", "0:0, 0.6:5, 0:10"],
&["0:0, 0:10", "0:0, 1:5, 0:10"],
polyline_vector_layer::Options {
duplicate_edges: graph::DuplicateEdges::Merge,
..Default::default()
},
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
..Options::default()
},
);
test_polyline_vector_with_builder_options(
&["0:0, 0:10", "0:0, 0.6:5, 0:10"],
&["0:0, 0:10"],
polyline_vector_layer::Options {
duplicate_edges: graph::DuplicateEdges::Merge,
..Default::default()
},
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_keeps_forced_vertices() {
let opts = Options {
snap_function: Box::new(IdentitySnapFunction::new(s1::Angle::from_radians(1e-15))),
simplify_edge_chains: true,
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Polyline::new(vec![])));
builder.start_layer(Box::new(polyline_layer::S2PolylineLayer::new_legacy(
Rc::clone(&output),
)));
builder.add_polyline(&text_format::make_polyline("0:0, 0:1, 0:2, 0:3"));
builder.force_vertex(text_format::parse_point("0:1"));
builder.build().unwrap();
assert_eq!(
text_format::polyline_to_string(&output.borrow()),
text_format::polyline_to_string(&text_format::make_polyline("0:0, 0:1, 0:3")),
);
}
#[test]
fn test_simplify_degenerate_edge_merging_easy() {
let mut graph_options = GraphOptions::default();
graph_options.degenerate_edges = graph::DegenerateEdges::Keep;
test_input_edge_ids_with_builder_options(
&["0:0, 0:0.1, 0:1.1, 0:1, 0:0.9, 0:2, 0:2.1"],
&[
("0:0, 0:0", &[0]),
("0:0, 0:2", &[1, 2, 3, 4]),
("0:2, 0:2", &[5]),
],
graph_options,
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_degenerate_edge_merging_hard() {
let graph_options = GraphOptions::default(); let builder_opts = Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
};
let input: &[&str] = &[
"0:1, 0:1.1", "0:0, 0:1, 0:2", "0:0, 0:0.9, 0:1, 0:1.1, 0:2", "0:2, 0:1, 0:0.9, 0:0", "0:2, 0:1, 0:0", "0:1.1, 0:1", "0:1, 0:1.1", ];
let expected: &[(&str, &[i32])] = &[
("0:0, 0:2", &[0, 1, 2]),
("0:0, 0:2", &[3, 4, 5, 6]),
("0:2, 0:0", &[7, 8, 9]),
("0:2, 0:0", &[10, 11, 12, 13]),
];
test_input_edge_ids_with_builder_options(
input,
expected,
graph_options.clone(),
builder_opts,
);
let mut undirected_expected: Vec<(&str, &[i32])> = expected.to_vec();
undirected_expected.push(("0:0, 0:2", &[]));
undirected_expected.push(("0:0, 0:2", &[]));
undirected_expected.push(("0:2, 0:0", &[]));
undirected_expected.push(("0:2, 0:0", &[]));
let mut ug = graph_options;
ug.edge_type = graph::EdgeType::Undirected;
test_input_edge_ids_with_builder_options(
input,
&undirected_expected,
ug,
Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
},
);
}
#[test]
fn test_simplify_degenerate_edge_merging_multiple_layers() {
let graph_options = GraphOptions::default();
let builder_opts = Options {
snap_function: Box::new(IntLatLngSnapFunction::new(0)),
simplify_edge_chains: true,
..Options::default()
};
let input: Vec<Vec<&str>> = vec![
vec![
"0.1:5, 0:5.2",
"0.1:0, 0:9.9",
"0:10.1, 0:0.1",
"0:3.1, 0:2.9",
],
vec![
"0.1:3, 0:3.2",
"-0.1:0, 0:4.1, 0:9.9",
"0.1:9.9, 0:7, 0.1:6.9, 0.1:0.2",
],
vec![
"0.2:0.3, 0.1:6, 0:5.9, 0.1:10.2",
"0.1:0.1, 0:9.8",
"0.1:2, 0:2.1",
],
];
let expected: Vec<Vec<(&str, &[i32])>> = vec![
vec![("0:0, 0:10", &[0, 1]), ("0:10, 0:0", &[2, 3])],
vec![("0:0, 0:10", &[4, 5, 6]), ("0:10, 0:0", &[7, 8, 9])],
vec![("0:0, 0:10", &[10, 11, 12]), ("0:0, 0:10", &[13, 14])],
];
let mut builder = S2Builder::new(builder_opts);
for (i, layer_input) in input.iter().enumerate() {
let expected_vec: Vec<(String, Vec<i32>)> = expected[i]
.iter()
.map(|(s, ids)| (s.to_string(), ids.to_vec()))
.collect();
builder.start_layer(Box::new(InputEdgeIdCheckingLayer::new(
expected_vec,
graph_options.clone(),
)));
for input_str in layer_input {
builder.add_polyline(&text_format::make_polyline(input_str));
}
}
let result = builder.build();
assert!(result.is_ok(), "build failed: {:?}", result.err());
}
#[test]
fn test_graph_persistence() {
let mut builder = S2Builder::new(Options::default());
let graph_options = GraphOptions::default();
let outputs: Vec<Rc<RefCell<Vec<(String, Vec<i32>)>>>> =
(0..20).map(|_| Rc::new(RefCell::new(Vec::new()))).collect();
for (i, output_rc) in outputs.iter().enumerate() {
builder.start_layer(Box::new(GraphCapturingLayer::new(
Rc::clone(output_rc),
graph_options.clone(),
)));
let p0 = choose_point(i * 3);
let p1 = choose_point(i * 3 + 1);
let p2 = choose_point(i * 3 + 2);
builder.add_edge(p0, p1);
builder.add_edge(p1, p2);
}
let result = builder.build();
assert!(result.is_ok(), "build failed: {:?}", result.err());
for (i, output) in outputs.iter().enumerate() {
let edges = output.borrow();
assert!(!edges.is_empty(), "layer {i} captured no edges");
}
}
#[test]
fn test_fractal_stress_test() {
use crate::s2::fractal::S2Fractal;
for iter in 0..50 {
let center = choose_point(iter * 7);
let radius = s1::Angle::from_degrees(0.1 + (iter as f64 * 0.1234).sin().abs() * 10.0);
let mut fractal = S2Fractal::new(iter as u64 + 42);
fractal.set_max_level(3);
fractal.set_fractal_dimension(1.0 + (iter as f64 * 0.3456).sin().abs() * 0.5);
let loop_shape = fractal.make_loop_at(center, radius);
let snap: Box<dyn SnapFunction> = match iter % 3 {
0 => Box::new(IntLatLngSnapFunction::new((iter % 10) as i32)),
1 => Box::new(S2CellIdSnapFunction::new(
crate::s2::coords::MAX_CELL_LEVEL.min((10 + iter % 15) as u8),
)),
_ => Box::new(IdentitySnapFunction::new(s1::Angle::from_degrees(
0.001 + (iter as f64 * 0.789).sin().abs() * 5.0,
))),
};
let mut builder = S2Builder::new(Options {
snap_function: snap,
..Options::default()
});
let output = Rc::new(RefCell::new(Polygon::empty()));
builder.start_layer(Box::new(S2PolygonLayer::with_options_legacy(
Rc::clone(&output),
polygon_layer::Options {
validate: true,
..polygon_layer::Options::default()
},
)));
builder.add_loop(&loop_shape);
let result = builder.build();
assert!(
result.is_ok(),
"fractal stress test iter {iter} failed: {:?}",
result.err()
);
}
}
#[derive(Debug)]
struct GraphCapturingLayer {
output: Rc<RefCell<Vec<(String, Vec<i32>)>>>,
graph_options: GraphOptions,
}
impl GraphCapturingLayer {
fn new(output: Rc<RefCell<Vec<(String, Vec<i32>)>>>, graph_options: GraphOptions) -> Self {
GraphCapturingLayer {
output,
graph_options,
}
}
}
impl Layer for GraphCapturingLayer {
fn graph_options(&self) -> GraphOptions {
self.graph_options.clone()
}
fn build(&mut self, g: &Graph, _error: &mut S2Error) {
let mut edges = Vec::new();
for e in (0..g.num_edges().0).map(EdgeId) {
let (v0, v1) = g.edge(e);
let edge_str = text_format::points_to_string(&[g.vertex(v0), g.vertex(v1)]);
let ids: Vec<i32> = g.input_edge_ids(e).clone();
edges.push((edge_str, ids));
}
*self.output.borrow_mut() = edges;
}
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
fn make_regular_loop(center: Point, radius: s1::Angle, num_vertices: usize) -> Loop {
use crate::s2::point::{from_frame, get_frame};
let frame = get_frame(center);
let (r_sin, r_cos) = radius.radians().sin_cos();
let radian_step = 2.0 * std::f64::consts::PI / num_vertices as f64;
let mut vertices = Vec::with_capacity(num_vertices);
for i in 0..num_vertices {
let angle = i as f64 * radian_step;
let (a_sin, a_cos) = angle.sin_cos();
let p = Point(crate::r3::Vector::new(r_sin * a_cos, r_sin * a_sin, r_cos));
vertices.push(from_frame(&frame, p).normalize());
}
Loop::new(vertices)
}
fn choose_point(seed: usize) -> Point {
let s = seed as f64;
let x = (s * 1.23456789 + 0.1).sin();
let y = (s * 2.34567891 + 0.2).sin();
let z = (s * 3.45678912 + 0.3).sin();
Point::from_coords(x, y, z).normalize()
}
fn perturb_point(p: Point, max_dist: f64, seed: usize) -> Point {
let s = seed as f64;
let dx = max_dist * (s * 1.111 + 0.1).sin();
let dy = max_dist * (s * 2.222 + 0.2).sin();
let dz = max_dist * (s * 3.333 + 0.3).sin();
Point::from_coords(p.x() + dx, p.y() + dy, p.z() + dz).normalize()
}
#[derive(Debug)]
struct InputEdgeIdCheckingLayer {
expected: Vec<(String, Vec<i32>)>,
graph_options: GraphOptions,
}
impl InputEdgeIdCheckingLayer {
fn new(expected: Vec<(String, Vec<i32>)>, graph_options: GraphOptions) -> Self {
InputEdgeIdCheckingLayer {
expected,
graph_options,
}
}
}
impl Layer for InputEdgeIdCheckingLayer {
fn graph_options(&self) -> GraphOptions {
self.graph_options.clone()
}
fn build(&mut self, g: &Graph, error: &mut S2Error) {
let mut actual = Vec::new();
for e in (0..g.num_edges().0).map(EdgeId) {
let (v0, v1) = g.edge(e);
let edge_str = text_format::points_to_string(&[g.vertex(v0), g.vertex(v1)]);
let ids: Vec<i32> = g.input_edge_ids(e).clone();
actual.push((edge_str, ids));
}
let mut missing = Vec::new();
let mut extra = Vec::new();
for p in &self.expected {
if !actual.contains(p) {
missing.push(p.clone());
}
}
for p in &actual {
if !self.expected.contains(p) {
extra.push(p.clone());
}
}
if !missing.is_empty() || !extra.is_empty() {
*error = S2Error::new(
S2ErrorCode::InvalidArgument,
format!("Missing: {missing:?}\nExtra: {extra:?}"),
);
}
}
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
fn test_input_edge_ids(
input_strs: &[&str],
expected: &[(&str, &[i32])],
graph_options: GraphOptions,
) {
let expected_vec: Vec<(String, Vec<i32>)> = expected
.iter()
.map(|(s, ids)| (s.to_string(), ids.to_vec()))
.collect();
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(InputEdgeIdCheckingLayer::new(
expected_vec,
graph_options,
)));
for input_str in input_strs {
builder.add_polyline(&text_format::make_polyline(input_str));
}
builder.build().expect("build failed");
}
#[test]
fn test_memory_tracker_basic() {
use crate::s2::memory_tracker::S2MemoryTracker;
use std::sync::{Arc, Mutex};
let tracker = Arc::new(Mutex::new(S2MemoryTracker::new()));
let opts = Options {
memory_tracker: Some(Arc::clone(&tracker)),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::<crate::s2::polyline::Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("0:0, 1:1, 2:2"));
builder.build().expect("build failed");
let t = tracker.lock().expect("lock");
assert!(t.max_usage_bytes() > 0, "expected memory tracking");
assert!(t.ok(), "expected tracker to be OK");
}
#[test]
fn test_memory_tracker_limit_exceeded() {
use crate::s2::memory_tracker::S2MemoryTracker;
use std::sync::{Arc, Mutex};
let mut tracker = S2MemoryTracker::new();
tracker.set_limit(1); let tracker = Arc::new(Mutex::new(tracker));
let opts = Options {
memory_tracker: Some(Arc::clone(&tracker)),
..Options::default()
};
let mut builder = S2Builder::new(opts);
let output = Rc::new(RefCell::new(Vec::<crate::s2::polyline::Polyline>::new()));
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("0:0, 1:1, 2:2"));
let result = builder.build();
assert!(result.is_err(), "expected build to fail with memory limit");
let err = result.unwrap_err();
assert_eq!(err.code, S2ErrorCode::ResourceExhausted);
}
#[test]
fn test_pipeline_polygon_cellid_snap_topology() {
use crate::s2::builder::polygon_layer::S2PolygonLayer;
use crate::s2::polygon::Polygon;
use crate::s2::text_format;
let input = text_format::make_polygon("0:0, 0:10, 10:10, 10:0; 2:2, 2:8, 8:8, 8:2");
let level = S2CellIdSnapFunction::level_for_max_snap_radius(s1::Angle::from_degrees(0.01));
let opts = Options::new(Box::new(S2CellIdSnapFunction::new(level)));
let output = Rc::new(RefCell::new(Polygon::empty()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_polygon(&input);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 2, "expected 2 loops (shell + hole)");
}
#[test]
fn test_pipeline_lax_polygon_intlatlng_snap() {
use crate::s2::builder::lax_polygon_layer::LaxPolygonLayer;
use crate::s2::lax_polygon::LaxPolygon;
use crate::s2::text_format;
let opts = Options::new(Box::new(IntLatLngSnapFunction::new(5))); let output = Rc::new(RefCell::new(LaxPolygon::default()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(LaxPolygonLayer::new_legacy(Rc::clone(&output))));
builder.add_shape(&text_format::make_lax_polygon(
"1.000005:2.000005, 3.000005:4.000005, 5.000005:6.000005",
));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
for i in 0..result.num_loop_vertices(0) {
let v = result.loop_vertex(0, i);
let ll = LatLng::from_point(v);
let lat_e5 = (ll.lat.degrees() * 1e5).round();
let lng_e5 = (ll.lng.degrees() * 1e5).round();
assert!(
(ll.lat.degrees() * 1e5 - lat_e5).abs() < 1e-4,
"vertex {i} lat not at E5 grid: {}",
ll.lat.degrees()
);
assert!(
(ll.lng.degrees() * 1e5 - lng_e5).abs() < 1e-4,
"vertex {i} lng not at E5 grid: {}",
ll.lng.degrees()
);
}
}
#[test]
fn test_pipeline_polyline_vector_multiple() {
use crate::s2::builder::polyline_vector_layer::S2PolylineVectorLayer;
use crate::s2::polyline::Polyline;
use crate::s2::text_format;
let output = Rc::new(RefCell::new(Vec::<Polyline>::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolylineVectorLayer::new_legacy(Rc::clone(
&output,
))));
builder.add_polyline(&text_format::make_polyline("0:0, 1:1, 2:0"));
builder.add_polyline(&text_format::make_polyline("10:10, 11:11"));
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.len(), 2, "expected 2 polylines");
let total_vertices: usize = result.iter().map(Polyline::num_vertices).sum();
assert_eq!(total_vertices, 5, "expected 5 total vertices");
}
#[test]
fn test_pipeline_multi_layer_build() {
use crate::s2::builder::lax_polygon_layer::LaxPolygonLayer;
use crate::s2::builder::point_vector_layer::S2PointVectorLayer;
use crate::s2::lax_polygon::LaxPolygon;
use crate::s2::text_format;
let mut builder = S2Builder::new(Options::default());
let points_out = Rc::new(RefCell::new(Vec::new()));
builder.start_layer(Box::new(S2PointVectorLayer::new_legacy(Rc::clone(
&points_out,
))));
builder.add_point(text_format::parse_point("0:0"));
builder.add_point(text_format::parse_point("1:1"));
let poly_out = Rc::new(RefCell::new(LaxPolygon::default()));
builder.start_layer(Box::new(LaxPolygonLayer::new_legacy(Rc::clone(&poly_out))));
builder.add_shape(&text_format::make_lax_polygon("10:10, 10:20, 20:20, 20:10"));
builder.build().expect("build failed");
assert_eq!(points_out.borrow().len(), 2);
assert_eq!(poly_out.borrow().num_loops(), 1);
}
#[test]
fn test_pipeline_force_vertex() {
use crate::s2::builder::polyline_layer::S2PolylineLayer;
use crate::s2::polyline::Polyline;
use crate::s2::text_format;
let snap = IdentitySnapFunction::new(s1::Angle::from_degrees(1.0));
let opts = Options::new(Box::new(snap));
let output = Rc::new(RefCell::new(Polyline::default()));
let mut builder = S2Builder::new(opts);
builder.start_layer(Box::new(S2PolylineLayer::new_legacy(Rc::clone(&output))));
builder.add_polyline(&text_format::make_polyline("0:0, 10:10"));
builder.force_vertex(text_format::parse_point("5:5"));
builder.build().expect("build failed");
let result = output.borrow();
assert!(
result.num_vertices() >= 3,
"expected at least 3 vertices with forced vertex, got {}",
result.num_vertices()
);
}
#[test]
fn test_pipeline_label_tracking_through_layers() {
use crate::s2::builder::polygon_layer::S2PolygonLayer;
use crate::s2::polygon::Polygon;
use crate::s2::text_format;
let output = Rc::new(RefCell::new(Polygon::empty()));
let label_set_ids = Rc::new(RefCell::new(Vec::new()));
let label_set_lexicon = Rc::new(RefCell::new(IdSetLexicon::new()));
let mut builder = S2Builder::new(Options::default());
builder.start_layer(Box::new(S2PolygonLayer::with_labels_legacy(
Rc::clone(&output),
Rc::clone(&label_set_ids),
Rc::clone(&label_set_lexicon),
polygon_layer::Options::default(),
)));
builder.set_label(42);
let poly = text_format::make_polygon("0:0, 0:1, 1:0");
builder.add_polygon(&poly);
builder.build().expect("build failed");
let result = output.borrow();
assert_eq!(result.num_loops(), 1);
let lsi = label_set_ids.borrow();
assert!(!lsi.is_empty(), "expected label_set_ids to be populated");
let lex = label_set_lexicon.borrow();
let has_42 = lsi.iter().any(|loop_ids| {
loop_ids.iter().any(|&id| {
let labels = lex.id_set(id);
labels.contains(&42)
})
});
assert!(has_42, "expected at least one edge to have label 42");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_s2error_roundtrip() {
let err = S2Error::new(S2ErrorCode::InvalidArgument, "bad input".to_string());
let json = serde_json::to_string(&err).unwrap();
let back: S2Error = serde_json::from_str(&json).unwrap();
assert_eq!(err.code, back.code);
assert_eq!(err.message, back.message);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_s2error_code_roundtrip() {
for code in [
S2ErrorCode::Ok,
S2ErrorCode::Unknown,
S2ErrorCode::InvalidArgument,
S2ErrorCode::LoopSelfIntersection,
S2ErrorCode::BuilderSnapRadiusTooSmall,
] {
let json = serde_json::to_string(&code).unwrap();
let back: S2ErrorCode = serde_json::from_str(&json).unwrap();
assert_eq!(code, back);
}
}
}