Skip to content

Commit

Permalink
Merge #847
Browse files Browse the repository at this point in the history
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
3 people committed Jun 18, 2022
2 parents 3c04f6d + 0336b60 commit 8db1830
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 2 deletions.
4 changes: 4 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes

## Unreleased
* Add densification algorithm for linear geometry components
* <https://github.com/georust/geo/pull/847>

## 0.21.0

* Boolean operations for `Polygon`s and `MultiPolygon`s: intersect, union, xor,
Expand Down
237 changes: 237 additions & 0 deletions geo/src/algorithm/densify.rs
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);
}
}
4 changes: 2 additions & 2 deletions geo/src/algorithm/line_interpolate_point.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::coords_iter::CoordsIter;
use std::ops::AddAssign;

use crate::{CoordFloat, EuclideanLength, Line, LineString, Point};
use std::ops::AddAssign;

/// Returns an option of the point that lies a given fraction along the line.
///
Expand Down Expand Up @@ -109,6 +108,7 @@ where

#[cfg(test)]
mod test {

use super::*;
use crate::{coord, point};
use crate::{ClosestPoint, LineLocatePoint};
Expand Down
4 changes: 4 additions & 0 deletions geo/src/algorithm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,7 @@ pub mod sweep;
/// Boolean Ops;
pub mod bool_ops;
pub use bool_ops::{BooleanOps, OpType};

/// Densify linear geometry components
pub mod densify;
pub use densify::Densify;
1 change: 1 addition & 0 deletions geo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
//! - **[`HaversineIntermediate`](HaversineIntermediate)**:
//! - **[`Proj`](Proj)**: Project geometries with the `proj` crate (requires the `use-proj` feature)
//! - **[`ChaikinSmoothing`](ChaikinSmoothing)**: Smoothen `LineString`, `Polygon`, `MultiLineString` and `MultiPolygon` using Chaikins algorithm.
//! - **[`Densify`](Densify)**: Densify linear geometry components by interpolating points
//!
//! # Features
//!
Expand Down

0 comments on commit 8db1830

Please sign in to comment.