Skip to main content

geometry_model/
point.rs

1//! The default `model::Point<T, D, Cs>`.
2//!
3//! Mirrors `boost::geometry::model::point<CoordinateType, DimensionCount,
4//! CoordinateSystem>` declared in `boost/geometry/geometries/point.hpp`,
5//! together with the matching trait specialisations (`tag`,
6//! `coordinate_type`, `coordinate_system`, `dimension`, `access`) the
7//! same header provides under `namespace traits`. The Rust port collapses
8//! those five specialisations to one `Point` impl, plus the
9//! [`Geometry`] super-bound for the tag.
10//!
11//! The two convenience aliases [`Point2D`] and [`Point3D`] play the role
12//! of `boost/geometry/geometries/point_xy.hpp` and
13//! `boost/geometry/geometries/point_xyz.hpp` — they fix the dimension
14//! parameter but reuse the same const-generic body.
15
16use core::marker::PhantomData;
17
18use geometry_coords::CoordinateScalar;
19use geometry_cs::{Cartesian, CoordinateSystem};
20use geometry_tag::PointTag;
21use geometry_trait::{Geometry, Point as PointTrait, PointMut};
22
23/// Default Point — a `[T; D]` array plus a phantom CS tag.
24///
25/// Mirrors `boost::geometry::model::point<CoordinateType, DimensionCount,
26/// CoordinateSystem>` from `boost/geometry/geometries/point.hpp`. The
27/// `#[repr(transparent)]` annotation guarantees the in-memory layout
28/// matches the underlying `[T; D]`, so callers can safely treat the
29/// point as an array of coordinates when interfacing with FFI or SIMD
30/// code — the same invariant Boost relies on for its
31/// `m_values[DimensionCount]` member.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[repr(transparent)]
34pub struct Point<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem = Cartesian> {
35    coords: [T; D],
36    _cs: PhantomData<Cs>,
37}
38
39// Serde support (behind the `serde` feature). `serde` cannot derive
40// over the const-generic `[T; D]` field, so `Point` gets a hand-written
41// impl that (de)serialises the coordinates as a length-`D` sequence and
42// reconstructs the phantom CS tag. The other model types are plain
43// derives (see their own modules).
44#[cfg(feature = "serde")]
45impl<T, const D: usize, Cs> serde::Serialize for Point<T, D, Cs>
46where
47    T: CoordinateScalar + serde::Serialize,
48    Cs: CoordinateSystem,
49{
50    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
51        use serde::ser::SerializeTuple;
52        let mut tup = serializer.serialize_tuple(D)?;
53        for coord in &self.coords {
54            tup.serialize_element(coord)?;
55        }
56        tup.end()
57    }
58}
59
60#[cfg(feature = "serde")]
61impl<'de, T, const D: usize, Cs> serde::Deserialize<'de> for Point<T, D, Cs>
62where
63    T: CoordinateScalar + serde::Deserialize<'de>,
64    Cs: CoordinateSystem,
65{
66    fn deserialize<De: serde::Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
67        struct CoordsVisitor<T, const D: usize>(PhantomData<T>);
68
69        impl<'de, T, const D: usize> serde::de::Visitor<'de> for CoordsVisitor<T, D>
70        where
71            T: CoordinateScalar + serde::Deserialize<'de>,
72        {
73            type Value = [T; D];
74
75            fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
76                write!(f, "a sequence of {D} coordinates")
77            }
78
79            fn visit_seq<A: serde::de::SeqAccess<'de>>(
80                self,
81                mut seq: A,
82            ) -> Result<[T; D], A::Error> {
83                // Build the array element by element; `[T; D]` has no
84                // `Default`, so collect into a temporary and convert.
85                let mut coords: [T; D] = [T::ZERO; D];
86                for (i, slot) in coords.iter_mut().enumerate() {
87                    *slot = seq
88                        .next_element::<T>()?
89                        .ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
90                }
91                Ok(coords)
92            }
93        }
94
95        let coords = deserializer.deserialize_tuple(D, CoordsVisitor::<T, D>(PhantomData))?;
96        Ok(Point {
97            coords,
98            _cs: PhantomData,
99        })
100    }
101}
102
103/// 2D point alias.
104///
105/// Mirrors `boost::geometry::model::d2::point_xy<CoordinateType,
106/// CoordinateSystem>` from `boost/geometry/geometries/point_xy.hpp`.
107pub type Point2D<T, Cs = Cartesian> = Point<T, 2, Cs>;
108
109/// 3D point alias.
110///
111/// Mirrors `boost::geometry::model::d3::point_xyz<CoordinateType,
112/// CoordinateSystem>` from `boost/geometry/geometries/point_xyz.hpp`.
113pub type Point3D<T, Cs = Cartesian> = Point<T, 3, Cs>;
114
115// ---- Constructors -----------------------------------------------------
116//
117// Boost exposes three overloads of `point(...)` gated on the arity
118// matching `DimensionCount` (`point.hpp:132-185`). Rust has no SFINAE,
119// so we split into three inherent impls keyed on a specific const `D`.
120// Calling `Point2D::new(v0, v1, v2)` then fails to type-check because
121// there is no `new(_, _, _)` on `Point<T, 2, Cs>` — the mirror of the
122// C++ `enable_if_t<is_coordinates_number_leq<C, 3, DimensionCount>>`
123// guard.
124
125impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 1, Cs> {
126    /// Construct a 1-D point.
127    ///
128    /// Mirrors the single-argument `point(v0)` constructor at
129    /// `boost/geometry/geometries/point.hpp:133-149`.
130    #[must_use]
131    pub const fn new(v0: T) -> Self {
132        Self {
133            coords: [v0],
134            _cs: PhantomData,
135        }
136    }
137}
138
139impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 2, Cs> {
140    /// Construct a 2-D point.
141    ///
142    /// Mirrors the two-argument `point(v0, v1)` constructor at
143    /// `boost/geometry/geometries/point.hpp:151-167`.
144    #[must_use]
145    pub const fn new(v0: T, v1: T) -> Self {
146        Self {
147            coords: [v0, v1],
148            _cs: PhantomData,
149        }
150    }
151}
152
153impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 3, Cs> {
154    /// Construct a 3-D point.
155    ///
156    /// Mirrors the three-argument `point(v0, v1, v2)` constructor at
157    /// `boost/geometry/geometries/point.hpp:169-185`.
158    #[must_use]
159    pub const fn new(v0: T, v1: T, v2: T) -> Self {
160        Self {
161            coords: [v0, v1, v2],
162            _cs: PhantomData,
163        }
164    }
165}
166
167// ---- Default ----------------------------------------------------------
168//
169// `#[derive(Default)]` would require `[T; D]: Default`, whose
170// const-generic blanket impl is still unstable. We provide an explicit
171// impl using `CoordinateScalar::ZERO` — the Rust analogue of Boost's
172// zero-init `m_values{}` at `point.hpp:113-119`.
173
174impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Default for Point<T, D, Cs> {
175    #[inline]
176    fn default() -> Self {
177        Self {
178            coords: [T::ZERO; D],
179            _cs: PhantomData,
180        }
181    }
182}
183
184// ---- Trait impls ------------------------------------------------------
185
186/// Tag this concrete type as a Point. Mirrors the
187/// `traits::tag<model::point<...>>` specialisation at
188/// `boost/geometry/geometries/point.hpp:241-244`.
189impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Geometry for Point<T, D, Cs> {
190    type Kind = PointTag;
191    type Point = Self;
192}
193
194/// Wire the Point concept up. Collapses the four trait specialisations
195/// at `boost/geometry/geometries/point.hpp:246-299`
196/// (`coordinate_type`, `coordinate_system`, `dimension`, `access`)
197/// into one Rust impl.
198impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointTrait for Point<T, D, Cs> {
199    type Scalar = T;
200    type Cs = Cs;
201    const DIM: usize = D;
202
203    #[inline]
204    fn get<const DIDX: usize>(&self) -> T {
205        self.coords[DIDX]
206    }
207}
208
209impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointMut for Point<T, D, Cs> {
210    #[inline]
211    fn set<const DIDX: usize>(&mut self, value: T) {
212        self.coords[DIDX] = value;
213    }
214}