1mod error;
40mod models;
41
42use std::fmt::Display;
43use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
44use country_boundaries::{CountryBoundaries, LatLon, BOUNDARIES_ODBL_360X180};
45use reqwest::Client;
46use error::error::UsgsError;
47use crate::models::models::{EarthquakeResponse, EarthquakeFeatures};
48
49fn local_time_as_utc() -> NaiveDateTime {
50 Utc::now().naive_utc()
51}
52
53fn local_time_to_utc(time: NaiveDateTime) -> NaiveDateTime {
54 let timezone = Local.from_local_datetime(&time).unwrap();
55 let utc = timezone.with_timezone(&Utc);
56 println!("{}", utc.naive_utc().to_string());
57 utc.naive_utc()
58}
59
60fn generate_custom_time(year: i32, month: u32, day: u32, hour: u32, min: u32) -> NaiveDateTime {
61 let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
62 let time = NaiveTime::from_hms_opt(hour, min, 00).unwrap();
63 NaiveDateTime::new(date, time)
64}
65
66
67#[derive(Debug)]
69pub enum AlertLevel {
70 Green,
72
73 Yellow,
75
76 Orange,
78
79 Red,
81
82 All
84}
85
86pub enum OrderBy {
87 Time,
89
90 TimeAsc,
92
93 Magnitude,
95
96 MagnitudeAsc
98}
99
100
101pub struct UsgsClient {
105 pub base_url: String,
107
108 pub client: Client,
110}
111
112
113impl UsgsClient {
114 pub fn new() -> Self {
116 Self {
117 base_url: "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson".to_string(),
118 client: Client::new(),
119 }
120 }
121
122 pub fn query(&self) -> UsgsQuery<'_> {
124 UsgsQuery {
125 client: &self.client,
126 base_url: self.base_url.clone(),
127 country_code: "US".to_string(),
128 start_time: None,
129 end_time: local_time_as_utc(),
130 min_magnitude: 0.0,
131 max_magnitude: 10.0,
132 alert_level: AlertLevel::All,
133 order_by: OrderBy::Time,
134 }
135 }
136}
137
138pub struct UsgsQuery<'a> {
142 client: & 'a Client,
143 base_url: String,
144 country_code: String,
145 start_time: Option<NaiveDateTime>,
146 end_time: NaiveDateTime,
147 min_magnitude: f32,
148 max_magnitude: f32,
149 alert_level: AlertLevel,
150 order_by: OrderBy,
151}
152
153impl<'a> UsgsQuery<'a> {
155
156 pub fn filter_by_country_code(mut self, country_code: &str) -> Self {
158 self.country_code = country_code.to_string();
159 self
160 }
161
162 pub fn start_time(mut self, year: i32, month: u32, day: u32, hour: u32, min: u32) -> Self {
164 self.start_time = Some(local_time_to_utc(generate_custom_time(year, month, day, hour, min)));
165 self
166 }
167
168 pub fn end_time(mut self, year: i32, month: u32, day: u32, hour: u32, min: u32) -> Self {
170 self.end_time = local_time_to_utc(generate_custom_time(year, month, day, hour, min));
171 self
172 }
173
174 pub fn min_magnitude(mut self, min: f32) -> Self {
176 self.min_magnitude = min;
177 self
178 }
179
180 pub fn max_magnitude(mut self, max: f32) -> Self {
182 self.max_magnitude = max;
183 self
184 }
185
186 pub fn alert_level(mut self, level: AlertLevel) -> Self {
188 self.alert_level = level;
189 self
190 }
191
192 pub fn order_by(mut self, order_by: OrderBy) -> Self {
194 self.order_by = order_by;
195 self
196 }
197
198 pub async fn fetch(self) -> Result<EarthquakeResponse, UsgsError> {
203
204 if self.start_time.is_none() {
205 return Err(UsgsError::EmptyStartTime)
206 }
207
208 let start_time = self.start_time.unwrap();
209
210 if start_time > self.end_time {
211 return Err(UsgsError::InvalidStartTime);
212 }
213
214 if start_time > local_time_as_utc() {
215 return Err(UsgsError::StartTimeInFuture)
216 }
217
218 if self.min_magnitude < 0.0 {
219 return Err(UsgsError::MinimumMagnitude)
220 }
221
222 if self.max_magnitude > 10.0 {
223 return Err(UsgsError::MaximumMagnitude)
224 }
225
226
227 let mut url = format!("{}&starttime={}&endtime={}&minmagnitude={}&maxmagnitude={}&alertlevel={}&orderby={}"
228 ,self.base_url, start_time, self.end_time, self.min_magnitude, self.max_magnitude, self.alert_level.to_string(), self.order_by.to_string());
229
230 if self.alert_level.to_string() == "all" {
231 url = format!("{}&starttime={}&endtime={}&minmagnitude={}&maxmagnitude={}&orderby={}"
232 ,self.base_url, start_time.and_utc(), self.end_time, self.min_magnitude, self.max_magnitude, self.order_by.to_string());
233 }
234
235 let response = self.client.get(&url).send().await?;
236 let mut body: EarthquakeResponse = response.json().await?;
237 if !self.country_code.is_empty() {
238 let boundaries = CountryBoundaries::from_reader(BOUNDARIES_ODBL_360X180).expect("Failed to parse BOUNDARIES_ODBL_360X180");
239 let target_code = &self.country_code;
240 let filtered_features: Vec<EarthquakeFeatures> = body.features.into_iter()
241 .filter(|eq| {
242 let coordinates = &eq.geometry.coordinates;
243 let lon = coordinates[0] as f64;
244 let lat = coordinates[1] as f64;
245 let country_codes = boundaries.ids(LatLon::new(lat, lon).expect("Failed to parse LatLon"));
246 country_codes.contains(&&**target_code)
247 })
248 .collect();
249
250 body.features = filtered_features;
251 body.metadata.count = body.features.len() as u32;
252 }
253 Ok(body)
254
255 }
256}
257
258impl Display for AlertLevel {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 let level = match self {
261 AlertLevel::Green => "green",
262 AlertLevel::Yellow => "yellow",
263 AlertLevel::Orange => "orange",
264 AlertLevel::Red => "red",
265 AlertLevel::All => "all"
266 };
267 write!(f, "{}", level)
268 }
269}
270
271
272impl Display for OrderBy {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 let s = match self {
275 OrderBy::Time => "time",
276 OrderBy::TimeAsc => "time-asc",
277 OrderBy::Magnitude => "magnitude",
278 OrderBy::MagnitudeAsc => "magnitude-asc",
279 };
280 write!(f, "{}", s)
281 }
282}