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_polygon_outer() {
94 let mut pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 3.)]];
95 append_to_ring(&mut pg, Pt::new(0., 3.), None);
96 assert_eq!(pg.exterior().points().count(), 4);
97 }
98
99 #[test]
100 fn append_to_polygon_interior_ring_by_index() {
101 let mut pg: Polygon<Pt> = polygon![
102 [(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
103 [(1., 1.), (2., 1.), (2., 2.)],
104 ];
105 append_to_ring(&mut pg, Pt::new(1., 2.), Some(0));
106 assert_eq!(pg.interiors().next().unwrap().points().count(), 4);
107 }
108
109 #[test]
110 fn append_to_out_of_range_interior_is_a_noop() {
111 // Regression: an out-of-range interior index must NOT panic — it
112 // is a silent no-op, matching Boost (append.hpp:96-104 guards the
113 // interior write and falls through on OOB).
114 let mut pg: Polygon<Pt> = polygon![[(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)]];
115 // No interior rings; index 0 is out of range.
116 append_to_ring(&mut pg, Pt::new(1., 1.), Some(0));
117 assert_eq!(pg.interiors().count(), 0, "no ring created, no panic");
118 // Outer untouched.
119 assert_eq!(pg.exterior().points().count(), 5);
120 }
121}