///|
pub(all) enum FusionSource {
GgaSource
RmcSource
VtgSource
} derive(Eq, Debug)
///|
pub(all) struct FieldProvenance {
field_value : String
source_value : FusionSource
record_index_value : Int
} derive(Eq, Debug)
///|
pub(all) struct FusedFix {
time_value : UtcTime
date_value : UtcDate?
latitude_value : Double?
longitude_value : Double?
altitude_value : ExactDecimal?
quality_value : GgaFixQuality?
satellites_value : Int?
speed_knots_value : ExactDecimal?
course_value : ExactDecimal?
usable_value : Bool
provenance_value : Array[FieldProvenance]
diagnostics_value : Array[Diagnostic]
source_record_indexes_value : Array[Int]
} derive(Eq, Debug)
///|
struct FusionGroup {
time_value : UtcTime
date_value : UtcDate?
mut gga_value : (GgaRecord, Int)?
mut rmc_value : (RmcRecord, Int)?
mut vtg_value : (VtgRecord, Int)?
diagnostics_value : Array[Diagnostic]
source_record_indexes_value : Array[Int]
}
///|
pub struct FixFusion {
groups_value : Array[FusionGroup]
}
///|
fn same_utc_time(left : UtcTime, right : UtcTime) -> Bool {
left.hour_value == right.hour_value &&
left.minute_value == right.minute_value &&
left.second_value == right.second_value &&
left.nanosecond_value == right.nanosecond_value
}
///|
fn fusion_diagnostic(record_index : Int, message : String) -> Diagnostic {
Diagnostic::new(
FusionConflict,
Error,
message,
SourceRef::sentence(record_index),
)
}
///|
pub fn FixFusion::new() -> FixFusion {
{ groups_value: [] }
}
///|
fn FixFusion::find_group(
self : FixFusion,
time : UtcTime,
date : UtcDate?,
) -> Int? {
for index = 0; index < self.groups_value.length(); index = index + 1 {
let group = self.groups_value[index]
if group.date_value == date && same_utc_time(group.time_value, time) {
return Some(index)
}
}
None
}
///|
fn FixFusion::group_for(
self : FixFusion,
time : UtcTime,
date : UtcDate?,
) -> FusionGroup {
match self.find_group(time, date) {
Some(index) => self.groups_value[index]
None => {
let group = {
time_value: time,
date_value: date,
gga_value: None,
rmc_value: None,
vtg_value: None,
diagnostics_value: [],
source_record_indexes_value: [],
}
self.groups_value.push(group)
group
}
}
}
///|
pub fn FixFusion::add_gga(
self : FixFusion,
record : GgaRecord,
record_index~ : Int,
date? : UtcDate? = None,
) -> Unit {
let group = self.group_for(record.time_value, date)
group.source_record_indexes_value.push(record_index)
match group.gga_value {
Some(_) =>
group.diagnostics_value.push(
fusion_diagnostic(record_index, "duplicate GGA evidence at exact time"),
)
None => group.gga_value = Some((record, record_index))
}
}
///|
pub fn FixFusion::add_rmc(
self : FixFusion,
record : RmcRecord,
record_index~ : Int,
) -> Unit {
let group = self.group_for(record.time_value, Some(record.date_value))
group.source_record_indexes_value.push(record_index)
match group.rmc_value {
Some(_) =>
group.diagnostics_value.push(
fusion_diagnostic(record_index, "duplicate RMC evidence at exact time"),
)
None => group.rmc_value = Some((record, record_index))
}
}
///|
pub fn FixFusion::add_vtg(
self : FixFusion,
record : VtgRecord,
time : UtcTime,
record_index~ : Int,
date? : UtcDate? = None,
) -> Unit {
let group = self.group_for(time, date)
group.source_record_indexes_value.push(record_index)
match group.vtg_value {
Some(_) =>
group.diagnostics_value.push(
fusion_diagnostic(record_index, "duplicate VTG evidence at exact time"),
)
None => group.vtg_value = Some((record, record_index))
}
}
///|
fn add_provenance(
values : Array[FieldProvenance],
field : String,
source : FusionSource,
record_index : Int,
) -> Unit {
values.push({
field_value: field,
source_value: source,
record_index_value: record_index,
})
}
///|
fn coordinates_conflict(
left_latitude : Coordinate,
left_longitude : Coordinate,
right_latitude : Coordinate,
right_longitude : Coordinate,
) -> Bool {
(left_latitude.degrees_value - right_latitude.degrees_value).abs() >
0.000000001 ||
(left_longitude.degrees_value - right_longitude.degrees_value).abs() >
0.000000001
}
///|
fn FusionGroup::build(self : FusionGroup) -> FusedFix {
let diagnostics = self.diagnostics_value.copy()
let provenance : Array[FieldProvenance] = []
let mut latitude : Double? = None
let mut longitude : Double? = None
let mut altitude : ExactDecimal? = None
let mut quality : GgaFixQuality? = None
let mut satellites : Int? = None
let mut speed : ExactDecimal? = None
let mut course : ExactDecimal? = None
let mut has_valid_status = false
match self.gga_value {
Some((gga, index)) => {
quality = Some(gga.quality_value)
satellites = Some(gga.satellites_value)
altitude = gga.altitude_value
add_provenance(provenance, "fix_quality", GgaSource, index)
add_provenance(provenance, "satellites", GgaSource, index)
if altitude is Some(_) {
add_provenance(provenance, "altitude", GgaSource, index)
}
match (gga.latitude_value, gga.longitude_value) {
(Some(lat), Some(lon)) => {
latitude = Some(lat.degrees_value)
longitude = Some(lon.degrees_value)
has_valid_status = gga.quality_value != InvalidFix
add_provenance(provenance, "latitude", GgaSource, index)
add_provenance(provenance, "longitude", GgaSource, index)
}
_ => ()
}
}
None => ()
}
match self.rmc_value {
Some((rmc, index)) => {
add_provenance(provenance, "date", RmcSource, index)
match (rmc.latitude_value, rmc.longitude_value) {
(Some(lat), Some(lon)) => {
if latitude is Some(_) {
match self.gga_value {
Some((gga, _)) =>
match (gga.latitude_value, gga.longitude_value) {
(Some(gga_lat), Some(gga_lon)) =>
if coordinates_conflict(gga_lat, gga_lon, lat, lon) {
diagnostics.push(
fusion_diagnostic(
index, "GGA and RMC positions conflict at exact time",
),
)
}
_ => ()
}
None => ()
}
} else {
latitude = Some(lat.degrees_value)
longitude = Some(lon.degrees_value)
add_provenance(provenance, "latitude", RmcSource, index)
add_provenance(provenance, "longitude", RmcSource, index)
}
has_valid_status = has_valid_status || rmc.status_value is Active
}
_ => ()
}
speed = rmc.speed_knots_value
course = rmc.course_value
if speed is Some(_) {
add_provenance(provenance, "speed_knots", RmcSource, index)
}
if course is Some(_) {
add_provenance(provenance, "course", RmcSource, index)
}
}
None => ()
}
match self.vtg_value {
Some((vtg, index)) => {
match (speed, vtg.speed_knots_value) {
(Some(left), Some(right)) =>
if left.compare(right) != 0 {
diagnostics.push(
fusion_diagnostic(
index, "RMC and VTG speed values conflict at exact time",
),
)
}
(None, Some(value)) => {
speed = Some(value)
add_provenance(provenance, "speed_knots", VtgSource, index)
}
_ => ()
}
match (course, vtg.true_course_value) {
(Some(left), Some(right)) =>
if left.compare(right) != 0 {
diagnostics.push(
fusion_diagnostic(
index, "RMC and VTG course values conflict at exact time",
),
)
}
(None, Some(value)) => {
course = Some(value)
add_provenance(provenance, "course", VtgSource, index)
}
_ => ()
}
}
None => ()
}
{
time_value: self.time_value,
date_value: self.date_value,
latitude_value: latitude,
longitude_value: longitude,
altitude_value: altitude,
quality_value: quality,
satellites_value: satellites,
speed_knots_value: speed,
course_value: course,
usable_value: latitude is Some(_) &&
longitude is Some(_) &&
has_valid_status,
provenance_value: provenance,
diagnostics_value: diagnostics,
source_record_indexes_value: self.source_record_indexes_value.copy(),
}
}
///|
pub fn FixFusion::finish(self : FixFusion) -> Array[FusedFix] {
let fixes : Array[FusedFix] = []
for group in self.groups_value {
fixes.push(group.build())
}
self.groups_value.clear()
fixes
}
///|
pub fn FieldProvenance::field(self : FieldProvenance) -> String {
self.field_value
}
///|
pub fn FieldProvenance::source(self : FieldProvenance) -> FusionSource {
self.source_value
}
///|
pub fn FieldProvenance::record_index(self : FieldProvenance) -> Int {
self.record_index_value
}
///|
pub fn FusedFix::time(self : FusedFix) -> UtcTime {
self.time_value
}
///|
pub fn FusedFix::date(self : FusedFix) -> UtcDate? {
self.date_value
}
///|
pub fn FusedFix::latitude(self : FusedFix) -> Double? {
self.latitude_value
}
///|
pub fn FusedFix::longitude(self : FusedFix) -> Double? {
self.longitude_value
}
///|
pub fn FusedFix::altitude(self : FusedFix) -> ExactDecimal? {
self.altitude_value
}
///|
pub fn FusedFix::quality(self : FusedFix) -> GgaFixQuality? {
self.quality_value
}
///|
pub fn FusedFix::satellites(self : FusedFix) -> Int? {
self.satellites_value
}
///|
pub fn FusedFix::speed_knots(self : FusedFix) -> ExactDecimal? {
self.speed_knots_value
}
///|
pub fn FusedFix::course(self : FusedFix) -> ExactDecimal? {
self.course_value
}
///|
pub fn FusedFix::usable(self : FusedFix) -> Bool {
self.usable_value
}
///|
pub fn FusedFix::provenance(self : FusedFix) -> Array[FieldProvenance] {
self.provenance_value.copy()
}
///|
pub fn FusedFix::diagnostics(self : FusedFix) -> Array[Diagnostic] {
self.diagnostics_value.copy()
}
///|
pub fn FusedFix::source_record_indexes(self : FusedFix) -> Array[Int] {
self.source_record_indexes_value.copy()
}