1use core::fmt;
5
6use kinavis_kernel::event::TargetId;
7use kinavis_kernel::position::Position;
8use kinavis_kernel::InlineStr;
9
10use crate::bits::Bits;
11use crate::error::AisError;
12use crate::fields::{byte, position, second, Fields};
13use crate::vessel::{Dimensions, PositionFixingDevice};
14
15const NAME_CHARS: usize = 20;
18const EXTENSION_CHARS: usize = 14;
19const ALL_NAME_CHARS: usize = NAME_CHARS + EXTENSION_CHARS;
20
21#[allow(clippy::struct_excessive_bools)]
24#[derive(Debug, Clone, Copy, PartialEq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct AidToNavigation {
27 pub mmsi: TargetId,
29 pub aid_type: Option<AidType>,
31 pub name: Option<InlineStr<ALL_NAME_CHARS>>,
33 pub accurate: bool,
35 pub position: Option<Position>,
37 pub dimensions: Option<Dimensions>,
39 pub fixing_device: Option<PositionFixingDevice>,
41 pub second: Option<u8>,
44 pub off_position: Option<bool>,
47 pub regional: u8,
49 pub raim: bool,
51 pub is_virtual: bool,
53 pub assigned: bool,
55}
56
57impl AidToNavigation {
58 const NEEDED: usize = 272;
60
61 pub(crate) fn decode(bits: &Bits) -> Result<Self, AisError> {
62 let field = Fields::of(bits, Self::NEEDED)?;
63 let second = second(field.unsigned(253, 6));
64 Ok(Self {
65 mmsi: TargetId::new(field.unsigned(8, 30)),
66 aid_type: AidType::from_code(byte(field.unsigned(38, 5))),
67 name: name(&field),
68 accurate: field.bit(163),
69 position: position(field.signed(164, 28), field.signed(192, 27))?,
70 dimensions: Dimensions::from_fields(
71 field.unsigned(219, 9),
72 field.unsigned(228, 9),
73 field.unsigned(237, 6),
74 field.unsigned(243, 6),
75 )?,
76 fixing_device: PositionFixingDevice::from_code(byte(field.unsigned(249, 4))),
77 second,
78 off_position: second.map(|_| field.bit(259)),
79 regional: byte(field.unsigned(260, 8)),
80 raim: field.bit(268),
81 is_virtual: field.bit(269),
82 assigned: field.bit(270),
83 })
84 }
85}
86
87fn name(field: &Fields<'_>) -> Option<InlineStr<ALL_NAME_CHARS>> {
89 let extension = ((field.len() - AidToNavigation::NEEDED) / 6).min(EXTENSION_CHARS);
90 let in_field = (0..NAME_CHARS).map(|index| 43 + index * 6);
91 let in_extension = (0..extension).map(|index| AidToNavigation::NEEDED + index * 6);
92 field.text_at(in_field.chain(in_extension))
93}
94
95#[non_exhaustive]
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub enum AidType {
102 ReferencePoint,
104 Racon,
106 FixedStructureOffshore,
108 Light {
110 sectored: bool,
112 },
113 LeadingLight {
115 rear: bool,
117 },
118 Beacon(Mark),
120 Floating(Mark),
122 LightVessel,
124 Reserved(u8),
126}
127
128impl AidType {
129 #[must_use]
131 pub const fn from_code(code: u8) -> Option<Self> {
132 Some(match code {
133 0 => return None,
134 1 => Self::ReferencePoint,
135 2 => Self::Racon,
136 3 => Self::FixedStructureOffshore,
137 5 => Self::Light { sectored: false },
138 6 => Self::Light { sectored: true },
139 7 => Self::LeadingLight { rear: false },
140 8 => Self::LeadingLight { rear: true },
141 9..=19 => Self::Beacon(Mark::from_offset(code - 9)),
142 20..=30 => Self::Floating(Mark::from_offset(code - 20)),
143 31 => Self::LightVessel,
144 _ => Self::Reserved(code),
145 })
146 }
147
148 #[must_use]
150 pub const fn code(self) -> u8 {
151 match self {
152 Self::ReferencePoint => 1,
153 Self::Racon => 2,
154 Self::FixedStructureOffshore => 3,
155 Self::Light { sectored: false } => 5,
156 Self::Light { sectored: true } => 6,
157 Self::LeadingLight { rear: false } => 7,
158 Self::LeadingLight { rear: true } => 8,
159 Self::Beacon(mark) => 9 + mark.offset(),
160 Self::Floating(mark) => 20 + mark.offset(),
161 Self::LightVessel => 31,
162 Self::Reserved(code) => code,
163 }
164 }
165}
166
167impl fmt::Display for AidType {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 Self::ReferencePoint => f.write_str("reference point"),
171 Self::Racon => f.write_str("RACON"),
172 Self::FixedStructureOffshore => f.write_str("fixed structure off shore"),
173 Self::Light { sectored: false } => f.write_str("light without sectors"),
174 Self::Light { sectored: true } => f.write_str("light with sectors"),
175 Self::LeadingLight { rear: false } => f.write_str("leading light, front"),
176 Self::LeadingLight { rear: true } => f.write_str("leading light, rear"),
177 Self::Beacon(mark) => write!(f, "beacon, {mark}"),
178 Self::Floating(mark) => write!(f, "floating mark, {mark}"),
179 Self::LightVessel => f.write_str("light vessel"),
180 Self::Reserved(code) => write!(f, "reserved aid type {code}"),
181 }
182 }
183}
184
185#[non_exhaustive]
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub enum Mark {
192 Cardinal(Quadrant),
194 PortHand,
196 StarboardHand,
198 PreferredChannelPortHand,
200 PreferredChannelStarboardHand,
202 IsolatedDanger,
204 SafeWater,
206 Special,
208}
209
210impl Mark {
211 const fn from_offset(offset: u8) -> Self {
213 match offset {
214 0 => Self::Cardinal(Quadrant::North),
215 1 => Self::Cardinal(Quadrant::East),
216 2 => Self::Cardinal(Quadrant::South),
217 3 => Self::Cardinal(Quadrant::West),
218 4 => Self::PortHand,
219 5 => Self::StarboardHand,
220 6 => Self::PreferredChannelPortHand,
221 7 => Self::PreferredChannelStarboardHand,
222 8 => Self::IsolatedDanger,
223 9 => Self::SafeWater,
224 _ => Self::Special,
225 }
226 }
227
228 const fn offset(self) -> u8 {
229 match self {
230 Self::Cardinal(Quadrant::North) => 0,
231 Self::Cardinal(Quadrant::East) => 1,
232 Self::Cardinal(Quadrant::South) => 2,
233 Self::Cardinal(Quadrant::West) => 3,
234 Self::PortHand => 4,
235 Self::StarboardHand => 5,
236 Self::PreferredChannelPortHand => 6,
237 Self::PreferredChannelStarboardHand => 7,
238 Self::IsolatedDanger => 8,
239 Self::SafeWater => 9,
240 Self::Special => 10,
241 }
242 }
243}
244
245impl fmt::Display for Mark {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 match self {
248 Self::Cardinal(quadrant) => write!(f, "cardinal {quadrant}"),
249 Self::PortHand => f.write_str("port hand"),
250 Self::StarboardHand => f.write_str("starboard hand"),
251 Self::PreferredChannelPortHand => f.write_str("preferred channel, port hand"),
252 Self::PreferredChannelStarboardHand => f.write_str("preferred channel, starboard hand"),
253 Self::IsolatedDanger => f.write_str("isolated danger"),
254 Self::SafeWater => f.write_str("safe water"),
255 Self::Special => f.write_str("special"),
256 }
257 }
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
262#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
263pub enum Quadrant {
264 North,
266 East,
268 South,
270 West,
272}
273
274impl fmt::Display for Quadrant {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.write_str(match self {
277 Self::North => "north",
278 Self::East => "east",
279 Self::South => "south",
280 Self::West => "west",
281 })
282 }
283}