From 717e25e870d6e0eaeac639c513ee4ef0ed8fe763 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 25 Sep 2024 16:55:28 -0700 Subject: [PATCH] Unify `Densify` trait across metric spaces Just like #1228, but for Densify rather than Length. This enables Geodesic, Haversine, Euclidean, and Rhumb Densification. This required implementing InterpolatePoint for Euclidean. Adjacent work: deprecated legacy DensifyHaversine linestring_segment is now implemented in terms of the new Densify NOTE: linestring_segment would be a good future candidate for a similar unification across all the metric spaces --- geo/src/algorithm/densify.rs | 263 --------------- geo/src/algorithm/densify_haversine.rs | 9 + geo/src/algorithm/line_measures/densify.rs | 314 ++++++++++++++++++ .../line_measures/metric_spaces/euclidean.rs | 28 +- geo/src/algorithm/line_measures/mod.rs | 3 + geo/src/algorithm/linestring_segment.rs | 18 +- geo/src/algorithm/mod.rs | 7 +- 7 files changed, 362 insertions(+), 280 deletions(-) delete mode 100644 geo/src/algorithm/densify.rs create mode 100644 geo/src/algorithm/line_measures/densify.rs diff --git a/geo/src/algorithm/densify.rs b/geo/src/algorithm/densify.rs deleted file mode 100644 index 3a930f904..000000000 --- a/geo/src/algorithm/densify.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::{ - CoordFloat, Line, LineInterpolatePoint, LineString, MultiLineString, MultiPolygon, Point, - Polygon, Rect, Triangle, -}; - -// This is still used in the trait constraints - but Densify too will soon be replaced with a -// generic version, at which point this implementation detail can be removed. -#[allow(deprecated)] -use crate::EuclideanLength; - -/// 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 = Line::new(coord! {x: 0.0, y: 6.0}, coord! {x: 1.0, y: 8.0}); -/// let correct: LineString = 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 { - type Output; - - fn densify(&self, max_distance: F) -> Self::Output; -} - -// Helper for densification trait -#[allow(deprecated)] -fn densify_line(line: Line, container: &mut Vec>, 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); - } -} - -#[allow(deprecated)] -impl Densify for MultiPolygon -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = MultiPolygon; - - fn densify(&self, max_distance: T) -> Self::Output { - MultiPolygon::new( - self.iter() - .map(|polygon| polygon.densify(max_distance)) - .collect(), - ) - } -} - -#[allow(deprecated)] -impl Densify for Polygon -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = Polygon; - - 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) - } -} - -#[allow(deprecated)] -impl Densify for MultiLineString -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = MultiLineString; - - fn densify(&self, max_distance: T) -> Self::Output { - MultiLineString::new( - self.iter() - .map(|linestring| linestring.densify(max_distance)) - .collect(), - ) - } -} - -#[allow(deprecated)] -impl Densify for LineString -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = LineString; - - fn densify(&self, max_distance: T) -> Self::Output { - if self.0.is_empty() { - return LineString::new(vec![]); - } - - 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) - } -} - -#[allow(deprecated)] -impl Densify for Line -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = LineString; - - 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) - } -} - -#[allow(deprecated)] -impl Densify for Triangle -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = Polygon; - - fn densify(&self, max_distance: T) -> Self::Output { - self.to_polygon().densify(max_distance) - } -} - -#[allow(deprecated)] -impl Densify for Rect -where - T: CoordFloat, - Line: EuclideanLength, - LineString: EuclideanLength, -{ - type Output = Polygon; - - fn densify(&self, max_distance: T) -> Self::Output { - self.to_polygon().densify(max_distance) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{coord, Coord}; - - #[test] - fn test_polygon_densify() { - let linestring: LineString = - vec![[-5.0, 0.0], [0.0, 5.0], [5.0, 0.0], [-5.0, 0.0]].into(); - let interior: LineString = - 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 = LineString(vec![ - Coord { x: -5.0, y: 0.0 }, - Coord { x: -3.75, y: 1.25 }, - Coord { x: -2.5, y: 2.5 }, - Coord { x: -1.25, y: 3.75 }, - Coord { x: 0.0, y: 5.0 }, - Coord { x: 1.25, y: 3.75 }, - Coord { x: 2.5, y: 2.5 }, - Coord { x: 3.75, y: 1.25 }, - Coord { x: 5.0, y: 0.0 }, - Coord { x: 3.0, y: 0.0 }, - Coord { x: 1.0, y: 0.0 }, - Coord { - x: -1.0000000000000009, - y: 0.0, - }, - Coord { x: -3.0, y: 0.0 }, - Coord { x: -5.0, y: 0.0 }, - ]); - let correct_int: LineString = LineString(vec![ - Coord { x: -3.0, y: 0.0 }, - Coord { x: -2.0, y: 1.0 }, - Coord { x: -1.0, y: 2.0 }, - Coord { x: 0.0, y: 3.0 }, - Coord { x: 1.0, y: 2.0 }, - Coord { x: 2.0, y: 1.0 }, - Coord { x: 3.0, y: 0.0 }, - Coord { x: 1.0, y: 0.0 }, - Coord { x: -1.0, y: 0.0 }, - Coord { 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_empty_linestring_densify() { - let linestring = LineString::::new(vec![]); - let max_dist = 2.0; - let densified = linestring.densify(max_dist); - assert!(densified.0.is_empty()); - } - - #[test] - fn test_linestring_densify() { - let linestring: LineString = - vec![[-1.0, 0.0], [0.0, 0.0], [0.0, 6.0], [1.0, 8.0]].into(); - let correct: LineString = 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 = Line::new(coord! {x: 0.0, y: 6.0}, coord! {x: 1.0, y: 8.0}); - let correct: LineString = 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); - } -} diff --git a/geo/src/algorithm/densify_haversine.rs b/geo/src/algorithm/densify_haversine.rs index e8b2f0dbc..84e9b973e 100644 --- a/geo/src/algorithm/densify_haversine.rs +++ b/geo/src/algorithm/densify_haversine.rs @@ -9,6 +9,10 @@ use crate::{ Triangle, }; +#[deprecated( + since = "0.29.0", + note = "Please use the `line.densify::()` via the `Densify` trait instead." +)] /// Returns a new spherical geometry containing both existing and new interpolated coordinates with /// a maximum distance of `max_distance` between them. /// @@ -21,6 +25,7 @@ use crate::{ /// # Examples /// ``` /// use geo::{coord, Line, LineString}; +/// #[allow(deprecated)] /// use geo::DensifyHaversine; /// /// let line = Line::new(coord! {x: 0.0, y: 0.0}, coord! { x: 0.0, y: 1.0 }); @@ -218,6 +223,7 @@ mod tests { ] .into(); + #[allow(deprecated)] let dense = polygon.densify_haversine(50000.0); assert_relative_eq!(dense.exterior(), &output_exterior); } @@ -250,6 +256,7 @@ mod tests { ] .into(); + #[allow(deprecated)] let dense = linestring.densify_haversine(110.0); assert_relative_eq!(dense, output); } @@ -258,6 +265,7 @@ mod tests { fn test_line_densify() { let output: LineString = vec![[0.0, 0.0], [0.0, 0.5], [0.0, 1.0]].into(); let line = Line::new(coord! {x: 0.0, y: 0.0}, coord! { x: 0.0, y: 1.0 }); + #[allow(deprecated)] let dense = line.densify_haversine(100000.0); assert_relative_eq!(dense, output); } @@ -265,6 +273,7 @@ mod tests { #[test] fn test_empty_linestring() { let linestring: LineString = LineString::new(vec![]); + #[allow(deprecated)] let dense = linestring.densify_haversine(10.0); assert_eq!(0, dense.coords_count()); } diff --git a/geo/src/algorithm/line_measures/densify.rs b/geo/src/algorithm/line_measures/densify.rs new file mode 100644 index 000000000..c8c8c7451 --- /dev/null +++ b/geo/src/algorithm/line_measures/densify.rs @@ -0,0 +1,314 @@ +use super::{Distance, InterpolatePoint}; +use crate::{ + CoordFloat, CoordsIter, Line, LineString, MultiLineString, MultiPolygon, Point, Polygon, Rect, + Triangle, +}; +use num_traits::FromPrimitive; + +pub trait Densify { + type Output; + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint; +} + +pub(crate) fn densify_between( + line_start: Point, + line_end: Point, + container: &mut Vec>, + max_segment_length: F, +) where + F: CoordFloat + FromPrimitive, + MetricSpace: Distance, Point> + InterpolatePoint, +{ + assert!(max_segment_length > F::zero()); + let num_segments = (MetricSpace::distance(line_start, line_end) / max_segment_length) + .ceil() + .to_u64() + .expect("unreasonable number of segments"); + + // distance "unit" for this line segment + let frac = F::one() / F::from(num_segments).unwrap(); + + for segment_num in 1..num_segments { + let ratio = frac * F::from(segment_num).unwrap(); + + // PERF TODO: We recompute "total_distance" every step of this loop. + // If we impl point_at_distance_between, we could compute it once and use it here. + // At that point, I think this function could be a good candidate to be *the single* basis + // for a unified generic of points_along_line for all metric spaces. + let interpolated_point = MetricSpace::point_at_ratio_between(line_start, line_end, ratio); + container.push(interpolated_point); + } +} + +impl Densify for Line { + type Output = LineString; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + let mut points = vec![self.start_point()]; + densify_between::( + self.start_point(), + self.end_point(), + &mut points, + max_segment_length, + ); + points.push(self.end_point()); + LineString::from(points) + } +} + +impl Densify for LineString { + type Output = Self; + + fn densify(&self, max_segment_length: F) -> LineString + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + if self.coords_count() == 0 { + return LineString::new(vec![]); + } + + let mut points = vec![]; + self.lines().for_each(|line| { + points.push(line.start_point()); + densify_between::( + line.start_point(), + line.end_point(), + &mut points, + max_segment_length, + ) + }); + + // we're done, push the last coordinate on to finish + let final_coord = *self + .0 + .last() + .expect("we already asserted the line string is not empty"); + points.push(final_coord.into()); + + LineString::from(points) + } +} + +impl Densify for MultiLineString { + type Output = Self; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + MultiLineString::new( + self.iter() + .map(|line_string| line_string.densify::(max_segment_length)) + .collect(), + ) + } +} + +impl Densify for Polygon { + type Output = Self; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + Polygon::new( + self.exterior().densify::(max_segment_length), + self.interiors() + .iter() + .map(|interior| interior.densify::(max_segment_length)) + .collect(), + ) + } +} + +impl Densify for MultiPolygon { + type Output = Self; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + MultiPolygon::new( + self.iter() + .map(|polygon| polygon.densify::(max_segment_length)) + .collect(), + ) + } +} + +impl Densify for Rect { + type Output = Polygon; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + self.to_polygon().densify::(max_segment_length) + } +} + +impl Densify for Triangle { + type Output = Polygon; + + fn densify(&self, max_segment_length: F) -> Self::Output + where + MetricSpace: Distance, Point> + InterpolatePoint, + { + self.to_polygon().densify::(max_segment_length) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{coord, polygon, Geodesic, Haversine, Rhumb}; + + #[test] + fn densify_line() { + // London to Paris + let line = Line::new( + coord!(x: -0.1278f64, y: 51.5074), + coord!(x: 2.3522, y: 48.8566), + ); + + let densified_line = line.densify::(100_000.0); // max segment length 100km + assert!(densified_line.coords_count() > 2); + + let densified_rhumb = line.densify::(100_000.0); + assert!(densified_rhumb.coords_count() > 2); + + let densified_haversine = line.densify::(100_000.0); + assert!(densified_haversine.coords_count() > 2); + } + + #[test] + fn densify_line_string() { + let line_string = LineString::new(vec![ + coord!(x: -58.3816f64, y: -34.6037), // Buenos Aires, Argentina + coord!(x: -77.0428, y: -12.0464), // Lima, Peru + coord!(x: -47.9292, y: -15.7801), // Brasília, Brazil + ]); + + let densified_ls = line_string.densify::(500_000.0); // 500 km max segment length + assert!(densified_ls.coords_count() > line_string.coords_count()); + + let densified_rhumb_ls = line_string.densify::(500_000.0); + assert!(densified_rhumb_ls.coords_count() > line_string.coords_count()); + + let densified_haversine_ls = line_string.densify::(500_000.0); + assert!(densified_haversine_ls.coords_count() > line_string.coords_count()); + } + + #[test] + fn densify_polygon() { + let polygon = polygon![ + (x: -58.3816f64, y: -34.6037), // Buenos Aires + (x: -77.0428, y: -12.0464), // Lima + (x: -47.9292, y: -15.7801), // Brasília + ]; + + let densified_polygon = polygon.densify::(500_000.0); // 500 km max segment length + assert!(densified_polygon.exterior().coords_count() > polygon.exterior().coords_count()); + } + + // ported from the now deprecated DensifyHaversine + mod lon_lat_tests { + use super::*; + use crate::{coord, wkt}; + use wkt::ToWkt; + + #[test] + fn test_polygon_densify() { + let polygon = wkt!(POLYGON(( + 4.925 45.804, + 4.732 45.941, + 4.935 46.513, + 5.821 46.103, + 5.627 45.611, + 5.355 45.883, + 4.925 45.804 + ))); + + let exepcted_haversine = wkt!(POLYGON(( + 4.925 45.804, + 4.732 45.941, + 4.8329711649985505 46.2270449096239, + 4.935 46.513, + 5.379659133344039 46.30885540136222, + 5.821 46.103, + 5.723570877658867 45.85704103535437, + 5.627 45.611, + 5.355 45.883, + 4.925 45.804 + ))); + + let actual_haversine = polygon.densify::(50000.0); + assert_relative_eq!(actual_haversine, exepcted_haversine); + + let expected_geodesic = wkt!(POLYGON(( + 4.925 45.804, + 4.732 45.941, + 4.832972865149862 46.22705224065524, + 4.935 46.513, + 5.379653814979939 46.30886184400083, + 5.821 46.103, + 5.723572275808633 45.85704648840237, + 5.627 45.611, + 5.355 45.883, + 4.925 45.804 + ))); + let actual_geodesic = polygon.densify::(50000.0); + assert_relative_eq!(actual_geodesic, expected_geodesic); + } + + #[test] + fn test_linestring_densify() { + let linestring = wkt!(LINESTRING( + -3.202 55.9471, + -3.2012 55.9476, + -3.1994 55.9476, + -3.1977 55.9481, + -3.196 55.9483, + -3.1947 55.9487, + -3.1944 55.9488, + -3.1944 55.949 + )); + + let expected = wkt!(LINESTRING( + -3.202 55.9471, + -3.2012 55.9476, + -3.2002999999999995 55.94760000327935, + -3.1994 55.9476, + -3.1985500054877773 55.94785000292509, + -3.1977 55.9481, + -3.196 55.9483, + -3.1947 55.9487, + -3.1944 55.9488, + -3.1944 55.949 + )); + + let dense = linestring.densify::(110.0); + assert_relative_eq!(dense, expected); + } + + #[test] + fn test_line_densify() { + let output = wkt!(LINESTRING(0.0 0.0, 0.0 0.5, 0.0 1.0)); + let line = Line::new(coord! {x: 0.0, y: 0.0}, coord! { x: 0.0, y: 1.0 }); + let dense = line.densify::(100000.0); + assert_relative_eq!(dense, output); + } + + #[test] + fn test_empty_linestring() { + let linestring: LineString = LineString::new(vec![]); + let dense = linestring.densify::(10.0); + assert_eq!(0, dense.coords_count()); + } + } +} diff --git a/geo/src/algorithm/line_measures/metric_spaces/euclidean.rs b/geo/src/algorithm/line_measures/metric_spaces/euclidean.rs index 74dcb81fe..75e2c90f9 100644 --- a/geo/src/algorithm/line_measures/metric_spaces/euclidean.rs +++ b/geo/src/algorithm/line_measures/metric_spaces/euclidean.rs @@ -1,5 +1,7 @@ -use super::super::Distance; +use super::super::{Distance, InterpolatePoint}; +use crate::line_measures::densify::densify_between; use crate::{CoordFloat, Point}; +use num_traits::FromPrimitive; /// Operations on the [Euclidean plane] measure distance with the pythagorean formula - /// what you'd measure with a ruler. @@ -53,6 +55,30 @@ impl Distance, Point> for Euclidean { } } +impl InterpolatePoint for Euclidean { + fn point_at_ratio_between(start: Point, end: Point, ratio_from_start: F) -> Point { + let diff = end - start; + start + diff * ratio_from_start + } + + fn points_along_line( + start: Point, + end: Point, + max_distance: F, + include_ends: bool, + ) -> impl Iterator> { + let mut container = vec![]; + if include_ends { + container.push(start); + } + densify_between::(start, end, &mut container, max_distance); + if include_ends { + container.push(end); + } + container.into_iter() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/geo/src/algorithm/line_measures/mod.rs b/geo/src/algorithm/line_measures/mod.rs index 3c2bc41e2..cb9208489 100644 --- a/geo/src/algorithm/line_measures/mod.rs +++ b/geo/src/algorithm/line_measures/mod.rs @@ -15,5 +15,8 @@ pub use interpolate_point::InterpolatePoint; mod length; pub use length::Length; +mod densify; +pub use densify::Densify; + pub mod metric_spaces; pub use metric_spaces::{Euclidean, Geodesic, Haversine, Rhumb}; diff --git a/geo/src/algorithm/linestring_segment.rs b/geo/src/algorithm/linestring_segment.rs index b361c1069..7ee5cd3e3 100644 --- a/geo/src/algorithm/linestring_segment.rs +++ b/geo/src/algorithm/linestring_segment.rs @@ -1,8 +1,6 @@ -use crate::line_interpolate_point::LineInterpolatePoint; -use crate::{ - Coord, Densify, DensifyHaversine, Euclidean, Haversine, Length, LineString, LinesIter, - MultiLineString, -}; +use crate::algorithm::{Densify, Length, LineInterpolatePoint, LinesIter}; +use crate::geometry::{Coord, LineString, MultiLineString}; +use crate::line_measures::{Euclidean, Haversine}; /// Segments a LineString into `segment_count` equal length LineStrings as a MultiLineString /// using Euclidean distance calculations. See `LineStringSegmentizeHaversine` @@ -47,7 +45,7 @@ pub trait LineStringSegmentizeHaversine { } macro_rules! implement_segmentize { - ($trait_name:ident, $method_name:ident, $metric_space:ty, $densify_method:ident) => { + ($trait_name:ident, $method_name:ident, $metric_space:ty) => { impl $trait_name for LineString { fn $method_name(&self, n: usize) -> Option { if (n == usize::MIN) || (n == usize::MAX) { @@ -62,7 +60,7 @@ macro_rules! implement_segmentize { let mut cum_length = 0_f64; let segment_prop = (1_f64) / (n as f64); let segment_length = total_length * segment_prop; - let densified = self.$densify_method(segment_length - f64::EPSILON); + let densified = self.densify::<$metric_space>(segment_length - f64::EPSILON); if densified.lines().count() == n { let linestrings = densified @@ -112,13 +110,11 @@ macro_rules! implement_segmentize { }; } -implement_segmentize!(LineStringSegmentize, line_segmentize, Euclidean, densify); - +implement_segmentize!(LineStringSegmentize, line_segmentize, Euclidean); implement_segmentize!( LineStringSegmentizeHaversine, line_segmentize_haversine, - Haversine, - densify_haversine + Haversine ); #[cfg(test)] diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 9ced466c7..32e140c7a 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -66,12 +66,9 @@ pub use coordinate_position::CoordinatePosition; pub mod coords_iter; pub use coords_iter::CoordsIter; -/// Densify linear geometry components -pub mod densify; -pub use densify::Densify; - /// Densify spherical geometry components pub mod densify_haversine; +#[allow(deprecated)] pub use densify_haversine::DensifyHaversine; /// Dimensionality of a geometry and its boundary, based on OGC-SFA. @@ -190,7 +187,7 @@ pub use lines_iter::LinesIter; pub mod line_measures; pub use line_measures::metric_spaces::{Euclidean, Geodesic, Haversine, Rhumb}; -pub use line_measures::{Bearing, Destination, Distance, InterpolatePoint, Length}; +pub use line_measures::{Bearing, Densify, Destination, Distance, InterpolatePoint, Length}; /// Split a LineString into n segments pub mod linestring_segment;