Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add translation trait #128

Merged
merged 1 commit into from
Aug 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/algorithm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ pub mod orient;
pub mod extremes;
/// Rotates a geometry around either its centroid or a point by an angle, given in degrees.
pub mod rotate;
/// Translates a geometry along the given offsets.
pub mod translate;
196 changes: 196 additions & 0 deletions src/algorithm/translate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use num_traits::{Float, FromPrimitive};
use types::{Point, Polygon, LineString, Line, MultiPoint, MultiPolygon, MultiLineString};

// translate a slice of points by the given offsets
fn translation_matrix<T>(x_offset: T, y_offset: T, points: &[Point<T>]) -> Vec<Point<T>>
where T: Float
{
points
.iter()
.map(|point| {
let new_x = point.x() + x_offset;
let new_y = point.y() + y_offset;
Point::new(new_x, new_y)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if we should add a Points::map method to map coordinates to new values

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on something like this which would do that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow, but if @rory does, I'll just wait on the PR!
IMO rotate / translate / skew / scale should all be implemented on top of a more general affine impl that populates an affine transformation matrix with the relevant values depending on the transformation type, but that would require us to pull in a linear algebra crate…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow, but if @rory does, I'll just wait on the PR!

for example:

fn translation_matrix<T>(x_offset: T, y_offset: T, points: &[Point<T>]) -> Vec<Point<T>>
    where T: Float
{
    points
        .iter()
        .map(|point| point.map(|x, y| (x + x_offset, y + y_offset))
        .collect::<Vec<_>>()
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My WIP is here: https://github.com/rory/rust-geo/tree/coord_change. Specifically you can see the file here amandasaurus@b7a01f5 . My idea is to allow you to pass in a function which will take a Coordinate and return a new Coordinate, which gives you maximum flexibility. I don't like the name (coord_change), but can't think of much better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe map_coords? That's what python-geojson calls it https://pypi.python.org/pypi/geojson#map-coords

})
.collect::<Vec<_>>()
}

pub trait Translate<T> {
/// Translate a Geometry along its axes by the given offsets
///
///
/// ```
/// use geo::{Point, LineString};
/// use geo::algorithm::translate::{Translate};
///
/// let mut vec = Vec::new();
/// vec.push(Point::new(0.0, 0.0));
/// vec.push(Point::new(5.0, 5.0));
/// vec.push(Point::new(10.0, 10.0));
/// let linestring = LineString(vec);
/// let translated = linestring.translate(1.5, 3.5);
/// let mut correct = Vec::new();
/// correct.push(Point::new(1.5, 3.5));
/// correct.push(Point::new(6.5, 8.5));
/// correct.push(Point::new(11.5, 13.5));
/// let correct_ls = LineString(correct);
/// assert_eq!(translated, correct_ls);
/// ```
fn translate(&self, xoff: T, yoff: T) -> Self where T: Float;
}

impl<T> Translate<T> for Point<T>
where T: Float
{
/// Translate the Point by the given offsets
fn translate(&self, xoff: T, yoff: T) -> Self {
Point::new(self.x() + xoff, self.y() + yoff)
}
}

impl<T> Translate<T> for LineString<T>
where T: Float
{
/// Translate the LineString by the given offsets
fn translate(&self, xoff: T, yoff: T) -> Self {
LineString(translation_matrix(xoff, yoff, &self.0))
}
}

impl<T> Translate<T> for Polygon<T>
where T: Float + FromPrimitive
{
/// Translate the Polygon by the given offsets
fn translate(&self, xoff: T, yoff: T) -> Self {
Polygon::new(LineString(translation_matrix(xoff, yoff, &self.exterior.0)),
self.interiors
.iter()
.map(|ring| LineString(translation_matrix(xoff, yoff, &ring.0)))
.collect())
}
}

impl<T> Translate<T> for Line<T>
where T: Float
{
fn translate(&self, xoff: T, yoff: T) -> Self {
let translated = translation_matrix(xoff, yoff, &vec![self.start, self.end]);
Line::new(translated[0], translated[1])
}
}

impl<T> Translate<T> for MultiPolygon<T>
where T: Float + FromPrimitive
{
fn translate(&self, xoff: T, yoff: T) -> Self {
MultiPolygon(self.0.iter().map(|poly| poly.translate(xoff, yoff)).collect())
}
}

impl<T> Translate<T> for MultiLineString<T>
where T: Float + FromPrimitive
{
fn translate(&self, xoff: T, yoff: T) -> Self {
MultiLineString(self.0.iter().map(|ls| ls.translate(xoff, yoff)).collect())
}
}

impl<T> Translate<T> for MultiPoint<T>
where T: Float + FromPrimitive
{
fn translate(&self, xoff: T, yoff: T) -> Self {
MultiPoint(self.0.iter().map(|p| p.translate(xoff, yoff)).collect())
}
}

#[cfg(test)]
mod test {
use types::{Point, LineString, Polygon};
use super::*;
#[test]
fn test_translate_point() {
let p = Point::new(1.0, 5.0);
let translated = p.translate(30.0, 20.0);
assert_eq!(translated, Point::new(31.0, 25.0));
}
#[test]
fn test_translate_linestring() {
let mut vec = Vec::new();
vec.push(Point::new(0.0, 0.0));
vec.push(Point::new(5.0, 1.0));
vec.push(Point::new(10.0, 0.0));
let linestring = LineString(vec);
let translated = linestring.translate(17.0, 18.0);
let mut correct = Vec::new();
correct.push(Point::new(17.0, 18.0));
correct.push(Point::new(22.0, 19.0));
correct.push(Point::new(27., 18.));
let correct_ls = LineString(correct);
assert_eq!(translated, correct_ls);
}
#[test]
fn test_translate_polygon() {
let points_raw = vec![(5., 1.), (4., 2.), (4., 3.), (5., 4.), (6., 4.), (7., 3.),
(7., 2.), (6., 1.), (5., 1.)];
let points = points_raw
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
let poly1 = Polygon::new(LineString(points), vec![]);
let translated = poly1.translate(17.0, 18.0);
let correct_outside = vec![(22.0, 19.0),
(21.0, 20.0),
(21.0, 21.0),
(22.0, 22.0),
(23.0, 22.0),
(24.0, 21.0),
(24.0, 20.0),
(23.0, 19.0),
(22.0, 19.0)];
let correct = Polygon::new(LineString(correct_outside
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>()),
vec![]);
// results agree with Shapely / GEOS
assert_eq!(translated, correct);
}
#[test]
fn test_rotate_polygon_holes() {
let ls1 = LineString(vec![Point::new(5.0, 1.0),
Point::new(4.0, 2.0),
Point::new(4.0, 3.0),
Point::new(5.0, 4.0),
Point::new(6.0, 4.0),
Point::new(7.0, 3.0),
Point::new(7.0, 2.0),
Point::new(6.0, 1.0),
Point::new(5.0, 1.0)]);

let ls2 = LineString(vec![Point::new(5.0, 1.3),
Point::new(5.5, 2.0),
Point::new(6.0, 1.3),
Point::new(5.0, 1.3)]);

let poly1 = Polygon::new(ls1, vec![ls2]);
let rotated = poly1.translate(17.0, 18.0);
let correct_outside = vec![(22.0, 19.0),
(21.0, 20.0),
(21.0, 21.0),
(22.0, 22.0),
(23.0, 22.0),
(24.0, 21.0),
(24.0, 20.0),
(23.0, 19.0),
(22.0, 19.0)]
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
let correct_inside = vec![(22.0, 19.3), (22.5, 20.0), (23.0, 19.3), (22.0, 19.3)]
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
assert_eq!(rotated.exterior.0, correct_outside);
assert_eq!(rotated.interiors[0].0, correct_inside);
}
}