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

Map coords inplace #170

Merged
merged 2 commits into from
Mar 1, 2018
Merged

Map coords inplace #170

merged 2 commits into from
Mar 1, 2018

Conversation

amandasaurus
Copy link
Member

To compliment the .map_coords() feature, I've added a .map_coords_inplace which will apply a provided function to all coordinatines in place, i.e. not doing any allocations.

Some of this has a lot of overlap with #164.

I've used that to add a .translate_inplace() feature.

@@ -23,15 +23,22 @@ pub trait Translate<T> {
/// assert_eq!(translated, correct_ls);
/// ```
fn translate(&self, xoff: T, yoff: T) -> Self where T: Float;

/// Translate a Geometry along its axes, but in place.
fn translate_inplace(&mut self, xoff: T, yoff: T) where T: Float;
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking about this last weekend. Seems like most of our "algorithms" (like Translate or Orient) should mutate self by default and if the user needs a new copy, they can just clone the object beforehand. As opposed to having _inplace variants of all our algorithms.

Copy link
Member

Choose a reason for hiding this comment

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

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the idea of mutating in-place by default. It also means you can use the various algorithms as trait objects, which makes things more flexible.

This seems related to a comment I made on #119:

One way you could make this easier (using the Translate algorithm as an example) is to add a required translate_inplace() and then add a translate() -> Self where Self: Sized + Clone which has a default impl that returns a mutated clone.

@frewsxcv
Copy link
Member

what if we combined both of our PRs and unify the coordinate iterators into a single trait like this?

trait CoordsIterator<T: Float> {
    fn iter_coords(&self) -> Box<Iterator<Item = &Coordinate<T>>>;

    fn iter_coords_mut(&mut self) -> Box<Iterator<Item = &mut Coordinate<T>>>;

    fn map_coords<NT: Float>(&self, f: &Fn(&Coordinate<T> -> Coordinate<NT>)) -> Self::Output;

    fn map_coords_inplace(&mut self, f: &Fn(&Coordinate<T> -> Coordinate<T>));
}

does some of this feel redundant? i'm undecided right now. even if they are a little redundant, they all do slightly different things and at this point i'm not too concerned. thoughts?

@amandasaurus
Copy link
Member Author

My main nagging bit with CoordsIter is that it's seems like a non-obvious way to get the same functionality as MapCoords. For (Multi)Point & LineString it makes sense to iterate over the points, but iterating over all the points in a Polygon (or MultiLinestring) without knowing what the Coordinate is (is it exterior? interior? Is this point part of the last linestring?) seems, well, a little silly. I can't see why you'd need it (maybe for bbox calculation 🤔). I can see how you can use CoordsIterMut to get a MapCoordsInplace functionality, but it's not as very clear as "here's a trait for mapping the coordinates". Sorry for being a little harsh.

I'm personally in favour of keeping an in-place & clone versions. I know you can replace the clone with the inplace easily, but I personally just like having an API which makes easy things easy. But it does increase the API and amount of code. You don't want to wide up like PHP with a mishmash of everything. I'm not wedded to the idea though. We should definitely have a way to do it inplace, so there's always an allocation-free option.

I tried to merge MapCoords and MapCoordsInplace as the same trait, but I wanted MapCoords to be able to have a different underlying type (i.e. allow mapping from Coordinate<T>Coordinate<U>), whereas MapCoordsInplace requires that the type is the same. I couldn't get that to compile though. So I took an easy way out. If anyone knows how to combine them all in one trait, I'm all ears.

@amandasaurus
Copy link
Member Author

I've rebased this. Any thoughts?

Copy link
Member

@frewsxcv frewsxcv left a comment

Choose a reason for hiding this comment

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

after looking over it again, seems good to me!

whenever you're ready, you should be able to merge by writing a github comment with r<equals>frewsxcv as seen here: https://bors.tech/documentation/

where T: Float, NT: Float;



Copy link
Member

Choose a reason for hiding this comment

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

lots of whitespace here

@Michael-F-Bryan
Copy link
Contributor

For my application at work I used the geo crate for a lot of prototyping before making our own geometry library which fits our domain better (we needed object-safety and built-in curves like splines, arcs, and ellipses). In our geometry library I found a really nice way to get the same functionality as coords_iter() and map_coords_inplace(). The geo crate was incredibly useful for getting up and running fast, so if the following ideas help feel free to use them 😃

The idea is you have a basic ForEach trait which iterates over all the "important" points in an object (spline handles, polyline vertices, etc) invoking some code.

This then lets you build nice things on top of ForEach, like a Translate trait which will translate the object in-place (with default methods that give you a translated clone and so on). The idea being you can almost trivially call for_each_mut() on the object and translate() each vertex in turn.

Our Rotate trait works in a very similar way.

Example trait definitions
pub trait ForEach {
    fn for_each<F>(&self, func: F)
    where
        F: FnMut(&Vector);
    fn for_each_mut<F>(&mut self, func: F)
    where
        F: FnMut(&mut Vector);
}

pub trait Translate {
    fn translate(&mut self, by: Vector);

    fn translated(&self, by: Vector) -> Self
    where
        Self: Sized + Clone,
    { ... }
    fn move_to(&mut self, new_centre: Vector)
    where
        Self: Centroid,
    { ... }
    fn moved(&self, new_centre: Vector) -> Self
    where
        Self: Centroid + Sized + Clone,
    { ... }
}

pub trait Rotate {
    fn rotate_about(&mut self, angle: f64, point: Vector);

    fn rotate(&mut self, angle: f64)
    where
        Self: Centroid,
    { ... }
    fn rotated(&self, angle: f64) -> Self
    where
        Self: Sized + Clone + Centroid,
    { ... }
}

@amandasaurus
Copy link
Member Author

bors r=frewsxcv

1 similar comment
@amandasaurus
Copy link
Member Author

bors r=frewsxcv

bors bot added a commit that referenced this pull request Mar 1, 2018
170: Map coords inplace r=frewsxcv a=rory

To compliment the `.map_coords()` feature, I've added a `.map_coords_inplace` which will apply a provided function to all coordinatines in place, i.e. not doing any allocations.

Some of this has a lot of overlap with #164.

I've used that to add a `.translate_inplace()` feature.
@bors
Copy link
Contributor

bors bot commented Mar 1, 2018

Build succeeded

@bors bors bot merged commit 31e66e2 into georust:master Mar 1, 2018
@amandasaurus amandasaurus deleted the map-coords-inplace branch March 3, 2018 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants