topology_checker_core/
merge.rs1use crate::{
2 Error, Layer,
3 engine::{
4 cluster::{Cluster, distance},
5 index,
6 line_graph::projection,
7 },
8 model::Invalid,
9};
10use geo::{BoundingRect, Coord, Geometry, LineString};
11use std::collections::BTreeMap;
12
13fn line_error(invalid: Invalid) -> Error {
16 match invalid {
17 Invalid::NonFinite(fid) | Invalid::DuplicateFid(fid) => Error::NonFiniteLine {
18 index: usize::try_from(fid).unwrap_or_default(),
19 },
20 }
21}
22
23pub fn merge_lines(
37 lines: impl IntoIterator<Item = LineString>,
38 tolerance: f64,
39) -> Result<Vec<LineString>, Error> {
40 if !tolerance.is_finite() || tolerance < 0. {
41 return Err(Error::Tolerance(tolerance));
42 }
43 let layer = Layer::from_geometries("lines", lines.into_iter().map(Geometry::LineString));
44 let parts = layer.parts().map_err(line_error)?;
45 let lines = parts.lines.iter().map(|p| &p.geometry).collect::<Vec<_>>();
46 let mut cluster = Cluster::new(tolerance);
47 let edges = lines
48 .iter()
49 .map(|l| [cluster.insert(l.0[0]), cluster.insert(l.0[l.0.len() - 1])])
50 .collect::<Vec<_>>();
51 let mut incident = vec![Vec::new(); cluster.coords.len()];
52 for (i, edge) in edges.iter().enumerate() {
53 incident[edge[0]].push((i, true));
54 incident[edge[1]].push((i, false));
55 }
56 let segments = lines
57 .iter()
58 .enumerate()
59 .flat_map(|(part, l)| {
60 l.lines()
61 .enumerate()
62 .map(move |(position, line)| (part, position, line))
63 })
64 .collect::<Vec<_>>();
65 let tree = index::build(
66 segments
67 .iter()
68 .enumerate()
69 .map(|(i, (_, _, line))| (i, line.bounding_rect())),
70 );
71 let mut blocked = vec![false; incident.len()];
72 for (part, line) in lines.iter().enumerate() {
73 for (end, &point) in [line.0[0], line.0[line.0.len() - 1]].iter().enumerate() {
74 let node = edges[part][end];
75 for item in tree.locate_in_envelope_intersecting(&index::at(point, tolerance)) {
76 let (owner, position, segment) = segments[item.data];
77 let (_, projected) = projection(segment, point);
78 if distance(point, projected) > tolerance {
79 continue;
80 }
81 let at_start = position == 0 && edges[owner][0] == node;
84 let at_end = position + 2 == lines[owner].0.len() && edges[owner][1] == node;
85 if !at_start && !at_end {
86 blocked[node] = true;
87 }
88 }
89 }
90 }
91 let mut visited = vec![false; edges.len()];
92 let mut outputs = Vec::new();
93 let walk = |start: usize, forward: bool, visited: &mut [bool]| {
94 let mut coords = Vec::new();
95 let mut current = (start, forward);
96 let mut forwards = 0;
97 let mut backwards = 0;
98 let mut minimum = start;
99 let mut minimum_forward = forward;
100 loop {
101 let (id, direction) = current;
102 if visited[id] {
103 break;
104 }
105 visited[id] = true;
106 if id < minimum {
107 minimum = id;
108 minimum_forward = direction;
109 }
110 if direction {
111 forwards += 1;
112 } else {
113 backwards += 1;
114 }
115 let first = edges[id][usize::from(!direction)];
116 let last = edges[id][usize::from(direction)];
117 let part = &lines[id].0;
118 coords.reserve(part.len() - usize::from(!coords.is_empty()));
120 if coords.is_empty() {
121 coords.push(cluster.coords[first]);
122 }
123 let interior = &part[1..part.len() - 1];
124 if direction {
125 coords.extend(interior.iter().copied());
126 } else {
127 coords.extend(interior.iter().rev().copied());
128 }
129 coords.push(cluster.coords[last]);
130 if blocked[last] || incident[last].len() != 2 {
131 break;
132 }
133 let Some(next) = incident[last].iter().copied().find(|(id, _)| !visited[*id]) else {
134 break;
135 };
136 current = next;
137 }
138 if backwards > forwards || (backwards == forwards && !minimum_forward) {
139 coords.reverse();
140 }
141 coords.dedup();
142 (minimum, LineString(coords))
143 };
144 for (node, uses) in incident.iter().enumerate() {
145 if uses.len() == 2 && !blocked[node] {
146 continue;
147 }
148 for &(id, forward) in uses {
149 if !visited[id] {
150 outputs.push(walk(id, forward, &mut visited));
151 }
152 }
153 }
154 for id in 0..edges.len() {
155 if !visited[id] {
156 outputs.push(walk(id, true, &mut visited));
157 }
158 }
159 outputs.sort_by_key(|(id, _)| *id);
160 Ok(outputs
161 .into_iter()
162 .map(|(_, line)| line)
163 .filter(|l| l.0.len() >= 2)
164 .collect())
165}
166
167pub fn explode_lines(
175 lines: impl IntoIterator<Item = LineString>,
176) -> Result<Vec<LineString>, Error> {
177 let layer = Layer::from_geometries("lines", lines.into_iter().map(Geometry::LineString));
178 Ok(layer
179 .parts()
180 .map_err(line_error)?
181 .lines
182 .iter()
183 .flat_map(|p| p.geometry.lines())
184 .map(LineString::from)
185 .collect())
186}
187
188pub(crate) fn merge_edges(coords: &[Coord], edges: &[[usize; 2]]) -> Vec<LineString> {
190 let mut incident: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
191 for (id, edge) in edges.iter().enumerate() {
192 for &node in edge {
193 incident.entry(node).or_default().push(id);
194 }
195 }
196 let mut visited = vec![false; edges.len()];
197 let mut output = Vec::new();
198 let walk = |start: usize, node: usize, visited: &mut [bool]| {
199 let mut current = start;
200 let mut node = node;
201 let mut path = vec![coords[node]];
202 loop {
203 if visited[current] {
204 break;
205 }
206 visited[current] = true;
207 let edge = edges[current];
208 node = if edge[0] == node { edge[1] } else { edge[0] };
209 path.push(coords[node]);
210 let uses = &incident[&node];
211 if uses.len() != 2 {
212 break;
213 }
214 let Some(next) = uses.iter().copied().find(|id| !visited[*id]) else {
215 break;
216 };
217 current = next;
218 }
219 if let (Some(first), Some(last)) = (path.first(), path.last())
220 && (last.x.total_cmp(&first.x).then(last.y.total_cmp(&first.y))).is_lt()
221 {
222 path.reverse();
223 }
224 LineString(path)
225 };
226 for (&node, uses) in &incident {
227 if uses.len() != 2 {
228 for &id in uses {
229 if !visited[id] {
230 output.push(walk(id, node, &mut visited));
231 }
232 }
233 }
234 }
235 for id in 0..edges.len() {
236 if !visited[id] {
237 output.push(walk(id, edges[id][0], &mut visited));
238 }
239 }
240 output
241}