From e9a720bccdf16fd9a2ec8afc6056908bab70e453 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Sun, 3 Jun 2018 11:13:20 -0400 Subject: [PATCH] Migrate Line/LineString to be a series of Coordinates (not Points). Relevant conversation in https://github.com/georust/rust-geo/issues/15. This is a breaking change for `geo-types`. --- geo-types/src/coordinate.rs | 44 +++++++++++++++ geo-types/src/lib.rs | 16 +++--- geo-types/src/line.rs | 19 ++++--- geo-types/src/line_string.rs | 106 +++++++++++++++++++++-------------- geo-types/src/point.rs | 44 +-------------- geo-types/src/polygon.rs | 18 ++++-- 6 files changed, 142 insertions(+), 105 deletions(-) diff --git a/geo-types/src/coordinate.rs b/geo-types/src/coordinate.rs index 41cc4bc95e..ab0c3ab78b 100644 --- a/geo-types/src/coordinate.rs +++ b/geo-types/src/coordinate.rs @@ -11,6 +11,50 @@ where pub y: T, } +impl Coordinate +where + T: CoordinateType, +{ + /// Returns the dot product of the two points: + /// `dot = x1 * x2 + y1 * y2` + /// + /// # Examples + /// + /// ``` + /// use geo_types::Coordinate; + /// + /// let coord = Coordinate { x: 1.5, y: 0.5 }; + /// let dot = coord.dot(&Coordinate { x: 2.0, y: 4.5 }); + /// + /// assert_eq!(dot, 5.25); + /// ``` + pub fn dot(&self, coord: &Coordinate) -> T { + self.x * coord.x + self.y * coord.y + } + + /// Returns the cross product of 3 points. A positive value implies + /// `self` → `point_b` → `point_c` is counter-clockwise, negative implies + /// clockwise. + /// + /// # Examples + /// + /// ``` + /// use geo_types::Coordinate; + /// + /// let coord_a = Coordinate { x: 1., y: 2. }; + /// let coord_b = Coordinate { x: 3., y: 5. }; + /// let coord_c = Coordinate { x: 7., y: 12. }; + /// + /// let cross = coord_a.cross_prod(&coord_b, &coord_c); + /// + /// assert_eq!(cross, 2.0) + /// ``` + pub fn cross_prod(&self, coord_b: &Coordinate, coord_c: &Coordinate) -> T { + (coord_b.x - self.x) * (coord_c.y - self.y) + - (coord_b.y - self.y) * (coord_c.x - self.x) + } +} + impl From<(T, T)> for Coordinate { fn from(coords: (T, T)) -> Self { Coordinate { diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index d6cf46dbbb..0d7ec265f1 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -88,16 +88,16 @@ mod test { #[test] fn polygon_new_test() { let exterior = LineString(vec![ - Point::new(0., 0.), - Point::new(1., 1.), - Point::new(1., 0.), - Point::new(0., 0.), + Coordinate { x: 0., y: 0. }, + Coordinate { x: 1., y: 1. }, + Coordinate { x: 1., y: 0. }, + Coordinate { x: 0., y: 0. }, ]); let interiors = vec![LineString(vec![ - Point::new(0.1, 0.1), - Point::new(0.9, 0.9), - Point::new(0.9, 0.1), - Point::new(0.1, 0.1), + Coordinate { x: 0.1, y: 0.1 }, + Coordinate { x: 0.9, y: 0.9 }, + Coordinate { x: 0.9, y: 0.1 }, + Coordinate { x: 0.1, y: 0.1 }, ])]; let p = Polygon::new(exterior.clone(), interiors.clone()); diff --git a/geo-types/src/line.rs b/geo-types/src/line.rs index f82ce430c4..aff22ae69b 100644 --- a/geo-types/src/line.rs +++ b/geo-types/src/line.rs @@ -1,4 +1,4 @@ -use {CoordinateType, Point}; +use {CoordinateType, Coordinate}; #[cfg(feature = "spade")] use algorithms::{BoundingBox, EuclideanDistance}; @@ -10,8 +10,8 @@ pub struct Line where T: CoordinateType, { - pub start: Point, - pub end: Point, + pub start: Coordinate, + pub end: Coordinate, } impl Line @@ -23,14 +23,17 @@ where /// # Examples /// /// ``` - /// use geo_types::{Point, Line}; + /// use geo_types::{Coordinate, Line}; /// - /// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); + /// let line = Line::new( + /// Coordinate { x: 0., y: 0. }, + /// Coordinate { x: 1., y: 2. }, + /// ); /// - /// assert_eq!(line.start, Point::new(0., 0.)); - /// assert_eq!(line.end, Point::new(1., 2.)); + /// assert_eq!(line.start, Coordinate { x: 0., y: 0. }); + /// assert_eq!(line.end, Coordinate { x: 1., y: 2. }); /// ``` - pub fn new(start: Point, end: Point) -> Line { + pub fn new(start: Coordinate, end: Coordinate) -> Line { Line { start, end diff --git a/geo-types/src/line_string.rs b/geo-types/src/line_string.rs index 3be309435f..e71c93e437 100644 --- a/geo-types/src/line_string.rs +++ b/geo-types/src/line_string.rs @@ -1,50 +1,73 @@ use std::iter::FromIterator; -use {CoordinateType, Line, Point}; +use {Coordinate, CoordinateType, Line}; -/// An ordered collection of two or more [`Point`s](struct.Point.html), representing a path between locations +/// An ordered collection of two or more [`Coordinate`s](struct.Coordinate.html), representing a +/// path between locations. /// /// # Examples /// /// Create a `LineString` by calling it directly: /// /// ``` -/// use geo_types::{LineString, Point}; -/// let line = LineString(vec![Point::new(0., 0.), Point::new(10., 0.)]); +/// use geo_types::{LineString, Coordinate}; +/// +/// let line_string = LineString(vec![ +/// Coordinate { x: 0., y: 0. }, +/// Coordinate { x: 10., y: 0. }, +/// ]); /// ``` /// -/// Converting a `Vec` of `Point`-like things: +/// Converting a `Vec` of `Coordinate`-like things: /// /// ``` -/// # use geo_types::{LineString, Point}; -/// let line: LineString = vec![(0., 0.), (10., 0.)].into(); +/// use geo_types::LineString; +/// +/// let line_string: LineString = vec![ +/// (0., 0.), +/// (10., 0.), +/// ].into(); /// ``` /// /// ``` -/// # use geo_types::{LineString, Point}; -/// let line: LineString = vec![[0., 0.], [10., 0.]].into(); +/// use geo_types::LineString; +/// +/// let line_string: LineString = vec![ +/// [0., 0.], +/// [10., 0.], +/// ].into(); /// ``` // -/// Or `collect`ing from a Point iterator +/// Or `collect`ing from a `Coordinate` iterator /// /// ``` -/// # use geo_types::{LineString, Point}; -/// let mut points = vec![Point::new(0., 0.), Point::new(10., 0.)]; -/// let line: LineString = points.into_iter().collect(); +/// use geo_types::{LineString, Coordinate}; +/// +/// let mut coords_iter = vec![ +/// Coordinate { x: 0., y: 0. }, +/// Coordinate { x: 10., y: 0. } +/// ].into_iter(); +/// +/// let line_string: LineString = coords_iter.collect(); /// ``` /// -/// You can iterate over the points in the `LineString` +/// You can iterate over the coordintes in the `LineString` /// /// ``` -/// use geo_types::{LineString, Point}; -/// let line = LineString(vec![Point::new(0., 0.), Point::new(10., 0.)]); -/// for point in line { -/// println!("Point x = {}, y = {}", point.x(), point.y()); +/// use geo_types::{LineString, Coordinate}; +/// +/// let line_string = LineString(vec![ +/// Coordinate { x: 0., y: 0. }, +/// Coordinate { x: 10., y: 0. }, +/// ]); +/// +/// for coord in line_string { +/// println!("Coordinate x = {}, y = {}", coord.x, coord.y); /// } /// ``` /// #[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct LineString(pub Vec>) +pub struct LineString(pub Vec>) where T: CoordinateType; @@ -55,57 +78,58 @@ impl LineString { /// # Examples /// /// ``` - /// use geo_types::{Line, LineString, Point}; + /// use geo_types::{Line, LineString, Coordinate}; /// - /// let mut points = vec![(0., 0.), (5., 0.), (7., 9.)]; - /// let linestring: LineString = points.into_iter().collect(); + /// let mut coords = vec![(0., 0.), (5., 0.), (7., 9.)]; + /// let line_string: LineString = coords.into_iter().collect(); /// - /// let mut lines = linestring.lines(); + /// let mut lines = line_string.lines(); /// assert_eq!( - /// Some(Line::new(Point::new(0., 0.), Point::new(5., 0.))), + /// Some(Line::new(Coordinate { x: 0., y: 0. }, Coordinate { x: 5., y: 0. })), /// lines.next() /// ); /// assert_eq!( - /// Some(Line::new(Point::new(5., 0.), Point::new(7., 9.))), + /// Some(Line::new(Coordinate { x: 5., y: 0. }, Coordinate { x: 7., y: 9. })), /// lines.next() /// ); /// assert!(lines.next().is_none()); /// ``` pub fn lines<'a>(&'a self) -> impl Iterator> + 'a { + // TODO add assert? self.0.windows(2).map(|w| unsafe { - // As long as the LineString has at least two points, we shouldn't + // As long as the LineString has at least two coordinates, we shouldn't // need to do bounds checking here. Line::new(*w.get_unchecked(0), *w.get_unchecked(1)) }) } - pub fn points(&self) -> ::std::slice::Iter> { - self.0.iter() - } + // pub fn points<'a>(&'a self) -> impl Iterator> { + // self.0.iter().map(|n| Point(n.clone()) + // } - pub fn points_mut(&mut self) -> ::std::slice::IterMut> { - self.0.iter_mut() - } + // pub fn points_mut(&mut self) -> ::std::slice::IterMut> { + // self.0.iter_mut() + // } } /// Turn a `Vec` of `Point`-ish objects into a `LineString`. -impl>> From> for LineString { - fn from(v: Vec) -> Self { - LineString(v.into_iter().map(|p| p.into()).collect()) +impl>> From> for LineString { + fn from(v: Vec) -> Self { + LineString(v.into_iter().map(|c| c.into()).collect()) } } /// Turn a `Point`-ish iterator into a `LineString`. -impl>> FromIterator for LineString { - fn from_iter>(iter: I) -> Self { - LineString(iter.into_iter().map(|p| p.into()).collect()) +impl>> FromIterator for LineString { + fn from_iter>(iter: I) -> Self { + LineString(iter.into_iter().map(|c| c.into()).collect()) } } -/// Iterate over all the [Point](struct.Point.html)s in this linestring +/// Iterate over all the [Coordinate](struct.Coordinates.html)s in this `LineString`. impl IntoIterator for LineString { - type Item = Point; - type IntoIter = ::std::vec::IntoIter>; + type Item = Coordinate; + type IntoIter = ::std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() diff --git a/geo-types/src/point.rs b/geo-types/src/point.rs index 914de3cf41..00823d3def 100644 --- a/geo-types/src/point.rs +++ b/geo-types/src/point.rs @@ -1,4 +1,4 @@ -use num_traits::{Float, ToPrimitive}; +use num_traits::ToPrimitive; use std::ops::Add; use std::ops::Neg; use std::ops::Sub; @@ -179,48 +179,6 @@ where pub fn set_lat(&mut self, lat: T) -> &mut Point { self.set_y(lat) } - - /// Returns the dot product of the two points: - /// `dot = x1 * x2 + y1 * y2` - /// - /// # Examples - /// - /// ``` - /// use geo_types::Point; - /// - /// let p = Point::new(1.5, 0.5); - /// let dot = p.dot(&Point::new(2.0, 4.5)); - /// - /// assert_eq!(dot, 5.25); - /// ``` - pub fn dot(&self, point: &Point) -> T { - self.x() * point.x() + self.y() * point.y() - } - - /// Returns the cross product of 3 points. A positive value implies - /// `self` → `point_b` → `point_c` is counter-clockwise, negative implies - /// clockwise. - /// - /// # Examples - /// - /// ``` - /// use geo_types::Point; - /// - /// let p_a = Point::new(1.0, 2.0); - /// let p_b = Point::new(3.0,5.0); - /// let p_c = Point::new(7.0,12.0); - /// - /// let cross = p_a.cross_prod(&p_b, &p_c); - /// - /// assert_eq!(cross, 2.0) - /// ``` - pub fn cross_prod(&self, point_b: &Point, point_c: &Point) -> T - where - T: Float, - { - (point_b.x() - self.x()) * (point_c.y() - self.y()) - - (point_b.y() - self.y()) * (point_c.x() - self.x()) - } } impl Neg for Point diff --git a/geo-types/src/polygon.rs b/geo-types/src/polygon.rs index 71187a9845..2dcc6fa5a4 100644 --- a/geo-types/src/polygon.rs +++ b/geo-types/src/polygon.rs @@ -33,12 +33,20 @@ where /// # Examples /// /// ``` - /// use geo_types::{Point, LineString, Polygon}; + /// use geo_types::{Coordinate, LineString, Polygon}; /// - /// let exterior = LineString(vec![Point::new(0., 0.), Point::new(1., 1.), - /// Point::new(1., 0.), Point::new(0., 0.)]); - /// let interiors = vec![LineString(vec![Point::new(0.1, 0.1), Point::new(0.9, 0.9), - /// Point::new(0.9, 0.1), Point::new(0.1, 0.1)])]; + /// let exterior = LineString(vec![ + /// Coordinate { x: 0., y: 0. }, + /// Coordinate { x: 1., y: 1. }, + /// Coordinate { x: 1., y: 0. }, + /// Coordinate { x: 0., y: 0. }, + /// ]); + /// let interiors = vec![LineString(vec![ + /// Coordinate { x: 0.1, y: 0.1 }, + /// Coordinate { x: 0.9, y: 0.9 }, + /// Coordinate { x: 0.9, y: 0.1 }, + /// Coordinate { x: 0.1, y: 0.1 }, + /// ])]; /// let p = Polygon::new(exterior.clone(), interiors.clone()); /// assert_eq!(p.exterior, exterior); /// assert_eq!(p.interiors, interiors);