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 more SpatialRef methods #145

Merged
merged 3 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changes

## Unreleased
* Add more functions to SpatialRef implementation
* <https://github.com/georust/gdal/pull/145>
* **Breaking**: Change `Feature::field` return type from
`Result<FieldValue>` to `Result<Option<FieldValue>>`. Fields
can be null. Before this change, if a field was null, the value
Expand Down
5 changes: 5 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,9 @@ pub enum GdalError {
to: String,
msg: Option<String>,
},
#[error("Axis not found for key '{key}' in method '{method_name}'")]
AxisNotFoundError {
key: String,
method_name: &'static str,
},
}
4 changes: 2 additions & 2 deletions src/spatial_ref/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod srs;

pub use srs::CoordTransform;
pub use srs::SpatialRef;
pub use gdal_sys::OGRAxisOrientation;
pub use srs::{AxisOrientationType, CoordTransform, SpatialRef};

#[cfg(test)]
mod tests;
157 changes: 157 additions & 0 deletions src/spatial_ref/srs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ impl CoordTransform {
}
}

#[derive(Debug, Clone)]
pub struct AreaOfUse {
pub west_lon_degree: f64,
pub south_lat_degree: f64,
pub east_lon_degree: f64,
pub north_lat_degree: f64,
pub name: String,
}

pub type AxisOrientationType = gdal_sys::OGRAxisOrientation::Type;

#[derive(Debug)]
pub struct SpatialRef(OGRSpatialReferenceH);

Expand Down Expand Up @@ -303,6 +314,123 @@ impl SpatialRef {
}
}

#[cfg(major_ge_3)]
pub fn get_name(&self) -> Result<String> {
let c_ptr = unsafe { gdal_sys::OSRGetName(self.0) };
if c_ptr.is_null() {
return Err(_last_null_pointer_err("OSRGetName"));
}
Ok(_string(c_ptr))
}

pub fn get_angular_units_name(&self) -> Result<String> {
let mut c_ptr = ptr::null_mut();
unsafe { gdal_sys::OSRGetAngularUnits(self.0, &mut c_ptr) };
if c_ptr.is_null() {
return Err(_last_null_pointer_err("OSRGetAngularUnits"));
}
Ok(_string(c_ptr))
}

pub fn get_angular_units(&self) -> f64 {
Copy link
Member

Choose a reason for hiding this comment

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

So if I want to get both the name and value I need to make two calls like this right?

let name = srs.get_angular_units_name();
let value = srs.get_angular_units();

Alternatively, you could consider making the api something like:

let (name, value) = srs.get_angular_units()
let name = srs.get_angular_units_name();
let value = srs.get_angular_units_value();

(I don't have any authority in whether or not this gets merged, I'm just a gdal crate user that watches PR's come by)

Copy link
Member

Choose a reason for hiding this comment

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

uh well i guess both ways are fine. I would suggest to keep it this way to avoid mixing Results.

Copy link
Contributor Author

@dmarteau dmarteau Jan 25, 2021

Choose a reason for hiding this comment

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

It tooks me some time to end up with this solution but many of the use case I have faced up involved returning either only the name or only the value for calculation, since returning the name implies allocation it considered that it was better to have a zero cost way to get the units factor value (note: this is also the choice of the python bindings).

unsafe { gdal_sys::OSRGetAngularUnits(self.0, ptr::null_mut()) }
}

pub fn get_linear_units_name(&self) -> Result<String> {
let mut c_ptr = ptr::null_mut();
unsafe { gdal_sys::OSRGetLinearUnits(self.0, &mut c_ptr) };
if c_ptr.is_null() {
return Err(_last_null_pointer_err("OSRGetLinearUnits"));
}
Ok(_string(c_ptr))
}

pub fn get_linear_units(&self) -> f64 {
unsafe { gdal_sys::OSRGetLinearUnits(self.0, ptr::null_mut()) }
}

#[inline]
pub fn is_geographic(&self) -> bool {
unsafe { gdal_sys::OSRIsGeographic(self.0) == 1 }
}

#[inline]
#[cfg(all(major_ge_3, minor_ge_1))]
pub fn is_derived_geographic(&self) -> bool {
unsafe { gdal_sys::OSRIsDerivedGeographic(self.0) == 1 }
}

#[inline]
pub fn is_local(&self) -> bool {
unsafe { gdal_sys::OSRIsLocal(self.0) == 1 }
}

#[inline]
pub fn is_projected(&self) -> bool {
unsafe { gdal_sys::OSRIsProjected(self.0) == 1 }
}

#[inline]
pub fn is_compound(&self) -> bool {
unsafe { gdal_sys::OSRIsCompound(self.0) == 1 }
}

#[inline]
pub fn is_geocentric(&self) -> bool {
unsafe { gdal_sys::OSRIsGeocentric(self.0) == 1 }
}

#[inline]
pub fn is_vertical(&self) -> bool {
unsafe { gdal_sys::OSRIsVertical(self.0) == 1 }
}

pub fn get_axis_orientation(&self, target_key: &str, axis: i32) -> Result<AxisOrientationType> {
let mut orientation = gdal_sys::OGRAxisOrientation::OAO_Other;
let c_ptr = unsafe {
gdal_sys::OSRGetAxis(
self.0,
CString::new(target_key)?.as_ptr(),
axis as c_int,
&mut orientation,
)
};
// null ptr indicate a failure (but no CPLError) see Gdal documentation.
if c_ptr.is_null() {
Err(GdalError::AxisNotFoundError {
key: target_key.into(),
method_name: "OSRGetAxis",
})
} else {
Ok(orientation)
}
}

pub fn get_axis_name(&self, target_key: &str, axis: i32) -> Result<String> {
// See get_axis_orientation
let c_ptr = unsafe {
gdal_sys::OSRGetAxis(
self.0,
CString::new(target_key)?.as_ptr(),
axis as c_int,
ptr::null_mut(),
)
};
if c_ptr.is_null() {
Err(GdalError::AxisNotFoundError {
key: target_key.into(),
method_name: "OSRGetAxis",
})
} else {
Ok(_string(c_ptr))
}
}

#[cfg(all(major_ge_3, minor_ge_1))]
pub fn get_axes_count(&self) -> i32 {
unsafe { gdal_sys::OSRGetAxesCount(self.0) }
}

#[cfg(major_ge_3)]
pub fn set_axis_mapping_strategy(&self, strategy: gdal_sys::OSRAxisMappingStrategy::Type) {
unsafe {
Expand All @@ -315,6 +443,35 @@ impl SpatialRef {
unsafe { gdal_sys::OSRGetAxisMappingStrategy(self.0) }
}

#[cfg(major_ge_3)]
pub fn get_area_of_use(&self) -> Option<AreaOfUse> {
let mut c_area_name: *const libc::c_char = ptr::null_mut();
let (mut w_long, mut s_lat, mut e_long, mut n_lat): (f64, f64, f64, f64) =
(0.0, 0.0, 0.0, 0.0);
let ret_val = unsafe {
gdal_sys::OSRGetAreaOfUse(
self.0,
&mut w_long,
&mut s_lat,
&mut e_long,
&mut n_lat,
&mut c_area_name,
) == 1
};

if ret_val {
Some(AreaOfUse {
west_lon_degree: w_long,
south_lat_degree: s_lat,
east_lon_degree: e_long,
north_lat_degree: n_lat,
name: _string(c_area_name),
})
} else {
None
}
}

// TODO: should this take self instead of &self?
pub fn to_c_hsrs(&self) -> OGRSpatialReferenceH {
self.0
Expand Down
81 changes: 81 additions & 0 deletions src/spatial_ref/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,84 @@ fn axis_mapping_strategy() {
gdal_sys::OSRAxisMappingStrategy::OAMS_TRADITIONAL_GIS_ORDER
);
}

#[cfg(major_ge_3)]
#[test]
fn get_area_of_use() {
let spatial_ref = SpatialRef::from_epsg(4326).unwrap();
let area_of_use = spatial_ref.get_area_of_use().unwrap();
assert_almost_eq(area_of_use.west_lon_degree, -180.0);
assert_almost_eq(area_of_use.south_lat_degree, -90.0);
assert_almost_eq(area_of_use.east_lon_degree, 180.0);
assert_almost_eq(area_of_use.north_lat_degree, 90.0);
}

#[cfg(major_ge_3)]
#[test]
fn get_name() {
let spatial_ref = SpatialRef::from_epsg(4326).unwrap();
let name = spatial_ref.get_name().unwrap();
assert_eq!(name, "WGS 84");
}

#[test]
fn get_units_epsg4326() {
let spatial_ref = SpatialRef::from_epsg(4326).unwrap();

let angular_units_name = spatial_ref.get_angular_units_name().unwrap();
assert_eq!(angular_units_name.to_lowercase(), "degree");
let to_radians = spatial_ref.get_angular_units();
assert_almost_eq(to_radians, 0.01745329);
}

#[test]
fn get_units_epsg2154() {
let spatial_ref = SpatialRef::from_epsg(2154).unwrap();
let linear_units_name = spatial_ref.get_linear_units_name().unwrap();
assert_eq!(linear_units_name.to_lowercase(), "metre");
let to_meters = spatial_ref.get_linear_units();
assert_almost_eq(to_meters, 1.0);
}

#[test]
fn predicats_epsg4326() {
let spatial_ref_4326 = SpatialRef::from_epsg(4326).unwrap();
assert!(spatial_ref_4326.is_geographic());
assert!(!spatial_ref_4326.is_local());
assert!(!spatial_ref_4326.is_projected());
assert!(!spatial_ref_4326.is_compound());
assert!(!spatial_ref_4326.is_geocentric());
assert!(!spatial_ref_4326.is_vertical());

#[cfg(all(major_ge_3, minor_ge_1))]
assert!(!spatial_ref_4326.is_derived_geographic());
}

#[test]
fn predicats_epsg2154() {
let spatial_ref_2154 = SpatialRef::from_epsg(2154).unwrap();
assert!(!spatial_ref_2154.is_geographic());
assert!(!spatial_ref_2154.is_local());
assert!(spatial_ref_2154.is_projected());
assert!(!spatial_ref_2154.is_compound());
assert!(!spatial_ref_2154.is_geocentric());

#[cfg(all(major_ge_3, minor_ge_1))]
assert!(!spatial_ref_2154.is_derived_geographic());
}

//XXX Gdal 2 implementation is partial
#[cfg(major_ge_3)]
#[test]
fn crs_axis() {
let spatial_ref = SpatialRef::from_epsg(4326).unwrap();

#[cfg(all(major_ge_3, minor_ge_1))]
assert_eq!(spatial_ref.get_axes_count(), 2);

let orientation = spatial_ref.get_axis_orientation("GEOGCS", 0).unwrap();
assert_eq!(orientation, gdal_sys::OGRAxisOrientation::OAO_North);
assert!(spatial_ref.get_axis_name("GEOGCS", 0).is_ok());
assert!(spatial_ref.get_axis_name("DO_NO_EXISTS", 0).is_err());
assert!(spatial_ref.get_axis_orientation("DO_NO_EXISTS", 0).is_err());
}