diff --git a/src/algorithm/area.rs b/src/algorithm/area.rs index b1b747c00..5c1905681 100644 --- a/src/algorithm/area.rs +++ b/src/algorithm/area.rs @@ -1,5 +1,5 @@ use num_traits::Float; -use types::{LineString, Polygon, MultiPolygon, Bbox}; +use types::{Line, LineString, Polygon, MultiPolygon, Bbox}; /// Calculation of the area. @@ -31,6 +31,13 @@ fn get_linestring_area(linestring: &LineString) -> T where T: Float { tmp / (T::one() + T::one()) } +impl Area for Line + where T: Float +{ + fn area(&self) -> T { + T::zero() + } +} impl Area for Polygon where T: Float @@ -59,7 +66,7 @@ impl Area for Bbox #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox}; + use types::{Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox}; use algorithm::area::Area; // Area of the polygon @@ -111,4 +118,10 @@ mod test { assert_eq!(mpoly.area(), 102.); assert_relative_eq!(mpoly.area(), 102.); } + #[test] + fn area_line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line1 = Line::new(p(0.0, 0.0), p(1.0, 1.0)); + assert_eq!(line1.area(), 0.); + } } diff --git a/src/algorithm/boundingbox.rs b/src/algorithm/boundingbox.rs index 503f92e07..0fe71e3fa 100644 --- a/src/algorithm/boundingbox.rs +++ b/src/algorithm/boundingbox.rs @@ -1,6 +1,6 @@ use num_traits::Float; -use types::{Bbox, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; +use types::{Bbox, Point, MultiPoint, Line, LineString, MultiLineString, Polygon, MultiPolygon}; /// Calculation of the bounding box of a geometry. @@ -65,6 +65,19 @@ impl BoundingBox for MultiPoint } } +impl BoundingBox for Line + where T: Float +{ + fn bbox(&self) -> Option> { + let a = self.start; + let b = self.end; + let (xmin, xmax) = if a.x() <= b.x() {(a.x(), b.x())} else {(b.x(), a.x())}; + let (ymin, ymax) = if a.y() <= b.y() {(a.y(), b.y())} else {(b.y(), a.y())}; + Some(Bbox {xmin: xmin, xmax: xmax, + ymin: ymin, ymax: ymax}) + } +} + impl BoundingBox for LineString where T: Float { @@ -114,7 +127,7 @@ impl BoundingBox for MultiPolygon #[cfg(test)] mod test { - use types::{Bbox, Coordinate, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; + use types::{Bbox, Coordinate, Point, MultiPoint, Line, LineString, MultiLineString, Polygon, MultiPolygon}; use algorithm::boundingbox::BoundingBox; #[test] @@ -175,4 +188,14 @@ mod test { let bbox = Bbox{xmin: -60., ymax: 80., xmax: 50., ymin: -70.}; assert_eq!(bbox, mpoly.bbox().unwrap()); } + #[test] + fn line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line1 = Line::new(p(0., 1.), p(2., 3.)); + let line2 = Line::new(p(2., 3.), p(0., 1.)); + assert_eq!(line1.bbox().unwrap(), + Bbox {xmin: 0., xmax: 2., ymin: 1., ymax: 3.}); + assert_eq!(line2.bbox().unwrap(), + Bbox {xmin: 0., xmax: 2., ymin: 1., ymax: 3.}); + } } diff --git a/src/algorithm/centroid.rs b/src/algorithm/centroid.rs index 0bb7cd61e..22a9caeab 100644 --- a/src/algorithm/centroid.rs +++ b/src/algorithm/centroid.rs @@ -1,6 +1,6 @@ use num_traits::{Float, FromPrimitive}; -use types::{Point, LineString, Polygon, MultiPolygon, Bbox}; +use types::{Point, Line, LineString, Polygon, MultiPolygon, Bbox}; use algorithm::area::Area; use algorithm::distance::Distance; @@ -54,6 +54,17 @@ fn simple_polygon_centroid(poly_ext: &LineString) -> Option> Some(Point::new(sum_x / (six * area), sum_y / (six * area))) } +impl Centroid for Line + where T: Float +{ + fn centroid(&self) -> Option> { + let two = T::one() + T::one(); + let x = (self.start.x() + self.end.x()) / two; + let y = (self.start.y() + self.end.y()) / two; + Some(Point::new(x, y)) + } +} + impl Centroid for LineString where T: Float { @@ -170,7 +181,7 @@ impl Centroid for Point #[cfg(test)] mod test { - use types::{COORD_PRECISION, Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox}; + use types::{COORD_PRECISION, Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox}; use algorithm::centroid::Centroid; use algorithm::distance::Distance; // Tests: Centroid of LineString @@ -293,4 +304,10 @@ mod test { let point = Point(Coordinate { x: 2., y: 75. }); assert_eq!(point, bbox.centroid().unwrap()); } + #[test] + fn line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line1 = Line::new(p(0., 1.), p(1., 3.)); + assert_eq!(line1.centroid(), Some(p(0.5, 2.))); + } } diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 8d54bbe8d..b32034003 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -1,6 +1,6 @@ use num_traits::{Float, ToPrimitive}; -use types::{COORD_PRECISION, Point, LineString, Polygon, MultiPolygon, Bbox}; +use types::{COORD_PRECISION, Point, Line, LineString, Polygon, MultiPolygon, Bbox}; use algorithm::intersects::Intersects; use algorithm::distance::Distance; @@ -70,6 +70,65 @@ impl Contains> for LineString false } } + +impl Contains> for Line + where T: Float +{ + fn contains(&self, p: &Point) -> bool { + self.intersects(p) + } +} + +impl Contains> for Line + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + self.contains(&line.start) & self.contains(&line.end) + } +} + +impl Contains> for Line + where T: Float +{ + fn contains(&self, linestring: &LineString) -> bool { + linestring.0.iter().all(|pt| self.contains(pt)) + } +} + +impl Contains> for LineString + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + let (p0, p1) = (line.start, line.end); + let mut look_for: Option> = None; + for l in self.0.windows(2) { + let segment = Line::new(l[0], l[1]); + if look_for.is_none() { + // If segment contains an endpoint of line, we mark the other endpoint as the + // one we are looking for. + if segment.contains(&p0) { + look_for = Some(p1); + } else if segment.contains(&p1) { + look_for = Some(p0); + } + } + if let Some(p) = look_for { + // If we are looking for an endpoint, we need to either find it, or show that we + // should continue to look for it + if segment.contains(&p) { + // If the segment contains the endpoint we are looking for we are done + return true; + } else if !line.contains(&segment.end) { + // If not, and the end of the segment is not on the line, we should stop + // looking + look_for = None + } + } + } + return false; + } +} + #[derive(PartialEq, Clone, Debug)] enum PositionPoint { OnBoundary, @@ -139,6 +198,19 @@ impl Contains> for MultiPolygon } } +impl Contains> for Polygon + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + // both endpoints are contained in the polygon and the line + // does NOT intersect the exterior or any of the interior boundaries + self.contains(&line.start) && + self.contains(&line.end) && + !self.exterior.intersects(line) && + !self.interiors.iter().any(|inner| inner.intersects(line)) + } +} + impl Contains> for Polygon where T: Float { @@ -152,7 +224,6 @@ impl Contains> for Polygon } } - impl Contains> for Bbox where T: Float { @@ -173,7 +244,7 @@ impl Contains> for Bbox #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox}; + use types::{Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox}; use algorithm::contains::Contains; /// Tests: Point in LineString #[test] @@ -321,4 +392,78 @@ mod test { assert_eq!(true, bbox_xl.contains(&bbox_sm)); assert_eq!(false, bbox_sm.contains(&bbox_xl)); } + #[test] + fn point_in_line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let p0 = p(2., 4.); + // vertical line + let line1 = Line::new(p(2., 0.), p(2., 5.)); + // point on line, but outside line segment + let line2 = Line::new(p(0., 6.), p(1.5, 4.5)); + // point on line + let line3 = Line::new(p(0., 6.), p(3., 3.)); + assert!(line1.contains(&p0)); + assert!(!line2.contains(&p0)); + assert!(line3.contains(&p0)); + } + #[test] + fn line_in_line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line0 = Line::new(p(0., 1.), p(3., 4.)); + // first point on line0, second not + let line1 = Line::new(p(1., 2.), p(2., 2.)); + // co-linear, but extends past the end of line0 + let line2 = Line::new(p(1., 2.), p(4., 5.)); + // contained in line0 + let line3 = Line::new(p(1., 2.), p(3., 4.)); + assert!(!line0.contains(&line1)); + assert!(!line0.contains(&line2)); + assert!(line0.contains(&line3)); + } + #[test] + fn linestring_in_line_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line = Line::new(p(0., 1.), p(3., 4.)); + // linestring0 in line + let linestring0 = LineString(vec![p(0.1, 1.1), p(1., 2.), p(1.5, 2.5)]); + // linestring1 starts and ends in line, but wanders in the middle + let linestring1 = LineString(vec![p(0.1, 1.1), p(2., 2.), p(1.5, 2.5)]); + // linestring2 is co-linear, but extends beyond line + let linestring2 = LineString(vec![p(0.1, 1.1), p(1., 2.), p(4., 5.)]); + // no part of linestring3 is contained in line + let linestring3 = LineString(vec![p(1.1, 1.1), p(2., 2.), p(2.5, 2.5)]); + assert!(line.contains(&linestring0)); + assert!(!line.contains(&linestring1)); + assert!(!line.contains(&linestring2)); + assert!(!line.contains(&linestring3)); + } + #[test] + fn line_in_polygon_test() { + let p = |x, y| Point(Coordinate { x: x, y: y }); + let line = Line::new(p(0., 1.), p(3., 4.)); + let linestring0 = LineString(vec![p(-1., 0.), p(5., 0.), p(5., 5.), p(0., 5.), p(-1., 0.)]); + let poly0 = Polygon::new(linestring0, Vec::new()); + let linestring1 = LineString(vec![p(0., 0.), p(0., 2.), p(2., 2.), p(2., 0.), p(0., 0.)]); + let poly1 = Polygon::new(linestring1, Vec::new()); + assert!(poly0.contains(&line)); + assert!(!poly1.contains(&line)); + } + #[test] + fn line_in_linestring_test() { + let line0 = Line::new(Point::new(1., 1.), Point::new(2., 2.)); + // line0 is completely contained in the second segment + let linestring0 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5), + Point::new(3., 3.)]); + // line0 is contained in the last three segments + let linestring1 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5), + Point::new(1.2, 1.2), Point::new(1.5, 1.5), + Point::new(3., 3.)]); + // line0 endpoints are contained in the linestring, but the fourth point is off the line + let linestring2 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5), + Point::new(1.2, 1.2), Point::new(1.5, 0.), + Point::new(2., 2.), Point::new(3., 3.)]); + assert!(linestring0.contains(&line0)); + assert!(linestring1.contains(&line0)); + assert!(!linestring2.contains(&line0)); + } } diff --git a/src/algorithm/distance.rs b/src/algorithm/distance.rs index 39539ef15..c198d6d3f 100644 --- a/src/algorithm/distance.rs +++ b/src/algorithm/distance.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use std::collections::BinaryHeap; use num_traits::{Float, ToPrimitive}; -use types::{Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; +use types::{Point, Line, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; use algorithm::contains::Contains; use num_traits::pow::pow; @@ -263,9 +263,26 @@ impl Distance> for LineString } } +impl Distance> for Line + where T: Float +{ + /// Minimum distance from a Line to a Point + fn distance(&self, point: &Point) -> T { + line_segment_distance(point, &self.start, &self.end) + } +} +impl Distance> for Point + where T: Float +{ + /// Minimum distance from a Line to a Point + fn distance(&self, line: &Line) -> T { + line.distance(self) + } +} + #[cfg(test)] mod test { - use types::{Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; + use types::{Point, Line, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon}; use algorithm::distance::{Distance, line_segment_distance}; #[test] @@ -457,4 +474,19 @@ mod test { let p = Point::new(50.0, 50.0); assert_eq!(p.distance(&mp), 64.03124237432849) } + #[test] + fn distance_line_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(5., 0.)); + let p0 = Point::new(2., 3.); + let p1 = Point::new(3., 0.); + let p2 = Point::new(6., 0.); + assert_eq!(line0.distance(&p0), 3.); + assert_eq!(p0.distance(&line0), 3.); + + assert_eq!(line0.distance(&p1), 0.); + assert_eq!(p1.distance(&line0), 0.); + + assert_eq!(line0.distance(&p2), 1.); + assert_eq!(p2.distance(&line0), 1.); + } } diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index e509bc79d..8e6e88a4e 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -1,5 +1,5 @@ use num_traits::Float; -use types::{LineString, Polygon, Bbox, Point}; +use types::{Line, LineString, Polygon, Bbox, Point}; use algorithm::contains::Contains; /// Checks if the geometry A intersects the geometry B. @@ -22,6 +22,115 @@ pub trait Intersects { fn intersects(&self, rhs: &Rhs) -> bool; } +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, p: &Point) -> bool { + let dx = self.end.x() - self.start.x(); + let dy = self.end.y() - self.start.y(); + let tx = if dx == T::zero() {None} else {Some((p.x() - self.start.x()) / dx)}; + let ty = if dy == T::zero() {None} else {Some((p.y() - self.start.y()) / dy)}; + match (tx, ty) { + (None, None) => { // Degenerate line + *p == self.start + }, + (Some(t), None) => { // Horizontal line + p.y() == self.start.y() && + T::zero() <= t && + t <= T::one() + }, + (None, Some(t)) => { // Vertical line + p.x() == self.start.x() && + T::zero() <= t && + t <= T::one() + }, + (Some(t_x), Some(t_y)) => { // All other lines + t_x.abs_sub(t_y) <= T::epsilon() && + T::zero() <= t_x && + t_x <= T::one() + } + } + } +} + +impl Intersects> for Point + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + // Using Cramer's Rule: + // https://en.wikipedia.org/wiki/Intersection_%28Euclidean_geometry%29#Two_line_segments + let (x1, y1, x2, y2) = (self.start.x(), self.start.y(), + self.end.x(), self.end.y()); + let (x3, y3, x4, y4) = (line.start.x(), line.start.y(), + line.end.x(), line.end.y()); + let a1 = x2 - x1; + let a2 = y2 - y1; + let b1 = x3 - x4; // == -(x4 - x3) + let b2 = y3 - y4; // == -(y4 - y3) + let c1 = x3 - x1; + let c2 = y3 - y1; + + let d = a1*b2 - a2*b1; + if d == T::zero() { + // lines are parallel + // return true iff at least one endpoint intersects the other line + self.start.intersects(&line) || self.end.intersects(&line) || + line.start.intersects(&self) || line.end.intersects(&self) + } else { + let s = (c1*b2 - c2*b1) / d; + let t = (a1*c2 - a2*c1) / d; + (T::zero() <= s) && (s <= T::one()) && + (T::zero() <= t) && (t <= T::one()) + } + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, linestring: &LineString) -> bool { + linestring.0 + .windows(2) + .map(|pts| Line::new(pts[0], pts[1])) + .any(|line| self.intersects(&line)) + } +} + +impl Intersects> for LineString + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, p: &Polygon) -> bool { + p.exterior.intersects(self) || + p.interiors.iter().any(|inner| inner.intersects(self)) || + p.contains(&self.start) || + p.contains(&self.end) + } +} + +impl Intersects> for Polygon + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) + } +} + impl Intersects> for LineString where T: Float { @@ -118,7 +227,7 @@ impl Intersects> for Polygon #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, Polygon, Bbox}; + use types::{Coordinate, Point, Line, LineString, Polygon, Bbox}; use algorithm::intersects::Intersects; /// Tests: intersection LineString and LineString #[test] @@ -330,4 +439,84 @@ mod test { assert_eq!(true, bbox_sm.intersects(&bbox_s2)); assert_eq!(true, bbox_s2.intersects(&bbox_sm)); } + #[test] + fn point_intersects_line_test() { + let p0 = Point::new(2., 4.); + // vertical line + let line1 = Line::new(Point::new(2., 0.), Point::new(2., 5.)); + // point on line, but outside line segment + let line2 = Line::new(Point::new(0., 6.), Point::new(1.5, 4.5)); + // point on line + let line3 = Line::new(Point::new(0., 6.), Point::new(3., 3.)); + assert!(line1.intersects(&p0)); + assert!(p0.intersects(&line1)); + assert!(!line2.intersects(&p0)); + assert!(!p0.intersects(&line2)); + assert!(line3.intersects(&p0)); + assert!(p0.intersects(&line3)); + } + #[test] + fn line_intersects_line_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + let line1 = Line::new(Point::new(2., 0.), Point::new(2., 5.)); + let line2 = Line::new(Point::new(0., 7.), Point::new(5., 4.)); + let line3 = Line::new(Point::new(0., 0.), Point::new(-3., -4.)); + assert!(line0.intersects(&line0)); + assert!(line0.intersects(&line1)); + assert!(!line0.intersects(&line2)); + assert!(line0.intersects(&line3)); + + assert!(line1.intersects(&line0)); + assert!(line1.intersects(&line1)); + assert!(!line1.intersects(&line2)); + assert!(!line1.intersects(&line3)); + + assert!(!line2.intersects(&line0)); + assert!(!line2.intersects(&line1)); + assert!(line2.intersects(&line2)); + assert!(!line1.intersects(&line3)); + } + #[test] + fn line_intersects_linestring_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + let linestring0 = LineString( + vec![Point::new(0., 1.), Point::new(1., 0.), Point::new(2., 0.)] + ); + let linestring1 = LineString( + vec![Point::new(0.5, 0.2), Point::new(1., 0.), Point::new(2., 0.)] + ); + assert!(line0.intersects(&linestring0)); + assert!(!line0.intersects(&linestring1)); + assert!(linestring0.intersects(&line0)); + assert!(!linestring1.intersects(&line0)); + } + #[test] + fn line_intersects_polygon_test() { + let line0 = Line::new(Point::new(0.5, 0.5), Point::new(2., 1.)); + let poly0 = Polygon::new( + LineString(vec![Point::new(0.,0.), Point::new(1., 2.), + Point::new(1., 0.), Point::new(0., 0.)]), + vec![] + ); + let poly1 = Polygon::new( + LineString(vec![Point::new(1., -1.), Point::new(2., -1.), + Point::new(2., -2.), Point::new(1., -1.)]), + vec![] + ); + // line contained in the hole + let poly2 = Polygon::new( + LineString(vec![Point::new(-1., -1.), Point::new(-1., 10.), + Point::new(10., -1.), Point::new(-1., -1.)]), + vec![LineString(vec![Point::new(0., 0.), Point::new(3., 4.), + Point::new(3., 0.), Point::new(0., 0.)])] + ); + assert!(line0.intersects(&poly0)); + assert!(poly0.intersects(&line0)); + + assert!(!line0.intersects(&poly1)); + assert!(!poly1.intersects(&line0)); + + assert!(!line0.intersects(&poly2)); + assert!(!poly2.intersects(&line0)); + } } diff --git a/src/algorithm/length.rs b/src/algorithm/length.rs index 890f308a2..6924ff5fc 100644 --- a/src/algorithm/length.rs +++ b/src/algorithm/length.rs @@ -1,6 +1,6 @@ use num_traits::Float; -use types::{LineString, MultiLineString}; +use types::{Line, LineString, MultiLineString}; use algorithm::distance::Distance; /// Calculation of the length @@ -23,6 +23,14 @@ pub trait Length { fn length(&self) -> T; } +impl Length for Line + where T: Float +{ + fn length(&self) -> T { + self.start.distance(&self.end) + } +} + impl Length for LineString where T: Float { @@ -42,7 +50,7 @@ impl Length for MultiLineString #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, MultiLineString}; + use types::{Coordinate, Point, Line, LineString, MultiLineString}; use algorithm::length::Length; #[test] @@ -68,4 +76,11 @@ mod test { LineString(vec![p(0., 0.), p(0., 5.)])]); assert_eq!(15.0_f64, mline.length()); } + #[test] + fn line_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 1.)); + let line1 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + assert_eq!(line0.length(), 1.); + assert_eq!(line1.length(), 5.); + } } diff --git a/src/algorithm/rotate.rs b/src/algorithm/rotate.rs index 2fc6c993d..5b5c6014d 100644 --- a/src/algorithm/rotate.rs +++ b/src/algorithm/rotate.rs @@ -1,5 +1,5 @@ use num_traits::{Float, FromPrimitive}; -use types::{Point, Polygon, LineString, MultiPoint, MultiPolygon, MultiLineString}; +use types::{Point, Line, Polygon, LineString, MultiPoint, MultiPolygon, MultiLineString}; use algorithm::centroid::Centroid; // rotate a slice of points "angle" degrees about an origin @@ -92,6 +92,26 @@ impl RotatePoint for Point } } +impl Rotate for Line + where T: Float +{ + fn rotate(&self, angle: T) -> Self { + let pts = vec![self.start, self.end]; + let rotated = rotation_matrix(angle, &self.centroid().unwrap(), &pts); + Line::new(rotated[0], rotated[1]) + } +} + +impl RotatePoint for Line + where T: Float +{ + fn rotate_around_point(&self, angle: T, point: &Point) -> Self { + let pts = vec![self.start, self.end]; + let rotated = rotation_matrix(angle, point, &pts); + Line::new(rotated[0], rotated[1]) + } +} + impl Rotate for LineString where T: Float { @@ -312,4 +332,16 @@ mod test { let rotated = p.rotate_around_point(-45., &Point::new(10., 34.)); assert_eq!(rotated, Point::new(-10.506096654409877, 20.564971157455595)); } + #[test] + fn test_rotate_line() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.)); + let line1 = Line::new(Point::new(1., 0.9999999999999999), Point::new(-1., 1.)); + assert_eq!(line0.rotate(90.), line1); + } + #[test] + fn test_rotate_line_around_point() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.)); + let line1 = Line::new(Point::new(0., 0.), Point::new(-2., 0.00000000000000012246467991473532)); + assert_eq!(line0.rotate_around_point(90., &Point::new(0., 0.)), line1); + } } diff --git a/src/types.rs b/src/types.rs index 9e54037b3..a38b96e7a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -318,6 +318,32 @@ impl AddAssign for Bbox #[derive(PartialEq, Clone, Debug)] pub struct MultiPoint(pub Vec>) where T: Float; +#[derive(PartialEq, Clone, Debug)] +pub struct Line + where T: Float +{ + pub start: Point, + pub end: Point +} + +impl Line + where T: Float +{ + /// Creates a new line segment. + /// + /// ``` + /// use geo::{Point, Line}; + /// + /// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); + /// + /// assert_eq!(line.start, Point::new(0., 0.)); + /// assert_eq!(line.end, Point::new(1., 2.)); + /// ``` + pub fn new(start: Point, end: Point) -> Line { + Line {start: start, end: end} + } +} + #[derive(PartialEq, Clone, Debug)] pub struct LineString(pub Vec>) where T: Float;