Skip to content

Commit

Permalink
Implement CoordTransformOptions and CoordTransform::new_ex constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
ttencate committed Feb 16, 2023
1 parent 409d577 commit 6c3577a
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Added `CoordTransform::new_ex` and `CoordTransformOptions`

- <https://github.com/georust/gdal/pull/372>

- Added `CslStringList::add_string`

- <https://github.com/georust/gdal/pull/364>
Expand Down
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub enum GdalError {
},
#[error("Can't cast to f64")]
CastToF64Error,
#[error("OCT method '{method_name}' returned error")]
OctError { method_name: &'static str },
#[error("OGR method '{method_name}' returned error: '{err:?}'")]
OgrError {
err: OGRErr::Type,
Expand Down
160 changes: 160 additions & 0 deletions src/spatial_ref/srs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,148 @@ use std::str::FromStr;

use crate::errors::*;

/// Options for [`CoordTransform::new_ex`].
#[derive(Debug)]
pub struct CoordTransformOptions {
inner: gdal_sys::OGRCoordinateTransformationOptionsH,
}

impl Drop for CoordTransformOptions {
fn drop(&mut self) {
unsafe { gdal_sys::OCTDestroyCoordinateTransformationOptions(self.inner) };
self.inner = ptr::null_mut();
}
}

impl CoordTransformOptions {
/// Creation options for [`CoordTransform`].
pub fn new() -> Result<CoordTransformOptions> {
let c_obj = unsafe { gdal_sys::OCTNewCoordinateTransformationOptions() };
if c_obj.is_null() {
return Err(_last_null_pointer_err(
"OCTNewCoordinateTransformationOptions",
));
}
Ok(CoordTransformOptions { inner: c_obj })
}

/// Sets an area of interest.
///
/// The west longitude is generally lower than the east longitude, except for areas of interest
/// that go across the anti-meridian.
///
/// # Arguments
///
/// - `west_longitude_deg` – West longitude (in degree). Must be in [-180,180]
/// - `south_latitude_deg` – South latitude (in degree). Must be in [-90,90]
/// - `east_longitude_deg` – East longitude (in degree). Must be in [-180,180]
/// - `north_latitude_deg` – North latitude (in degree). Must be in [-90,90]
pub fn set_area_of_interest(
&mut self,
west_longitude_deg: f64,
south_latitude_deg: f64,
east_longitude_deg: f64,
north_latitude_deg: f64,
) -> Result<()> {
let ret_val = unsafe {
gdal_sys::OCTCoordinateTransformationOptionsSetAreaOfInterest(
self.inner,
west_longitude_deg,
south_latitude_deg,
east_longitude_deg,
north_latitude_deg,
)
};
if ret_val == 0 {
return Err(GdalError::OctError {
method_name: "OCTCoordinateTransformationOptionsSetAreaOfInterest",
});
}
Ok(())
}

/// Sets the desired accuracy for coordinate operations.
///
/// Only coordinate operations that offer an accuracy of at least the one specified will be
/// considered.
///
/// An accuracy of 0 is valid and means a coordinate operation made only of one or several
/// conversions (map projections, unit conversion, etc.) Operations involving ballpark
/// transformations have a unknown accuracy, and will be filtered out by any dfAccuracy >= 0
/// value.
///
/// If this option is specified with PROJ < 8, the `OGR_CT_OP_SELECTION` configuration option
/// will default to `BEST_ACCURACY`.
#[cfg(all(major_ge_3, minor_ge_3))]
pub fn desired_accuracy(&mut self, accuracy: f64) -> Result<()> {
let ret_val = unsafe {
gdal_sys::OCTCoordinateTransformationOptionsSetDesiredAccuracy(self.inner, accuracy)
};
if ret_val == 0 {
return Err(GdalError::OctError {
method_name: "OCTCoordinateTransformationOptionsSetDesiredAccuracy",
});
}
Ok(())
}

/// Sets whether ballpark transformations are allowed.
///
/// By default, PROJ may generate "ballpark transformations" (see
/// <https://proj.org/glossary.html>) when precise datum transformations are missing. For high
/// accuracy use cases, such transformations might not be allowed.
///
/// If this option is specified with PROJ < 8, the `OGR_CT_OP_SELECTION` configuration option
/// will default to `BEST_ACCURACY`.
#[cfg(all(major_ge_3, minor_ge_3))]
pub fn set_ballpark_allowed(&mut self, ballpark_allowed: bool) -> Result<()> {
let ret_val = unsafe {
gdal_sys::OCTCoordinateTransformationOptionsSetBallparkAllowed(
self.inner,
ballpark_allowed as libc::c_int,
)
};
if ret_val == 0 {
return Err(GdalError::OctError {
method_name: "OCTCoordinateTransformationOptionsSetBallparkAllowed",
});
}
Ok(())
}

/// Sets a coordinate operation.
///
/// This is a user override to be used instead of the normally computed pipeline.
///
/// The pipeline must take into account the axis order of the source and target SRS.
///
/// The pipeline may be provided as a PROJ string (single step operation or multiple step
/// string starting with +proj=pipeline), a WKT2 string describing a CoordinateOperation, or a
/// "urn:ogc:def:coordinateOperation:EPSG::XXXX" URN.
///
/// # Arguments
///
/// - `co`: PROJ or WKT string describing a coordinate operation
/// - `reverse`: Whether the PROJ or WKT string should be evaluated in the reverse path
pub fn set_coordinate_operation(&mut self, co: &str, reverse: bool) -> Result<()> {
let c_co = CString::new(co)?;
let ret_val = unsafe {
gdal_sys::OCTCoordinateTransformationOptionsSetOperation(
self.inner,
c_co.as_ptr(),
reverse as libc::c_int,
)
};
if ret_val == 0 {
return Err(GdalError::OctError {
method_name: "OCTCoordinateTransformationOptionsSetOperation",
});
}
Ok(())
}
}

#[derive(Debug)]
pub struct CoordTransform {
inner: OGRCoordinateTransformationH,
from: String,
Expand All @@ -33,6 +175,24 @@ impl CoordTransform {
})
}

pub fn new_ex(
sp_ref1: &SpatialRef,
sp_ref2: &SpatialRef,
options: &CoordTransformOptions,
) -> Result<CoordTransform> {
let c_obj = unsafe {
gdal_sys::OCTNewCoordinateTransformationEx(sp_ref1.0, sp_ref2.0, options.inner)
};
if c_obj.is_null() {
return Err(_last_null_pointer_err("OCTNewCoordinateTransformation"));
}
Ok(CoordTransform {
inner: c_obj,
from: sp_ref1.authority().or_else(|_| sp_ref1.to_proj4())?,
to: sp_ref2.authority().or_else(|_| sp_ref2.to_proj4())?,
})
}

/// Transform bounding box, densifying the edges to account for nonlinear
/// transformations.
///
Expand Down
19 changes: 19 additions & 0 deletions src/spatial_ref/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,25 @@ fn failing_transformation() {
}
}

#[test]
#[cfg(all(major_ge_3, minor_ge_3))]
fn invalid_transformation() {
use super::srs::CoordTransformOptions;

// This transformation can be constructed only if we allow ballpark transformations (enabled by
// default).
let ma = SpatialRef::from_epsg(6491).unwrap(); // Massachusetts
let nl = SpatialRef::from_epsg(28992).unwrap(); // Netherlands
let trafo = CoordTransform::new(&ma, &nl);
assert!(trafo.is_ok());

let mut options = CoordTransformOptions::new().unwrap();
options.set_ballpark_allowed(false).unwrap();
let trafo = CoordTransform::new_ex(&ma, &nl, &options);
let err = trafo.unwrap_err();
assert!(matches!(err, GdalError::NullPointer { .. }), "{:?}", err);
}

#[test]
fn auto_identify() {
// retreived from https://epsg.io/32632, but deleted the `AUTHORITY["EPSG","32632"]`
Expand Down

0 comments on commit 6c3577a

Please sign in to comment.