use crate::s2::Point;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum Dimension {
Point = 0,
Polyline = 1,
Polygon = 2,
}
impl Dimension {
pub const fn as_usize(self) -> usize {
self as usize
}
}
impl std::fmt::Display for Dimension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Dimension::Point => write!(f, "0"),
Dimension::Polyline => write!(f, "1"),
Dimension::Polygon => write!(f, "2"),
}
}
}
impl From<Dimension> for u8 {
fn from(d: Dimension) -> u8 {
d as u8
}
}
impl From<Dimension> for i8 {
fn from(d: Dimension) -> i8 {
d as i8
}
}
impl From<Dimension> for i32 {
fn from(d: Dimension) -> i32 {
d as i32
}
}
impl From<Dimension> for usize {
fn from(d: Dimension) -> usize {
d as usize
}
}
impl TryFrom<u8> for Dimension {
type Error = &'static str;
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
0 => Ok(Dimension::Point),
1 => Ok(Dimension::Polyline),
2 => Ok(Dimension::Polygon),
_ => Err("dimension must be 0, 1, or 2"),
}
}
}
impl TryFrom<i8> for Dimension {
type Error = &'static str;
fn try_from(v: i8) -> Result<Self, Self::Error> {
match v {
0 => Ok(Dimension::Point),
1 => Ok(Dimension::Polyline),
2 => Ok(Dimension::Polygon),
_ => Err("dimension must be 0, 1, or 2"),
}
}
}
impl TryFrom<i32> for Dimension {
type Error = &'static str;
fn try_from(v: i32) -> Result<Self, Self::Error> {
match v {
0 => Ok(Dimension::Point),
1 => Ok(Dimension::Polyline),
2 => Ok(Dimension::Polygon),
_ => Err("dimension must be 0, 1, or 2"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Edge {
pub v0: Point,
pub v1: Point,
}
impl Edge {
pub fn new(v0: Point, v1: Point) -> Self {
Edge { v0, v1 }
}
pub fn reversed(self) -> Edge {
Edge {
v0: self.v1,
v1: self.v0,
}
}
pub fn is_degenerate(self) -> bool {
self.v0 == self.v1
}
#[expect(
clippy::should_implement_trait,
reason = "named constructor avoids ambiguity with std traits"
)]
pub fn cmp(&self, other: &Edge) -> std::cmp::Ordering {
let c = self.v0.cmp_point(other.v0);
if c != std::cmp::Ordering::Equal {
return c;
}
self.v1.cmp_point(other.v1)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShapeId(pub i32);
impl ShapeId {
pub const fn new(v: i32) -> Self {
ShapeId(v)
}
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, "ShapeId must be non-negative for indexing");
self.0 as usize
}
}
impl std::ops::Add<i32> for ShapeId {
type Output = ShapeId;
fn add(self, rhs: i32) -> ShapeId {
ShapeId(self.0 + rhs)
}
}
impl std::ops::Sub<i32> for ShapeId {
type Output = ShapeId;
fn sub(self, rhs: i32) -> ShapeId {
ShapeId(self.0 - rhs)
}
}
impl std::ops::AddAssign<i32> for ShapeId {
fn add_assign(&mut self, rhs: i32) {
self.0 += rhs;
}
}
impl PartialEq<i32> for ShapeId {
fn eq(&self, other: &i32) -> bool {
self.0 == *other
}
}
impl PartialOrd<i32> for ShapeId {
fn partial_cmp(&self, other: &i32) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl std::fmt::Display for ShapeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<i32> for ShapeId {
fn from(v: i32) -> Self {
ShapeId(v)
}
}
impl From<ShapeId> for i32 {
fn from(id: ShapeId) -> i32 {
id.0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShapeEdgeId {
pub shape_id: ShapeId,
pub edge_id: i32,
}
impl ShapeEdgeId {
pub fn new(shape_id: impl Into<ShapeId>, edge_id: i32) -> Self {
ShapeEdgeId {
shape_id: shape_id.into(),
edge_id,
}
}
}
impl std::fmt::Display for ShapeEdgeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.shape_id, self.edge_id)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShapeEdge {
pub id: ShapeEdgeId,
pub edge: Edge,
}
impl ShapeEdge {
pub fn new(id: ShapeEdgeId, edge: Edge) -> Self {
ShapeEdge { id, edge }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Chain {
pub start: usize,
pub length: usize,
}
impl Chain {
pub fn new(start: usize, length: usize) -> Self {
Chain { start, length }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ChainPosition {
pub chain_id: usize,
pub offset: usize,
}
impl ChainPosition {
pub fn new(chain_id: usize, offset: usize) -> Self {
ChainPosition { chain_id, offset }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ReferencePoint {
pub point: Point,
pub contained: bool,
}
impl ReferencePoint {
pub fn new(point: Point, contained: bool) -> Self {
ReferencePoint { point, contained }
}
}
impl Default for ReferencePoint {
fn default() -> Self {
ReferencePoint {
point: Point::origin(),
contained: false,
}
}
}
pub trait Shape: Send + Sync + std::fmt::Debug {
fn num_edges(&self) -> usize;
fn edge(&self, id: usize) -> Edge;
fn reference_point(&self) -> ReferencePoint;
fn num_chains(&self) -> usize;
fn chain(&self, chain_id: usize) -> Chain;
fn chain_edge(&self, chain_id: usize, offset: usize) -> Edge;
fn chain_position(&self, edge_id: usize) -> ChainPosition;
fn dimension(&self) -> Dimension;
fn is_empty(&self) -> bool {
self.num_edges() == 0 && (self.dimension() < Dimension::Polygon || self.num_chains() == 0)
}
fn is_full(&self) -> bool {
!self.is_empty()
&& self.dimension() == Dimension::Polygon
&& self.reference_point().contained
}
fn has_interior(&self) -> bool {
self.dimension() == Dimension::Polygon
}
fn type_tag(&self) -> u32 {
0
}
fn encode_tagged(
&self,
_w: &mut dyn std::io::Write,
_hint: super::encoded_s2point_vector::CodingHint,
) -> std::io::Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"this shape type does not support encoding",
))
}
}
pub fn reference_point_for_shape(shape: &dyn Shape) -> ReferencePoint {
if shape.num_edges() == 0 {
return ReferencePoint::new(Point::origin(), shape.num_chains() > 0);
}
let edge = shape.edge(0);
if let Some(rp) = reference_point_at_vertex(shape, edge.v0) {
return rp;
}
let n = shape.num_edges();
let mut edges: Vec<Edge> = (0..n).map(|i| shape.edge(i)).collect();
let mut rev_edges: Vec<Edge> = edges.iter().map(|e| e.reversed()).collect();
edges.sort_by(Edge::cmp);
rev_edges.sort_by(Edge::cmp);
for i in 0..n {
if edges[i].cmp(&rev_edges[i]) == std::cmp::Ordering::Less
&& let Some(rp) = reference_point_at_vertex(shape, edges[i].v0)
{
return rp;
}
if rev_edges[i].cmp(&edges[i]) == std::cmp::Ordering::Less
&& let Some(rp) = reference_point_at_vertex(shape, rev_edges[i].v0)
{
return rp;
}
}
for i in 0..shape.num_chains() {
if shape.chain(i).length == 0 {
return ReferencePoint::new(Point::origin(), true);
}
}
ReferencePoint::new(Point::origin(), false)
}
fn reference_point_at_vertex(shape: &dyn Shape, v_test: Point) -> Option<ReferencePoint> {
use crate::s2::contains_vertex_query::ContainsVertexQuery;
let mut query = ContainsVertexQuery::new(v_test);
let n = shape.num_edges();
for i in 0..n {
let edge = shape.edge(i);
if edge.v0 == v_test {
query.add_edge(edge.v1, 1);
}
if edge.v1 == v_test {
query.add_edge(edge.v0, -1);
}
}
let sign = query.contains_vertex();
if sign == 0 {
return None; }
Some(ReferencePoint::new(v_test, sign > 0))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::r3::Vector;
fn is_send_sync<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn edge_is_send_sync() {
is_send_sync::<Edge>();
}
#[test]
fn shape_edge_id_is_send_sync() {
is_send_sync::<ShapeEdgeId>();
}
#[test]
fn chain_is_send_sync() {
is_send_sync::<Chain>();
}
#[test]
fn chain_position_is_send_sync() {
is_send_sync::<ChainPosition>();
}
#[test]
fn reference_point_is_send_sync() {
is_send_sync::<ReferencePoint>();
}
#[test]
fn test_edge_reversed() {
let a = Point(Vector {
x: 1.0,
y: 0.0,
z: 0.0,
});
let b = Point(Vector {
x: 0.0,
y: 1.0,
z: 0.0,
});
let e = Edge::new(a, b);
let r = e.reversed();
assert_eq!(r.v0, b);
assert_eq!(r.v1, a);
}
#[test]
fn test_edge_is_degenerate() {
let a = Point(Vector {
x: 1.0,
y: 0.0,
z: 0.0,
});
let b = Point(Vector {
x: 0.0,
y: 1.0,
z: 0.0,
});
assert!(!Edge::new(a, b).is_degenerate());
assert!(Edge::new(a, a).is_degenerate());
}
#[test]
fn test_shape_edge_id_display() {
let id = ShapeEdgeId::new(3, 7);
assert_eq!(format!("{id}"), "3:7");
}
#[test]
fn test_shape_edge_id_ordering() {
let a = ShapeEdgeId::new(1, 5);
let b = ShapeEdgeId::new(1, 6);
let c = ShapeEdgeId::new(2, 0);
assert!(a < b);
assert!(b < c);
}
#[test]
fn test_chain_new() {
let c = Chain::new(10, 5);
assert_eq!(c.start, 10);
assert_eq!(c.length, 5);
}
#[test]
fn test_chain_position_new() {
let cp = ChainPosition::new(2, 3);
assert_eq!(cp.chain_id, 2);
assert_eq!(cp.offset, 3);
}
#[test]
fn test_reference_point_default() {
let rp = ReferencePoint::default();
assert!(!rp.contained);
}
#[derive(Debug)]
struct TestShape {
points: Vec<Point>,
}
impl Shape for TestShape {
fn num_edges(&self) -> usize {
self.points.len()
}
fn edge(&self, id: usize) -> Edge {
Edge::new(self.points[id], self.points[id])
}
fn reference_point(&self) -> ReferencePoint {
ReferencePoint::default()
}
fn num_chains(&self) -> usize {
self.points.len()
}
fn chain(&self, chain_id: usize) -> Chain {
Chain::new(chain_id, 1)
}
fn chain_edge(&self, chain_id: usize, _offset: usize) -> Edge {
self.edge(chain_id)
}
fn chain_position(&self, edge_id: usize) -> ChainPosition {
ChainPosition::new(edge_id, 0)
}
fn dimension(&self) -> Dimension {
Dimension::Point
}
}
#[test]
fn test_shape_trait() {
let shape = TestShape {
points: vec![
Point(Vector {
x: 1.0,
y: 0.0,
z: 0.0,
}),
Point(Vector {
x: 0.0,
y: 1.0,
z: 0.0,
}),
],
};
assert_eq!(shape.num_edges(), 2);
assert_eq!(shape.dimension(), Dimension::Point);
assert!(!shape.is_empty());
assert!(!shape.is_full());
assert!(!shape.has_interior());
}
#[test]
fn test_shape_empty() {
let shape = TestShape { points: vec![] };
assert!(shape.is_empty());
assert!(!shape.is_full());
}
#[test]
fn test_dyn_shape_is_object_safe() {
let shape: Box<dyn Shape> = Box::new(TestShape {
points: vec![Point(Vector {
x: 1.0,
y: 0.0,
z: 0.0,
})],
});
assert_eq!(shape.num_edges(), 1);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_roundtrip() {
let p0 = Point::from_coords(1.0, 0.0, 0.0);
let p1 = Point::from_coords(0.0, 1.0, 0.0);
let edge = Edge::new(p0, p1);
let json = serde_json::to_string(&edge).unwrap();
let back: Edge = serde_json::from_str(&json).unwrap();
assert_eq!(edge, back);
let seid = ShapeEdgeId::new(3, 7);
let json = serde_json::to_string(&seid).unwrap();
let back: ShapeEdgeId = serde_json::from_str(&json).unwrap();
assert_eq!(seid, back);
let se = ShapeEdge::new(seid, edge);
let json = serde_json::to_string(&se).unwrap();
let back: ShapeEdge = serde_json::from_str(&json).unwrap();
assert_eq!(se, back);
let chain = Chain {
start: 0,
length: 5,
};
let json = serde_json::to_string(&chain).unwrap();
let back: Chain = serde_json::from_str(&json).unwrap();
assert_eq!(chain, back);
let cp = ChainPosition::new(2, 3);
let json = serde_json::to_string(&cp).unwrap();
let back: ChainPosition = serde_json::from_str(&json).unwrap();
assert_eq!(cp, back);
let rp = ReferencePoint::new(p0, true);
let json = serde_json::to_string(&rp).unwrap();
let back: ReferencePoint = serde_json::from_str(&json).unwrap();
assert_eq!(rp, back);
}
}
#[cfg(test)]
#[path = "shape_tests.rs"]
mod shape_tests;