gdal/spatial_ref/
transform_opts.rs

1use std::ffi::{c_int, CString};
2
3use gdal_sys::CPLErr;
4
5use crate::errors;
6use crate::errors::*;
7#[allow(unused)] // Referenced in doc comments.
8use crate::spatial_ref::transform::CoordTransform;
9use crate::utils::{_last_cpl_err, _last_null_pointer_err};
10
11/// Options for [`CoordTransform::new_with_options`].
12#[derive(Debug)]
13pub struct CoordTransformOptions {
14    inner: gdal_sys::OGRCoordinateTransformationOptionsH,
15}
16
17impl Drop for CoordTransformOptions {
18    fn drop(&mut self) {
19        unsafe { gdal_sys::OCTDestroyCoordinateTransformationOptions(self.inner) };
20    }
21}
22
23impl CoordTransformOptions {
24    /// Creation options for [`CoordTransform`].
25    pub fn new() -> errors::Result<CoordTransformOptions> {
26        let c_obj = unsafe { gdal_sys::OCTNewCoordinateTransformationOptions() };
27        if c_obj.is_null() {
28            return Err(_last_null_pointer_err(
29                "OCTNewCoordinateTransformationOptions",
30            ));
31        }
32        Ok(CoordTransformOptions { inner: c_obj })
33    }
34
35    /// Returns a C pointer to the allocated [`gdal_sys::OGRCoordinateTransformationOptionsH`] memory.
36    ///
37    /// # Safety
38    /// This method returns a raw C pointer
39    pub(crate) unsafe fn c_options(&self) -> gdal_sys::OGRCoordinateTransformationOptionsH {
40        self.inner
41    }
42
43    /// Sets an area of interest.
44    ///
45    /// The west longitude is generally lower than the east longitude, except for areas of interest
46    /// that go across the anti-meridian.
47    ///
48    /// For more information, see
49    /// [Advanced Coordinate Transformation Tutorial](https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation).
50    ///
51    /// # Arguments
52    ///
53    /// - `west_longitude_deg` – West longitude (in degree). Must be in [-180,180]
54    /// - `south_latitude_deg` – South latitude (in degree). Must be in [-90,90]
55    /// - `east_longitude_deg` – East longitude (in degree). Must be in [-180,180]
56    /// - `north_latitude_deg` – North latitude (in degree). Must be in [-90,90]
57    pub fn set_area_of_interest(
58        &mut self,
59        west_longitude_deg: f64,
60        south_latitude_deg: f64,
61        east_longitude_deg: f64,
62        north_latitude_deg: f64,
63    ) -> Result<()> {
64        let ret_val = unsafe {
65            gdal_sys::OCTCoordinateTransformationOptionsSetAreaOfInterest(
66                self.inner,
67                west_longitude_deg,
68                south_latitude_deg,
69                east_longitude_deg,
70                north_latitude_deg,
71            )
72        };
73        if ret_val == 0 {
74            return Err(_last_cpl_err(CPLErr::CE_Failure));
75        }
76        Ok(())
77    }
78
79    /// Sets the desired accuracy for coordinate operations.
80    ///
81    /// Only coordinate operations that offer an accuracy of at least the one specified will be
82    /// considered.
83    ///
84    /// An accuracy of 0 is valid and means a coordinate operation made only of one or several
85    /// conversions (map projections, unit conversion, etc.) Operations involving ballpark
86    /// transformations have a unknown accuracy, and will be filtered out by any dfAccuracy >= 0
87    /// value.
88    ///
89    /// If this option is specified with PROJ < 8, the `OGR_CT_OP_SELECTION` configuration option
90    /// will default to `BEST_ACCURACY`.
91    pub fn desired_accuracy(&mut self, accuracy: f64) -> Result<()> {
92        let ret_val = unsafe {
93            gdal_sys::OCTCoordinateTransformationOptionsSetDesiredAccuracy(self.inner, accuracy)
94        };
95        if ret_val == 0 {
96            return Err(_last_cpl_err(CPLErr::CE_Failure));
97        }
98        Ok(())
99    }
100
101    /// Sets whether ballpark transformations are allowed.
102    ///
103    /// By default, PROJ may generate "ballpark transformations"
104    /// (see [Glossary](https://proj.org/glossary.html))
105    /// when precise datum transformations are missing. For high
106    /// accuracy use cases, such transformations might not be allowed.
107    ///
108    /// If this option is specified with PROJ < 8, the `OGR_CT_OP_SELECTION` configuration option
109    /// will default to `BEST_ACCURACY`.
110    pub fn set_ballpark_allowed(&mut self, ballpark_allowed: bool) -> Result<()> {
111        let ret_val = unsafe {
112            gdal_sys::OCTCoordinateTransformationOptionsSetBallparkAllowed(
113                self.inner,
114                ballpark_allowed as c_int,
115            )
116        };
117        if ret_val == 0 {
118            return Err(_last_cpl_err(CPLErr::CE_Failure));
119        }
120        Ok(())
121    }
122
123    /// Sets a coordinate operation.
124    ///
125    /// This is a user override to be used instead of the normally computed pipeline.
126    ///
127    /// The pipeline must take into account the axis order of the source and target SRS.
128    ///
129    /// The pipeline may be provided as a PROJ string (single step operation or multiple step
130    /// string starting with `+proj=pipeline`), a WKT2 string describing a `CoordinateOperation`,
131    /// or a `"urn:ogc:def:coordinateOperation:EPSG::XXXX"` URN.
132    ///
133    /// For more information, see
134    /// [Advanced Coordinate Transformation Tutorial](https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation).
135    ///
136    /// # Arguments
137    ///
138    /// - `co`: PROJ or WKT string describing a coordinate operation
139    /// - `reverse`: Whether the PROJ or WKT string should be evaluated in the reverse path
140    pub fn set_coordinate_operation(&mut self, co: &str, reverse: bool) -> Result<()> {
141        let c_co = CString::new(co)?;
142        let ret_val = unsafe {
143            gdal_sys::OCTCoordinateTransformationOptionsSetOperation(
144                self.inner,
145                c_co.as_ptr(),
146                reverse as c_int,
147            )
148        };
149        if ret_val == 0 {
150            return Err(_last_cpl_err(CPLErr::CE_Failure));
151        }
152        Ok(())
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use crate::spatial_ref::SpatialRef;
160
161    #[test]
162    fn invalid_transformation() {
163        // This transformation can be constructed only if we allow ballpark transformations (enabled by
164        // default).
165        let ma = SpatialRef::from_epsg(6491).unwrap(); // Massachusetts
166        let nl = SpatialRef::from_epsg(28992).unwrap(); // Netherlands
167        let trafo = CoordTransform::new(&ma, &nl);
168        assert!(trafo.is_ok());
169
170        let mut options = CoordTransformOptions::new().unwrap();
171        options.set_ballpark_allowed(false).unwrap();
172        let trafo = CoordTransform::new_with_options(&ma, &nl, &options);
173        let err = trafo.unwrap_err();
174        assert!(matches!(err, GdalError::NullPointer { .. }), "{err:?}");
175    }
176
177    #[test]
178    fn set_coordinate_operation() {
179        // Test case taken from:
180        // https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation
181        let mut options = CoordTransformOptions::new().unwrap();
182        options
183            .set_coordinate_operation("urn:ogc:def:coordinateOperation:EPSG::8599", false)
184            .unwrap();
185        let nad27 = SpatialRef::from_epsg(4267).unwrap();
186        let wgs84 = SpatialRef::from_epsg(4326).unwrap();
187        let trafo = CoordTransform::new_with_options(&nad27, &wgs84, &options);
188        assert!(trafo.is_ok());
189    }
190}