solar_positioning/
error.rs1use crate::math::normalize_degrees_0_to_360;
4use core::fmt;
5
6pub type Result<T> = core::result::Result<T, Error>;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Error {
12 InvalidLatitude {
14 value: f64,
16 },
17 InvalidLongitude {
19 value: f64,
21 },
22 InvalidElevationAngle {
24 value: f64,
26 },
27 InvalidPressure {
29 value: f64,
31 },
32 InvalidTemperature {
34 value: f64,
36 },
37 InvalidDateTime {
39 message: &'static str,
41 },
42 ComputationError {
44 message: &'static str,
46 },
47}
48
49impl fmt::Display for Error {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::InvalidLatitude { value } => {
53 write!(
54 f,
55 "invalid latitude {value}° (must be between -90° and +90°)"
56 )
57 }
58 Self::InvalidLongitude { value } => {
59 write!(
60 f,
61 "invalid longitude {value}° (must be between -180° and +180°)"
62 )
63 }
64 Self::InvalidElevationAngle { value } => {
65 write!(
66 f,
67 "invalid elevation angle {value}° (must be between -90° and +90°)"
68 )
69 }
70 Self::InvalidPressure { value } => {
71 write!(f, "invalid pressure {value} mbar (must be positive)")
72 }
73 Self::InvalidTemperature { value } => {
74 write!(
75 f,
76 "invalid temperature {value}°C (must be above absolute zero)"
77 )
78 }
79 Self::InvalidDateTime { message } => {
80 write!(f, "invalid date/time: {message}")
81 }
82 Self::ComputationError { message } => {
83 write!(f, "computation error: {message}")
84 }
85 }
86 }
87}
88
89#[cfg(feature = "std")]
90impl std::error::Error for Error {}
91
92impl Error {
93 #[must_use]
95 pub const fn invalid_latitude(value: f64) -> Self {
96 Self::InvalidLatitude { value }
97 }
98
99 #[must_use]
101 pub const fn invalid_longitude(value: f64) -> Self {
102 Self::InvalidLongitude { value }
103 }
104
105 #[must_use]
107 pub const fn invalid_elevation_angle(value: f64) -> Self {
108 Self::InvalidElevationAngle { value }
109 }
110
111 #[must_use]
113 pub const fn invalid_pressure(value: f64) -> Self {
114 Self::InvalidPressure { value }
115 }
116
117 #[must_use]
119 pub const fn invalid_temperature(value: f64) -> Self {
120 Self::InvalidTemperature { value }
121 }
122
123 #[must_use]
125 pub const fn invalid_datetime(message: &'static str) -> Self {
126 Self::InvalidDateTime { message }
127 }
128
129 #[must_use]
131 pub const fn computation_error(message: &'static str) -> Self {
132 Self::ComputationError { message }
133 }
134}
135
136pub fn check_latitude(latitude: f64) -> Result<()> {
141 if !(-90.0..=90.0).contains(&latitude) {
142 return Err(Error::invalid_latitude(latitude));
143 }
144 Ok(())
145}
146
147pub fn check_longitude(longitude: f64) -> Result<()> {
152 if !(-180.0..=180.0).contains(&longitude) {
153 return Err(Error::invalid_longitude(longitude));
154 }
155 Ok(())
156}
157
158pub fn check_coordinates(latitude: f64, longitude: f64) -> Result<()> {
163 check_latitude(latitude)?;
164 check_longitude(longitude)?;
165 Ok(())
166}
167
168pub fn check_pressure(pressure: f64) -> Result<()> {
173 if pressure <= 0.0 || pressure > 2000.0 {
174 return Err(Error::invalid_pressure(pressure));
175 }
176 Ok(())
177}
178
179pub fn check_temperature(temperature: f64) -> Result<()> {
184 if !(-273.15..=100.0).contains(&temperature) {
185 return Err(Error::invalid_temperature(temperature));
186 }
187 Ok(())
188}
189
190pub fn check_azimuth(azimuth: f64) -> Result<f64> {
201 if !azimuth.is_finite() {
202 return Err(Error::computation_error("azimuth is not finite"));
203 }
204 Ok(normalize_degrees_0_to_360(azimuth))
205}
206
207pub fn check_zenith_angle(zenith: f64) -> Result<f64> {
218 if !zenith.is_finite() {
219 return Err(Error::computation_error("zenith angle is not finite"));
220 }
221 if !(0.0..=180.0).contains(&zenith) {
222 return Err(Error::computation_error(
223 "zenith angle must be between 0° and 180°",
224 ));
225 }
226 Ok(zenith)
227}
228
229#[must_use]
238pub fn check_refraction_params_usable(pressure: f64, temperature: f64) -> bool {
239 pressure.is_finite()
240 && temperature.is_finite()
241 && pressure > 0.0
242 && pressure < 3000.0
243 && temperature > -273.0
244 && temperature < 273.0
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_latitude_validation() {
253 assert!(check_latitude(0.0).is_ok());
254 assert!(check_latitude(90.0).is_ok());
255 assert!(check_latitude(-90.0).is_ok());
256 assert!(check_latitude(45.5).is_ok());
257
258 assert!(check_latitude(91.0).is_err());
259 assert!(check_latitude(-91.0).is_err());
260 assert!(check_latitude(f64::NAN).is_err());
261 assert!(check_latitude(f64::INFINITY).is_err());
262 }
263
264 #[test]
265 fn test_longitude_validation() {
266 assert!(check_longitude(0.0).is_ok());
267 assert!(check_longitude(180.0).is_ok());
268 assert!(check_longitude(-180.0).is_ok());
269 assert!(check_longitude(122.5).is_ok());
270
271 assert!(check_longitude(181.0).is_err());
272 assert!(check_longitude(-181.0).is_err());
273 assert!(check_longitude(f64::NAN).is_err());
274 assert!(check_longitude(f64::INFINITY).is_err());
275 }
276
277 #[test]
278 fn test_pressure_validation() {
279 assert!(check_pressure(1013.25).is_ok());
280 assert!(check_pressure(1000.0).is_ok());
281 assert!(check_pressure(500.0).is_ok());
282
283 assert!(check_pressure(0.0).is_err());
284 assert!(check_pressure(-100.0).is_err());
285 assert!(check_pressure(3000.0).is_err());
286 }
287
288 #[test]
289 fn test_temperature_validation() {
290 assert!(check_temperature(15.0).is_ok());
291 assert!(check_temperature(0.0).is_ok());
292 assert!(check_temperature(-40.0).is_ok());
293 assert!(check_temperature(50.0).is_ok());
294
295 assert!(check_temperature(-300.0).is_err());
296 assert!(check_temperature(150.0).is_err());
297 }
298
299 #[test]
300 fn test_error_display() {
301 let err = Error::invalid_latitude(95.0);
302 assert_eq!(
303 err.to_string(),
304 "invalid latitude 95° (must be between -90° and +90°)"
305 );
306
307 let err = Error::invalid_longitude(185.0);
308 assert_eq!(
309 err.to_string(),
310 "invalid longitude 185° (must be between -180° and +180°)"
311 );
312
313 let err = Error::computation_error("convergence failed");
314 assert_eq!(err.to_string(), "computation error: convergence failed");
315 }
316
317 #[test]
318 fn test_check_azimuth() {
319 assert!(check_azimuth(0.0).is_ok());
320 assert!(check_azimuth(180.0).is_ok());
321 assert!(check_azimuth(360.0).is_ok());
322 assert!(check_azimuth(450.0).is_ok());
323 assert!(check_azimuth(-90.0).is_ok());
324
325 assert_eq!(check_azimuth(-90.0).unwrap(), 270.0);
327 assert_eq!(check_azimuth(450.0).unwrap(), 90.0);
328
329 assert!(check_azimuth(f64::NAN).is_err());
330 assert!(check_azimuth(f64::INFINITY).is_err());
331 }
332
333 #[test]
334 fn test_check_zenith_angle() {
335 assert!(check_zenith_angle(0.0).is_ok());
336 assert!(check_zenith_angle(90.0).is_ok());
337 assert!(check_zenith_angle(180.0).is_ok());
338
339 assert!(check_zenith_angle(-1.0).is_err());
340 assert!(check_zenith_angle(181.0).is_err());
341 assert!(check_zenith_angle(f64::NAN).is_err());
342 assert!(check_zenith_angle(f64::INFINITY).is_err());
343 }
344}