///|
pub fn quality_sample(
sample : FieldSample,
flag : QualityFlag,
uncertainty : Double,
source : String,
) -> QualitySample {
{ sample, flag, uncertainty, source }
}
///|
pub fn quality_summary(samples : ArrayView[QualitySample]) -> QualitySummary {
let mut usable = 0
let mut missing = 0
let mut invalid = 0
let mut suspect = 0
let mut outlier = 0
for sample in samples {
match sample.flag {
Good => usable += 1
Missing => missing += 1
Invalid => invalid += 1
Suspect => suspect += 1
Outlier => outlier += 1
}
}
{ total: samples.length(), usable, missing, invalid, suspect, outlier }
}
///|
pub fn quality_from_samples(
samples : ArrayView[FieldSample],
source : String,
) -> Array[QualitySample] {
let output : Array[QualitySample] = []
for sample in samples {
let flag = if !is_finite(sample.value) {
Missing
} else if !valid_point(sample.point) {
Invalid
} else {
Good
}
output.push({ sample, flag, uncertainty: 0.0, source })
}
output
}
///|
pub fn quality_reflag_missing(
samples : ArrayView[QualitySample],
) -> Array[QualitySample] {
let output : Array[QualitySample] = []
for sample in samples {
if !is_finite(sample.sample.value) {
output.push({ ..sample, flag: Missing })
} else {
output.push(sample)
}
}
output
}
///|
pub fn quality_reflag_range(
samples : ArrayView[QualitySample],
range : NumericRange,
) -> Array[QualitySample] {
let output : Array[QualitySample] = []
for sample in samples {
let flag = if !is_finite(sample.sample.value) {
Missing
} else if range_contains(range, sample.sample.value) {
sample.flag
} else {
Invalid
}
output.push({ ..sample, flag, })
}
output
}
///|
pub fn quality_reflag_outliers(
samples : ArrayView[QualitySample],
threshold : Double,
) -> Array[QualitySample] {
let values : Array[Double] = []
for sample in samples {
values.push(sample.sample.value)
}
let report = outlier_report(values, threshold~)
let outlier_indices : Map[Int, Bool] = Map([])
for index in report.indices {
outlier_indices[index] = true
}
let output : Array[QualitySample] = []
for i in 0.. Array[QualitySample] {
samples.map(fn(sample) { { ..sample, uncertainty: uncertainty.abs() } })
}
///|
pub fn quality_usable_samples(
samples : ArrayView[QualitySample],
) -> Array[QualitySample] {
samples.filter(fn(sample) { quality_is_usable(sample.flag) })
}
///|
pub fn quality_values(
samples : ArrayView[QualitySample],
policy : MissingPolicy,
) -> Array[Double] {
let output : Array[Double] = []
for sample in samples {
if quality_is_usable(sample.flag) && is_finite(sample.sample.value) {
output.push(sample.sample.value)
} else {
match policy {
SkipMissing => ()
PreserveMissing => output.push(0.0 / 0.0)
ReplaceWith(value~) => output.push(value)
}
}
}
output
}
///|
pub fn quality_weighted_mean(samples : ArrayView[QualitySample]) -> Double {
let mut weighted = 0.0
let mut total_weight = 0.0
for sample in samples {
if quality_is_usable(sample.flag) && is_finite(sample.sample.value) {
let weight = if sample.uncertainty <= 0.0 {
1.0
} else {
1.0 / (sample.uncertainty * sample.uncertainty)
}
weighted += sample.sample.value * weight
total_weight += weight
}
}
if total_weight == 0.0 {
0.0 / 0.0
} else {
weighted / total_weight
}
}
///|
pub fn quality_weighted_variance(samples : ArrayView[QualitySample]) -> Double {
let mean = quality_weighted_mean(samples)
let mut weighted = 0.0
let mut total_weight = 0.0
for sample in samples {
if quality_is_usable(sample.flag) && is_finite(sample.sample.value) {
let weight = if sample.uncertainty <= 0.0 {
1.0
} else {
1.0 / (sample.uncertainty * sample.uncertainty)
}
weighted += weight *
(sample.sample.value - mean) *
(sample.sample.value - mean)
total_weight += weight
}
}
if total_weight == 0.0 {
0.0 / 0.0
} else {
weighted / total_weight
}
}
///|
pub fn quality_flag_counts(samples : ArrayView[QualitySample]) -> Array[Int] {
let summary = quality_summary(samples)
[
summary.usable,
summary.suspect,
summary.missing,
summary.invalid,
summary.outlier,
]
}
///|
pub fn quality_completeness(samples : ArrayView[QualitySample]) -> Double {
if samples.length() == 0 {
0.0
} else {
Double::from_int(quality_summary(samples).usable) /
Double::from_int(samples.length())
}
}
///|
pub fn quality_merge(
left : ArrayView[QualitySample],
right : ArrayView[QualitySample],
) -> Array[QualitySample] {
let output = left.to_owned()
for sample in right {
output.push(sample)
}
output
}
///|
pub fn quality_deduplicate(
samples : ArrayView[QualitySample],
tolerance_m : Double,
) -> Array[QualitySample] {
let output : Array[QualitySample] = []
for sample in samples {
let mut found = false
for existing in output {
if point_approximately_equal(
sample.sample.point,
existing.sample.point,
tolerance_m,
) {
found = true
}
}
if !found {
output.push(sample)
}
}
output
}
///|
pub fn quality_sort_by_coordinate(
samples : ArrayView[QualitySample],
origin : GeoPoint,
) -> Array[QualitySample] {
let output = samples.to_owned()
output.sort_by(fn(a, b) {
haversine_distance_m(origin, a.sample.point).compare(
haversine_distance_m(origin, b.sample.point),
)
})
output
}
///|
pub fn quality_apply_offset(
samples : ArrayView[QualitySample],
offset : Double,
) -> Array[QualitySample] {
samples.map(fn(sample) {
{
..sample,
sample: { ..sample.sample, value: sample.sample.value + offset },
}
})
}
///|
pub fn quality_to_samples(
samples : ArrayView[QualitySample],
) -> Array[FieldSample] {
samples.map(fn(sample) { sample.sample })
}