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 AffineOps trait to transform an entire geometry (plus some tweaks) #871

Merged
merged 1 commit into from
Jul 1, 2022

Conversation

michaelkirk
Copy link
Member

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

As discussed in this comment, adds the ability to compose a transformation and apply the thing to an entire geometry (saving users the hassle of having to map_coords themselves:

let center = ls.bounding_rect().unwrap().center();
let mut transform = AffineTransform::skew(40.0, 40.0, center).rotated(45.0, center);
ls.affine_transform(&transform);

I also included some tweaks based on my experience using AffineTransform in another project.

@michaelkirk michaelkirk requested a review from urschrei June 30, 2022 22:20
@michaelkirk
Copy link
Member Author

Actually - hold off. I'm going to think about naming a bit more...

@michaelkirk michaelkirk reopened this Jun 30, 2022

/// Apply `transform` immutably, outputting a new geometry.
#[must_use]
fn affine_transform(&self, transform: &AffineTransform<T>) -> Self;
Copy link
Member Author

@michaelkirk michaelkirk Jun 30, 2022

Choose a reason for hiding this comment

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

Originally I wanted to do:

mutable: affine_transform
immutable: affine_transformed

But it just conflicts too much with our existing implementation conventions. I'm going to follow up with another PR (update: see #872) to add mutable flavors with consistent naming for Scale/Skew/Rotate/Translate.

Copy link
Member Author

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

Ok - this is ready for review!

@@ -127,6 +183,10 @@ impl<T: CoordNum> AffineTransform<T> {
)
}

pub fn is_identity(&self) -> bool {
Copy link
Member Author

Choose a reason for hiding this comment

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

This came in handy while trying to skip application in my code base.

Copy link
Member

Choose a reason for hiding this comment

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

Could we get a brief example (or explanation as a doc comment)?

@@ -141,8 +201,8 @@ impl<T: CoordNum> AffineTransform<T> {
/// xoff = origin.x - (origin.x * xfact)
/// yoff = origin.y - (origin.y * yfact)
/// ```
pub fn scale(xfact: T, yfact: T, origin: Point<T>) -> Self {
let (x0, y0) = origin.x_y();
pub fn scale(xfact: T, yfact: T, origin: impl Into<Coordinate<T>>) -> Self {
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 if this is controversial - but otherwise this is always kind of a papercut for me at the call site.

Copy link
Member

Choose a reason for hiding this comment

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

IIRC using Into in function signatures can lead to less informative error messages but I can't find the ref now and I would hope that the signature makes it obvious what's going on, so 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Sometimes I daydream about getting rid of one of Point/Coord... 🔥

fn affine_transform_mut(&mut self, transform: &AffineTransform<T>);

/// Apply `transform` immutably, outputting a new geometry.
#[must_use]
Copy link
Member

Choose a reason for hiding this comment

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

TIL about must_use. This is so good.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, using a builder pattern on something which is Copy makes it very easy to forget that this is wrong:

let mut transform = AffineTransform::identity();
transorm.translated(1.0, 2.0);
geometry.affine_transform(&transform);

It should be:

let mut transform = AffineTransform::identity();
transform = transorm.translated(1.0, 2.0);
geometry.affine_transform(&transform);

And now the user will at least get a warning to clue them in.

@@ -13,7 +66,7 @@ use std::fmt;
/// `AffineTransform` is a row-major matrix.
/// 2D affine transforms require six matrix parameters:
///
/// `[a, b, d, e, xoff, yoff]`
/// `[a, b, xoff, d, e, yoff]`
Copy link
Member

Choose a reason for hiding this comment

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

Ah, yes. I knew I'd forgotten to update something.

Copy link
Member

@urschrei urschrei left a comment

Choose a reason for hiding this comment

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

This is great. The only addition I'd like to see is a short note in the docs (maybe using a ## Performance header?) for algorithm::Scale and co., noting that chaining them isn't as efficient as using a chained AffineTransform and affine_transform(_mut). WDYT?

- mark AffineTransform builder methods as must_use
- default AffineTransform to f64
- accept coordinates for origin param
- add AffineTransformation.is_identity
- update docs
@michaelkirk
Copy link
Member Author

I added the new docs in e2e17fc and then squashed all my commits, which were a bit messy.

@urschrei
Copy link
Member

urschrei commented Jul 1, 2022

Thanks, LGTM! Merge when you're ready.

@michaelkirk
Copy link
Member Author

bors r=urschrei

Thanks for the review!

@bors
Copy link
Contributor

bors bot commented Jul 1, 2022

Build succeeded:

@bors bors bot merged commit dcaaa9a into main Jul 1, 2022
@bors bors bot deleted the mkirk/affine branch July 1, 2022 17:09
bors bot added a commit that referenced this pull request Jul 15, 2022
872: Add helpful methods to affine backed traits r=urschrei a=michaelkirk

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

Leverages the new `AffineOps::affine_transform` trait to drive our various affine affiliated traits.

This doubles down on the `do_the_thing() -> Self` | `do_the_thing_mut()` naming convention from #871

Also, I was able to generically re-implement the Rotate trait in terms of other traits, which was neat. 

And I added some useful (I think) helper methods to our other Affine driven traits.


FIXES #287 (somewhat incidentally)

Co-authored-by: Michael Kirk <michael.code@endoftheworl.de>
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.

2 participants