///|
/// Geometry diagnostics for a station array.
pub struct ArrayGeometryDiagnostics {
station_count : Int
aperture_km : Double
minimum_spacing_km : Double
maximum_spacing_km : Double
mean_spacing_km : Double
azimuthal_gap_degrees : Double
usable : Bool
}
///|
/// Calculate pairwise geometry diagnostics.
pub fn array_geometry_diagnostics(
stations : Array[StationPosition],
) -> ArrayGeometryDiagnostics {
if stations.length() < 2 {
return {
station_count: stations.length(),
aperture_km: 0.0,
minimum_spacing_km: 0.0,
maximum_spacing_km: 0.0,
mean_spacing_km: 0.0,
azimuthal_gap_degrees: 360.0,
usable: false,
}
}
let mut minimum = 1.0e300
let mut maximum = 0.0
let mut total = 0.0
let mut pair_count = 0
let azimuths = Array::new()
for left = 0; left < stations.length(); left = left + 1 {
for right = left + 1; right < stations.length(); right = right + 1 {
let distance = surface_distance_km(stations[left], stations[right])
minimum = minimum.min(distance)
maximum = maximum.max(distance)
total += distance
pair_count += 1
}
azimuths.push(azimuth_degrees(stations[0], stations[left]))
}
azimuths.sort()
let mut largest_gap = 0.0
for index = 1; index < azimuths.length(); index = index + 1 {
largest_gap = largest_gap.max(azimuths[index] - azimuths[index - 1])
}
if azimuths.length() > 1 {
largest_gap = largest_gap.max(
azimuths[0] + 360.0 - azimuths[azimuths.length() - 1],
)
}
{
station_count: stations.length(),
aperture_km: maximum,
minimum_spacing_km: minimum,
maximum_spacing_km: maximum,
mean_spacing_km: if pair_count == 0 {
0.0
} else {
total / pair_count.to_double()
},
azimuthal_gap_degrees: largest_gap,
usable: minimum > 0.0 && largest_gap < 270.0,
}
}
///|
/// Return stations inside an array aperture radius.
pub fn stations_inside_radius(
stations : Array[StationPosition],
reference : StationPosition,
radius_km : Double,
) -> Array[StationPosition] {
stations.filter(station => {
surface_distance_km(reference, station) <= radius_km.abs()
})
}
///|
/// Estimate the maximum useful slowness from array aperture.
pub fn useful_slowness_limit(
stations : Array[StationPosition],
sample_rate : Double,
) -> Double {
let diagnostics = array_geometry_diagnostics(stations)
if diagnostics.aperture_km <= 0.0 || sample_rate <= 0.0 {
0.0
} else {
sample_rate / diagnostics.aperture_km
}
}
///|
/// Export array geometry diagnostics.
pub fn array_geometry_text(diagnostics : ArrayGeometryDiagnostics) -> String {
"stations=" +
diagnostics.station_count.to_string() +
" aperture_km=" +
diagnostics.aperture_km.to_string() +
" min_spacing_km=" +
diagnostics.minimum_spacing_km.to_string() +
" max_spacing_km=" +
diagnostics.maximum_spacing_km.to_string() +
" mean_spacing_km=" +
diagnostics.mean_spacing_km.to_string() +
" azimuthal_gap=" +
diagnostics.azimuthal_gap_degrees.to_string() +
" usable=" +
diagnostics.usable.to_string()
}