///|
/// A named SAC analysis section over a trace.
pub struct SacAnalysisSection {
name : String
trace : Trace
begin_time : Double
end_time : Double
sample_count : Int
rms : Double
peak : Double
}
///|
/// Pick markers commonly stored in SAC event headers.
pub struct SacPickMarkers {
origin : Double?
p_arrival : Double?
s_arrival : Double?
coda_end : Double?
user_markers : Array[Double]
}
///|
/// SAC-oriented metadata validation result.
pub struct SacMetadataDiagnostics {
valid : Bool
sample_rate : Double
duration : Double
station : String
channel : String
warnings : Array[String]
}
///|
/// Cut a trace into a named section and calculate section measures.
pub fn make_sac_section(
name : String,
trace : Trace,
start_time : Double,
end_time : Double,
) -> SacAnalysisSection {
let section = clip_trace_time(trace, start_time, end_time)
{
name,
trace: section,
begin_time: section.start_time,
end_time: section.start_time + section.duration(),
sample_count: section.npts(),
rms: rms_samples(section.data),
peak: absolute_peak(section.data),
}
}
///|
/// Split a trace into fixed physical-duration SAC sections.
pub fn split_sac_sections(
trace : Trace,
duration_seconds : Double,
step_seconds : Double,
prefix : String,
) -> Array[SacAnalysisSection] {
let result = Array::new()
if duration_seconds <= 0.0 ||
step_seconds <= 0.0 ||
trace.sampling_rate <= 0.0 {
return result
}
let mut start = trace.start_time
let end = trace.start_time + trace.duration()
let mut sequence = 0
while start < end {
let finish = start + duration_seconds
if finish > end {
break
}
result.push(
make_sac_section(prefix + sequence.to_string(), trace, start, finish),
)
sequence += 1
start += step_seconds
}
result
}
///|
/// Merge contiguous SAC sections while checking metadata.
pub fn merge_sac_sections(
sections : Array[SacAnalysisSection],
) -> Result[Trace, String] {
if sections.is_empty() {
return Err("cannot merge empty SAC sections")
}
let sorted = sections.copy()
sorted.sort_by((left, right) => {
if left.begin_time < right.begin_time {
-1
} else if left.begin_time > right.begin_time {
1
} else {
0
}
})
let mut trace = sorted[0].trace.clone()
for index = 1; index < sorted.length(); index = index + 1 {
let section = sorted[index]
match concatenate_traces(trace, section.trace) {
Ok(value) => trace = value
Err(message) => return Err(message)
}
}
Ok(trace)
}
///|
/// Validate trace values before SAC serialization.
pub fn validate_sac_metadata(trace : Trace) -> SacMetadataDiagnostics {
let warnings : Array[String] = Array::new()
if trace.station == "" {
warnings.push("station is empty")
}
if trace.channel == "" {
warnings.push("channel is empty")
}
if trace.sampling_rate <= 0.0 {
warnings.push("sample rate is not positive")
}
if trace.npts() == 0 {
warnings.push("trace has no samples")
}
{
valid: warnings.is_empty(),
sample_rate: trace.sampling_rate,
duration: trace.duration(),
station: trace.station,
channel: trace.channel,
warnings,
}
}
///|
/// Create empty SAC markers.
pub fn empty_sac_pick_markers() -> SacPickMarkers {
{
origin: None,
p_arrival: None,
s_arrival: None,
coda_end: None,
user_markers: [],
}
}
///|
/// Add a user marker while retaining immutable marker semantics.
pub fn add_sac_user_marker(
markers : SacPickMarkers,
marker : Double,
) -> SacPickMarkers {
let user_markers = markers.user_markers.copy()
user_markers.push(marker)
{
origin: markers.origin,
p_arrival: markers.p_arrival,
s_arrival: markers.s_arrival,
coda_end: markers.coda_end,
user_markers,
}
}
///|
/// Compute P/S separation when both markers exist.
pub fn sac_phase_separation(markers : SacPickMarkers) -> Double? {
match (markers.p_arrival, markers.s_arrival) {
(Some(p), Some(s)) => Some(s - p)
_ => None
}
}
///|
/// Return a section CSV table for QC dashboards.
pub fn sac_sections_to_csv(sections : Array[SacAnalysisSection]) -> String {
let mut output = "name,begin,end,samples,rms,peak\n"
for section in sections {
output = output +
section.name +
"," +
section.begin_time.to_string() +
"," +
section.end_time.to_string() +
"," +
section.sample_count.to_string() +
"," +
section.rms.to_string() +
"," +
section.peak.to_string() +
"\n"
}
output
}
///|
/// Format SAC metadata diagnostics.
pub fn sac_metadata_text(diagnostics : SacMetadataDiagnostics) -> String {
"station=" +
diagnostics.station +
" channel=" +
diagnostics.channel +
" valid=" +
diagnostics.valid.to_string() +
" rate=" +
diagnostics.sample_rate.to_string() +
" duration=" +
diagnostics.duration.to_string() +
" warnings=" +
diagnostics.warnings.length().to_string()
}