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

Mapping of additional methods for SpatialRef. #406

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changes

## Unreleased

- Added `SpatialRef::semi_major`, `semi_minor`, `set_proj_param`, `get_proj_param`, `get_proj_param_or_default`, `set_attr_value`, `get_attr_value` and `geog_cs`.

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

- Added pre-built bindings for GDAL 3.7

- <https://github.com/georust/gdal/pull/401>
Expand Down
204 changes: 204 additions & 0 deletions src/spatial_ref/srs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,120 @@ impl SpatialRef {
None
}
}

/// Get spheroid semi major axis. It returns [`Err`] variant in case of non [`OGRERR_NONE`](https://gdal.org/api/vector_c_api.html#c.OGRERR_NONE) error result from orginal method.
tombieli marked this conversation as resolved.
Show resolved Hide resolved
///
/// See: [`OSRGetSemiMajor`](https://gdal.org/api/ogr_srs_api.html#_CPPv415OSRGetSemiMajor20OGRSpatialReferenceHP6OGRErr)
pub fn semi_major(&self) -> Result<f64> {
let mut rv = OGRErr::OGRERR_NONE;
let a = unsafe { gdal_sys::OSRGetSemiMajor(self.0, &mut rv as *mut u32) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "OSRGetSemiMajor",
});
}
Ok(a)
}

/// Get spheroid semi minor axis. It returns [`Err`] variant in case of non [`OGRERR_NONE`](https://gdal.org/api/vector_c_api.html#c.OGRERR_NONE) error result from orginal method.
///
/// See: [`OSRGetSemiMinor`](https://gdal.org/api/ogr_srs_api.html#_CPPv415OSRGetSemiMinor20OGRSpatialReferenceHP6OGRErr)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn semi_minor(&self) -> Result<f64> {
let mut rv = OGRErr::OGRERR_NONE;
let b = unsafe { gdal_sys::OSRGetSemiMinor(self.0, &mut rv as *mut u32) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "OSRGetSemiMinor",
});
}
Ok(b)
}

/// Set a projection parameter value. It returns [`Err`] variant in case of non [`OGRERR_NONE`](https://gdal.org/api/vector_c_api.html#c.OGRERR_NONE) result from orginal method.
tombieli marked this conversation as resolved.
Show resolved Hide resolved
///
/// See: [`OSRSetProjParm`](https://gdal.org/api/ogr_srs_api.html#_CPPv414OSRSetProjParm20OGRSpatialReferenceHPKcd)
pub fn set_proj_param(&mut self, name: &str, value: f64) -> Result<()> {
let c_name = CString::new(name)?;
let rv = unsafe { gdal_sys::OSRSetProjParm(self.0, c_name.as_ptr(), value) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "OSRSetProjParm",
});
}
Ok(())
}

/// Fetch a projection parameter value. It returns [`Err`] variant in case of non [`OGRERR_NONE`](https://gdal.org/api/vector_c_api.html#c.OGRERR_NONE) error result from orginal method.
///
/// See: [`OSRGetProjParm`](https://gdal.org/api/ogr_srs_api.html#_CPPv414OSRGetProjParm20OGRSpatialReferenceHPKcdP6OGRErr)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_proj_param(&self, name: &str) -> Result<f64> {
Copy link
Member

@lnicola lnicola May 26, 2023

Choose a reason for hiding this comment

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

So ideally, this would return something like Result<Option<f64>> and the user could handle the missing case. But since we can't do that, should we take the default as an argument? Just noticed the other method, should we drop this one and rename?

It's currently ambiguous. Are the parameters never 0 in practice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Behavior of OSRGetProjParm is quite tricky, a least from Rust point of view.

In C and C++ if value is found it is returned normally, but if value is not found, a value from default parameter is returned, but also error code (if pointer is not null) is set to OGRERR_FAILURE. However looking from Rust point of view it may mean not found (therefore is candidate for Option::None) or can mean any other failure.
You can see this in GDAL source code for OSRGetProjParm.

To sum up:

  • on existing value: we receive the value itself,
  • if value does not exist: we receive default value and very general error code.

That's why I used only Result<f64> as return type and I also created also get_proj_param_or_default.

Copy link
Member

@lnicola lnicola May 28, 2023

Choose a reason for hiding this comment

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

Yeah, I'm inclined towards something like a single pub fn get_proj_param(&self, name: &str) -> Result<Option<f64>>, which returns Err on a string conversion error (basically never), and Ok(None) on OGRERR_FAILURE. It's not that awkward to use, and should be pretty clear.

Both error cases in the implementation are similar, and boil down to "there's no parameter with that name on the PROJCS".

EDIT: removed the default param.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is all unfortunate legacy API leakage. I don't know what we should do, or if there are other examples out there (e.g. proj?) but what if we had a new error type defined that indicates NotSet? Then, you clould go back to returning Result<f64> and users who care about a default can match on Err(NotSet) to handle it?

let c_name = CString::new(name)?;
let mut rv = OGRErr::OGRERR_NONE;
let p = unsafe { gdal_sys::OSRGetProjParm(self.0, c_name.as_ptr(), 0.0, &mut rv as *mut u32) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "OSRGetProjParm",
});
}
Ok(p)
}

/// Fetch a projection parameter value. In case of any error returns defualt value.
/// This associated function is variant of [`SpatialRef::get_proj_param`] which incorporates default fallback mechanism from orginal library.
///
/// See: [`OSRGetProjParm`](https://gdal.org/api/ogr_srs_api.html#_CPPv414OSRGetProjParm20OGRSpatialReferenceHPKcdP6OGRErr)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_proj_param_or_default(&self, name: &str, default: f64) -> f64 {
match CString::new(name) {
Ok(c_name) => unsafe { gdal_sys::OSRGetProjParm(self.0, c_name.as_ptr(), default, ptr::null_mut()) },
Err(_) => default
}
}

/// Set attribute value in spatial reference. It returns [`Err`] variant in case of non [`OGRERR_NONE`](https://gdal.org/api/vector_c_api.html#c.OGRERR_NONE) result from orginal method.
///
/// See: [`OSRSetAttrValue`](https://gdal.org/api/ogr_srs_api.html#_CPPv415OSRSetAttrValue20OGRSpatialReferenceHPKcPKc)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn set_attr_value(&self, node_path: &str, new_value: &str) -> Result<()> {
let c_node_path = CString::new(node_path)?;
let c_new_value = CString::new(new_value)?;
let rv = unsafe { gdal_sys::OSRSetAttrValue(self.0, c_node_path.as_ptr(), c_new_value.as_ptr()) };
if rv != OGRErr::OGRERR_NONE {
return Err(GdalError::OgrError {
err: rv,
method_name: "OSRSetAttrValue",
});
}
Ok(())
}

/// Fetch indicated attribute of named node. It returns [`Err`] variant if orginal method returns `nullptr`.
///
/// See: [`OSRGetProjParm`](https://gdal.org/api/ogr_srs_api.html#_CPPv415OSRGetAttrValue20OGRSpatialReferenceHPKci)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_attr_value(&self, node_path: &str, child: u32) -> Result<String> {
tombieli marked this conversation as resolved.
Show resolved Hide resolved
let c_node_path = CString::new(node_path)?;
let c_ptr_value = unsafe { gdal_sys::OSRGetAttrValue(self.0, c_node_path.as_ptr(), child as libc::c_int) };
if c_ptr_value.is_null() {
return Err(_last_null_pointer_err("OSRGetAttrValue"));
}
Ok(_string(c_ptr_value))
}


/// Make a duplicate of the GEOGCS node of this [`SpatialRef`]. It returns [`Err`] variant if orginal method returns `nullptr`.
///
/// Seff: [OSRCloneGeogCS](https://gdal.org/api/ogr_srs_api.html#_CPPv414OSRCloneGeogCS20OGRSpatialReferenceH)
tombieli marked this conversation as resolved.
Show resolved Hide resolved
pub fn geog_cs(&self) -> Result<SpatialRef> {
let raw_ret = unsafe {gdal_sys::OSRCloneGeogCS(self.0)};
if raw_ret.is_null() {
return Err(_last_null_pointer_err("OSRCloneGeogCS"));
}

Ok(SpatialRef(raw_ret))
}

}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -700,4 +814,94 @@ mod tests {
assert!(spatial_ref.axis_name("DO_NO_EXISTS", 0).is_err());
assert!(spatial_ref.axis_orientation("DO_NO_EXISTS", 0).is_err());
}

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

let semi_major = spatial_ref.semi_major().unwrap();
assert_almost_eq(semi_major, 6_378_137.0);

let semi_minor = spatial_ref.semi_minor().unwrap();
assert_almost_eq(semi_minor, 6_356_752.31);
}


#[test]
fn proj_params() {
let spatial_ref = SpatialRef::from_proj4("+proj=geos +lon_0=42 +h=35785831 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs").unwrap();

let central_meridian = spatial_ref.get_proj_param("central_meridian").unwrap();
assert_almost_eq(central_meridian, 42.0);

let satellite_height = spatial_ref.get_proj_param("satellite_height").unwrap();
assert_almost_eq(satellite_height, 35_785_831.0);

let satellite_height = spatial_ref.get_proj_param_or_default("satellite_height", 0.0);
assert_almost_eq(satellite_height, 35_785_831.0);
}

#[test]
fn setting_proj_param() {
let mut spatial_ref = SpatialRef::from_proj4("+proj=geos +lon_0=42 +h=35785831 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs").unwrap();

spatial_ref.set_proj_param("central_meridian", -15.0).unwrap();

let central_meridian = spatial_ref.get_proj_param("central_meridian").unwrap();

assert_almost_eq(central_meridian, -15.0);

}

#[test]
#[should_panic = "OgrError { err: 6, method_name: \"OSRGetProjParm\" }"]
fn non_existing_proj_param() {
let spatial_ref = SpatialRef::from_epsg(4326).unwrap();

spatial_ref.get_proj_param("spam").unwrap();
}

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

let spam = spatial_ref.get_proj_param_or_default("spam", 15.0);

assert_almost_eq(spam, 15.0);
}

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

let geog_cs = spatial_ref.get_attr_value("GEOGCS", 0).unwrap();

assert_eq!(geog_cs, "WGS 84");
}

#[test]
fn geog_cs() {
let spatial_ref = SpatialRef::from_proj4("+proj=geos +lon_0=42 +h=35785831 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs").unwrap();
let expected_geog_cs = SpatialRef::from_wkt(
r#"
GEOGCS["unknown",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],
AXIS["Longitude",EAST],
AXIS["Latitude",NORTH]
]
"#
).unwrap();

let geog_cs = spatial_ref.geog_cs().unwrap();

assert_eq!(
geog_cs, expected_geog_cs,
"GEOGCS of geos spatial reference: \"{:?}\"\n does not equal to expected one: {:?}", geog_cs.to_wkt(), expected_geog_cs.to_wkt()
);
}

}