use core::fmt;
use kinavis_kernel::units::Distance;
use crate::error::AisError;
use crate::fields::value_of;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShipType(u8);
impl ShipType {
#[must_use]
pub const fn from_code(code: u8) -> Option<Self> {
if code == 0 {
None
} else {
Some(Self(code))
}
}
#[must_use]
pub const fn code(self) -> u8 {
self.0
}
#[must_use]
pub const fn category(self) -> ShipCategory {
match self.0 {
20..=29 => ShipCategory::WingInGround,
30 => ShipCategory::Fishing,
31 => ShipCategory::Towing,
32 => ShipCategory::TowingLarge,
33 => ShipCategory::DredgingOrUnderwaterOperations,
34 => ShipCategory::DivingOperations,
35 => ShipCategory::MilitaryOperations,
36 => ShipCategory::Sailing,
37 => ShipCategory::PleasureCraft,
40..=49 => ShipCategory::HighSpeedCraft,
50 => ShipCategory::PilotVessel,
51 => ShipCategory::SearchAndRescue,
52 => ShipCategory::Tug,
53 => ShipCategory::PortTender,
54 => ShipCategory::AntiPollution,
55 => ShipCategory::LawEnforcement,
58 => ShipCategory::MedicalTransport,
59 => ShipCategory::Noncombatant,
60..=69 => ShipCategory::Passenger,
70..=79 => ShipCategory::Cargo,
80..=89 => ShipCategory::Tanker,
90..=99 => ShipCategory::Other,
_ => ShipCategory::Reserved,
}
}
#[must_use]
pub const fn hazard(self) -> Option<HazardCategory> {
let grouped = matches!(self.0, 20..=29 | 40..=49 | 60..=99);
if !grouped {
return None;
}
Some(match self.0 % 10 {
1 => HazardCategory::A,
2 => HazardCategory::B,
3 => HazardCategory::C,
4 => HazardCategory::D,
_ => return None,
})
}
}
impl fmt::Display for ShipType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.category() {
ShipCategory::Reserved => write!(f, "ship type {}", self.0)?,
category => write!(f, "{category}")?,
}
if let Some(hazard) = self.hazard() {
write!(f, ", hazard category {hazard}")?;
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ShipCategory {
WingInGround,
Fishing,
Towing,
TowingLarge,
DredgingOrUnderwaterOperations,
DivingOperations,
MilitaryOperations,
Sailing,
PleasureCraft,
HighSpeedCraft,
PilotVessel,
SearchAndRescue,
Tug,
PortTender,
AntiPollution,
LawEnforcement,
MedicalTransport,
Noncombatant,
Passenger,
Cargo,
Tanker,
Other,
Reserved,
}
impl fmt::Display for ShipCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::WingInGround => "wing in ground",
Self::Fishing => "fishing",
Self::Towing => "towing",
Self::TowingLarge => "towing a large tow",
Self::DredgingOrUnderwaterOperations => "dredging or underwater operations",
Self::DivingOperations => "diving operations",
Self::MilitaryOperations => "military operations",
Self::Sailing => "sailing",
Self::PleasureCraft => "pleasure craft",
Self::HighSpeedCraft => "high speed craft",
Self::PilotVessel => "pilot vessel",
Self::SearchAndRescue => "search and rescue",
Self::Tug => "tug",
Self::PortTender => "port tender",
Self::AntiPollution => "anti-pollution",
Self::LawEnforcement => "law enforcement",
Self::MedicalTransport => "medical transport",
Self::Noncombatant => "noncombatant",
Self::Passenger => "passenger",
Self::Cargo => "cargo",
Self::Tanker => "tanker",
Self::Other => "other",
Self::Reserved => "reserved",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HazardCategory {
A,
B,
C,
D,
}
impl fmt::Display for HazardCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::A => "A",
Self::B => "B",
Self::C => "C",
Self::D => "D",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dimensions {
pub to_bow: Distance,
pub to_stern: Distance,
pub to_port: Distance,
pub to_starboard: Distance,
}
impl Dimensions {
pub(crate) fn from_fields(
to_bow: u32,
to_stern: u32,
to_port: u32,
to_starboard: u32,
) -> Result<Option<Self>, AisError> {
if to_bow == 0 && to_stern == 0 && to_port == 0 && to_starboard == 0 {
return Ok(None);
}
let metres =
|field: u32| Distance::from_metres(f64::from(field)).map_err(value_of("dimensions"));
Ok(Some(Self {
to_bow: metres(to_bow)?,
to_stern: metres(to_stern)?,
to_port: metres(to_port)?,
to_starboard: metres(to_starboard)?,
}))
}
#[must_use]
pub fn length(&self) -> Distance {
self.to_bow + self.to_stern
}
#[must_use]
pub fn beam(&self) -> Distance {
self.to_port + self.to_starboard
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PositionFixingDevice {
Gps,
Glonass,
GpsAndGlonass,
LoranC,
Chayka,
IntegratedNavigationSystem,
Surveyed,
Galileo,
InternalGnss,
Reserved(u8),
}
impl PositionFixingDevice {
#[must_use]
pub const fn from_code(code: u8) -> Option<Self> {
Some(match code {
0 => return None,
1 => Self::Gps,
2 => Self::Glonass,
3 => Self::GpsAndGlonass,
4 => Self::LoranC,
5 => Self::Chayka,
6 => Self::IntegratedNavigationSystem,
7 => Self::Surveyed,
8 => Self::Galileo,
15 => Self::InternalGnss,
_ => Self::Reserved(code),
})
}
#[must_use]
pub const fn code(self) -> u8 {
match self {
Self::Gps => 1,
Self::Glonass => 2,
Self::GpsAndGlonass => 3,
Self::LoranC => 4,
Self::Chayka => 5,
Self::IntegratedNavigationSystem => 6,
Self::Surveyed => 7,
Self::Galileo => 8,
Self::InternalGnss => 15,
Self::Reserved(code) => code,
}
}
}
impl fmt::Display for PositionFixingDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Gps => f.write_str("GPS"),
Self::Glonass => f.write_str("GLONASS"),
Self::GpsAndGlonass => f.write_str("GPS and GLONASS"),
Self::LoranC => f.write_str("Loran-C"),
Self::Chayka => f.write_str("Chayka"),
Self::IntegratedNavigationSystem => f.write_str("integrated navigation system"),
Self::Surveyed => f.write_str("surveyed"),
Self::Galileo => f.write_str("Galileo"),
Self::InternalGnss => f.write_str("internal GNSS"),
Self::Reserved(code) => write!(f, "reserved device {code}"),
}
}
}