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 wrappers for GDALBuildVRT, GDALApplyGeoTransform, GDALInvGeoTransform #239

Merged
merged 20 commits into from Jan 28, 2022
Merged

Add wrappers for GDALBuildVRT, GDALApplyGeoTransform, GDALInvGeoTransform #239

merged 20 commits into from Jan 28, 2022

Conversation

amartin96
Copy link
Contributor

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

I did not know where to put apply_geo_transform and inv_geo_transform, so I left them in lib.rs. Please let me know where they should go.

I also did not know what manner of tests should be included. Please let me know what I should contribute in that regard.

src/lib.rs Outdated Show resolved Hide resolved
src/raster_programs.rs Outdated Show resolved Hide resolved
src/raster_programs.rs Outdated Show resolved Hide resolved
) -> Result<Dataset> {
// Convert dest to raw string
let dest = match dest {
Some(s) => CString::new(s)?.into_raw(),
Copy link
Member

Choose a reason for hiding this comment

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

Same as below, dest.map(|p| p.as_mut_ptr()).

src/raster_programs.rs Outdated Show resolved Hide resolved
src/raster_programs.rs Outdated Show resolved Hide resolved
src/raster_programs.rs Outdated Show resolved Hide resolved
@amartin96
Copy link
Contributor Author

Thanks for the feedback. I've summarized my updates below:

  • invert_geo_transform now returns a result.
  • Added struct BuildVRTOptions that frees the owned pointer on drop.
  • build_vrt
    • Uses _last_null_pointer_err to populate the error result.
    • Employs that thin generic wrapper pattern. (is there a name for this?)
    • Takes dest: Option<&Path> instead of String, though it gets converted using Path::to_string_lossy

I didn't take the suggestions re: raw strings verbatim, as I didn't see how they added terminating null bytes. I did look at GDALBuildVRTOptionsNew and determine that it doesn't actually mutate the strings, so I left them owned by the CStrings, which hopefully addresses part of the concern.


fn _build_vrt(dest: Option<&Path>, datasets: &[&Dataset], args: Vec<String>) -> Result<Dataset> {
// Convert dest to CString
let dest = match dest {
Copy link
Member

@lnicola lnicola Jan 12, 2022

Choose a reason for hiding this comment

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

It's not great, but let's use utils::_path_to_c_string here.

(untested)

let dest = dest.map(crate::_path_to_c_string).transpose()?;
let c_dest = dest.map(CString::as_ptr).unwrap_or(ptr::null());

/// [GDALBuildVRTOptionsNew]: https://gdal.org/api/gdal_utils.html#_CPPv422GDALBuildVRTOptionsNewPPcP28GDALBuildVRTOptionsForBinary
fn new(args: Vec<String>) -> Result<Self> {
// Convert args to CStrings to add terminating null bytes
let mut cstr_args = Vec::<CString>::new();
Copy link
Member

Choose a reason for hiding this comment

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

Untested, but:

let cstr_args = args.into_iter()
    .map(|arg| CString::new(arg))
    .collect::<Result<Vec<_>>>()?;

This looks nicer and (as a minor point) pre-allocates the destination Vec.

@amartin96
Copy link
Contributor Author

Incorporated latest feedback.

/// [GDALBuildVRT]: https://gdal.org/api/gdal_utils.html#gdal__utils_8h_1a057aaea8b0ed0476809a781ffa377ea4
/// [program docs]: https://gdal.org/programs/gdalbuildvrt.html
pub fn build_vrt<D: Borrow<Dataset>>(
dest: Option<&Path>,
Copy link
Member

Choose a reason for hiding this comment

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

Sorry for missing this. I guess dest: Option<P> ... where P: AsRef<Path> would be nicer, so you can call it with strings.

@lnicola
Copy link
Member

lnicola commented Jan 12, 2022

r? @jdroenner

@amartin96
Copy link
Contributor Author

@lnicola Taking dest: Option<P> ... where P: AsRef<Path> has made it so a type annotation is needed when passing None:

gdal::raster_programs::build_vrt(None, datasets, args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `P` declared on the function `build_vrt`

A default type cannot be specified because that is not allowed for functions:

error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
pub fn build_vrt<D: Borrow<Dataset>, P: AsRef<Path> = &'static str>(
                                     ^

Is there a cleaner way to do this?

@lnicola
Copy link
Member

lnicola commented Jan 12, 2022

Ah, good point. You can still specify the type, but it's an ugly API. Let's revert this change .

Copy link
Contributor

@rmanoka rmanoka left a comment

Choose a reason for hiding this comment

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

@amartin96 Nice PR! The overall code looks great; my suggestions are mostly cosmetic.

src/lib.rs Outdated
@@ -42,6 +43,43 @@ pub use dataset::{
pub use driver::Driver;
pub use metadata::Metadata;

/// Apply GeoTransform to x/y coordinate.
Copy link
Contributor

Choose a reason for hiding this comment

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

The GeoTransform relevant methods might be better placed inside the dataset or the rasters module. It is anyway only relevant to rasters IIUC.

src/lib.rs Outdated
/// Wraps [GDALApplyGeoTransform].
///
/// [GDALApplyGeoTransform]: https://gdal.org/api/raster_c_api.html#_CPPv421GDALApplyGeoTransformPdddPdPd
pub fn apply_geo_transform(geo_transform: &GeoTransform, pixel: f64, line: f64) -> (f64, f64) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be more ergonomic to throw these inside a trait that is impl by GeoTransform so we get the nicer:

let (wx, wy) = geo_t.apply(pix, line);
let inv_t = geo_t.invert()?;

Optional suggestion.

@@ -0,0 +1,107 @@
use crate::{
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be nice to move these into a sub-module raster_programs::vrt or similar. Not a major concern, but this mod. might get cluttered if we bring in more of the programs like Warp, etc.

///
/// [GDALBuildVRT]: https://gdal.org/api/gdal_utils.html#gdal__utils_8h_1a057aaea8b0ed0476809a781ffa377ea4
/// [program docs]: https://gdal.org/programs/gdalbuildvrt.html
pub fn build_vrt<D: Borrow<Dataset>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we expose the BuildVRTOptions struct, and let this method take an Option<BuildVrtOptions> instead of a Vec<String> ? Seems more similar to the C api, and also a bit more natural.

/// See [GDALBuildVRTOptionsNew].
///
/// [GDALBuildVRTOptionsNew]: https://gdal.org/api/gdal_utils.html#_CPPv422GDALBuildVRTOptionsNewPPcP28GDALBuildVRTOptionsForBinary
fn new(args: Vec<String>) -> Result<Self> {
Copy link
Contributor

@rmanoka rmanoka Jan 14, 2022

Choose a reason for hiding this comment

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

Does args have to be a Vec<String>? Wouldn't any IntoIterator<Item = ...> work? This would avoid allocations if it's a fixed sized array.

Going one more step, does the inner type have to be String or anything that impls TryInto<CString> (like Vec<U8> or Vec<NonZeroU8>, etc.)?

We can leave it as-is if this makes the API too complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I attempted to implement this as follows:

pub fn new<S: TryInto<CString>, I: IntoIterator<Item = S>>(args: I) -> Result<Self> {
    let cstr_args = args.into_iter().map(|x| x.try_into()).collect::<std::result::Result<Vec<CString>, _>>()?;

However, I do not know how to convert TryInto<CString>::Error to GdalError::FfiNulError.

Can you show me how its done? Is the rest of it as you intended?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, good pt.; we can't give a good error for arbitrary conversion failures, and the user could anyway map as req. before passing it. How about:

fn new<S: Into<Vec<u8>>, I: IntoIterator<Item = S>>(args: I) -> Result<Self> {
    let c_args = args
        .into_iter()
        .map(|x| CString::new(x))
        .collect::<std::result::Result<Vec<CString>, _>>()?;
    ....
}

This is inline with the rest of the lib, where we expect &str to pass CString to the FFI. The above seems to accept both &str, and String, so that should be good.

@amartin96
Copy link
Contributor Author

I will look into applying the latest feedback this weekend.

@rmanoka
Copy link
Contributor

rmanoka commented Jan 27, 2022

Changes look good to me; thanks again @amartin96 . Pinging @lnicola @jdroenner : could you pls. allow running CI and merge if all looks good?

@lnicola
Copy link
Member

lnicola commented Jan 27, 2022

I took a hard page fault on this one 😄.

CHANGES.md Outdated Show resolved Hide resolved
@rmanoka
Copy link
Contributor

rmanoka commented Jan 27, 2022

@amartin96 One final nitpick: it would be good to rename GeoTransformTrait as GeoTransformEx. That's the typical rust naming convention for traits to add functionalities.

src/dataset.rs Outdated
///
/// [GDALInvGeoTransform]: https://gdal.org/api/raster_c_api.html#_CPPv419GDALInvGeoTransformPdPd
fn invert(&self) -> Result<GeoTransform> {
let mut gt_out: GeoTransform = Default::default();
Copy link
Member

Choose a reason for hiding this comment

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

We could use MaybeUninit here, but it's fine to leave it like this.

Minor nit: I prefer keeping as much code as possible outside unsafe blocks, as in let rv = unsafe { ... }; if rv == 0 { ... }.

.map(|x| x.c_options as *const GDALBuildVRTOptions)
.unwrap_or(null());

let result = unsafe {
Copy link
Member

Choose a reason for hiding this comment

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

Same nit about large unsafe blocks.

CHANGES.md Outdated
@@ -2,6 +2,13 @@

## Unreleased

- Add `programs::raster::build_vrt`
- Add `GeoTransformTrait` extension trait with `apply` and `invert`
- Add `apply_geo_transform`
Copy link
Member

Choose a reason for hiding this comment

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

These two lines need to be removed

@lnicola
Copy link
Member

lnicola commented Jan 28, 2022

bors d+

@bors
Copy link
Contributor

bors bot commented Jan 28, 2022

✌️ amartin96 can now approve this pull request. To approve and merge a pull request, simply reply with bors r+. More detailed instructions are available here.

@amartin96
Copy link
Contributor Author

bors r+

@bors
Copy link
Contributor

bors bot commented Jan 28, 2022

Build succeeded:

@bors bors bot merged commit 59d8c0e into georust:master Jan 28, 2022
bors bot added a commit that referenced this pull request Oct 21, 2022
321: Convert `byte_no_cf` and `cf_nasa_4326` to Zarr r=metasim,jdroenner a=lnicola

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

Closes #239

I left `alldatatypes.nc` untouched because it's string arrays are not supported. Unfortunately, I had to disable these tests on pre-3.4 GDAL, where Zarr is not supported.

Co-authored-by: Laurențiu Nicola <lnicola@dend.ro>
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.

None yet

3 participants