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

Implement getter methods on AffineTransform to access internal elements #1159

Merged
merged 8 commits into from
May 8, 2024

Conversation

weiji14
Copy link
Member

@weiji14 weiji14 commented Mar 12, 2024

Enable internal elements of the affine transform matrix to be retrieved using getter methods. Included some short documentation and a unit test.

Example usage:

use geo::AffineTransform;

let transform = AffineTransform::new(10.0, 0.0, 400_000.0, 0.0, -10.0, 500_000.0);

let a: f64 = transform.a();
let b: f64 = transform.b();
let xoff: f64 = transform.xoff();
let d: f64 = transform.d();
let e: f64 = transform.e();
let yoff: f64 = transform.yoff();

assert_eq!(transform, AffineTransform::new(a, b, xoff, d, e, yoff))
  • 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.

Fixes #1158

Enable internal elements of the affine transform matrix to be retrieved using letters "a", "b", "xoff", "d", "e", "yoff". Included some short documentation and a unit test.
Comment on lines 473 to 478
"a" => &self.0[0][0],
"b" => &self.0[0][1],
"xoff" => &self.0[0][2],
"d" => &self.0[1][0],
"e" => &self.0[1][1],
"yoff" => &self.0[1][2],
Copy link
Member Author

Choose a reason for hiding this comment

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

Need to decide if using these names are ok, or maybe just do a, b, c, d, e, f. See discussion at #1158.

"d" => &self.0[1][0],
"e" => &self.0[1][1],
"yoff" => &self.0[1][2],
_ => &f64::NAN,
Copy link
Member Author

Choose a reason for hiding this comment

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

Not ideal to return NaN here, would prefer if the possible indexes are limited to just 6 possible values, maybe with an enum?

Copy link
Member

Choose a reason for hiding this comment

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

With getters this wouldn't be an issue.

Copy link
Member

Choose a reason for hiding this comment

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

Out-of-bounds indexing in Rust is usually a panic, right? I'm not sure if this is strictly out of bounds, though.

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 on second thought, getter methods might actually be better. I've refactored things in commit a0fdea5

@lnicola
Copy link
Member

lnicola commented Mar 12, 2024

I personally prefer methods (getters) instead of Index.

More ergonomic access since it allow type-ahead, and no risk of out of bound errors.
@weiji14 weiji14 changed the title Implement Index trait on AffineTransform to access internal elements Implement getter methods on AffineTransform to access internal elements Mar 12, 2024
/// ```
pub fn new(a: T, b: T, xoff: T, d: T, e: T, yoff: T) -> Self {
Self([[a, b, xoff], [d, e, yoff], [T::zero(), T::zero(), T::one()]])
}

/// Get the x-resolution value.
pub fn a(&self) -> &T {
Copy link
Member

Choose a reason for hiding this comment

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

CoordNum implements Copy, so is it better to return T here instead?

Copy link
Member Author

@weiji14 weiji14 Mar 13, 2024

Choose a reason for hiding this comment

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

Yeah, finding out that I need to deref or call .to_owned() on the value in order to use it elsewhere. Maybe it is better to return a cloned copy?

Edit: done at 6431033

Copy link
Member

Choose a reason for hiding this comment

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

since T is copy, you should be able to just remove the & I think

/// let c: f64 = transform.xoff();
/// let d: f64 = transform.d();
/// let e: f64 = transform.e();
/// let f: f64 = transform.yoff();
Copy link
Member

Choose a reason for hiding this comment

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

To make this an actual doctest, you could create a second transform with these values and assert it's equal?

assert_eq!(transform, AffineTransform::new(a, b, c, d, e, f))

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, done at 771061c!

///
/// let a: f64 = transform.a();
/// let b: f64 = transform.b();
/// let c: f64 = transform.xoff();
Copy link
Member

Choose a reason for hiding this comment

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

Maybe name this variable binding xoff? Not sure

Copy link
Member

Choose a reason for hiding this comment

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

Yes, xoff please.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in a6138f1

///
/// let a: f64 = transform.a();
/// let b: f64 = transform.b();
/// let c: f64 = transform.xoff();
Copy link
Member

Choose a reason for hiding this comment

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

Yes, xoff please.

/// Get the y-offset value.
pub fn yoff(&self) -> T {
self.0[1][2]
}
Copy link
Member

Choose a reason for hiding this comment

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

These doc comments – if they're needed at all – should not refer to the terminology ("resolution", "rotation" etc) used in Matt's blog post, as these methods aren't solely intended for vector <--> raster coordinate transformations.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the methods here are only used to retrieve the element values, not for coordinate transformation. So what would be good descriptions for each of these getter methods? Do we simply don't assign any meaning to them, and assume that the user can figure out what the a, b, xoff, c, d, yoff values represent?

Copy link
Member

Choose a reason for hiding this comment

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

That depends on whether it's reasonable to assume that someone who's trying to build a custom affine transform is familiar with its mechanics, and has (or is likely to) look at https://docs.rs/geo/latest/geo/algorithm/affine_ops/struct.AffineTransform.html, where the position and thus meaning of a, b, xoff etc are obvious.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally, there should be some description when a user hovers over the method in their IDE, so that they don't have to refer to the online documentation. Having a method with a single letter like .a() or .b() unfortunately isn't very descriptive...

Copy link
Member

Choose a reason for hiding this comment

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

How about just a link back to the constructor docs, which has an ascii diagram of the matrix?

// See [new] for this value's role in the affine transformation.

Copy link
Member Author

Choose a reason for hiding this comment

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

How about just a link back to the constructor docs, which has an ascii diagram of the matrix?

Tried this locally, and clicking on the AffineTransform::new link in my editor still takes me to the online documentation page.

Screenshot at 2024-04-26 16-42-12

But I'll take this as a good-enough compromise, unless anyone has any other suggestions?

Copy link
Member

@kylebarron kylebarron Apr 26, 2024

Choose a reason for hiding this comment

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

In the rendered docs online that link should point to the correct anchor.

weiji14 added a commit to weiji14/cog3pio that referenced this pull request Mar 25, 2024
Work out the list of x and y coordinates for the raster grid from the Affine transformation matrix. Requires changes being developed at georust/geo#1159.
Link to the AffineTransform::new docs for each of the a, b, xoff, d, e, yoff methods, instead of using descriptions like 'x-resolution', 'x-rotation', 'x-offset', 'y-rotation', 'y-resolution', and 'y-offset' respectively.
@weiji14 weiji14 marked this pull request as ready for review April 26, 2024 04:54
@weiji14
Copy link
Member Author

weiji14 commented May 7, 2024

Anything else I can do to move this PR along?

Copy link
Member

@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.

LGTM - and it looks like @urschrei's feedback has been addressed.

@michaelkirk michaelkirk enabled auto-merge May 8, 2024 01:22
@michaelkirk michaelkirk added this pull request to the merge queue May 8, 2024
Merged via the queue into georust:main with commit 481196b May 8, 2024
15 checks passed
@weiji14 weiji14 deleted the affinetransform-index branch May 8, 2024 11:38
weiji14 added a commit to weiji14/cog3pio that referenced this pull request May 9, 2024
Getter methods on AffineTransform implemented in georust/geo#1159 has been merged into the main branch of georust/geo, so no longer need to point to my personal fork. Also fixed a mismatched type when accessing the x_res and y_res attributes.
weiji14 added a commit to weiji14/cog3pio that referenced this pull request May 9, 2024
* ✨ Implement cog3pio xarray BackendEntrypoint

Initial implementation of a 'cog3pio' xarray BackendEntrypoint for decoding GeoTIFF files! Following instructions at https://docs.xarray.dev/en/v2024.02.0/internals/how-to-add-new-backend.html on registering a backend. Only a minimal implementation for now to read the pixel array data, with dummy x and y coordinates.

* ➕ Add xarray

N-D labeled arrays and datasets in Python!

* 🌐 Decode x and y coordinates from affine transform

Work out the list of x and y coordinates for the raster grid from the Affine transformation matrix. Requires changes being developed at georust/geo#1159.

* 👷 Disable CI builds on 32-bit Windows

Getting an error with compiling pandas 2.2.1 on Windows x86 (32-bit), and pandas has moved away from providing 32-bit Windows wheels, see pandas-dev/pandas#54979, so might as well not build cog3pio wheels for 32-bit Windows since we can't test them properly.

* ⚗️ Benchmark cog3pio engine against rioxarray

Parametrized test to compare loading a GeoTIFF using cog3pio vs rioxarray via their respective xarray BackendEntrypoints. Made a new extras dependency group in pyproject.toml called 'benchmark', and listed both `pytest-codspeed` and `rioxarray` under it.

* 🐛 Add half pixel offset to get centrepoint of top left pixel

The affine transform from the GeoTIFF represents the top-left corner of the top-left pixel (gridline registration), but we need to convert that to the center of the top-left pixel (pixel registration) which is what xarray typically assumes. Added some more comments on how the xy_coords is calculated, and updated unit tests with new x/y min/max bounds. Commented out the mean value assertion for now as NaN handling is not implemented yet.

* 📌 Pin to georust/geo at rev 481196b

Getter methods on AffineTransform implemented in georust/geo#1159 has been merged into the main branch of georust/geo, so no longer need to point to my personal fork. Also fixed a mismatched type when accessing the x_res and y_res attributes.

* 📝 Document usage of cog3pio xarray backend engine in main README.md

Show how a GeoTIFF file can be read into an xarray.DataArray object by passing `engine="cog3pio"` to xarray.open_dataarray.

* 👥 Push out multi-dtype feature in roadmap to Q2

The multiple dtype feature is a little trickier than expected, so will push this out as a medium term goal for Q2 2024. Likely will depend on how the georust/geotiff crate's progress is like.
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.

Expose fields in AffineTransform struct (or have a way to access the internal values)?
5 participants