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> {
195 if !azimuth.is_finite() {
196 return Err(Error::computation_error("azimuth is not finite"));
197 }
198 Ok(normalize_degrees_0_to_360(azimuth))
199}
200
201pub fn check_zenith_angle(zenith: f64) -> Result<f64> {
206 if !zenith.is_finite() {
207 return Err(Error::computation_error("zenith angle is not finite"));
208 }
209 if !(0.0..=180.0).contains(&zenith) {
210 return Err(Error::computation_error(
211 "zenith angle must be between 0° and 180°",
212 ));
213 }
214 Ok(zenith)
215}
216
217#[must_use]
219pub fn check_refraction_params_usable(pressure: f64, temperature: f64) -> bool {
220 pressure.is_finite()
221 && temperature.is_finite()
222 && pressure > 0.0
223 && pressure < 3000.0
224 && temperature > -273.0
225 && temperature < 273.0
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_latitude_validation() {
234 assert!(check_latitude(0.0).is_ok());
235 assert!(check_latitude(90.0).is_ok());
236 assert!(check_latitude(-90.0).is_ok());
237 assert!(check_latitude(45.5).is_ok());
238
239 assert!(check_latitude(91.0).is_err());
240 assert!(check_latitude(-91.0).is_err());
241 assert!(check_latitude(f64::NAN).is_err());
242 assert!(check_latitude(f64::INFINITY).is_err());
243 }
244
245 #[test]
246 fn test_longitude_validation() {
247 assert!(check_longitude(0.0).is_ok());
248 assert!(check_longitude(180.0).is_ok());
249 assert!(check_longitude(-180.0).is_ok());
250 assert!(check_longitude(122.5).is_ok());
251
252 assert!(check_longitude(181.0).is_err());
253 assert!(check_longitude(-181.0).is_err());
254 assert!(check_longitude(f64::NAN).is_err());
255 assert!(check_longitude(f64::INFINITY).is_err());
256 }
257
258 #[test]
259 fn test_pressure_validation() {
260 assert!(check_pressure(1013.25).is_ok());
261 assert!(check_pressure(1000.0).is_ok());
262 assert!(check_pressure(500.0).is_ok());
263
264 assert!(check_pressure(0.0).is_err());
265 assert!(check_pressure(-100.0).is_err());
266 assert!(check_pressure(3000.0).is_err());
267 }
268
269 #[test]
270 fn test_temperature_validation() {
271 assert!(check_temperature(15.0).is_ok());
272 assert!(check_temperature(0.0).is_ok());
273 assert!(check_temperature(-40.0).is_ok());
274 assert!(check_temperature(50.0).is_ok());
275
276 assert!(check_temperature(-300.0).is_err());
277 assert!(check_temperature(150.0).is_err());
278 }
279
280 #[test]
281 #[cfg(feature = "std")]
282 fn test_error_display() {
283 let err = Error::invalid_latitude(95.0);
284 assert_eq!(
285 err.to_string(),
286 "invalid latitude 95° (must be between -90° and +90°)"
287 );
288
289 let err = Error::invalid_longitude(185.0);
290 assert_eq!(
291 err.to_string(),
292 "invalid longitude 185° (must be between -180° and +180°)"
293 );
294
295 let err = Error::computation_error("convergence failed");
296 assert_eq!(err.to_string(), "computation error: convergence failed");
297 }
298
299 #[test]
300 fn test_check_azimuth() {
301 assert!(check_azimuth(0.0).is_ok());
302 assert!(check_azimuth(180.0).is_ok());
303 assert!(check_azimuth(360.0).is_ok());
304 assert!(check_azimuth(450.0).is_ok());
305 assert!(check_azimuth(-90.0).is_ok());
306
307 assert_eq!(check_azimuth(-90.0).unwrap(), 270.0);
309 assert_eq!(check_azimuth(450.0).unwrap(), 90.0);
310
311 assert!(check_azimuth(f64::NAN).is_err());
312 assert!(check_azimuth(f64::INFINITY).is_err());
313 }
314
315 #[test]
316 fn test_check_zenith_angle() {
317 assert!(check_zenith_angle(0.0).is_ok());
318 assert!(check_zenith_angle(90.0).is_ok());
319 assert!(check_zenith_angle(180.0).is_ok());
320
321 assert!(check_zenith_angle(-1.0).is_err());
322 assert!(check_zenith_angle(181.0).is_err());
323 assert!(check_zenith_angle(f64::NAN).is_err());
324 assert!(check_zenith_angle(f64::INFINITY).is_err());
325 }
326}