#[cfg(feature = "no_std")]
use alloc::string::String;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum ProjError {
#[error("invalid PROJ string syntax or operation")]
InvalidOp,
#[error("invalid PROJ string syntax")]
WrongSyntax,
#[error("missing required operation parameter")]
MissingArg,
#[error("one of the operation parameters has an illegal value")]
IllegalArgValue,
#[error("mutually exclusive arguments")]
MutuallyExclusiveArgs,
#[error("file not found or with invalid content")]
FileNotFound,
#[error("value does not correspond to an ellipsoid")]
NotEllipsoid,
#[error("generic error of coordinate transformation")]
CoordTransfm,
#[error("invalid coordinate")]
InvalidCoord,
#[error("coordinate outside projection domain")]
OutsideProjectionDomain,
#[error("no operation found matching criteria")]
NoOperation,
#[error("point to transform falls outside grid or subgrid")]
OutsideGrid,
#[error("point to transform falls in a grid cell that evaluates to nodata")]
GridAtNodata,
#[error("iterative algorithm did not converge")]
NoConvergence,
#[error("missing required time coordinate")]
MissingTime,
#[error("Invalid CRS definition: {message}")]
InvalidCrs {
message: String,
suggestion: Option<String>,
},
#[error("Coordinate out of bounds: {message}")]
OutOfBounds {
message: String,
suggestion: Option<String>,
},
#[error("Projection failed: {message}")]
ProjectionFailed {
message: String,
},
#[error("operation not supported")]
UnsupportedOperation,
#[error("other error")]
Other,
#[error("error caused by incorrect use of PROJ API")]
ApiMisuse,
#[error("no inverse operation")]
NoInverseOp,
#[error("failure when accessing a network resource")]
NetworkError,
}
impl ProjError {
pub fn code(&self) -> i32 {
match self {
ProjError::InvalidOp => 1024,
ProjError::WrongSyntax => 1025,
ProjError::MissingArg => 1026,
ProjError::IllegalArgValue => 1027,
ProjError::MutuallyExclusiveArgs => 1028,
ProjError::FileNotFound => 1029,
ProjError::NotEllipsoid => 1027,
ProjError::CoordTransfm => 2048,
ProjError::InvalidCoord => 2049,
ProjError::OutsideProjectionDomain => 2050,
ProjError::NoOperation => 2051,
ProjError::OutsideGrid => 2052,
ProjError::GridAtNodata => 2053,
ProjError::NoConvergence => 2054,
ProjError::MissingTime => 2055,
ProjError::InvalidCrs { .. } => 1024,
ProjError::OutOfBounds { .. } => 2050,
ProjError::ProjectionFailed { .. } => 2048,
ProjError::UnsupportedOperation => 4096,
ProjError::Other => 4096,
ProjError::ApiMisuse => 4097,
ProjError::NoInverseOp => 4098,
ProjError::NetworkError => 4099,
}
}
pub fn suggestion(&self) -> Option<&str> {
match self {
ProjError::InvalidCrs { suggestion, .. } => suggestion.as_deref(),
ProjError::OutOfBounds { suggestion, .. } => suggestion.as_deref(),
ProjError::MissingArg => Some(
"verify that +proj= is specified; \
common values: merc, lcc, tmerc, utm, stere, aeqd, longlat",
),
ProjError::IllegalArgValue => {
Some("check that all numeric parameters are valid and in the expected range")
}
ProjError::FileNotFound => Some(
"ensure the grid file is accessible; \
use create_with_ctx() to register in-memory grids",
),
ProjError::WrongSyntax => Some(
"check the proj-string syntax; \
each keyword must start with + (e.g. +proj=merc +ellps=WGS84)",
),
ProjError::NoInverseOp => Some(
"this projection does not support an inverse operation; \
try a pipeline with an explicit inverse step",
),
ProjError::InvalidCoord => Some(
"verify that input coordinates are in the expected unit \
(degrees or metres) and within the projection domain",
),
ProjError::OutsideGrid => Some(
"the coordinate falls outside all available grid files; \
verify the grid coverage area",
),
_ => None,
}
}
}
pub type ProjResult<T> = Result<T, ProjError>;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "no_std")]
use alloc::string::ToString;
#[test]
fn invalid_op_code() {
assert_eq!(ProjError::InvalidOp.code(), 1024);
}
#[test]
fn wrong_syntax_code() {
assert_eq!(ProjError::WrongSyntax.code(), 1025);
}
#[test]
fn missing_arg_code() {
assert_eq!(ProjError::MissingArg.code(), 1026);
}
#[test]
fn illegal_arg_value_code() {
assert_eq!(ProjError::IllegalArgValue.code(), 1027);
}
#[test]
fn mutually_exclusive_args_code() {
assert_eq!(ProjError::MutuallyExclusiveArgs.code(), 1028);
}
#[test]
fn file_not_found_code() {
assert_eq!(ProjError::FileNotFound.code(), 1029);
}
#[test]
fn not_ellipsoid_code() {
assert_eq!(ProjError::NotEllipsoid.code(), 1027);
}
#[test]
fn coord_transfm_code() {
assert_eq!(ProjError::CoordTransfm.code(), 2048);
}
#[test]
fn invalid_coord_code() {
assert_eq!(ProjError::InvalidCoord.code(), 2049);
}
#[test]
fn outside_projection_domain_code() {
assert_eq!(ProjError::OutsideProjectionDomain.code(), 2050);
}
#[test]
fn no_operation_code() {
assert_eq!(ProjError::NoOperation.code(), 2051);
}
#[test]
fn outside_grid_code() {
assert_eq!(ProjError::OutsideGrid.code(), 2052);
}
#[test]
fn grid_at_nodata_code() {
assert_eq!(ProjError::GridAtNodata.code(), 2053);
}
#[test]
fn no_convergence_code() {
assert_eq!(ProjError::NoConvergence.code(), 2054);
}
#[test]
fn missing_time_code() {
assert_eq!(ProjError::MissingTime.code(), 2055);
}
#[test]
fn other_code() {
assert_eq!(ProjError::Other.code(), 4096);
}
#[test]
fn api_misuse_code() {
assert_eq!(ProjError::ApiMisuse.code(), 4097);
}
#[test]
fn no_inverse_op_code() {
assert_eq!(ProjError::NoInverseOp.code(), 4098);
}
#[test]
fn network_error_code() {
assert_eq!(ProjError::NetworkError.code(), 4099);
}
#[test]
fn display_messages_non_empty() {
assert!(!ProjError::InvalidCoord.to_string().is_empty());
assert!(!ProjError::OutsideProjectionDomain.to_string().is_empty());
}
#[test]
fn proj_result_alias_works() {
let ok: ProjResult<i32> = Ok(7);
let err: ProjResult<i32> = Err(ProjError::Other);
assert_eq!(ok, Ok(7));
assert_eq!(err, Err(ProjError::Other));
}
#[test]
fn invalid_crs_code_and_suggestion() {
let e = ProjError::InvalidCrs {
message: "bad epsg".to_string(),
suggestion: Some("try EPSG:4326".to_string()),
};
assert_eq!(e.code(), 1024);
assert_eq!(e.suggestion(), Some("try EPSG:4326"));
assert!(!e.to_string().is_empty());
}
#[test]
fn out_of_bounds_code_and_suggestion() {
let e = ProjError::OutOfBounds {
message: "lat 100".to_string(),
suggestion: None,
};
assert_eq!(e.code(), 2050);
assert_eq!(e.suggestion(), None);
}
#[test]
fn projection_failed_code() {
let e = ProjError::ProjectionFailed {
message: "singular matrix".to_string(),
};
assert_eq!(e.code(), 2048);
assert_eq!(e.suggestion(), None);
}
#[test]
fn suggestion_returns_none_for_unit_variants() {
assert_eq!(ProjError::Other.suggestion(), None);
assert_eq!(ProjError::InvalidOp.suggestion(), None);
}
#[test]
fn suggestion_missing_arg() {
assert!(ProjError::MissingArg.suggestion().is_some());
}
#[test]
fn suggestion_illegal_arg_value() {
assert!(ProjError::IllegalArgValue.suggestion().is_some());
}
#[test]
fn suggestion_file_not_found() {
assert!(ProjError::FileNotFound.suggestion().is_some());
}
#[test]
fn suggestion_wrong_syntax() {
assert!(ProjError::WrongSyntax.suggestion().is_some());
}
#[test]
fn suggestion_no_inverse_op() {
assert!(ProjError::NoInverseOp.suggestion().is_some());
}
#[test]
fn suggestion_invalid_coord() {
assert!(ProjError::InvalidCoord.suggestion().is_some());
}
#[test]
fn suggestion_outside_grid() {
assert!(ProjError::OutsideGrid.suggestion().is_some());
}
#[test]
fn suggestion_other_returns_none() {
assert!(ProjError::Other.suggestion().is_none());
assert!(ProjError::CoordTransfm.suggestion().is_none());
assert!(ProjError::NetworkError.suggestion().is_none());
}
}