geo/algorithm/relate/geomgraph/edge_end_bundle.rs
1use super::{CoordPos, Direction, Edge, EdgeEnd, GeometryGraph, IntersectionMatrix, Label};
2use crate::{Coord, GeoFloat};
3
4/// A collection of [`EdgeEnds`](EdgeEnd) which obey the following invariant:
5/// They originate at the same node and have the same direction.
6///
7/// This is based on [JTS's `EdgeEndBundle` as of 1.18.1](https://github.com/locationtech/jts/blob/jts-1.18.1/modules/core/src/main/java/org/locationtech/jts/operation/relate/EdgeEndBundle.java)
8#[derive(Clone, Debug)]
9pub(crate) struct EdgeEndBundle<F>
10where
11 F: GeoFloat,
12{
13 coordinate: Coord<F>,
14 edge_ends: Vec<EdgeEnd<F>>,
15}
16
17impl<F> EdgeEndBundle<F>
18where
19 F: GeoFloat,
20{
21 pub(crate) fn new(coordinate: Coord<F>) -> Self {
22 Self {
23 coordinate,
24 edge_ends: vec![],
25 }
26 }
27
28 fn edge_ends_iter(&self) -> impl Iterator<Item = &EdgeEnd<F>> {
29 self.edge_ends.iter()
30 }
31
32 fn edge_ends_iter_mut(&mut self) -> impl Iterator<Item = &mut EdgeEnd<F>> {
33 self.edge_ends.iter_mut()
34 }
35
36 pub(crate) fn insert(&mut self, edge_end: EdgeEnd<F>) {
37 self.edge_ends.push(edge_end);
38 }
39
40 pub(crate) fn into_labeled(mut self) -> LabeledEdgeEndBundle<F> {
41 let is_area = self
42 .edge_ends_iter()
43 .any(|edge_end| edge_end.label().is_area());
44
45 let mut label = if is_area {
46 Label::empty_area()
47 } else {
48 Label::empty_line_or_point()
49 };
50
51 for i in 0..2 {
52 self.compute_label_on(&mut label, i);
53 if is_area {
54 self.compute_label_side(&mut label, i, Direction::Left);
55 self.compute_label_side(&mut label, i, Direction::Right);
56 }
57 }
58
59 LabeledEdgeEndBundle {
60 label,
61 edge_end_bundle: self,
62 }
63 }
64
65 /// Compute the overall ON position for the list of EdgeEnds.
66 /// (This is essentially equivalent to computing the self-overlay of a single Geometry)
67 ///
68 /// EdgeEnds can be either on the boundary (e.g. Polygon edge)
69 /// OR in the interior (e.g. segment of a LineString)
70 /// of their parent Geometry.
71 ///
72 /// In addition, GeometryCollections use a boundary node rule to determine whether a segment is
73 /// on the boundary or not.
74 ///
75 /// Finally, in GeometryCollections it can occur that an edge is both
76 /// on the boundary and in the interior (e.g. a LineString segment lying on
77 /// top of a Polygon edge.) In this case the Boundary is given precedence.
78 ///
79 /// These observations result in the following rules for computing the ON location:
80 /// - if there are an odd number of Bdy edges, the attribute is Bdy
81 /// - if there are an even number >= 2 of Bdy edges, the attribute is Int
82 /// - if there are any Int edges, the attribute is Int
83 /// - otherwise, the attribute is None
84 ///
85 fn compute_label_on(&mut self, label: &mut Label, geom_index: usize) {
86 let mut boundary_count = 0;
87 let mut found_interior = false;
88
89 for edge_end in self.edge_ends_iter() {
90 match edge_end.label().on_position(geom_index) {
91 Some(CoordPos::OnBoundary) => {
92 boundary_count += 1;
93 }
94 Some(CoordPos::Inside) => {
95 found_interior = true;
96 }
97 None | Some(CoordPos::Outside) => {}
98 }
99 }
100
101 let mut position = None;
102 if found_interior {
103 position = Some(CoordPos::Inside);
104 }
105
106 if boundary_count > 0 {
107 position = Some(GeometryGraph::<'_, F>::determine_boundary(boundary_count));
108 }
109
110 if let Some(location) = position {
111 label.set_on_position(geom_index, location);
112 } else {
113 // This is technically a diversion from JTS, but I don't think we'd ever
114 // get here, unless `l.on_location` was *already* None, in which cases this is a
115 // no-op, so assert that assumption.
116 // If this assert is rightfully triggered, we may need to add a method like
117 // `l.clear_on_location(geom_index)`
118 debug_assert!(
119 label.on_position(geom_index).is_none(),
120 "diverging from JTS, which would have replaced the existing Location with None"
121 );
122 }
123 }
124
125 /// To compute the summary label for a side, the algorithm is:
126 /// FOR all edges
127 /// IF any edge's location is INTERIOR for the side, side location = INTERIOR
128 /// ELSE IF there is at least one EXTERIOR attribute, side location = EXTERIOR
129 /// ELSE side location = NULL
130 /// Note that it is possible for two sides to have apparently contradictory information
131 /// i.e. one edge side may indicate that it is in the interior of a geometry, while
132 /// another edge side may indicate the exterior of the same geometry. This is
133 /// not an incompatibility - GeometryCollections may contain two Polygons that touch
134 /// along an edge. This is the reason for Interior-primacy rule above - it
135 /// results in the summary label having the Geometry interior on _both_ sides.
136 fn compute_label_side(&mut self, label: &mut Label, geom_index: usize, side: Direction) {
137 let mut position = None;
138 for edge_end in self.edge_ends_iter_mut() {
139 if edge_end.label().is_area() {
140 match edge_end.label_mut().position(geom_index, side) {
141 Some(CoordPos::Inside) => {
142 position = Some(CoordPos::Inside);
143 break;
144 }
145 Some(CoordPos::Outside) => {
146 position = Some(CoordPos::Outside);
147 }
148 None | Some(CoordPos::OnBoundary) => {}
149 }
150 }
151 }
152
153 if let Some(position) = position {
154 label.set_position(geom_index, side, position);
155 }
156 }
157}
158
159/// An [`EdgeEndBundle`] whose topological relationships have been aggregated into a single
160/// [`Label`].
161///
162/// `update_intersection_matrix` applies this aggregated topology to an `IntersectionMatrix`.
163#[derive(Clone, Debug)]
164pub(crate) struct LabeledEdgeEndBundle<F>
165where
166 F: GeoFloat,
167{
168 label: Label,
169 edge_end_bundle: EdgeEndBundle<F>,
170}
171
172impl<F> LabeledEdgeEndBundle<F>
173where
174 F: GeoFloat,
175{
176 pub fn label(&self) -> &Label {
177 &self.label
178 }
179
180 pub fn label_mut(&mut self) -> &mut Label {
181 &mut self.label
182 }
183
184 pub fn update_intersection_matrix(&self, intersection_matrix: &mut IntersectionMatrix) {
185 Edge::<F>::update_intersection_matrix(self.label(), intersection_matrix);
186 }
187
188 pub fn coordinate(&self) -> &Coord<F> {
189 &self.edge_end_bundle.coordinate
190 }
191}