Skip to content

Commit

Permalink
Implements Ramer-Douglas-Peucker simplification algorithm (TheAlgorit…
Browse files Browse the repository at this point in the history
…hms#710)

* Implements Ramer-Douglas-Peucker simplification algorithm

* Apply requested changes

* Cargo format

* Cargo clippy

* Apply requested changes

* style: use slice

---------

Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com>
  • Loading branch information
2 people authored and TruongNhanNguyen committed May 20, 2024
1 parent c4c5068 commit db54413
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
* [Jarvis Scan](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/jarvis_scan.rs)
* [Point](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/point.rs)
* [Polygon Points](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/polygon_points.rs)
* [Ramer-Douglas-Peucker](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/ramer_douglas_peucker.rs)
* [Segment](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/segment.rs)
* Graph
* [Astar](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/astar.rs)
Expand Down
2 changes: 2 additions & 0 deletions src/geometry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ mod graham_scan;
mod jarvis_scan;
mod point;
mod polygon_points;
mod ramer_douglas_peucker;
mod segment;

pub use self::closest_points::closest_points;
pub use self::graham_scan::graham_scan;
pub use self::jarvis_scan::jarvis_march;
pub use self::point::Point;
pub use self::polygon_points::lattice_points;
pub use self::ramer_douglas_peucker::ramer_douglas_peucker;
pub use self::segment::Segment;
115 changes: 115 additions & 0 deletions src/geometry/ramer_douglas_peucker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use crate::geometry::Point;

pub fn ramer_douglas_peucker(points: &[Point], epsilon: f64) -> Vec<Point> {
if points.len() < 3 {
return points.to_vec();
}
let mut dmax = 0.0;
let mut index = 0;
let end = points.len() - 1;

for i in 1..end {
let d = perpendicular_distance(&points[i], &points[0], &points[end]);
if d > dmax {
index = i;
dmax = d;
}
}

if dmax > epsilon {
let mut results = ramer_douglas_peucker(&points[..=index], epsilon);
results.pop();
results.extend(ramer_douglas_peucker(&points[index..], epsilon));
results
} else {
vec![points[0].clone(), points[end].clone()]
}
}

fn perpendicular_distance(p: &Point, a: &Point, b: &Point) -> f64 {
let num = (b.y - a.y) * p.x - (b.x - a.x) * p.y + b.x * a.y - b.y * a.x;
let den = a.euclidean_distance(b);
num.abs() / den
}

#[cfg(test)]
mod tests {
use super::*;

macro_rules! test_perpendicular_distance {
($($name:ident: $test_case:expr,)*) => {
$(
#[test]
fn $name() {
let (p, a, b, expected) = $test_case;
assert_eq!(perpendicular_distance(&p, &a, &b), expected);
assert_eq!(perpendicular_distance(&p, &b, &a), expected);
}
)*
};
}

test_perpendicular_distance! {
basic: (Point::new(4.0, 0.0), Point::new(0.0, 0.0), Point::new(0.0, 3.0), 4.0),
basic_shifted_1: (Point::new(4.0, 1.0), Point::new(0.0, 1.0), Point::new(0.0, 4.0), 4.0),
basic_shifted_2: (Point::new(2.0, 1.0), Point::new(-2.0, 1.0), Point::new(-2.0, 4.0), 4.0),
}

#[test]
fn test_ramer_douglas_peucker_polygon() {
let a = Point::new(0.0, 0.0);
let b = Point::new(1.0, 0.0);
let c = Point::new(2.0, 0.0);
let d = Point::new(2.0, 1.0);
let e = Point::new(2.0, 2.0);
let f = Point::new(1.0, 2.0);
let g = Point::new(0.0, 2.0);
let h = Point::new(0.0, 1.0);
let polygon = vec![
a.clone(),
b,
c.clone(),
d,
e.clone(),
f,
g.clone(),
h.clone(),
];
let epsilon = 0.7;
let result = ramer_douglas_peucker(&polygon, epsilon);
assert_eq!(result, vec![a, c, e, g, h]);
}

#[test]
fn test_ramer_douglas_peucker_polygonal_chain() {
let a = Point::new(0., 0.);
let b = Point::new(2., 0.5);
let c = Point::new(3., 3.);
let d = Point::new(6., 3.);
let e = Point::new(8., 4.);

let points = vec![a.clone(), b, c, d, e.clone()];

let epsilon = 3.; // The epsilon is quite large, so the result will be a single line
let result = ramer_douglas_peucker(&points, epsilon);
assert_eq!(result, vec![a, e]);
}

#[test]
fn test_less_than_three_points() {
let a = Point::new(0., 0.);
let b = Point::new(1., 1.);

let epsilon = 0.1;

assert_eq!(ramer_douglas_peucker(&[], epsilon), vec![]);
assert_eq!(
ramer_douglas_peucker(&[a.clone()], epsilon),
vec![a.clone()]
);
assert_eq!(
ramer_douglas_peucker(&[a.clone(), b.clone()], epsilon),
vec![a, b]
);
}
}

0 comments on commit db54413

Please sign in to comment.