dizhen/sources/
usgs.rs

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
17/** United States Geological Survey data source.
18 *
19 * Limit max 20000 records per request.
20 *
21 * Raw data are in GeoJSON, which we for simplicity just hand wrote a
22 * Deserialize conversion.
23 */
24pub 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>>
39// ureq::Request::query_pairs
40where 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	//ids: String,
90}
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			// returned result is in milliseconds
111			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		//USGSSource::new().get_latest()?;
138		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