Skip to content

Commit

Permalink
Merge pull request #1182 from georust/mkirk/docs
Browse files Browse the repository at this point in the history
improve docs for HaversineIntermediate and LineStringSegment
  • Loading branch information
frewsxcv authored Jul 4, 2024
2 parents c4ad2cd + bbb3742 commit ff0eccb
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 40 deletions.
55 changes: 31 additions & 24 deletions geo/src/algorithm/haversine_intermediate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ use num_traits::FromPrimitive;
/// Returns a new Point along a great circle route between two existing points

pub trait HaversineIntermediate<T: CoordFloat> {
/// Returns a new Point along a great circle route between two existing points.
/// Returns a new `Point` along a great circle route between `self` and `other`.
///
/// * `other` - The other point to interpolate towards.
/// * `ratio` - How far along the route should the new point be, with 0.0 being at `self`
/// and 1.0 being at `other`.
///
/// # Examples
///
Expand All @@ -15,21 +19,21 @@ pub trait HaversineIntermediate<T: CoordFloat> {
///
/// let p1 = Point::new(10.0, 20.0);
/// let p2 = Point::new(125.0, 25.0);
///
/// let i20 = p1.haversine_intermediate(&p2, 0.2);
/// let i50 = p1.haversine_intermediate(&p2, 0.5);
/// assert_relative_eq!(i20, Point::new(29.8, 29.9), epsilon = 0.2);
///
/// let i80 = p1.haversine_intermediate(&p2, 0.8);
/// let i20_should = Point::new(29.8, 29.9);
/// let i50_should = Point::new(65.8, 37.7);
/// let i80_should = Point::new(103.5, 33.5);
/// assert_relative_eq!(i20.x(), i20_should.x(), epsilon = 0.2);
/// assert_relative_eq!(i20.y(), i20_should.y(), epsilon = 0.2);
/// assert_relative_eq!(i50.x(), i50_should.x(), epsilon = 0.2);
/// assert_relative_eq!(i50.y(), i50_should.y(), epsilon = 0.2);
/// assert_relative_eq!(i80.x(), i80_should.x(), epsilon = 0.2);
/// assert_relative_eq!(i80.y(), i80_should.y(), epsilon = 0.2);
/// assert_relative_eq!(i80, Point::new(103.5, 33.5), epsilon = 0.2);
/// ```
fn haversine_intermediate(&self, other: &Point<T>, ratio: T) -> Point<T>;

fn haversine_intermediate(&self, other: &Point<T>, f: T) -> Point<T>;
/// Interpolates `Point`s along a great circle route between self and `other`.
///
/// As many points as necessary will be added such that the distance between points
/// never exceeds `max_dist`.
///
/// `include_ends`: Should the start and end points be included in the output?
fn haversine_intermediate_fill(
&self,
other: &Point<T>,
Expand All @@ -42,9 +46,9 @@ impl<T> HaversineIntermediate<T> for Point<T>
where
T: CoordFloat + FromPrimitive,
{
fn haversine_intermediate(&self, other: &Point<T>, f: T) -> Point<T> {
fn haversine_intermediate(&self, other: &Point<T>, ratio: T) -> Point<T> {
let params = get_params(self, other);
get_point(&params, f)
get_point(&params, ratio)
}

fn haversine_intermediate_fill(
Expand Down Expand Up @@ -98,7 +102,7 @@ struct HaversineParams<T: num_traits::Float + FromPrimitive> {
}

#[allow(clippy::many_single_char_names)]
fn get_point<T: CoordFloat + FromPrimitive>(params: &HaversineParams<T>, f: T) -> Point<T> {
fn get_point<T: CoordFloat + FromPrimitive>(params: &HaversineParams<T>, ratio: T) -> Point<T> {
let one = T::one();

let HaversineParams {
Expand All @@ -111,8 +115,8 @@ fn get_point<T: CoordFloat + FromPrimitive>(params: &HaversineParams<T>, f: T) -
s,
} = *params;

let a = ((one - f) * d).sin() / d.sin();
let b = (f * d).sin() / d.sin();
let a = ((one - ratio) * d).sin() / d.sin();
let b = (ratio * d).sin() / d.sin();

let x = a * n + b * o;
let y = a * p + b * q;
Expand Down Expand Up @@ -211,8 +215,7 @@ mod test {
let p1 = Point::new(30.0, 40.0);
let p2 = Point::new(40.0, 50.0);
let max_dist = 1500000.0; // meters
let include_ends = true;
let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
let route = p1.haversine_intermediate_fill(&p2, max_dist, true);
assert_eq!(route, vec![p1, p2]);
}

Expand All @@ -221,22 +224,26 @@ mod test {
let p1 = Point::new(30.0, 40.0);
let p2 = Point::new(40.0, 50.0);
let max_dist = 1000000.0; // meters
let include_ends = true;
let i50 = p1.clone().haversine_intermediate(&p2, 0.5);
let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
assert_eq!(route, vec![p1, i50, p2]);
assert_eq!(
p1.haversine_intermediate_fill(&p2, max_dist, true),
vec![p1, i50, p2]
);
assert_eq!(
p1.haversine_intermediate_fill(&p2, max_dist, false),
vec![i50]
);
}

#[test]
fn should_add_i25_i50_i75_test() {
let p1 = Point::new(30.0, 40.0);
let p2 = Point::new(40.0, 50.0);
let max_dist = 400000.0; // meters
let include_ends = true;
let i25 = p1.clone().haversine_intermediate(&p2, 0.25);
let i50 = p1.clone().haversine_intermediate(&p2, 0.5);
let i75 = p1.clone().haversine_intermediate(&p2, 0.75);
let route = p1.haversine_intermediate_fill(&p2, max_dist, include_ends);
let route = p1.haversine_intermediate_fill(&p2, max_dist, true);
assert_eq!(route, vec![p1, i25, i50, i75, p2]);
}
}
32 changes: 16 additions & 16 deletions geo/src/algorithm/linestring_segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,46 @@ use crate::{
MultiLineString,
};

/// Segments a LineString into `n` equal length LineStrings as a MultiLineString.
/// `None` will be returned when `n` is equal to 0 or when a point
/// cannot be interpolated on a `Line` segment.
/// Segments a LineString into `segment_count` equal length LineStrings as a MultiLineString
/// using Euclidean distance calculations. See `LineStringSegmentizeHaversine`
/// if you are dealing with geographic coordinates (lat/lon).
///
/// `None` will be returned when `segment_count` is equal to 0 or when a point
/// cannot be interpolated on a `Line` segment.
///
/// # Examples
/// ```
/// use geo::{LineString, MultiLineString, LineStringSegmentize};
/// // Create a simple line string
/// let lns: LineString<f64> = vec![[0.0, 0.0], [1.0, 2.0], [3.0, 6.0]].into();
/// // Segment it into n LineStrings inside of a MultiLineString
/// let n = 6;
/// let segmentized = lns.line_segmentize(n).unwrap();
/// // Segment it into 6 LineStrings inside of a MultiLineString
/// let segmentized = lns.line_segmentize(6).unwrap();
/// // Compare the number of elements
/// assert_eq!(n, segmentized.0.len());
/// assert_eq!(6, segmentized.0.len());
///```
pub trait LineStringSegmentize {
fn line_segmentize(&self, n: usize) -> Option<MultiLineString>;
fn line_segmentize(&self, segment_count: usize) -> Option<MultiLineString>;
}

/// Segments a LineString into `n` equal length LineStrings as a MultiLineString
/// Segments a LineString into `segment_count` equal length LineStrings as a MultiLineString
/// using Haversine distance calculations. Use this over `LineStringSegmentize`
/// when using data from a geographic coordinate system.
/// `None` will be returned when `n` is equal to 0 or when a point
/// cannot be interpolated on a `Line` segment.
///
/// `None` will be returned when `segment_count` is equal to 0 or when a point
/// cannot be interpolated on a `Line` segment.
///
/// # Examples
/// ```
/// use geo::{LineString, MultiLineString, LineStringSegmentizeHaversine};
/// // Create a simple line string
/// let lns: LineString<f64> = vec![[0.0, 0.0], [1.0, 2.0], [3.0, 6.0]].into();
/// // Segment it into n LineStrings inside of a MultiLineString
/// let n = 6;
/// let segmentized = lns.line_segmentize_haversine(n).unwrap();
/// // Segment it into 6 LineStrings inside of a MultiLineString
/// let segmentized = lns.line_segmentize_haversine(6).unwrap();
/// // Compare the number of elements
/// assert_eq!(n, segmentized.0.len());
/// assert_eq!(6, segmentized.0.len());
///```
pub trait LineStringSegmentizeHaversine {
fn line_segmentize_haversine(&self, n: usize) -> Option<MultiLineString>;
fn line_segmentize_haversine(&self, segment_count: usize) -> Option<MultiLineString>;
}

macro_rules! implement_segmentize {
Expand Down

0 comments on commit ff0eccb

Please sign in to comment.