#![expect(clippy::cast_sign_loss, reason = "EdgeId (i32) used as Vec indices")]
#![expect(
clippy::cast_possible_truncation,
reason = "EdgeId (i32) -> usize for Vec indexing"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "usize -> i32 for EdgeId — always in range"
)]
use crate::s1::ChordAngle;
use crate::s2::Point;
use crate::s2::edge_distances;
use crate::s2::shape::{Dimension, Edge};
use crate::s2::shape_index::ShapeIndex;
use crate::s2::shape_util;
#[derive(Clone, Debug, PartialEq)]
pub struct EdgeQueryOptions {
pub max_results: usize,
pub distance_limit: ChordAngle,
pub max_error: ChordAngle,
pub include_interiors: bool,
pub use_brute_force: bool,
}
impl Default for EdgeQueryOptions {
fn default() -> Self {
EdgeQueryOptions {
max_results: usize::MAX,
distance_limit: ChordAngle::INFINITY,
max_error: ChordAngle::ZERO,
include_interiors: true,
use_brute_force: true,
}
}
}
impl EdgeQueryOptions {
pub fn max_results(mut self, n: usize) -> Self {
self.max_results = n;
self
}
pub fn distance_limit(mut self, limit: ChordAngle) -> Self {
self.distance_limit = limit;
self
}
pub fn max_error(mut self, err: ChordAngle) -> Self {
self.max_error = err;
self
}
pub fn include_interiors(mut self, include: bool) -> Self {
self.include_interiors = include;
self
}
pub fn use_brute_force(mut self, brute_force: bool) -> Self {
self.use_brute_force = brute_force;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EdgeQueryResult {
pub distance: ChordAngle,
pub shape_id: i32,
pub edge_id: i32,
}
impl EdgeQueryResult {
pub fn is_interior(&self) -> bool {
self.shape_id >= 0 && self.edge_id < 0
}
pub fn is_empty(&self) -> bool {
self.shape_id < 0
}
}
impl Default for EdgeQueryResult {
fn default() -> Self {
EdgeQueryResult {
distance: ChordAngle::INFINITY,
shape_id: -1,
edge_id: -1,
}
}
}
pub trait DistanceTarget {
fn update_min_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle>;
fn update_min_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle>;
fn update_max_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle>;
fn update_max_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle>;
fn interior_point(&self) -> Option<Point> {
None
}
}
#[derive(Debug)]
pub struct PointTarget {
point: Point,
}
impl PointTarget {
pub fn new(point: Point) -> Self {
PointTarget { point }
}
}
impl DistanceTarget for PointTarget {
fn update_min_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle> {
let d = self.point.chord_angle(p);
if d < dist { Some(d) } else { None }
}
fn update_min_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) = edge_distances::update_min_distance(self.point, e.v0, e.v1, dist);
if ok { Some(d) } else { None }
}
fn update_max_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle> {
let d = self.point.chord_angle(p);
if d > dist { Some(d) } else { None }
}
fn update_max_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) = edge_distances::update_max_distance(self.point, e.v0, e.v1, dist);
if ok { Some(d) } else { None }
}
fn interior_point(&self) -> Option<Point> {
Some(self.point)
}
}
#[derive(Debug)]
pub struct EdgeTarget {
a: Point,
b: Point,
}
impl EdgeTarget {
pub fn new(a: Point, b: Point) -> Self {
EdgeTarget { a, b }
}
}
impl DistanceTarget for EdgeTarget {
fn update_min_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) = edge_distances::update_min_distance(p, self.a, self.b, dist);
if ok { Some(d) } else { None }
}
fn update_min_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) =
edge_distances::update_edge_pair_min_distance(self.a, self.b, e.v0, e.v1, dist);
if ok { Some(d) } else { None }
}
fn update_max_distance_to_point(&self, p: Point, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) = edge_distances::update_max_distance(p, self.a, self.b, dist);
if ok { Some(d) } else { None }
}
fn update_max_distance_to_edge(&self, e: Edge, dist: ChordAngle) -> Option<ChordAngle> {
let (d, ok) =
edge_distances::update_edge_pair_max_distance(self.a, self.b, e.v0, e.v1, dist);
if ok { Some(d) } else { None }
}
fn interior_point(&self) -> Option<Point> {
Some(Point::from_coords(
f64::midpoint(self.a.0.x, self.b.0.x),
f64::midpoint(self.a.0.y, self.b.0.y),
f64::midpoint(self.a.0.z, self.b.0.z),
))
}
}
#[derive(Debug)]
pub struct ClosestEdgeQuery<'a> {
index: &'a ShapeIndex,
}
impl<'a> ClosestEdgeQuery<'a> {
pub fn new(index: &'a ShapeIndex) -> Self {
ClosestEdgeQuery { index }
}
pub fn index(&self) -> &ShapeIndex {
self.index
}
pub fn find_closest_to_point(&self, point: Point) -> EdgeQueryResult {
let target = PointTarget::new(point);
let opts = EdgeQueryOptions::default().max_results(1);
let results = self.find_edges(&target, &opts);
results.into_iter().next().unwrap_or_default()
}
pub fn find_closest_to_edge(&self, a: Point, b: Point) -> EdgeQueryResult {
let target = EdgeTarget::new(a, b);
let opts = EdgeQueryOptions::default().max_results(1);
let results = self.find_edges(&target, &opts);
results.into_iter().next().unwrap_or_default()
}
pub fn distance_to_point(&self, point: Point) -> ChordAngle {
self.find_closest_to_point(point).distance
}
pub fn is_distance_less(&self, point: Point, limit: ChordAngle) -> bool {
let target = PointTarget::new(point);
let opts = EdgeQueryOptions::default()
.max_results(1)
.distance_limit(limit)
.max_error(limit);
!self.find_edges(&target, &opts).is_empty()
}
pub fn find_edges(
&self,
target: &dyn DistanceTarget,
opts: &EdgeQueryOptions,
) -> Vec<EdgeQueryResult> {
debug_assert!(opts.max_results >= 1, "max_results must be >= 1");
let mut results = Vec::new();
let mut dist_limit = opts.distance_limit;
if opts.include_interiors
&& let Some(p) = target.interior_point()
{
for shape_id in 0..self.index.len() as i32 {
let Some(shape) = self.index.shape(shape_id) else {
continue;
};
if shape.dimension() == Dimension::Polygon
&& shape_util::contains_brute_force(shape, p)
{
results.push(EdgeQueryResult {
distance: ChordAngle::ZERO,
shape_id,
edge_id: -1,
});
if opts.max_results == 1 {
dist_limit = ChordAngle::ZERO;
}
}
}
}
for shape_id in 0..self.index.len() as i32 {
let Some(shape) = self.index.shape(shape_id) else {
continue;
};
for edge_id in 0..shape.num_edges() as i32 {
let edge = shape.edge(edge_id as usize);
if let Some(d) = target.update_min_distance_to_edge(edge, dist_limit) {
let result = EdgeQueryResult {
distance: d,
shape_id,
edge_id,
};
results.push(result);
if opts.max_results == 1 {
dist_limit = d;
}
}
}
}
results.sort_unstable_by(|a, b| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(std::cmp::Ordering::Equal)
});
if results.len() > opts.max_results {
results.truncate(opts.max_results);
}
if opts.max_results == 1 && results.len() > 1 {
results.truncate(1);
}
results
}
}
#[derive(Debug)]
pub struct FurthestEdgeQuery<'a> {
index: &'a ShapeIndex,
}
impl<'a> FurthestEdgeQuery<'a> {
pub fn new(index: &'a ShapeIndex) -> Self {
FurthestEdgeQuery { index }
}
pub fn find_furthest_from_point(&self, point: Point) -> EdgeQueryResult {
let target = PointTarget::new(point);
let mut best = EdgeQueryResult {
distance: ChordAngle::ZERO, ..Default::default()
};
for shape_id in 0..self.index.len() as i32 {
let Some(shape) = self.index.shape(shape_id) else {
continue;
};
for edge_id in 0..shape.num_edges() as i32 {
let edge = shape.edge(edge_id as usize);
if let Some(d) = target.update_max_distance_to_edge(edge, best.distance) {
best = EdgeQueryResult {
distance: d,
shape_id,
edge_id,
};
}
}
}
best
}
pub fn distance_to_point(&self, point: Point) -> ChordAngle {
self.find_furthest_from_point(point).distance
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::s2::LatLng;
use crate::s2::lax_loop::LaxLoop;
use crate::s2::lax_polyline::LaxPolyline;
fn p(lat: f64, lng: f64) -> Point {
LatLng::from_degrees(lat, lng).to_point()
}
#[test]
fn test_empty_index_closest() {
let index = ShapeIndex::new();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_point(p(0.0, 0.0));
assert!(result.is_empty());
}
#[test]
fn test_closest_to_point_single_edge() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_point(p(0.0, 5.0));
assert!(!result.is_empty());
assert_eq!(result.shape_id, 0);
assert_eq!(result.edge_id, 0);
assert!(result.distance.length2() < 0.001);
}
#[test]
fn test_closest_to_point_triangle() {
let shape = LaxLoop::new(vec![p(0.0, 0.0), p(0.0, 10.0), p(10.0, 0.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_point(p(0.01, 0.01));
assert!(!result.is_empty());
assert_eq!(result.shape_id, 0);
assert!(result.distance.length2() < 0.01);
}
#[test]
fn test_closest_to_point_far_away() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 1.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_point(p(80.0, 80.0));
assert!(!result.is_empty());
assert!(result.distance.length2() > 1.0);
}
#[test]
fn test_distance_to_point() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let dist = query.distance_to_point(p(1.0, 5.0));
assert!(dist.length2() > 0.0);
assert!(dist.length2() < 0.01); }
#[test]
fn test_is_distance_less() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
assert!(query.is_distance_less(p(0.0, 5.0), ChordAngle::from_length2(0.01)));
assert!(!query.is_distance_less(p(80.0, 80.0), ChordAngle::from_length2(0.01)));
}
#[test]
fn test_closest_to_edge() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_edge(p(1.0, 2.0), p(1.0, 8.0));
assert!(!result.is_empty());
assert!(result.distance.length2() < 0.01);
}
#[test]
fn test_multiple_shapes() {
let shape1 = LaxPolyline::new(vec![p(10.0, 10.0), p(10.0, 20.0)]);
let shape2 = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 1.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape1));
index.add(Box::new(shape2));
index.build();
let query = ClosestEdgeQuery::new(&index);
let result = query.find_closest_to_point(p(0.0, 0.5));
assert_eq!(result.shape_id, 1);
assert_eq!(result.edge_id, 0);
}
#[test]
fn test_furthest_from_point() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = FurthestEdgeQuery::new(&index);
let result = query.find_furthest_from_point(p(0.0, 0.0));
assert!(!result.is_empty());
assert!(result.distance.length2() > 0.0);
}
#[test]
fn test_find_edges_with_distance_limit() {
let shape = LaxPolyline::new(vec![p(0.0, 0.0), p(0.0, 10.0), p(0.0, 20.0), p(0.0, 30.0)]);
let mut index = ShapeIndex::new();
index.add(Box::new(shape));
index.build();
let query = ClosestEdgeQuery::new(&index);
let target = PointTarget::new(p(0.0, 5.0));
let opts = EdgeQueryOptions::default().distance_limit(ChordAngle::from_length2(0.05));
let results = query.find_edges(&target, &opts);
assert!(!results.is_empty());
}
}