1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
use std::ffi::{c_int, CString};
use gdal_sys::CPLErr;
use crate::errors;
use crate::errors::*;
#[allow(unused)] // Referenced in doc comments.
use crate::spatial_ref::transform::CoordTransform;
use crate::utils::{_last_cpl_err, _last_null_pointer_err};
/// Options for [`CoordTransform::new_with_options`].
#[derive(Debug)]
pub struct CoordTransformOptions {
inner: gdal_sys::OGRCoordinateTransformationOptionsH,
}
impl Drop for CoordTransformOptions {
fn drop(&mut self) {
unsafe { gdal_sys::OCTDestroyCoordinateTransformationOptions(self.inner) };
}
}
impl CoordTransformOptions {
/// Creation options for [`CoordTransform`].
pub fn new() -> errors::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 })
}
/// Returns a C pointer to the allocated [`gdal_sys::OGRCoordinateTransformationOptionsH`] memory.
///
/// # Safety
/// This method returns a raw C pointer
pub(crate) unsafe fn c_options(&self) -> gdal_sys::OGRCoordinateTransformationOptionsH {
self.inner
}
/// 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.
///
/// For more information, see
/// [Advanced Coordinate Transformation Tutorial](https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation).
///
/// # 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(_last_cpl_err(CPLErr::CE_Failure));
}
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`.
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(_last_cpl_err(CPLErr::CE_Failure));
}
Ok(())
}
/// Sets whether ballpark transformations are allowed.
///
/// By default, PROJ may generate "ballpark transformations"
/// (see [Glossary](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`.
pub fn set_ballpark_allowed(&mut self, ballpark_allowed: bool) -> Result<()> {
let ret_val = unsafe {
gdal_sys::OCTCoordinateTransformationOptionsSetBallparkAllowed(
self.inner,
ballpark_allowed as c_int,
)
};
if ret_val == 0 {
return Err(_last_cpl_err(CPLErr::CE_Failure));
}
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.
///
/// For more information, see
/// [Advanced Coordinate Transformation Tutorial](https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation).
///
/// # 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 c_int,
)
};
if ret_val == 0 {
return Err(_last_cpl_err(CPLErr::CE_Failure));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spatial_ref::SpatialRef;
#[test]
fn invalid_transformation() {
// 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_with_options(&ma, &nl, &options);
let err = trafo.unwrap_err();
assert!(matches!(err, GdalError::NullPointer { .. }), "{err:?}");
}
#[test]
fn set_coordinate_operation() {
// Test case taken from:
// https://gdal.org/tutorials/osr_api_tut.html#advanced-coordinate-transformation
let mut options = CoordTransformOptions::new().unwrap();
options
.set_coordinate_operation("urn:ogc:def:coordinateOperation:EPSG::8599", false)
.unwrap();
let nad27 = SpatialRef::from_epsg(4267).unwrap();
let wgs84 = SpatialRef::from_epsg(4326).unwrap();
let trafo = CoordTransform::new_with_options(&nad27, &wgs84, &options);
assert!(trafo.is_ok());
}
}