-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
847: Add interpolated coordinate feature r=michaelkirk a=urschrei - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- This PR adds "densification" methods to all geometries with linear components (Line, LineString, Polygon, MultiLinestring, MultiPolygon). The algorithm works on line segments, "densifying" them by adding additional segments with length `max_distance` between the existing line endpoints, producing a new `LineString`. Actual logic: https://github.com/georust/geo/pull/847/files#diff-1e6b7ec38d282e44d7225459b81fa17d1ca9b7529763d44d0550c303077f6826R29-R46 Fixes #846 Co-authored-by: Stephan Hügel <shugel@tcd.ie> Co-authored-by: Stephan Hügel <urschrei@gmail.com>
- Loading branch information
Showing
5 changed files
with
248 additions
and
2 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
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,237 @@ | ||
use crate::{ | ||
CoordFloat, EuclideanLength, Line, LineInterpolatePoint, LineString, MultiLineString, | ||
MultiPolygon, Point, Polygon, Rect, Triangle, | ||
}; | ||
|
||
/// Return a new linear geometry containing both existing and new interpolated coordinates with | ||
/// a maximum distance of `max_distance` between them. | ||
/// | ||
/// Note: `max_distance` must be greater than 0. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// use geo::{coord, Line, LineString}; | ||
/// use geo::Densify; | ||
/// | ||
/// let line: Line<f64> = Line::new(coord! {x: 0.0, y: 6.0}, coord! {x: 1.0, y: 8.0}); | ||
/// let correct: LineString<f64> = vec![[0.0, 6.0], [0.5, 7.0], [1.0, 8.0]].into(); | ||
/// let max_dist = 2.0; | ||
/// let densified = line.densify(max_dist); | ||
/// assert_eq!(densified, correct); | ||
///``` | ||
pub trait Densify<F: CoordFloat> { | ||
type Output; | ||
|
||
fn densify(&self, max_distance: F) -> Self::Output; | ||
} | ||
|
||
// Helper for densification trait | ||
fn densify_line<T: CoordFloat>(line: Line<T>, container: &mut Vec<Point<T>>, max_distance: T) { | ||
assert!(max_distance > T::zero()); | ||
container.push(line.start_point()); | ||
let num_segments = (line.euclidean_length() / max_distance) | ||
.ceil() | ||
.to_u64() | ||
.unwrap(); | ||
// distance "unit" for this line segment | ||
let frac = T::one() / T::from(num_segments).unwrap(); | ||
for segment_idx in 1..num_segments { | ||
let ratio = frac * T::from(segment_idx).unwrap(); | ||
let interpolated_point = line | ||
.line_interpolate_point(ratio) | ||
.expect("ratio should be between 0..1"); | ||
container.push(interpolated_point); | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for MultiPolygon<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = MultiPolygon<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
MultiPolygon::new( | ||
self.iter() | ||
.map(|polygon| polygon.densify(max_distance)) | ||
.collect(), | ||
) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for Polygon<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = Polygon<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
let densified_exterior = self.exterior().densify(max_distance); | ||
let densified_interiors = self | ||
.interiors() | ||
.iter() | ||
.map(|ring| ring.densify(max_distance)) | ||
.collect(); | ||
Polygon::new(densified_exterior, densified_interiors) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for MultiLineString<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = MultiLineString<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
MultiLineString::new( | ||
self.iter() | ||
.map(|linestring| linestring.densify(max_distance)) | ||
.collect(), | ||
) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for LineString<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = LineString<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
let mut new_line = vec![]; | ||
self.lines() | ||
.for_each(|line| densify_line(line, &mut new_line, max_distance)); | ||
// we're done, push the last coordinate on to finish | ||
new_line.push(self.points().last().unwrap()); | ||
LineString::from(new_line) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for Line<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = LineString<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
let mut new_line = vec![]; | ||
densify_line(*self, &mut new_line, max_distance); | ||
// we're done, push the last coordinate on to finish | ||
new_line.push(self.end_point()); | ||
LineString::from(new_line) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for Triangle<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = Polygon<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
self.to_polygon().densify(max_distance) | ||
} | ||
} | ||
|
||
impl<T> Densify<T> for Rect<T> | ||
where | ||
T: CoordFloat, | ||
Line<T>: EuclideanLength<T>, | ||
LineString<T>: EuclideanLength<T>, | ||
{ | ||
type Output = Polygon<T>; | ||
|
||
fn densify(&self, max_distance: T) -> Self::Output { | ||
self.to_polygon().densify(max_distance) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::{coord, Coordinate}; | ||
|
||
#[test] | ||
fn test_polygon_densify() { | ||
let linestring: LineString<f64> = | ||
vec![[-5.0, 0.0], [0.0, 5.0], [5.0, 0.0], [-5.0, 0.0]].into(); | ||
let interior: LineString<f64> = | ||
vec![[-3.0, 0.0], [0.0, 3.0], [3.0, 0.0], [-3.0, 0.0]].into(); | ||
let polygon = Polygon::new(linestring, vec![interior]); | ||
let correct_ext: LineString<f64> = LineString(vec![ | ||
Coordinate { x: -5.0, y: 0.0 }, | ||
Coordinate { x: -3.75, y: 1.25 }, | ||
Coordinate { x: -2.5, y: 2.5 }, | ||
Coordinate { x: -1.25, y: 3.75 }, | ||
Coordinate { x: 0.0, y: 5.0 }, | ||
Coordinate { x: 1.25, y: 3.75 }, | ||
Coordinate { x: 2.5, y: 2.5 }, | ||
Coordinate { x: 3.75, y: 1.25 }, | ||
Coordinate { x: 5.0, y: 0.0 }, | ||
Coordinate { x: 3.0, y: 0.0 }, | ||
Coordinate { x: 1.0, y: 0.0 }, | ||
Coordinate { | ||
x: -1.0000000000000009, | ||
y: 0.0, | ||
}, | ||
Coordinate { x: -3.0, y: 0.0 }, | ||
Coordinate { x: -5.0, y: 0.0 }, | ||
]); | ||
let correct_int: LineString<f64> = LineString(vec![ | ||
Coordinate { x: -3.0, y: 0.0 }, | ||
Coordinate { x: -2.0, y: 1.0 }, | ||
Coordinate { x: -1.0, y: 2.0 }, | ||
Coordinate { x: 0.0, y: 3.0 }, | ||
Coordinate { x: 1.0, y: 2.0 }, | ||
Coordinate { x: 2.0, y: 1.0 }, | ||
Coordinate { x: 3.0, y: 0.0 }, | ||
Coordinate { x: 1.0, y: 0.0 }, | ||
Coordinate { x: -1.0, y: 0.0 }, | ||
Coordinate { x: -3.0, y: 0.0 }, | ||
]); | ||
let correct_polygon = Polygon::new(correct_ext, vec![correct_int]); | ||
let max_dist = 2.0; | ||
let densified = polygon.densify(max_dist); | ||
assert_eq!(densified, correct_polygon); | ||
} | ||
|
||
#[test] | ||
fn test_linestring_densify() { | ||
let linestring: LineString<f64> = | ||
vec![[-1.0, 0.0], [0.0, 0.0], [0.0, 6.0], [1.0, 8.0]].into(); | ||
let correct: LineString<f64> = vec![ | ||
[-1.0, 0.0], | ||
[0.0, 0.0], | ||
[0.0, 2.0], | ||
[0.0, 4.0], | ||
[0.0, 6.0], | ||
[0.5, 7.0], | ||
[1.0, 8.0], | ||
] | ||
.into(); | ||
let max_dist = 2.0; | ||
let densified = linestring.densify(max_dist); | ||
assert_eq!(densified, correct); | ||
} | ||
|
||
#[test] | ||
fn test_line_densify() { | ||
let line: Line<f64> = Line::new(coord! {x: 0.0, y: 6.0}, coord! {x: 1.0, y: 8.0}); | ||
let correct: LineString<f64> = vec![[0.0, 6.0], [0.5, 7.0], [1.0, 8.0]].into(); | ||
let max_dist = 2.0; | ||
let densified = line.densify(max_dist); | ||
assert_eq!(densified, correct); | ||
} | ||
} |
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
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