oxigdal_algorithms/vector/
simplify.rs1use crate::error::{AlgorithmError, Result};
32use crate::vector::douglas_peucker::simplify_linestring as dp_simplify;
33use oxigdal_core::vector::{Coordinate, LineString, Polygon};
34
35#[cfg(feature = "std")]
36use std::vec::Vec;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum SimplifyMethod {
41 DouglasPeucker,
43 VisvalingamWhyatt,
45 TopologyPreserving,
47}
48
49pub fn simplify_linestring(
87 line: &LineString,
88 tolerance: f64,
89 method: SimplifyMethod,
90) -> Result<LineString> {
91 if line.coords.is_empty() {
92 return Err(AlgorithmError::EmptyInput {
93 operation: "simplify_linestring",
94 });
95 }
96
97 if tolerance < 0.0 {
98 return Err(AlgorithmError::InvalidParameter {
99 parameter: "tolerance",
100 message: "tolerance must be non-negative".to_string(),
101 });
102 }
103
104 match method {
105 SimplifyMethod::DouglasPeucker => dp_simplify(line, tolerance),
106 SimplifyMethod::VisvalingamWhyatt => simplify_visvalingam_whyatt(line, tolerance),
107 SimplifyMethod::TopologyPreserving => simplify_topology_preserving(line, tolerance),
108 }
109}
110
111pub fn simplify_polygon(
130 polygon: &Polygon,
131 tolerance: f64,
132 method: SimplifyMethod,
133) -> Result<Polygon> {
134 if polygon.exterior.coords.len() < 4 {
135 return Err(AlgorithmError::InsufficientData {
136 operation: "simplify_polygon",
137 message: "polygon exterior must have at least 4 coordinates".to_string(),
138 });
139 }
140
141 let simplified_exterior = simplify_linestring(&polygon.exterior, tolerance, method)?;
143
144 if simplified_exterior.coords.len() < 4 {
146 return Err(AlgorithmError::GeometryError {
147 message: "simplified exterior would have fewer than 4 points".to_string(),
148 });
149 }
150
151 let mut simplified_interiors = Vec::with_capacity(polygon.interiors.len());
153 for interior in &polygon.interiors {
154 let simplified_interior = simplify_linestring(interior, tolerance, method)?;
155
156 if simplified_interior.coords.len() >= 4 {
158 simplified_interiors.push(simplified_interior);
159 }
160 }
161
162 Polygon::new(simplified_exterior, simplified_interiors).map_err(AlgorithmError::Core)
163}
164
165fn simplify_visvalingam_whyatt(line: &LineString, tolerance: f64) -> Result<LineString> {
171 if line.coords.len() <= 2 {
172 return Ok(line.clone());
173 }
174
175 let n = line.coords.len();
176 let mut keep = vec![true; n];
177 let mut areas = vec![f64::INFINITY; n];
178
179 keep[0] = true;
181 keep[n - 1] = true;
182
183 for i in 1..n - 1 {
185 areas[i] = triangle_area(&line.coords[i - 1], &line.coords[i], &line.coords[i + 1]);
186 }
187
188 let mut removed_count = 0;
190 let target_count = n - 2; while removed_count < target_count {
193 let mut min_area = f64::INFINITY;
195 let mut min_idx = 0;
196
197 for i in 1..n - 1 {
198 if keep[i] && areas[i] < min_area {
199 min_area = areas[i];
200 min_idx = i;
201 }
202 }
203
204 if min_area > tolerance {
206 break;
207 }
208
209 keep[min_idx] = false;
211 removed_count += 1;
212
213 let prev = find_prev_kept(&keep, min_idx);
215 let next = find_next_kept(&keep, min_idx);
216
217 if let (Some(p), Some(n)) = (prev, next) {
218 if p > 0 {
220 if let Some(pp) = find_prev_kept(&keep, p) {
221 areas[p] = triangle_area(&line.coords[pp], &line.coords[p], &line.coords[n]);
222 }
223 }
224
225 if n < line.coords.len() - 1 {
227 if let Some(nn) = find_next_kept(&keep, n) {
228 areas[n] = triangle_area(&line.coords[p], &line.coords[n], &line.coords[nn]);
229 }
230 }
231 }
232 }
233
234 let simplified_coords: Vec<Coordinate> = line
236 .coords
237 .iter()
238 .zip(keep.iter())
239 .filter(|&(_, &k)| k)
240 .map(|(c, _)| *c)
241 .collect();
242
243 LineString::new(simplified_coords).map_err(AlgorithmError::Core)
244}
245
246fn simplify_topology_preserving(line: &LineString, tolerance: f64) -> Result<LineString> {
251 let simplified = dp_simplify(line, tolerance)?;
253
254 if has_self_intersection(&simplified) {
256 let conservative_tolerance = tolerance * 0.5;
258 if conservative_tolerance < 1e-10 {
259 return Ok(line.clone());
261 }
262 return simplify_topology_preserving(line, conservative_tolerance);
263 }
264
265 Ok(simplified)
266}
267
268fn triangle_area(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate) -> f64 {
270 let ab_x = p2.x - p1.x;
272 let ab_y = p2.y - p1.y;
273 let ac_x = p3.x - p1.x;
274 let ac_y = p3.y - p1.y;
275
276 ((ab_x * ac_y - ab_y * ac_x).abs()) / 2.0
277}
278
279fn find_prev_kept(keep: &[bool], from: usize) -> Option<usize> {
281 if from == 0 {
282 return None;
283 }
284
285 (0..from).rev().find(|&i| keep[i])
286}
287
288fn find_next_kept(keep: &[bool], from: usize) -> Option<usize> {
290 for i in from + 1..keep.len() {
291 if keep[i] {
292 return Some(i);
293 }
294 }
295
296 None
297}
298
299fn has_self_intersection(line: &LineString) -> bool {
301 let n = line.coords.len();
302 if n < 4 {
303 return false; }
305
306 for i in 0..n - 1 {
308 for j in i + 2..n - 1 {
309 if j == i + 1 || (i == 0 && j == n - 2) {
311 continue;
312 }
313
314 if segments_intersect(
315 &line.coords[i],
316 &line.coords[i + 1],
317 &line.coords[j],
318 &line.coords[j + 1],
319 ) {
320 return true;
321 }
322 }
323 }
324
325 false
326}
327
328fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
330 let d1 = direction(p3, p4, p1);
331 let d2 = direction(p3, p4, p2);
332 let d3 = direction(p1, p2, p3);
333 let d4 = direction(p1, p2, p4);
334
335 if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
336 && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
337 {
338 return true;
339 }
340
341 if d1.abs() < f64::EPSILON && on_segment(p3, p1, p4) {
343 return true;
344 }
345 if d2.abs() < f64::EPSILON && on_segment(p3, p2, p4) {
346 return true;
347 }
348 if d3.abs() < f64::EPSILON && on_segment(p1, p3, p2) {
349 return true;
350 }
351 if d4.abs() < f64::EPSILON && on_segment(p1, p4, p2) {
352 return true;
353 }
354
355 false
356}
357
358fn direction(a: &Coordinate, b: &Coordinate, p: &Coordinate) -> f64 {
360 (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)
361}
362
363fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
365 q.x <= p.x.max(r.x) && q.x >= p.x.min(r.x) && q.y <= p.y.max(r.y) && q.y >= p.y.min(r.y)
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 fn create_zigzag_line() -> Result<LineString> {
373 let coords = vec![
374 Coordinate::new_2d(0.0, 0.0),
375 Coordinate::new_2d(1.0, 1.0),
376 Coordinate::new_2d(2.0, 0.0),
377 Coordinate::new_2d(3.0, 1.0),
378 Coordinate::new_2d(4.0, 0.0),
379 ];
380 LineString::new(coords).map_err(|e| AlgorithmError::Core(e))
381 }
382
383 fn create_square_polygon() -> Result<Polygon> {
384 let coords = vec![
385 Coordinate::new_2d(0.0, 0.0),
386 Coordinate::new_2d(10.0, 0.0),
387 Coordinate::new_2d(10.0, 10.0),
388 Coordinate::new_2d(0.0, 10.0),
389 Coordinate::new_2d(0.0, 0.0),
390 ];
391 let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
392 Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
393 }
394
395 #[test]
396 fn test_simplify_linestring_douglas_peucker() {
397 let line = create_zigzag_line();
398 assert!(line.is_ok());
399
400 if let Ok(l) = line {
401 let result = simplify_linestring(&l, 0.5, SimplifyMethod::DouglasPeucker);
402 assert!(result.is_ok());
403
404 if let Ok(simplified) = result {
405 assert!(simplified.len() >= 2);
407 assert!(simplified.len() <= l.len());
408 }
409 }
410 }
411
412 #[test]
413 fn test_simplify_linestring_visvalingam() {
414 let line = create_zigzag_line();
415 assert!(line.is_ok());
416
417 if let Ok(l) = line {
418 let result = simplify_linestring(&l, 0.5, SimplifyMethod::VisvalingamWhyatt);
419 assert!(result.is_ok());
420
421 if let Ok(simplified) = result {
422 assert!(simplified.len() >= 2);
423 assert!(simplified.len() <= l.len());
424 }
425 }
426 }
427
428 #[test]
429 fn test_simplify_linestring_topology_preserving() {
430 let line = create_zigzag_line();
431 assert!(line.is_ok());
432
433 if let Ok(l) = line {
434 let result = simplify_linestring(&l, 0.5, SimplifyMethod::TopologyPreserving);
435 assert!(result.is_ok());
436
437 if let Ok(simplified) = result {
438 assert!(!has_self_intersection(&simplified));
440 }
441 }
442 }
443
444 #[test]
445 fn test_simplify_polygon() {
446 let poly = create_square_polygon();
447 assert!(poly.is_ok());
448
449 if let Ok(p) = poly {
450 let result = simplify_polygon(&p, 0.1, SimplifyMethod::DouglasPeucker);
451 assert!(result.is_ok());
452
453 if let Ok(simplified) = result {
454 assert_eq!(simplified.exterior.len(), 5);
456 }
457 }
458 }
459
460 #[test]
461 fn test_triangle_area() {
462 let p1 = Coordinate::new_2d(0.0, 0.0);
463 let p2 = Coordinate::new_2d(2.0, 0.0);
464 let p3 = Coordinate::new_2d(1.0, 2.0);
465
466 let area = triangle_area(&p1, &p2, &p3);
467 assert!((area - 2.0).abs() < 1e-10); }
469
470 #[test]
471 fn test_segments_intersect() {
472 let p1 = Coordinate::new_2d(0.0, 0.0);
474 let p2 = Coordinate::new_2d(2.0, 2.0);
475 let p3 = Coordinate::new_2d(0.0, 2.0);
476 let p4 = Coordinate::new_2d(2.0, 0.0);
477
478 assert!(segments_intersect(&p1, &p2, &p3, &p4));
479 }
480
481 #[test]
482 fn test_segments_no_intersect() {
483 let p1 = Coordinate::new_2d(0.0, 0.0);
485 let p2 = Coordinate::new_2d(1.0, 0.0);
486 let p3 = Coordinate::new_2d(0.0, 1.0);
487 let p4 = Coordinate::new_2d(1.0, 1.0);
488
489 assert!(!segments_intersect(&p1, &p2, &p3, &p4));
490 }
491
492 #[test]
493 fn test_has_self_intersection_false() {
494 let coords = vec![
495 Coordinate::new_2d(0.0, 0.0),
496 Coordinate::new_2d(1.0, 0.0),
497 Coordinate::new_2d(1.0, 1.0),
498 Coordinate::new_2d(0.0, 1.0),
499 ];
500 let line = LineString::new(coords);
501 assert!(line.is_ok());
502
503 if let Ok(l) = line {
504 assert!(!has_self_intersection(&l));
505 }
506 }
507
508 #[test]
509 fn test_simplify_empty_line() {
510 let coords: Vec<Coordinate> = vec![];
511 let line = LineString::new(coords);
512
513 assert!(line.is_err());
515 }
516
517 #[test]
518 fn test_simplify_negative_tolerance() {
519 let line = create_zigzag_line();
520 assert!(line.is_ok());
521
522 if let Ok(l) = line {
523 let result = simplify_linestring(&l, -1.0, SimplifyMethod::DouglasPeucker);
524 assert!(result.is_err());
525 }
526 }
527
528 #[test]
529 fn test_find_prev_next_kept() {
530 let keep = vec![true, false, true, false, true];
531
532 assert_eq!(find_prev_kept(&keep, 2), Some(0));
533 assert_eq!(find_next_kept(&keep, 2), Some(4));
534 assert_eq!(find_prev_kept(&keep, 0), None);
535 assert_eq!(find_next_kept(&keep, 4), None);
536 }
537}