#![allow(clippy::upper_case_acronyms)]
use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use crate::record::Precision;
use crate::ParseError;
#[derive(Debug, Clone)]
pub enum Property {
T(Coords),
Name(String),
Type(HashSet<Tag>),
Parent(u64),
Next(u64),
CallSign(String),
Registration(String),
Squawk(String),
ICAO24(String),
Pilot(String),
Group(String),
Country(String),
Coalition(String),
Color(Color),
Shape(String),
Debug(String),
Label(String),
FocusedTarget(u64),
LockedTarget(u64),
Importance(f64),
Slot(u64),
Disabled(bool),
Visible(bool),
Health(f64),
Length(f64),
Width(f64),
Height(f64),
Radius(f64),
IAS(f64),
CAS(f64),
TAS(f64),
Mach(f64),
AOA(f64),
AOS(f64),
AGL(f64),
HDG(f64),
HDM(f64),
Throttle(f64),
Afterburner(f64),
AirBrakes(f64),
Flaps(f64),
LandingGear(f64),
LandingGearHandle(f64),
Tailhook(f64),
Parachute(f64),
DragChute(f64),
FuelWeight(u8, f64),
FuelVolume(u8, f64),
FuelFlowWeight(u8, f64),
FuelFlowVolume(u8, f64),
RadarMode(f64),
RadarAzimuth(f64),
RadarElevation(f64),
RadarRoll(f64),
RadarRange(f64),
RadarHorizontalBeamwidth(f64),
RadarVerticalBeamwidth(f64),
LockedTargetMode(f64),
LockedTargetAzimuth(f64),
LockedTargetElevation(f64),
LockedTargetRange(f64),
EngagementMode(f64),
EngagementMode2(f64),
EngagementRange(f64),
EngagementRange2(f64),
VerticalEngagementRange(f64),
VerticalEngagementRange2(f64),
RollControlInput(f64),
PitchControlInput(f64),
YawControlInput(f64),
RollControlPosition(f64),
PitchControlPosition(f64),
YawControlPosition(f64),
RollTrimTab(f64),
PitchTrimTab(f64),
YawTrimTab(f64),
AileronLeft(f64),
AileronRight(f64),
Elevator(f64),
Rudder(f64),
PilotHeadRoll(f64),
PilotHeadPitch(f64),
PilotHeadYaw(f64),
VerticalGForce(f64),
LongitudinalGForce(f64),
LateralGForce(f64),
ENL(f64),
Unknown(String, String),
}
#[derive(Debug, Default, Clone)]
pub struct Coords {
pub longitude: Option<f64>,
pub latitude: Option<f64>,
pub altitude: Option<f64>,
pub u: Option<f64>,
pub v: Option<f64>,
pub roll: Option<f64>,
pub pitch: Option<f64>,
pub yaw: Option<f64>,
pub heading: Option<f64>,
}
impl Coords {
pub fn update(&mut self, other: &Coords, reference_latitude: f64, reference_longitude: f64) {
if let Some(longitude) = other.longitude {
self.longitude = Some(longitude + reference_longitude);
}
if let Some(latitude) = other.latitude {
self.latitude = Some(latitude + reference_latitude);
}
if let Some(altitude) = other.altitude {
self.altitude = Some(altitude);
}
if let Some(u) = other.u {
self.u = Some(u);
}
if let Some(v) = other.v {
self.v = Some(v);
}
if let Some(roll) = other.roll {
self.roll = Some(roll);
}
if let Some(pitch) = other.pitch {
self.pitch = Some(pitch);
}
if let Some(yaw) = other.yaw {
self.yaw = Some(yaw);
}
if let Some(heading) = other.heading {
self.heading = Some(heading);
}
}
pub fn position(mut self, lat: f64, lon: f64, alt: f64) -> Self {
self.latitude = Some(lat);
self.longitude = Some(lon);
self.altitude = Some(alt);
self
}
pub fn uv(mut self, u: f64, v: f64) -> Self {
self.u = Some(u);
self.v = Some(v);
self
}
pub fn orientation(mut self, yaw: f64, pitch: f64, roll: f64) -> Self {
self.yaw = Some(yaw);
self.pitch = Some(pitch);
self.roll = Some(roll);
self
}
pub fn heading(mut self, v: f64) -> Self {
self.heading = Some(v);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Color {
Red,
Orange,
Green,
Blue,
Violet,
Grey,
Unknown(String),
}
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub enum Tag {
Air,
Ground,
Sea,
Weapon,
Sensor,
Navaid,
Misc,
Static,
Heavy,
Medium,
Light,
Minor,
FixedWing,
Rotorcraft,
Armor,
AntiAircraft,
Vehicle,
Watercraft,
Human,
Biologic,
Missile,
Rocket,
Bomb,
Torpedo,
Projectile,
Beam,
Decoy,
Building,
Bullseye,
Waypoint,
Tank,
Warship,
AircraftCarrier,
Submarine,
Infantry,
Parachutist,
Shell,
Bullet,
Flare,
Chaff,
SmokeGrenade,
Aerodrome,
Container,
Shrapnel,
Unknown(String),
}
impl FromStr for Property {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (name, value) = s.split_once('=').ok_or(ParseError::MissingDelimiter('='))?;
Ok(match name {
"T" => Property::T(Coords::from_str(value)?),
"Name" => Property::Name(value.to_string()),
"Type" => Property::Type(value.split('+').map(Tag::from).collect()),
"Parent" => Property::Parent(u64::from_str_radix(value, 16)?),
"Next" => Property::Next(u64::from_str_radix(value, 16)?),
"CallSign" => Property::CallSign(value.to_string()),
"Registration" => Property::Registration(value.to_string()),
"Squawk" => Property::Squawk(value.to_string()),
"ICAO24" => Property::ICAO24(value.to_string()),
"Pilot" => Property::Pilot(value.to_string()),
"Group" => Property::Group(value.to_string()),
"Country" => Property::Country(value.to_string()),
"Coalition" => Property::Coalition(value.to_string()),
"Color" => Property::Color(Color::from(value)),
"Shape" => Property::Shape(value.to_string()),
"Debug" => Property::Debug(value.to_string()),
"Label" => Property::Label(value.to_string()),
"FocusedTarget" => Property::FocusedTarget(u64::from_str_radix(value, 16)?),
"LockedTarget" => Property::LockedTarget(u64::from_str_radix(value, 16)?),
"Importance" => Property::Importance(FromStr::from_str(value)?),
"Slot" => Property::Slot(FromStr::from_str(value)?),
"Disabled" => Property::Disabled(i64::from_str(value)? != 0),
"Visible" => Property::Visible(i64::from_str(value)? != 0),
"Health" => Property::Health(FromStr::from_str(value)?),
"Length" => Property::Length(FromStr::from_str(value)?),
"Width" => Property::Width(FromStr::from_str(value)?),
"Height" => Property::Height(FromStr::from_str(value)?),
"Radius" => Property::Radius(FromStr::from_str(value)?),
"IAS" => Property::IAS(FromStr::from_str(value)?),
"CAS" => Property::CAS(FromStr::from_str(value)?),
"TAS" => Property::TAS(FromStr::from_str(value)?),
"Mach" => Property::Mach(FromStr::from_str(value)?),
"AOA" => Property::AOA(FromStr::from_str(value)?),
"AOS" => Property::AOS(FromStr::from_str(value)?),
"AGL" => Property::AGL(FromStr::from_str(value)?),
"HDG" => Property::HDG(FromStr::from_str(value)?),
"HDM" => Property::HDM(FromStr::from_str(value)?),
"Throttle" => Property::Throttle(FromStr::from_str(value)?),
"Afterburner" => Property::Afterburner(FromStr::from_str(value)?),
"AirBrakes" => Property::AirBrakes(FromStr::from_str(value)?),
"Flaps" => Property::Flaps(FromStr::from_str(value)?),
"LandingGear" => Property::LandingGear(FromStr::from_str(value)?),
"LandingGearHandle" => Property::LandingGearHandle(FromStr::from_str(value)?),
"Tailhook" => Property::Tailhook(FromStr::from_str(value)?),
"Parachute" => Property::Parachute(FromStr::from_str(value)?),
"DragChute" => Property::DragChute(FromStr::from_str(value)?),
"FuelWeight" => Property::FuelWeight(0, FromStr::from_str(value)?),
"FuelWeight2" => Property::FuelWeight(1, FromStr::from_str(value)?),
"FuelWeight3" => Property::FuelWeight(2, FromStr::from_str(value)?),
"FuelWeight4" => Property::FuelWeight(3, FromStr::from_str(value)?),
"FuelWeight5" => Property::FuelWeight(4, FromStr::from_str(value)?),
"FuelWeight6" => Property::FuelWeight(5, FromStr::from_str(value)?),
"FuelWeight7" => Property::FuelWeight(6, FromStr::from_str(value)?),
"FuelWeight8" => Property::FuelWeight(7, FromStr::from_str(value)?),
"FuelWeight9" => Property::FuelWeight(8, FromStr::from_str(value)?),
"FuelVolume" => Property::FuelVolume(0, FromStr::from_str(value)?),
"FuelVolume1" => Property::FuelVolume(1, FromStr::from_str(value)?),
"FuelVolume2" => Property::FuelVolume(2, FromStr::from_str(value)?),
"FuelVolume3" => Property::FuelVolume(3, FromStr::from_str(value)?),
"FuelVolume4" => Property::FuelVolume(4, FromStr::from_str(value)?),
"FuelVolume5" => Property::FuelVolume(5, FromStr::from_str(value)?),
"FuelVolume6" => Property::FuelVolume(6, FromStr::from_str(value)?),
"FuelVolume7" => Property::FuelVolume(7, FromStr::from_str(value)?),
"FuelVolume8" => Property::FuelVolume(8, FromStr::from_str(value)?),
"FuelVolume9" => Property::FuelVolume(9, FromStr::from_str(value)?),
"FuelFlowWeight" => Property::FuelFlowWeight(0, FromStr::from_str(value)?),
"FuelFlowWeight2" => Property::FuelFlowWeight(1, FromStr::from_str(value)?),
"FuelFlowWeight3" => Property::FuelFlowWeight(2, FromStr::from_str(value)?),
"FuelFlowWeight4" => Property::FuelFlowWeight(3, FromStr::from_str(value)?),
"FuelFlowWeight5" => Property::FuelFlowWeight(4, FromStr::from_str(value)?),
"FuelFlowWeight6" => Property::FuelFlowWeight(5, FromStr::from_str(value)?),
"FuelFlowWeight7" => Property::FuelFlowWeight(6, FromStr::from_str(value)?),
"FuelFlowWeight8" => Property::FuelFlowWeight(7, FromStr::from_str(value)?),
"FuelFlowVolume" => Property::FuelFlowVolume(0, FromStr::from_str(value)?),
"FuelFlowVolume2" => Property::FuelFlowVolume(1, FromStr::from_str(value)?),
"FuelFlowVolume3" => Property::FuelFlowVolume(2, FromStr::from_str(value)?),
"FuelFlowVolume4" => Property::FuelFlowVolume(3, FromStr::from_str(value)?),
"FuelFlowVolume5" => Property::FuelFlowVolume(4, FromStr::from_str(value)?),
"FuelFlowVolume6" => Property::FuelFlowVolume(5, FromStr::from_str(value)?),
"FuelFlowVolume7" => Property::FuelFlowVolume(6, FromStr::from_str(value)?),
"FuelFlowVolume8" => Property::FuelFlowVolume(7, FromStr::from_str(value)?),
"RadarMode" => Property::RadarMode(FromStr::from_str(value)?),
"RadarAzimuth" => Property::RadarAzimuth(FromStr::from_str(value)?),
"RadarElevation" => Property::RadarElevation(FromStr::from_str(value)?),
"RadarRoll" => Property::RadarRoll(FromStr::from_str(value)?),
"RadarRange" => Property::RadarRange(FromStr::from_str(value)?),
"RadarHorizontalBeamwidth" => {
Property::RadarHorizontalBeamwidth(FromStr::from_str(value)?)
}
"RadarVerticalBeamwidth" => Property::RadarVerticalBeamwidth(FromStr::from_str(value)?),
"LockedTargetMode" => Property::LockedTargetMode(FromStr::from_str(value)?),
"LockedTargetAzimuth" => Property::LockedTargetAzimuth(FromStr::from_str(value)?),
"LockedTargetElevation" => Property::LockedTargetElevation(FromStr::from_str(value)?),
"LockedTargetRange" => Property::LockedTargetRange(FromStr::from_str(value)?),
"EngagementMode" => Property::EngagementMode(FromStr::from_str(value)?),
"EngagementMode2" => Property::EngagementMode2(FromStr::from_str(value)?),
"EngagementRange" => Property::EngagementRange(FromStr::from_str(value)?),
"EngagementRange2" => Property::EngagementRange2(FromStr::from_str(value)?),
"VerticalEngagementRange" => {
Property::VerticalEngagementRange(FromStr::from_str(value)?)
}
"VerticalEngagementRange2" => {
Property::VerticalEngagementRange2(FromStr::from_str(value)?)
}
"RollControlInput" => Property::RollControlInput(FromStr::from_str(value)?),
"PitchControlInput" => Property::PitchControlInput(FromStr::from_str(value)?),
"YawControlInput" => Property::YawControlInput(FromStr::from_str(value)?),
"RollControlPosition" => Property::RollControlPosition(FromStr::from_str(value)?),
"PitchControlPosition" => Property::PitchControlPosition(FromStr::from_str(value)?),
"YawControlPosition" => Property::YawControlPosition(FromStr::from_str(value)?),
"RollTrimTab" => Property::RollTrimTab(FromStr::from_str(value)?),
"PitchTrimTab" => Property::PitchTrimTab(FromStr::from_str(value)?),
"YawTrimTab" => Property::YawTrimTab(FromStr::from_str(value)?),
"AileronLeft" => Property::AileronLeft(FromStr::from_str(value)?),
"AileronRight" => Property::AileronRight(FromStr::from_str(value)?),
"Elevator" => Property::Elevator(FromStr::from_str(value)?),
"Rudder" => Property::Rudder(FromStr::from_str(value)?),
"PilotHeadRoll" => Property::PilotHeadRoll(FromStr::from_str(value)?),
"PilotHeadPitch" => Property::PilotHeadPitch(FromStr::from_str(value)?),
"PilotHeadYaw" => Property::PilotHeadYaw(FromStr::from_str(value)?),
"VerticalGForce" => Property::VerticalGForce(FromStr::from_str(value)?),
"LongitudinalGForce" => Property::LongitudinalGForce(FromStr::from_str(value)?),
"LateralGForce" => Property::LateralGForce(FromStr::from_str(value)?),
"ENL" => Property::ENL(FromStr::from_str(value)?),
name => Self::Unknown(name.to_string(), value.to_string()),
})
}
}
impl Display for Property {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Property::*;
match self {
T(v) => write!(f, "T={}", v),
Name(v) => write!(f, "Name={}", v),
Type(v) => write!(f, "Type={}", join(v.iter().map(|v| v.as_str()), "+")),
Parent(v) => write!(f, "Parent={:x}", v),
Next(v) => write!(f, "Next={:x}", v),
CallSign(v) => write!(f, "CallSign={}", v),
Registration(v) => write!(f, "Registration={}", v),
Squawk(v) => write!(f, "Squawk={}", v),
ICAO24(v) => write!(f, "ICAO24={}", v),
Pilot(v) => write!(f, "Pilot={}", v),
Group(v) => write!(f, "Group={}", v),
Country(v) => write!(f, "Country={}", v),
Coalition(v) => write!(f, "Coalition={}", v),
Color(v) => write!(f, "Color={}", v.as_str()),
Shape(v) => write!(f, "Shape={}", v),
Debug(v) => write!(f, "Debug={}", v),
Label(v) => write!(f, "Label={}", v),
FocusedTarget(v) => write!(f, "FocusedTarget={:x}", v),
LockedTarget(v) => write!(f, "LockedTarget={:x}", v),
Importance(v) => write!(f, "Importance={}", v),
Slot(v) => write!(f, "Slot={}", v),
Disabled(v) => write!(f, "Disabled={}", *v as i32),
Visible(v) => write!(f, "Visible={}", *v as i32),
Health(v) => write!(f, "Health={}", v),
Length(v) => write!(f, "Length={}", v),
Width(v) => write!(f, "Width={}", v),
Height(v) => write!(f, "Height={}", v),
Radius(v) => write!(f, "Radius={}", v),
IAS(v) => write!(f, "IAS={}", v),
CAS(v) => write!(f, "CAS={}", v),
TAS(v) => write!(f, "TAS={}", v),
Mach(v) => write!(f, "Mach={}", v),
AOA(v) => write!(f, "AOA={}", v.max_precision(2)),
AOS(v) => write!(f, "AOS={}", v),
AGL(v) => write!(f, "AGL={}", v),
HDG(v) => write!(f, "HDG={}", v),
HDM(v) => write!(f, "HDM={}", v),
Throttle(v) => write!(f, "Throttle={}", v),
Afterburner(v) => write!(f, "Afterburner={}", v),
AirBrakes(v) => write!(f, "AirBrakes={}", v),
Flaps(v) => write!(f, "Flaps={}", v),
LandingGear(v) => write!(f, "LandingGear={}", v),
LandingGearHandle(v) => write!(f, "LandingGearHandle={}", v),
Tailhook(v) => write!(f, "Tailhook={}", v),
Parachute(v) => write!(f, "Parachute={}", v),
DragChute(v) => write!(f, "DragChute={}", v),
FuelWeight(i, v) => write!(f, "FuelWeight{}={}", to_index(*i), v),
FuelVolume(i, v) => write!(f, "FuelVolume{}={}", to_index(*i), v),
FuelFlowWeight(i, v) => write!(f, "FuelFlowWeight{}={}", to_index(*i), v),
FuelFlowVolume(i, v) => write!(f, "FuelFlowVolume{}={}", to_index(*i), v),
RadarMode(v) => write!(f, "RadarMode={}", v),
RadarAzimuth(v) => write!(f, "RadarAzimuth={}", v),
RadarElevation(v) => write!(f, "RadarElevation={}", v),
RadarRoll(v) => write!(f, "RadarRoll={}", v),
RadarRange(v) => write!(f, "RadarRange={}", v),
RadarHorizontalBeamwidth(v) => write!(f, "RadarHorizontalBeamwidth={}", v),
RadarVerticalBeamwidth(v) => write!(f, "RadarVerticalBeamwidth={}", v),
LockedTargetMode(v) => write!(f, "LockedTargetMode={}", v),
LockedTargetAzimuth(v) => write!(f, "LockedTargetAzimuth={}", v),
LockedTargetElevation(v) => write!(f, "LockedTargetElevation={}", v),
LockedTargetRange(v) => write!(f, "LockedTargetRange={}", v),
EngagementMode(v) => write!(f, "EngagementMode={}", v),
EngagementMode2(v) => write!(f, "EngagementMode2={}", v),
EngagementRange(v) => write!(f, "EngagementRange={}", v),
EngagementRange2(v) => write!(f, "EngagementRange2={}", v),
VerticalEngagementRange(v) => write!(f, "VerticalEngagementRange={}", v),
VerticalEngagementRange2(v) => write!(f, "VerticalEngagementRange2={}", v),
RollControlInput(v) => write!(f, "RollControlInput={}", v),
PitchControlInput(v) => write!(f, "PitchControlInput={}", v),
YawControlInput(v) => write!(f, "YawControlInput={}", v),
RollControlPosition(v) => write!(f, "RollControlPosition={}", v),
PitchControlPosition(v) => write!(f, "PitchControlPosition={}", v),
YawControlPosition(v) => write!(f, "YawControlPosition={}", v),
RollTrimTab(v) => write!(f, "RollTrimTab={}", v),
PitchTrimTab(v) => write!(f, "PitchTrimTab={}", v),
YawTrimTab(v) => write!(f, "YawTrimTab={}", v),
AileronLeft(v) => write!(f, "AileronLeft={}", v),
AileronRight(v) => write!(f, "AileronRight={}", v),
Elevator(v) => write!(f, "Elevator={}", v),
Rudder(v) => write!(f, "Rudder={}", v),
PilotHeadRoll(v) => write!(f, "PilotHeadRoll={}", v),
PilotHeadPitch(v) => write!(f, "PilotHeadPitch={}", v),
PilotHeadYaw(v) => write!(f, "PilotHeadYaw={}", v),
VerticalGForce(v) => write!(f, "VerticalGForce={}", v),
LongitudinalGForce(v) => write!(f, "LongitudinalGForce={}", v),
LateralGForce(v) => write!(f, "LateralGForce={}", v),
ENL(v) => write!(f, "ENL={}", v),
Unknown(k, v) => write!(f, "{}={}", k, v),
}
}
}
impl<'a> From<&'a str> for Color {
fn from(s: &str) -> Self {
match s {
"Red" => Self::Red,
"Orange" => Self::Orange,
"Green" => Self::Green,
"Blue" => Self::Blue,
"Violet" => Self::Violet,
color => Self::Unknown(color.to_string()),
}
}
}
impl Color {
fn as_str(&self) -> &str {
use Color::*;
match self {
Red => "Red",
Orange => "Orange",
Green => "Green",
Blue => "Blue",
Violet => "Violet",
Grey => "Grey",
Unknown(color) => color,
}
}
}
impl<'a> From<&'a str> for Tag {
fn from(s: &str) -> Self {
match s {
"Air" => Self::Air,
"Ground" => Self::Ground,
"Sea" => Self::Sea,
"Weapon" => Self::Weapon,
"Sensor" => Self::Sensor,
"Navaid" => Self::Navaid,
"Misc" => Self::Misc,
"Static" => Self::Static,
"Heavy" => Self::Heavy,
"Medium" => Self::Medium,
"Light" => Self::Light,
"Minor" => Self::Minor,
"FixedWing" => Self::FixedWing,
"Rotorcraft" => Self::Rotorcraft,
"Armor" => Self::Armor,
"AntiAircraft" => Self::AntiAircraft,
"Vehicle" => Self::Vehicle,
"Watercraft" => Self::Watercraft,
"Human" => Self::Human,
"Biologic" => Self::Biologic,
"Missile" => Self::Missile,
"Rocket" => Self::Rocket,
"Bomb" => Self::Bomb,
"Torpedo" => Self::Torpedo,
"Projectile" => Self::Projectile,
"Beam" => Self::Beam,
"Decoy" => Self::Decoy,
"Building" => Self::Building,
"Bullseye" => Self::Bullseye,
"Waypoint" => Self::Waypoint,
"Tank" => Self::Tank,
"Warship" => Self::Warship,
"AircraftCarrier" => Self::AircraftCarrier,
"Submarine" => Self::Submarine,
"Infantry" => Self::Infantry,
"Parachutist" => Self::Parachutist,
"Shell" => Self::Shell,
"Bullet" => Self::Bullet,
"Flare" => Self::Flare,
"Chaff" => Self::Chaff,
"SmokeGrenade" => Self::SmokeGrenade,
"Aerodrome" => Self::Aerodrome,
"Container" => Self::Container,
"Shrapnel" => Self::Shrapnel,
tag => Self::Unknown(tag.to_string()),
}
}
}
impl Tag {
fn as_str(&self) -> &str {
use Tag::*;
match self {
Air => "Air",
Ground => "Ground",
Sea => "Sea",
Weapon => "Weapon",
Sensor => "Sensor",
Navaid => "Navaid",
Misc => "Misc",
Static => "Static",
Heavy => "Heavy",
Medium => "Medium",
Light => "Light",
Minor => "Minor",
FixedWing => "FixedWing",
Rotorcraft => "Rotorcraft",
Armor => "Armor",
AntiAircraft => "AntiAircraft",
Vehicle => "Vehicle",
Watercraft => "Watercraft",
Human => "Human",
Biologic => "Biologic",
Missile => "Missile",
Rocket => "Rocket",
Bomb => "Bomb",
Torpedo => "Torpedo",
Projectile => "Projectile",
Beam => "Beam",
Decoy => "Decoy",
Building => "Building",
Bullseye => "Bullseye",
Waypoint => "Waypoint",
Tank => "Tank",
Warship => "Warship",
AircraftCarrier => "AircraftCarrier",
Submarine => "Submarine",
Infantry => "Infantry",
Parachutist => "Parachutist",
Shell => "Shell",
Bullet => "Bullet",
Flare => "Flare",
Chaff => "Chaff",
SmokeGrenade => "SmokeGrenade",
Aerodrome => "Aerodrome",
Container => "Container",
Shrapnel => "Shrapnel",
Unknown(tag) => tag,
}
}
}
impl FromStr for Coords {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split('|').collect::<Vec<_>>();
let mut coords = Coords::default();
match &parts[..] {
[longitude, latitude, altitude] => {
if !longitude.is_empty() {
coords.longitude = Some(f64::from_str(longitude)?);
}
if !latitude.is_empty() {
coords.latitude = Some(f64::from_str(latitude)?);
}
if !altitude.is_empty() {
coords.altitude = Some(f64::from_str(altitude)?);
}
}
[longitude, latitude, altitude, u, v] => {
if !longitude.is_empty() {
coords.longitude = Some(f64::from_str(longitude)?);
}
if !latitude.is_empty() {
coords.latitude = Some(f64::from_str(latitude)?);
}
if !altitude.is_empty() {
coords.altitude = Some(f64::from_str(altitude)?);
}
if !u.is_empty() {
coords.u = Some(f64::from_str(u)?);
}
if !v.is_empty() {
coords.v = Some(f64::from_str(v)?);
}
}
[longitude, latitude, altitude, roll, pitch, yaw] => {
if !longitude.is_empty() {
coords.longitude = Some(f64::from_str(longitude)?);
}
if !latitude.is_empty() {
coords.latitude = Some(f64::from_str(latitude)?);
}
if !altitude.is_empty() {
coords.altitude = Some(f64::from_str(altitude)?);
}
if !roll.is_empty() {
coords.roll = Some(f64::from_str(roll)?);
}
if !pitch.is_empty() {
coords.pitch = Some(f64::from_str(pitch)?);
}
if !yaw.is_empty() {
coords.yaw = Some(f64::from_str(yaw)?);
}
}
[longitude, latitude, altitude, roll, pitch, yaw, u, v, heading] => {
if !longitude.is_empty() {
coords.longitude = Some(f64::from_str(longitude)?);
}
if !latitude.is_empty() {
coords.latitude = Some(f64::from_str(latitude)?);
}
if !altitude.is_empty() {
coords.altitude = Some(f64::from_str(altitude)?);
}
if !roll.is_empty() {
coords.roll = Some(f64::from_str(roll)?);
}
if !pitch.is_empty() {
coords.pitch = Some(f64::from_str(pitch)?);
}
if !yaw.is_empty() {
coords.yaw = Some(f64::from_str(yaw)?);
}
if !u.is_empty() {
coords.u = Some(f64::from_str(u)?);
}
if !v.is_empty() {
coords.v = Some(f64::from_str(v)?);
}
if !heading.is_empty() {
coords.heading = Some(f64::from_str(heading)?);
}
}
_ => return Err(ParseError::InvalidCoordinateFormat),
}
Ok(coords)
}
}
impl Display for Coords {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let has_orientation = self.yaw.is_some() || self.pitch.is_some() || self.roll.is_some();
let has_uv = self.u.is_some() || self.v.is_some();
if self.heading.is_some() || (has_orientation && has_uv) {
write!(
f,
"{}|{}|{}|{}|{}|{}|{}|{}|{}",
NoneAsEmpty(self.longitude.max_precision(7)),
NoneAsEmpty(self.latitude.max_precision(7)),
NoneAsEmpty(self.altitude.max_precision(2)),
NoneAsEmpty(self.roll.max_precision(1)),
NoneAsEmpty(self.pitch.max_precision(1)),
NoneAsEmpty(self.yaw.max_precision(1)),
NoneAsEmpty(self.u.max_precision(2)),
NoneAsEmpty(self.v.max_precision(2)),
NoneAsEmpty(self.heading.max_precision(1))
)
} else if has_orientation {
write!(
f,
"{}|{}|{}|{}|{}|{}",
NoneAsEmpty(self.longitude.max_precision(7)),
NoneAsEmpty(self.latitude.max_precision(7)),
NoneAsEmpty(self.altitude.max_precision(2)),
NoneAsEmpty(self.roll.max_precision(1)),
NoneAsEmpty(self.pitch.max_precision(1)),
NoneAsEmpty(self.yaw.max_precision(1)),
)
} else if has_uv {
write!(
f,
"{}|{}|{}|{}|{}",
NoneAsEmpty(self.longitude.max_precision(7)),
NoneAsEmpty(self.latitude.max_precision(7)),
NoneAsEmpty(self.altitude.max_precision(2)),
NoneAsEmpty(self.u.max_precision(2)),
NoneAsEmpty(self.v.max_precision(2)),
)
} else {
write!(
f,
"{}|{}|{}",
NoneAsEmpty(self.longitude.max_precision(7)),
NoneAsEmpty(self.latitude.max_precision(7)),
NoneAsEmpty(self.altitude.max_precision(2)),
)
}
}
}
fn join<'a>(iter: impl Iterator<Item = &'a str>, sep: &'a str) -> String {
iter.fold(String::new(), |mut acc, v| {
if !acc.is_empty() {
acc += sep;
}
acc + v
})
}
struct NoneAsEmpty<V>(Option<V>);
impl<V: Display> Display for NoneAsEmpty<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(v) = &self.0 {
v.fmt(f)
} else {
Ok(())
}
}
}
fn to_index(i: u8) -> Cow<'static, str> {
match i {
0 => Cow::Borrowed(""),
1 => Cow::Borrowed("2"),
2 => Cow::Borrowed("3"),
3 => Cow::Borrowed("4"),
4 => Cow::Borrowed("5"),
5 => Cow::Borrowed("6"),
6 => Cow::Borrowed("7"),
7 => Cow::Borrowed("8"),
8 => Cow::Borrowed("9"),
i => Cow::Owned((i + 1).to_string()),
}
}