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}