gistools/geometry/tools/polys/
dekink.rs1use crate::geometry::{
2 Area, InterPointLookup, NextRingChunk, PolyPath, PolyPathRef, RingChunkRef,
3 RingIntersectionLookup, Segment, build_paths_and_chunks, merge_intersection_pairs,
4 polygons_intersections_lookup, polyline_in_polyline,
5};
6use alloc::{
7 collections::{BTreeMap, BTreeSet},
8 vec,
9 vec::Vec,
10};
11use core::{cmp::Ordering, mem::take};
12use s2json::{BBox, FullXY};
13
14pub fn dekink_polygons<P: FullXY>(polygons: &[Vec<Vec<P>>]) -> Option<(Vec<Vec<Vec<P>>>, BBox)> {
22 polygons.iter().filter_map(|p| dekink_polygon(p)).try_fold(
23 (vec![], BBox::default()),
24 |(mut res, mut bbox), mut p| {
25 res.append(&mut p.0);
26 bbox.merge_in_place(&p.1);
27 Some((res, bbox))
28 },
29 )
30}
31
32pub fn dekink_polygon<P: FullXY>(polygon: &[Vec<P>]) -> Option<(Vec<Vec<Vec<P>>>, BBox)> {
40 if polygon.is_empty() {
42 return None;
43 }
44 let vector_polygons = vec![polygon.to_vec()];
45
46 let mut ring_int_lookup: RingIntersectionLookup = polygons_intersections_lookup(
48 &vector_polygons,
49 Some(|seg1: &Segment, seg2: &Segment| -> bool {
50 seg2.id != seg1.id &&
52 seg2.id > seg1.id &&
54 seg2.ring_index == seg1.ring_index
58 }),
59 );
60
61 let (mut paths, _, chnks, mut int_lookup, _b) =
66 build_paths_and_chunks(&vector_polygons, &mut ring_int_lookup);
67
68 build_paths_from_chunks(&mut paths, &mut int_lookup, &chnks);
71
72 let coordinates: Vec<Vec<Vec<P>>> =
74 paths.iter_mut().filter_map(|p| p.borrow_mut().get_path()).collect();
75 let bbox = paths.iter().map(|p| p.borrow().bbox).fold(BBox::default(), |mut acc, b| {
76 acc.merge_in_place(&b);
77 acc
78 });
79 if coordinates.is_empty() { None } else { Some((coordinates, bbox)) }
80}
81
82#[derive(Debug)]
84struct Ring<P: FullXY> {
85 linestring: Vec<P>,
86 is_ccw: bool,
87 is_hole: bool,
88 bbox: BBox,
89 area: f64, }
91impl<P: FullXY> Ring<P> {
92 fn new(linestring: Vec<P>, is_ccw: bool, is_hole: bool, bbox: BBox, area: f64) -> Self {
93 Self { linestring, is_ccw, is_hole, bbox, area }
94 }
95}
96type RingStore<P> = BTreeMap<usize, Vec<Ring<P>>>;
99
100fn build_paths_from_chunks<P: FullXY>(
107 paths: &mut Vec<PolyPathRef<P>>,
108 intersections: &mut InterPointLookup<P>,
109 chunks: &Vec<RingChunkRef<P>>,
110) {
111 let mut ring_store: RingStore<P> = BTreeMap::new();
112 for path in paths.iter() {
114 let PolyPath { outer, polys_consumed, bbox, holes, .. } = &mut *path.borrow_mut();
115 let poly_index = polys_consumed.first().unwrap();
116 let poly_store = ring_store.entry(*poly_index).or_insert(vec![]);
117 if let Some(outer) = outer {
118 poly_store.push(Ring::new(take(outer), true, false, *bbox, bbox.area()));
119 }
120 for hole in holes {
121 poly_store.push(Ring::new(take(hole), false, true, *bbox, bbox.area()));
122 }
123 }
124 paths.clear();
125 for int in intersections.lookup.values() {
127 merge_intersection_pairs(int);
128 }
129 for chunk in chunks {
131 if chunk.borrow().visted {
132 continue;
133 }
134 let start = chunk.borrow().from;
136 let mut curr_chunk: RingChunkRef<P> = chunk.clone();
137 let mut linestring: Vec<P> = vec![P::new_xy(start.0, start.1)];
138 let mut bbox = curr_chunk.borrow().bbox;
139 loop {
140 if curr_chunk.borrow().visted {
141 break;
142 }
143 curr_chunk.borrow_mut().visted = true;
144 linestring.append(&mut curr_chunk.borrow_mut().mid);
145 bbox.merge_in_place(&curr_chunk.borrow().bbox);
146 if curr_chunk.borrow().next.is_none() {
147 break;
148 }
149 let (next_chunk, int_point) = {
150 let chnk = &mut curr_chunk.borrow_mut();
151 let NextRingChunk { chunk, int_point } = chnk.next.as_ref().unwrap();
152 (chunk.clone(), *int_point)
153 };
154 linestring.push(P::new_xy(int_point.0, int_point.1));
155 curr_chunk = next_chunk;
156 if int_point == start {
157 break;
158 }
159 }
160 let area = (&linestring).area(Some(1.));
161 if area == 0. || linestring.len() < 4 || linestring.first() != linestring.last() {
162 continue;
163 }
164 let is_ccw = area > 0.;
166 let is_hole = chunk.borrow().ring_index != 0;
167 let poly_store = ring_store.entry(chunk.borrow().poly_index).or_insert(vec![]);
169 poly_store.push(Ring::new(linestring, is_ccw, is_hole, bbox, area));
170 }
171 for rings in ring_store.values_mut() {
173 ring_set_to_paths(paths, rings);
174 }
175}
176
177fn ring_set_to_paths<P: FullXY>(paths: &mut Vec<PolyPathRef<P>>, ring_set: &mut [Ring<P>]) {
183 if ring_set.is_empty() {
184 return;
185 }
186 ring_set.sort_by(|a, b| b.area.partial_cmp(&a.area).unwrap_or(Ordering::Equal));
188 let mut actual_outers: Vec<PolyPathRef<P>> = vec![];
189 {
193 let mut outers: Vec<&mut Ring<P>> =
194 ring_set.iter_mut().filter(|r| !r.is_hole && r.is_ccw).collect();
195 if !outers.is_empty() {
198 let first = outers.remove(0);
199 actual_outers.push(PolyPath::new_ref(vec![], BTreeSet::new(), true, Some(first.bbox)));
201 for outer in outers {
203 if outer.bbox.inside(&first.bbox)
204 && polyline_in_polyline(&outer.linestring, &first.linestring)
205 {
206 continue;
207 }
208 actual_outers.push(PolyPath::new_ref(
209 take(&mut outer.linestring),
210 BTreeSet::new(),
211 true,
212 Some(outer.bbox),
213 ));
214 }
215 actual_outers[0].borrow_mut().outer = Some(take(&mut first.linestring));
217 }
218 }
219
220 {
222 let outers_maybe_hole: Vec<&mut Ring<P>> =
223 ring_set.iter_mut().filter(|r| !r.is_hole && !r.is_ccw).collect();
224 for Ring { linestring, bbox, .. } in outers_maybe_hole {
225 let mut found = false;
226 for actual_outer in actual_outers.iter() {
227 let actual_outer = &mut actual_outer.borrow_mut();
228 if bbox.inside(&actual_outer.bbox)
229 && polyline_in_polyline(linestring, actual_outer.outer.as_ref().unwrap())
230 {
231 actual_outer.holes.push(take(linestring));
233 found = true;
234 break;
235 }
236 }
237 if found {
238 continue;
239 }
240 linestring.reverse();
242 actual_outers.push(PolyPath::new_ref(
243 take(linestring),
244 BTreeSet::new(),
245 true,
246 Some(*bbox),
247 ))
248 }
249 }
250
251 {
253 let holes: Vec<&mut Ring<P>> =
254 ring_set.iter_mut().filter(|r| r.is_hole && !r.is_ccw).collect();
255 if !actual_outers.is_empty() {
256 for Ring { linestring, bbox, .. } in holes {
257 if actual_outers.len() == 1 {
258 actual_outers[0].borrow_mut().holes.push(take(linestring));
259 } else {
260 for actual_outer in actual_outers.iter() {
262 if bbox.inside(&actual_outer.borrow().bbox)
263 && polyline_in_polyline(
264 linestring,
265 actual_outer.borrow().outer.as_ref().unwrap(),
266 )
267 {
268 actual_outer.borrow_mut().holes.push(take(linestring));
270 break;
271 }
272 }
273 }
274 }
275 }
276 }
277
278 for actual_outer in actual_outers.iter() {
280 paths.push(actual_outer.clone());
281 }
282}