Skip to main content

space_weather/
lib.rs

1//! Space weather indices and parsers for aerospace applications.
2//!
3//! Provides types, parsers, and a query store for space weather data from
4//! CelesTrak and SET (Space Environment Technologies). Supports `no_std`
5//! (with `alloc`). Optional features enable HTTP fetching and Python bindings.
6
7#![cfg_attr(not(feature = "std"), no_std)]
8extern crate alloc;
9
10pub mod centered_average;
11#[cfg(feature = "fetch")]
12pub mod fetch;
13pub mod parsers;
14#[cfg(feature = "python")]
15pub mod python;
16pub mod store;
17
18use alloc::string::String;
19use alloc::vec::Vec;
20use core::fmt;
21
22/// Calendar date (year, month, day) used as the primary key for space weather records.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct Date {
25    pub year: i32,
26    pub month: u8,
27    pub day: u8,
28}
29
30impl Date {
31    pub fn validate(&self) -> Result<(), SpaceWeatherError> {
32        if self.month < 1 || self.month > 12 || self.day < 1 || self.day > 31 {
33            return Err(SpaceWeatherError::InvalidDate);
34        }
35        Ok(())
36    }
37}
38
39/// A single day's space weather indices from one or more data sources.
40///
41/// Fields are `Option` because not every source provides every index.
42/// Use [`store::SpaceWeatherStore::merge`] to combine records from different sources.
43#[derive(Clone, Debug)]
44pub struct SpaceWeatherRecord {
45    pub date: Date,
46    pub f10_7_obs: Option<f64>,
47    pub f10_7_adj: Option<f64>,
48    pub f10_7_jb: Option<f64>,
49    pub f10_7_jb_81c: Option<f64>,
50    pub ap_daily: Option<f64>,
51    pub ap_3hr: Option<[f64; 8]>,
52    pub kp_3hr: Option<[f64; 8]>,
53    pub s10_7: Option<f64>,
54    pub m10_7: Option<f64>,
55    pub y10_7: Option<f64>,
56    pub dtc: Option<f64>,
57}
58
59impl SpaceWeatherRecord {
60    pub fn validate(&self) -> Result<(), SpaceWeatherError> {
61        self.date.validate()?;
62        for v in [
63            self.f10_7_obs,
64            self.f10_7_adj,
65            self.f10_7_jb,
66            self.f10_7_jb_81c,
67            self.ap_daily,
68            self.s10_7,
69            self.m10_7,
70            self.y10_7,
71            self.dtc,
72        ]
73        .into_iter()
74        .flatten()
75        {
76            if v < 0.0 {
77                return Err(SpaceWeatherError::InvalidIndex);
78            }
79        }
80        Ok(())
81    }
82}
83
84/// Trait for querying space weather data by date.
85pub trait SpaceWeatherIndex {
86    /// Returns the record for an exact date, or `None` if not present.
87    fn get(&self, date: Date) -> Option<&SpaceWeatherRecord>;
88
89    /// Returns all records in the inclusive date range `[start, end]`.
90    fn get_range(&self, start: Date, end: Date) -> Vec<&SpaceWeatherRecord>;
91}
92
93/// Errors returned by parsing, validation, and store operations.
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum SpaceWeatherError {
96    InvalidDate,
97    InvalidIndex,
98    InvalidHeader,
99    InvalidWindow,
100    ParseError { row: usize, message: String },
101}
102
103impl fmt::Display for SpaceWeatherError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::InvalidDate => write!(f, "invalid date"),
107            Self::InvalidIndex => write!(f, "invalid index value"),
108            Self::InvalidHeader => write!(f, "invalid or missing CSV header"),
109            Self::InvalidWindow => write!(f, "window must be a positive odd number"),
110            Self::ParseError { row, message } => {
111                write!(f, "parse error at row {}: {}", row, message)
112            }
113        }
114    }
115}