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    /// Return the x ordinate.
153    ///
154    /// Mirrors `model::d2::point_xy::x() const` from
155    /// `geometries/point_xy.hpp:67-68`.
156    #[inline]
157    #[must_use]
158    pub const fn x(&self) -> T {
159        self.coords[0]
160    }
161
162    /// Return the y ordinate.
163    ///
164    /// Mirrors `model::d2::point_xy::y() const` from
165    /// `geometries/point_xy.hpp:71-72`.
166    #[inline]
167    #[must_use]
168    pub const fn y(&self) -> T {
169        self.coords[1]
170    }
171
172    /// Set the x ordinate.
173    ///
174    /// Mirrors the setter overload `model::d2::point_xy::x(v)` from
175    /// `geometries/point_xy.hpp:75-76`. Rust uses a distinct method name
176    /// because it has no inherent-method overloading.
177    #[inline]
178    pub fn set_x(&mut self, value: T) {
179        self.coords[0] = value;
180    }
181
182    /// Set the y ordinate.
183    ///
184    /// Mirrors the setter overload `model::d2::point_xy::y(v)` from
185    /// `geometries/point_xy.hpp:79-80`.
186    #[inline]
187    pub fn set_y(&mut self, value: T) {
188        self.coords[1] = value;
189    }
190}
191
192impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 3, Cs> {
193    /// Construct a 3-D point.
194    ///
195    /// Mirrors the three-argument `point(v0, v1, v2)` constructor at
196    /// `boost/geometry/geometries/point.hpp:169-185`.
197    #[must_use]
198    pub const fn new(v0: T, v1: T, v2: T) -> Self {
199        Self {
200            coords: [v0, v1, v2],
201            _cs: PhantomData,
202        }
203    }
204
205    /// Return the x ordinate.
206    ///
207    /// Mirrors `model::d3::point_xyz::x() const` from
208    /// `geometries/point_xyz.hpp:55-56`.
209    #[inline]
210    #[must_use]
211    pub const fn x(&self) -> T {
212        self.coords[0]
213    }
214
215    /// Return the y ordinate.
216    ///
217    /// Mirrors `model::d3::point_xyz::y() const` from
218    /// `geometries/point_xyz.hpp:59-60`.
219    #[inline]
220    #[must_use]
221    pub const fn y(&self) -> T {
222        self.coords[1]
223    }
224
225    /// Return the z ordinate.
226    ///
227    /// Mirrors `model::d3::point_xyz::z() const` from
228    /// `geometries/point_xyz.hpp:63-64`.
229    #[inline]
230    #[must_use]
231    pub const fn z(&self) -> T {
232        self.coords[2]
233    }
234
235    /// Set the x ordinate.
236    ///
237    /// Mirrors the setter overload `model::d3::point_xyz::x(v)` from
238    /// `geometries/point_xyz.hpp:67-68`. Rust uses a distinct name because
239    /// it has no inherent-method overloading.
240    #[inline]
241    pub fn set_x(&mut self, value: T) {
242        self.coords[0] = value;
243    }
244
245    /// Set the y ordinate.
246    ///
247    /// Mirrors the setter overload `model::d3::point_xyz::y(v)` from
248    /// `geometries/point_xyz.hpp:71-72`.
249    #[inline]
250    pub fn set_y(&mut self, value: T) {
251        self.coords[1] = value;
252    }
253
254    /// Set the z ordinate.
255    ///
256    /// Mirrors the setter overload `model::d3::point_xyz::z(v)` from
257    /// `geometries/point_xyz.hpp:75-76`.
258    #[inline]
259    pub fn set_z(&mut self, value: T) {
260        self.coords[2] = value;
261    }
262}
263
264// ---- Default ----------------------------------------------------------
265//
266// `#[derive(Default)]` would require `[T; D]: Default`, whose
267// const-generic blanket impl is still unstable. We provide an explicit
268// impl using `CoordinateScalar::ZERO` — the Rust analogue of Boost's
269// zero-init `m_values{}` at `point.hpp:113-119`.
270
271impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Default for Point<T, D, Cs> {
272    #[inline]
273    fn default() -> Self {
274        Self {
275            coords: [T::ZERO; D],
276            _cs: PhantomData,
277        }
278    }
279}
280
281// ---- Trait impls ------------------------------------------------------
282
283/// Tag this concrete type as a Point. Mirrors the
284/// `traits::tag<model::point<...>>` specialisation at
285/// `boost/geometry/geometries/point.hpp:241-244`.
286impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Geometry for Point<T, D, Cs> {
287    type Kind = PointTag;
288    type Point = Self;
289}
290
291/// Wire the Point concept up. Collapses the four trait specialisations
292/// at `boost/geometry/geometries/point.hpp:246-299`
293/// (`coordinate_type`, `coordinate_system`, `dimension`, `access`)
294/// into one Rust impl.
295impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointTrait for Point<T, D, Cs> {
296    type Scalar = T;
297    type Cs = Cs;
298    const DIM: usize = D;
299
300    #[inline]
301    fn get<const DIDX: usize>(&self) -> T {
302        self.coords[DIDX]
303    }
304}
305
306impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointMut for Point<T, D, Cs> {
307    #[inline]
308    fn set<const DIDX: usize>(&mut self, value: T) {
309        self.coords[DIDX] = value;
310    }
311}