-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8bb3fb4
commit 162542b
Showing
14 changed files
with
4,094 additions
and
8,085 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,46 @@ | ||
precision,bits,rate_of_improvement,uncorrected_error,corrected_error | ||
4,4,88.86,0.10,0.00 | ||
4,5,114.84,0.19,0.00 | ||
4,6,321.85,0.21,0.00 | ||
5,4,374.00,0.10,0.00 | ||
5,5,171.85,0.15,0.00 | ||
5,6,91.44,0.15,0.00 | ||
6,4,237.23,0.09,0.00 | ||
6,5,88.95,0.10,0.00 | ||
6,6,140.74,0.12,0.00 | ||
7,4,95.07,0.06,0.00 | ||
7,5,105.34,0.08,0.00 | ||
7,6,191.71,0.10,0.00 | ||
8,4,107.41,0.04,0.00 | ||
8,5,138.87,0.05,0.00 | ||
8,6,196.99,0.06,0.00 | ||
9,4,217.83,0.02,0.00 | ||
9,5,330.99,0.03,0.00 | ||
9,6,399.49,0.04,0.00 | ||
10,4,68.54,0.02,0.00 | ||
10,5,142.40,0.02,0.00 | ||
10,6,208.17,0.03,0.00 | ||
11,4,192.85,0.01,0.00 | ||
11,5,154.71,0.02,0.00 | ||
11,6,250.10,0.02,0.00 | ||
12,4,196.71,0.01,0.00 | ||
12,5,308.11,0.01,0.00 | ||
12,6,390.63,0.02,0.00 | ||
13,4,244.59,0.01,0.00 | ||
13,5,242.39,0.01,0.00 | ||
13,6,448.92,0.01,0.00 | ||
14,4,346.10,0.01,0.00 | ||
14,5,278.19,0.01,0.00 | ||
14,6,629.92,0.01,0.00 | ||
15,4,529.22,0.00,0.00 | ||
15,5,275.43,0.01,0.00 | ||
15,6,1108.07,0.01,0.00 | ||
16,4,699.21,0.00,0.00 | ||
16,5,1037.62,0.01,0.00 | ||
16,6,740.38,0.01,0.00 | ||
17,4,634.79,0.00,0.00 | ||
17,5,718.83,0.00,0.00 | ||
17,6,490.78,0.01,0.00 | ||
18,4,549.52,0.00,0.00 | ||
18,5,447.08,0.00,0.00 | ||
18,6,1994.33,0.01,0.00 | ||
4,4,34.54,0.10,0.00 | ||
4,5,12.91,0.19,0.01 | ||
4,6,52.81,0.21,0.00 | ||
5,4,60.29,0.10,0.00 | ||
5,5,45.72,0.15,0.00 | ||
5,6,31.58,0.15,0.00 | ||
6,4,208.68,0.09,0.00 | ||
6,5,31.29,0.10,0.00 | ||
6,6,44.81,0.12,0.00 | ||
7,4,38.90,0.07,0.00 | ||
7,5,116.65,0.09,0.00 | ||
7,6,111.08,0.12,0.00 | ||
8,4,147.23,0.06,0.00 | ||
8,5,155.03,0.08,0.00 | ||
8,6,56.54,0.11,0.00 | ||
9,4,78.23,0.06,0.00 | ||
9,5,50.94,0.08,0.00 | ||
9,6,62.57,0.10,0.00 | ||
10,4,235.59,0.05,0.00 | ||
10,5,72.32,0.07,0.00 | ||
10,6,239.29,0.10,0.00 | ||
11,4,66.73,0.04,0.00 | ||
11,5,272.52,0.07,0.00 | ||
11,6,269.94,0.09,0.00 | ||
12,4,165.10,0.05,0.00 | ||
12,5,414.81,0.07,0.00 | ||
12,6,1206.77,0.09,0.00 | ||
13,4,292.16,0.05,0.00 | ||
13,5,489.75,0.07,0.00 | ||
13,6,469.96,0.09,0.00 | ||
14,4,573.18,0.05,0.00 | ||
14,5,1510.12,0.07,0.00 | ||
14,6,1476.07,0.09,0.00 | ||
15,4,1554.05,0.05,0.00 | ||
15,5,1751.94,0.07,0.00 | ||
15,6,1665.32,0.09,0.00 | ||
16,4,398.07,0.05,0.00 | ||
16,5,644.39,0.07,0.00 | ||
16,6,622.48,0.09,0.00 | ||
17,4,815.35,0.05,0.00 | ||
17,5,1391.01,0.07,0.00 | ||
17,6,1709.16,0.09,0.00 | ||
18,4,1876.76,0.05,0.00 | ||
18,5,2064.07,0.07,0.00 | ||
18,6,2135.24,0.09,0.00 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
//! Submodule providing the Ramer-Douglas-Peucker algorithm for line simplification. | ||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] | ||
pub struct Point { | ||
x: f64, | ||
y: f64, | ||
} | ||
|
||
impl From<(f64, f64)> for Point { | ||
fn from((x, y): (f64, f64)) -> Self { | ||
Self { x, y } | ||
} | ||
} | ||
|
||
impl Point { | ||
fn distance_to(&self, other: &Point) -> f64 { | ||
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt() | ||
} | ||
|
||
/// Get the x-coordinate of the point | ||
pub fn x(&self) -> f64 { | ||
self.x | ||
} | ||
|
||
/// Get the y-coordinate of the point | ||
pub fn y(&self) -> f64 { | ||
self.y | ||
} | ||
} | ||
|
||
// Compute the perpendicular distance from point 'p' to the line segment from 'start' to 'end' | ||
fn perpendicular_distance(point: &Point, start: &Point, end: &Point) -> f64 { | ||
let line_length = start.distance_to(end); | ||
if line_length == 0.0 { | ||
return point.distance_to(start); | ||
} | ||
|
||
// Compute the projection of 'p' onto the line segment | ||
let t = ((point.x - start.x) * (end.x - start.x) + (point.y - start.y) * (end.y - start.y)) | ||
/ line_length.powi(2); | ||
|
||
// If the projection is outside the line segment, return the distance to the closest endpoint | ||
if t < 0.0 { | ||
point.distance_to(start) | ||
} else if t > 1.0 { | ||
point.distance_to(end) | ||
} else { | ||
// Otherwise, return the distance to the line | ||
let projection = Point { | ||
x: start.x + t * (end.x - start.x), | ||
y: start.y + t * (end.y - start.y), | ||
}; | ||
point.distance_to(&projection) | ||
} | ||
} | ||
|
||
/// Recursive function for the Ramer-Douglas-Peucker algorithm | ||
/// | ||
/// # Arguments | ||
/// * `points` - The list of points to simplify | ||
/// * `tolerance` - The maximum distance from the simplified line | ||
/// * `max_points` - The maximum number of points to return | ||
pub fn rdp(points: &[Point], tolerance: f64, max_points: usize) -> Vec<Point> { | ||
if points.len() < 2 { | ||
return points.to_vec(); | ||
} | ||
|
||
// Find the point with the maximum distance from the line segment connecting the first and last points | ||
let (index, max_distance) = points | ||
.iter() | ||
.enumerate() | ||
.skip(1) | ||
.take(points.len() - 2) | ||
.map(|(i, point)| { | ||
( | ||
i, | ||
perpendicular_distance(point, &points[0], &points[points.len() - 1]), | ||
) | ||
}) | ||
.fold((0, 0.0), |(max_index, max_dist), (i, dist)| { | ||
if dist > max_dist { | ||
(i, dist) | ||
} else { | ||
(max_index, max_dist) | ||
} | ||
}); | ||
|
||
// If the maximum distance is greater than the tolerance, recursively simplify | ||
if max_distance > tolerance && max_points > 2 { | ||
let mut result1 = rdp(&points[..=index], tolerance, max_points / 2); | ||
let mut result2 = rdp(&points[index..], tolerance, max_points / 2); | ||
|
||
// Combine the results, removing the duplicate point at index | ||
result1.pop(); | ||
result1.append(&mut result2); | ||
|
||
result1 | ||
} else { | ||
// If no point is farther than the tolerance, return just the endpoints | ||
vec![points[0], points[points.len() - 1]] | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_no_simplification() { | ||
let points = vec![ | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 1.0, y: 0.0 }, | ||
Point { x: 2.0, y: 0.0 }, | ||
Point { x: 3.0, y: 0.0 }, | ||
Point { x: 4.0, y: 0.0 }, | ||
]; | ||
|
||
let tolerance = 0.001; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// With a low tolerance, no simplification should occur | ||
assert_eq!(simplified.len(), 2); | ||
assert_eq!(simplified, [points[0], points[points.len() - 1]]); | ||
} | ||
|
||
#[test] | ||
fn test_full_simplification() { | ||
let points = vec![ | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 1.0, y: 0.0 }, | ||
Point { x: 2.0, y: 0.0 }, | ||
Point { x: 3.0, y: 0.0 }, | ||
Point { x: 4.0, y: 0.0 }, | ||
]; | ||
|
||
let tolerance = 1.0; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// All points are on a straight line, so the algorithm should return just the endpoints | ||
assert_eq!(simplified.len(), 2); | ||
assert_eq!(simplified, vec![points[0], points[points.len() - 1]]); | ||
} | ||
|
||
#[test] | ||
fn test_partial_simplification() { | ||
let points = vec![ | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 1.0, y: 0.1 }, | ||
Point { x: 2.0, y: -0.1 }, | ||
Point { x: 3.0, y: 5.0 }, | ||
Point { x: 4.0, y: 6.0 }, | ||
Point { x: 5.0, y: 7.0 }, | ||
]; | ||
|
||
let tolerance = 1.0; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
assert_eq!(simplified.len(), 4); | ||
assert_eq!(simplified, vec![points[0], points[2], points[3], points[5]]); | ||
} | ||
|
||
#[test] | ||
fn test_high_tolerance() { | ||
let points = vec![ | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 1.0, y: 2.0 }, | ||
Point { x: 2.0, y: 1.0 }, | ||
Point { x: 3.0, y: 4.0 }, | ||
Point { x: 4.0, y: 3.0 }, | ||
Point { x: 5.0, y: 6.0 }, | ||
]; | ||
|
||
let tolerance = 5.0; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// With a high tolerance, the algorithm should return just the first and last points | ||
assert_eq!(simplified.len(), 2); | ||
assert_eq!(simplified, vec![points[0], points[points.len() - 1]]); | ||
} | ||
|
||
#[test] | ||
fn test_single_point() { | ||
let points = vec![Point { x: 0.0, y: 0.0 }]; | ||
|
||
let tolerance = 0.1; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// A single point cannot be simplified | ||
assert_eq!(simplified.len(), 1); | ||
assert_eq!(simplified, points); | ||
} | ||
|
||
#[test] | ||
fn test_two_points() { | ||
let points = vec![Point { x: 0.0, y: 0.0 }, Point { x: 1.0, y: 1.0 }]; | ||
|
||
let tolerance = 0.1; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// Two points cannot be simplified further | ||
assert_eq!(simplified.len(), 2); | ||
assert_eq!(simplified, points); | ||
} | ||
|
||
#[test] | ||
fn test_identical_points() { | ||
let points = vec![ | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 0.0, y: 0.0 }, | ||
Point { x: 0.0, y: 0.0 }, | ||
]; | ||
|
||
let tolerance = 0.1; | ||
let simplified = rdp(&points, tolerance, 10); | ||
|
||
// If all points are the same, the algorithm should return just one point | ||
assert_eq!(simplified.len(), 2); | ||
assert_eq!(simplified, vec![points[0], points[0]]); | ||
} | ||
} |
Oops, something went wrong.