mod base_types;
use base_types::{
GroundTruthLevel, OriginUncertaintyDescription, RealQuantity, ResourceReference, TimeQuantity,
};
use std::fmt;
use std::fmt::Formatter;
use std::fs;
use std::path::PathBuf;
use chrono::{DateTime, Utc};
use quick_xml::de::from_str;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct CreationInfo {
agency_id: Option<String>,
agency_uri: Option<String>, creation_time: DateTime<Utc>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct ConfidenceEllipsoid {
semi_major_axis_length: f64,
semi_minor_axis_length: f64,
semi_intermediate_axis_length: f64,
major_axis_plunge: f64,
major_axis_azimuth: f64,
major_axis_rotation: f64,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct OriginQuality {
associated_phase_count: Option<i32>,
used_phase_count: Option<i32>,
associated_station_count: Option<i32>,
used_station_count: Option<i32>,
depth_phase_count: Option<i32>,
standard_error: Option<f64>,
azimuthal_gap: Option<f64>,
secondary_azimuthal_gap: Option<f64>,
ground_truth_level: Option<GroundTruthLevel>,
maximum_distance: Option<f64>,
minimum_distance: Option<f64>,
median_distance: Option<f64>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct OriginUncertainty {
horizontal_uncertainty: Option<f64>,
min_horizontal_uncertainty: Option<f64>,
max_horizontal_uncertainty: Option<f64>,
azimuth_max_horizontal_uncertainty: Option<f64>,
confidence_ellipsoid: Option<ConfidenceEllipsoid>,
preferred_description: Option<OriginUncertaintyDescription>,
confidence_level: Option<f64>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Origin {
#[serde(rename = "publicID")]
public_id: ResourceReference,
time: TimeQuantity,
longitude: RealQuantity,
latitude: RealQuantity,
depth: RealQuantity,
origin_uncertainty: Option<OriginUncertainty>,
quality: Option<OriginQuality>,
evaluation_mode: Option<String>,
creation_info: Option<CreationInfo>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Magnitude {
mag: RealQuantity,
creation_time: Option<DateTime<Utc>>,
#[serde(rename = "publicID")]
public_id: ResourceReference,
#[serde(rename = "originID")]
origin_id: Option<ResourceReference>,
#[serde(rename = "methodID")]
method_id: Option<ResourceReference>,
#[serde(rename = "type")]
_type: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
struct EventDescription {
text: String,
#[serde(rename = "type")]
_type: Option<String>,
}
fn empty_vector_magnitude() -> Vec<Magnitude> {
Vec::new()
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Event {
origin: Vec<Origin>,
#[serde(rename = "publicID")]
public_id: ResourceReference,
#[serde(rename = "magnitude", default = "empty_vector_magnitude")]
magnitudes: Vec<Magnitude>,
description: Option<Vec<EventDescription>>,
#[serde(rename = "preferredOriginID")]
preferred_origin_id: Option<ResourceReference>,
preferred_magnitude_id: Option<ResourceReference>,
}
impl Event {
fn preferred_origin(&self) -> Option<&Origin> {
if self.origin.len() == 1 {
return Some(&self.origin[0]);
} else {
let preferred_origin = self
.origin
.iter()
.find(|&origin| origin.public_id == *self.preferred_origin_id.as_ref().unwrap())
.expect("Didn't find an origin with preferred_origin_id");
Some(&preferred_origin)
}
}
fn preferred_magnitude(&self) -> Option<f64> {
if self.magnitudes.len() == 0 {
return None;
}
let mut preferred_magnitude = &self.magnitudes[0];
if self.magnitudes.len() >= 2 {
preferred_magnitude = self
.magnitudes
.iter()
.find(|&mag| mag.public_id == *self.preferred_magnitude_id.as_ref().unwrap())
.expect("Didn't find a magnitude with preferred_magnitude_id");
}
Some(preferred_magnitude.mag.value)
}
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct EventParameters {
#[serde(rename = "event")]
events: Vec<Event>,
creation_info: CreationInfo,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct QuakeML {
event_parameters: EventParameters,
}
impl QuakeML {
pub fn from_str(data: &String) -> Self {
let quakeml: QuakeML = from_str(data).expect("something went wrong");
quakeml
}
fn min_magnitude(&self) -> Option<f64> {
let min_mag_event = &self
.event_parameters
.events
.iter()
.filter(|a| !a.preferred_magnitude().is_none())
.min_by(|a, b| {
a.preferred_magnitude()
.unwrap()
.partial_cmp(&b.preferred_magnitude().unwrap())
.unwrap()
});
return min_mag_event.unwrap().preferred_magnitude();
}
fn max_magnitude(&self) -> Option<f64> {
let max_mag_event = &self
.event_parameters
.events
.iter()
.filter(|a| !a.preferred_magnitude().is_none())
.max_by(|a, b| {
a.preferred_magnitude()
.unwrap()
.partial_cmp(&b.preferred_magnitude().unwrap())
.unwrap()
});
return max_mag_event.unwrap().preferred_magnitude();
}
}
impl fmt::Display for QuakeML {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Events in catalog: {}\n Max magnitude: {}\n Min magnitude: {}",
self.event_parameters.events.len(),
self.max_magnitude().unwrap(),
self.min_magnitude().unwrap(),
)
}
}
pub fn read_quakeml(filename: &PathBuf) -> QuakeML {
let data = fs::read_to_string(filename).expect("Failed to read quakeml into string");
QuakeML::from_str(&data)
}
#[cfg(test)]
mod tests {
use crate::read_quakeml;
use std::path::PathBuf;
#[test]
fn deserialize() {
let mut data_sample = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
data_sample.push("resources/sample.quakeml");
let _catalog = read_quakeml(&data_sample);
assert_eq!(
_catalog.event_parameters.events[0].public_id,
"quakeml:earthquake.usgs.gov/fdsnws/event/1/query?eventid=ci14517572&format=quakeml"
);
}
}