1use {
2 ureq::get,
3 serde::Deserialize,
4 chrono::{NaiveDateTime, Utc},
5 crate::{
6 Event, ReportStatus,
7 sources::{Source, SourceInfo, SourceId, QueryPlan, opt_to_str},
8 DateTimeUtc, Result, Error
9 },
10};
11
12pub fn fixed_datetime_from_timestamp(ts: i64) -> Result<DateTimeUtc> {
13 Ok(DateTimeUtc::from_utc(NaiveDateTime::from_timestamp_millis(ts)
14 .ok_or(Error::TimeNone)?, Utc))
15}
16
17pub struct USGSSource;
25
26impl USGSSource {
27 pub fn new() -> Self {
28 Self{}
29 }
30}
31
32impl SourceInfo for USGSSource {
33 const SOURCE_ID: SourceId = "USGS";
34 const LATEST_API_URL: &'static str = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson";
35 const HISTORY_API_URL: &'static str = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson";
36}
37
38fn request<'a, P>(base: &str, queries: P) -> Result<Vec<Event>>
39where P: IntoIterator<Item = (&'a str, &'a str)> {
41 let resp = get(base)
42 .query_pairs(queries);
43 let resp = resp
44 .call()?
45 .into_string().map_err(|e| Error::InvalidResponse { source: e })?;
46 let raw_data: USGSRawData = serde_json::from_str(&resp)?;
47 let mut events: Vec<Event> = raw_data.features
48 .into_iter().filter_map(|i| i.try_into().ok()).collect();
49 events.sort_by(|a, b| b.time.cmp(&a.time));
50 Ok(events)
51}
52
53impl Source for USGSSource {
54 fn get_latest(&self) -> Result<Vec<Event>> {
55 request(Self::LATEST_API_URL, [])
56 }
57
58 fn run_query(&self, query: &QueryPlan) -> Result<Vec<Event>> {
59 request(Self::HISTORY_API_URL, [
60 ("starttime", opt_to_str(query.since.map(|v| v.to_rfc3339())).as_str()),
61 ("endtime", opt_to_str(query.until.map(|v| v.to_rfc3339())).as_str()),
62 ("minlongitude", opt_to_str(query.min_lon).as_str()),
63 ("maxlongitude", opt_to_str(query.max_lon).as_str()),
64 ("minlatitude", opt_to_str(query.min_lat).as_str()),
65 ("maxlatitude", opt_to_str(query.max_lat).as_str()),
66 ("mindepth", opt_to_str(query.min_depth).as_str()),
67 ("maxdepth", opt_to_str(query.max_depth).as_str()),
68 ("minmagnitude", opt_to_str(query.min_mag).as_str()),
69 ("maxmagnitude", opt_to_str(query.max_mag).as_str()),
70 ("offset", (query.page_size * (query.page - 1) + 1).to_string().as_str()),
71 ("limit", query.page_size.to_string().as_str()),
72 ])
73 }
74}
75
76#[derive(Deserialize)]
77struct Geometry {
78 coordinates: Vec<f64>,
79}
80
81#[derive(Deserialize)]
82struct Properties {
83 mag: f64,
84 place: Option<String>,
85 time: i64,
86 updated: i64,
87 url: String,
88 status: String,
89 }
91
92#[derive(Deserialize)]
93struct USGSRawItem {
94 properties: Properties,
95 geometry: Geometry,
96 id: String,
97}
98
99#[derive(Deserialize)]
100struct USGSRawData {
101 features: Vec<USGSRawItem>,
102}
103
104impl TryFrom<USGSRawItem> for Event {
105 type Error = Error;
106
107 fn try_from(value: USGSRawItem) -> Result<Self> {
108 Ok(Self{
109 id: value.id,
110 url: value.properties.url,
112 source: USGSSource::SOURCE_ID,
113 time: fixed_datetime_from_timestamp(value.properties.time)?,
114 updated: fixed_datetime_from_timestamp(value.properties.updated)?,
115 status: match value.properties.status.as_str() {
116 "automatic" => ReportStatus::Automatic,
117 "reviewed" => ReportStatus::Reviewed,
118 "deleted" => ReportStatus::Deleted,
119 _ => return Err(Error::UnexpectedValue),
120 },
121
122 longitude: value.geometry.coordinates[0],
123 latitude: value.geometry.coordinates[1],
124 depth: value.geometry.coordinates[2],
125 magnitude: value.properties.mag,
126 place: opt_to_str(value.properties.place),
127 })
128 }
129}
130
131#[cfg(test)]
132mod test {
133 use crate::{Source, USGSSource, DateTimeF, Result};
134
135 #[test]
136 fn test_get_latest() -> Result<()> {
137 Ok(())
139 }
140
141 #[test]
142 fn test_query() -> Result<()> {
143 let since = DateTimeF::parse_from_rfc3339("2023-01-01T00:00:00Z")?.into();
144 let until = DateTimeF::parse_from_rfc3339("2023-01-02T00:00:00Z")?.into();
145 let events = USGSSource::new().query()
146 .since(&since).until(&until).min_mag(5.5)
147 .run()?;
148 assert_eq!(
149 vec!["us7000j1u6", "us7000j1g5"],
150 events.iter().map(|e| e.id.as_str()).collect::<Vec<&str>>()
151 );
152 Ok(())
153 }
154}
155