Skip to content

Commit

Permalink
Migrate Line/LineString to be a series of Coordinates (not Points).
Browse files Browse the repository at this point in the history
Relevant conversation in #15.

This is a breaking change for `geo-types`.
  • Loading branch information
frewsxcv committed Jun 3, 2018
1 parent 6284b6a commit e9a720b
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 105 deletions.
44 changes: 44 additions & 0 deletions geo-types/src/coordinate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,50 @@ where
pub y: T,
}

impl<T> Coordinate<T>
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>) -> 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<T>, coord_c: &Coordinate<T>) -> T {
(coord_b.x - self.x) * (coord_c.y - self.y)
- (coord_b.y - self.y) * (coord_c.x - self.x)
}
}

impl<T: CoordinateType> From<(T, T)> for Coordinate<T> {
fn from(coords: (T, T)) -> Self {
Coordinate {
Expand Down
16 changes: 8 additions & 8 deletions geo-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
19 changes: 11 additions & 8 deletions geo-types/src/line.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use {CoordinateType, Point};
use {CoordinateType, Coordinate};

#[cfg(feature = "spade")]
use algorithms::{BoundingBox, EuclideanDistance};
Expand All @@ -10,8 +10,8 @@ pub struct Line<T>
where
T: CoordinateType,
{
pub start: Point<T>,
pub end: Point<T>,
pub start: Coordinate<T>,
pub end: Coordinate<T>,
}

impl<T> Line<T>
Expand All @@ -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<T>, end: Point<T>) -> Line<T> {
pub fn new(start: Coordinate<T>, end: Coordinate<T>) -> Line<T> {
Line {
start,
end
Expand Down
106 changes: 65 additions & 41 deletions geo-types/src/line_string.rs
Original file line number Diff line number Diff line change
@@ -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<f32> = vec![(0., 0.), (10., 0.)].into();
/// use geo_types::LineString;
///
/// let line_string: LineString<f32> = vec![
/// (0., 0.),
/// (10., 0.),
/// ].into();
/// ```
///
/// ```
/// # use geo_types::{LineString, Point};
/// let line: LineString<f64> = vec![[0., 0.], [10., 0.]].into();
/// use geo_types::LineString;
///
/// let line_string: LineString<f64> = 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<f32> = 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<f32> = 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<T>(pub Vec<Point<T>>)
pub struct LineString<T>(pub Vec<Coordinate<T>>)
where
T: CoordinateType;

Expand All @@ -55,57 +78,58 @@ impl<T: CoordinateType> LineString<T> {
/// # 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<f32> = points.into_iter().collect();
/// let mut coords = vec![(0., 0.), (5., 0.), (7., 9.)];
/// let line_string: LineString<f32> = 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<Item = Line<T>> + '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<Point<T>> {
self.0.iter()
}
// pub fn points<'a>(&'a self) -> impl Iterator<Item = Point<T>> {
// self.0.iter().map(|n| Point(n.clone())
// }

pub fn points_mut(&mut self) -> ::std::slice::IterMut<Point<T>> {
self.0.iter_mut()
}
// pub fn points_mut(&mut self) -> ::std::slice::IterMut<Point<T>> {
// self.0.iter_mut()
// }
}

/// Turn a `Vec` of `Point`-ish objects into a `LineString`.
impl<T: CoordinateType, IP: Into<Point<T>>> From<Vec<IP>> for LineString<T> {
fn from(v: Vec<IP>) -> Self {
LineString(v.into_iter().map(|p| p.into()).collect())
impl<T: CoordinateType, IC: Into<Coordinate<T>>> From<Vec<IC>> for LineString<T> {
fn from(v: Vec<IC>) -> Self {
LineString(v.into_iter().map(|c| c.into()).collect())
}
}

/// Turn a `Point`-ish iterator into a `LineString`.
impl<T: CoordinateType, IP: Into<Point<T>>> FromIterator<IP> for LineString<T> {
fn from_iter<I: IntoIterator<Item = IP>>(iter: I) -> Self {
LineString(iter.into_iter().map(|p| p.into()).collect())
impl<T: CoordinateType, IC: Into<Coordinate<T>>> FromIterator<IC> for LineString<T> {
fn from_iter<I: IntoIterator<Item = IC>>(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<T: CoordinateType> IntoIterator for LineString<T> {
type Item = Point<T>;
type IntoIter = ::std::vec::IntoIter<Point<T>>;
type Item = Coordinate<T>;
type IntoIter = ::std::vec::IntoIter<Coordinate<T>>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
Expand Down
44 changes: 1 addition & 43 deletions geo-types/src/point.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -179,48 +179,6 @@ where
pub fn set_lat(&mut self, lat: T) -> &mut Point<T> {
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>) -> 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<T>, point_c: &Point<T>) -> 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<T> Neg for Point<T>
Expand Down
18 changes: 13 additions & 5 deletions geo-types/src/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit e9a720b

Please sign in to comment.