Skip to main content

geometry_algorithm/
append.rs

1//! `append(&mut g, point)` and the ring-index variant.
2//!
3//! Mirrors `boost::geometry::append` from
4//! `boost/geometry/algorithms/append.hpp`. Boost ships a single
5//! overloaded `append(...)` that dispatches on the source geometry
6//! kind; the Rust port uses two free functions — [`append`] for the
7//! common linear cases and [`append_to_ring`] for the polygon
8//! ring-index overload — so each call site disambiguates without a
9//! defaulted argument (which Rust methods cannot express).
10
11use geometry_model::{Linestring, Polygon, Ring};
12use geometry_trait::Point as PointTrait;
13
14/// Push `point` to the end of a [`Linestring`] or [`Ring`].
15///
16/// Mirrors `boost::geometry::append(g, point)` from
17/// `boost/geometry/algorithms/append.hpp` for the linear kinds.
18pub fn append<G, P>(g: &mut G, point: P)
19where
20    G: Append<P>,
21{
22    g.append(point);
23}
24
25/// Push `point` onto the polygon's outer ring (`ring_index = None`)
26/// or its `ring_index`-th interior ring (`ring_index = Some(i)`).
27///
28/// Mirrors the `append(polygon, point, ring_index)` overload of
29/// `boost::geometry::append` from
30/// `boost/geometry/algorithms/append.hpp`, where the outer ring is
31/// index `-1` and interior rings are `0..n`.
32///
33/// An out-of-range interior `ring_index` is a **no-op**, matching Boost:
34/// `append.hpp:96-104` guards the interior write with
35/// `else if (ring_index < num_interior_rings(polygon))` and has no
36/// fall-through, so an index past the last hole silently does nothing.
37pub fn append_to_ring<P, const CW: bool, const CL: bool>(
38    polygon: &mut Polygon<P, CW, CL>,
39    point: P,
40    ring_index: Option<usize>,
41) where
42    P: PointTrait,
43{
44    match ring_index {
45        None => polygon.outer.0.push(point),
46        Some(i) if i < polygon.inners.len() => polygon.inners[i].0.push(point),
47        // Out-of-range interior index: no-op, as Boost.
48        Some(_) => {}
49    }
50}
51
52/// Per-kind append dispatch for the linear kinds. Implemented for
53/// [`Linestring`] and [`Ring`]; the [`Polygon`] ring-index overload
54/// lives on the separate [`append_to_ring`] free function.
55#[doc(hidden)]
56pub trait Append<P> {
57    fn append(&mut self, point: P);
58}
59
60impl<P: PointTrait> Append<P> for Linestring<P> {
61    fn append(&mut self, point: P) {
62        self.0.push(point);
63    }
64}
65
66impl<P: PointTrait, const CW: bool, const CL: bool> Append<P> for Ring<P, CW, CL> {
67    fn append(&mut self, point: P) {
68        self.0.push(point);
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    //! Reference behaviour from
75    //! `boost/geometry/test/algorithms/append.cpp` — appending grows
76    //! the target ring by one point.
77
78    use super::{append, append_to_ring};
79    use geometry_cs::Cartesian;
80    use geometry_model::{Linestring, Point2D, Polygon, linestring, polygon};
81    use geometry_trait::{Linestring as _, Polygon as _, Ring as _};
82
83    type Pt = Point2D<f64, Cartesian>;
84
85    #[test]
86    fn append_to_linestring() {
87        let mut ls: Linestring<Pt> = linestring![(0., 0.), (1., 1.)];
88        append(&mut ls, Pt::new(2., 2.));
89        assert_eq!(ls.points().count(), 3);
90    }
91
92    #[test]
93    fn append_to_ring_grows_by_one() {
94        let mut r: geometry_model::Ring<Pt> =
95            geometry_model::Ring::from_vec(vec![Pt::new(0., 0.), Pt::new(4., 0.)]);
96        append(&mut r, Pt::new(4., 3.));
97        assert_eq!(r.points().count(), 3);
98    }
99
100    #[test]
101    fn append_to_polygon_outer() {
102        let mut pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 3.)]];
103        append_to_ring(&mut pg, Pt::new(0., 3.), None);
104        assert_eq!(pg.exterior().points().count(), 4);
105    }
106
107    #[test]
108    fn append_to_polygon_interior_ring_by_index() {
109        let mut pg: Polygon<Pt> = polygon![
110            [(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
111            [(1., 1.), (2., 1.), (2., 2.)],
112        ];
113        append_to_ring(&mut pg, Pt::new(1., 2.), Some(0));
114        assert_eq!(pg.interiors().next().unwrap().points().count(), 4);
115    }
116
117    #[test]
118    fn append_to_out_of_range_interior_is_a_noop() {
119        // Regression: an out-of-range interior index must NOT panic — it
120        // is a silent no-op, matching Boost (append.hpp:96-104 guards the
121        // interior write and falls through on OOB).
122        let mut pg: Polygon<Pt> = polygon![[(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)]];
123        // No interior rings; index 0 is out of range.
124        append_to_ring(&mut pg, Pt::new(1., 1.), Some(0));
125        assert_eq!(pg.interiors().count(), 0, "no ring created, no panic");
126        // Outer untouched.
127        assert_eq!(pg.exterior().points().count(), 5);
128    }
129}