nmea_parser/ais/
vdm_t21.rs

1/*
2Copyright 2020 Timo Saarinen
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use super::*;
18
19// -------------------------------------------------------------------------------------------------
20
21/// Type 21: Aid-to-Navigation Report
22#[derive(Default, Clone, Debug, PartialEq)]
23pub struct AidToNavigationReport {
24    /// True if the data is about own vessel, false if about other.
25    pub own_vessel: bool,
26
27    /// AIS station type.
28    pub station: Station,
29
30    /// User ID (30 bits)
31    pub mmsi: u32,
32
33    /// Aid type (5 bits)
34    pub aid_type: NavAidType,
35
36    /// Name (120 bits)
37    pub name: String,
38
39    /// Position accuracy.
40    high_position_accuracy: bool,
41
42    /// Latitude
43    pub latitude: Option<f64>,
44
45    /// Longitude
46    pub longitude: Option<f64>,
47
48    /// Overall dimension / reference for position A (9 bits)
49    pub dimension_to_bow: Option<u16>,
50    /// Overall dimension / reference for position B (9 bits)
51    pub dimension_to_stern: Option<u16>,
52    /// Overall dimension / reference for position C (6 bits)
53    pub dimension_to_port: Option<u16>,
54    /// Overall dimension / reference for position C (6 bits)
55    pub dimension_to_starboard: Option<u16>,
56
57    // Type of electronic position fixing device.
58    pub position_fix_type: Option<PositionFixType>,
59
60    /// Derived from UTC second (6 bits)
61    pub timestamp_seconds: u8,
62
63    /// Off-position indicator (1 bit):
64    /// true = off position, false = on position
65    pub off_position_indicator: bool,
66
67    /// Regional reserved, uninterpreted.
68    pub regional: u8,
69
70    /// Riverine And Inland Navigation systems blue sign:
71    /// RAIM (Receiver autonomous integrity monitoring) flag of electronic position
72    /// fixing device; false = RAIM not in use = default; true = RAIM in use
73    pub raim_flag: bool,
74
75    /// Virtual aid flag:
76    /// true = virtual aid to navigation simulated by nearby AIS station
77    /// false = real aid to navigation at indicated position
78    pub virtual_aid_flag: bool,
79
80    /// Assigned-mode flag
81    pub assigned_mode_flag: bool,
82}
83
84impl LatLon for AidToNavigationReport {
85    fn latitude(&self) -> Option<f64> {
86        self.latitude
87    }
88
89    fn longitude(&self) -> Option<f64> {
90        self.longitude
91    }
92}
93
94/// Type of navigation aid
95#[derive(Clone, Copy, Debug, PartialEq)]
96pub enum NavAidType {
97    /// Default, type not specified
98    NotSpecified, // 0
99
100    /// Reference point
101    ReferencePoint, // 1
102
103    /// RACON (radar transponder marking a navigation hazard)
104    Racon, // 2
105
106    /// Fixed structure off shore
107    FixedStructure, // 3
108
109    /// Reserved for future use.
110    Reserved4, // 4
111
112    /// Light without sectors
113    LightWithoutSectors, // 5
114
115    /// Light with sectors
116    LightWithSectors, // 6
117
118    /// Leading light front
119    LeadingLightFront, // 7
120
121    /// Leading light rear
122    LeadingLightRear, // 8
123
124    /// Beacon, Cardinal North
125    BeaconCardinalNorth, // 9
126
127    /// Beacon, Cardinal East
128    BeaconCardinalEast, // 10
129
130    /// Beacon, Cardinal South
131    BeaconCardinalSouth, // 11
132
133    /// Beacon, Cardinal West
134    BeaconCardinalWest, // 12
135
136    /// Beacon, Port
137    BeaconLateralPort, // 13
138
139    /// Beacon, Starboard
140    BeaconLateralStarboard, // 14
141
142    /// Beacon, preferred channel port
143    BeaconLateralPreferredChannelPort, // 15
144
145    /// Beacon, preferred channel starboard
146    BeaconLateralPreferredChannelStarboard, // 16
147
148    /// Beacon, isolated danger
149    BeaconIsolatedDanger, // 17
150
151    /// Beacon, safe water
152    BeaconSafeWater, // 18
153
154    /// Beacon, special mark
155    BeaconSpecialMark, // 19
156
157    /// Cardinal Mark, north
158    CardinalMarkNorth, // 20
159
160    /// Cardinal Mark, east
161    CardinalMarkEast, // 21
162
163    /// Cardinal Mark, south
164    CardinalMarkSouth, // 22
165
166    /// Cardinal Mark, west
167    CardinalMarkWest, // 23
168
169    /// Port hand mark
170    PortHandMark, // 24
171
172    /// Starboard hand mark
173    StarboardHandMark, // 25
174
175    /// Preferred channel, port
176    PreferredChannelPort, // 26
177
178    /// Preferred channel, starboard
179    PreferredChannelStarboard, // 27
180
181    /// Isolated danger
182    IsolatedDanger, // 28
183
184    /// Safe Water
185    SafeWater, // 29
186
187    /// Special mark
188    SpecialMark, // 30
189
190    /// Light vessel / LANBY / rigs
191    LightVessel, // 31
192}
193
194impl NavAidType {
195    fn new(raw: u8) -> Result<NavAidType, ParseError> {
196        match raw {
197            0 => Ok(NavAidType::NotSpecified),
198            1 => Ok(NavAidType::ReferencePoint),
199            2 => Ok(NavAidType::Racon),
200            3 => Ok(NavAidType::FixedStructure),
201            4 => Ok(NavAidType::Reserved4),
202            5 => Ok(NavAidType::LightWithoutSectors),
203            6 => Ok(NavAidType::LightWithSectors),
204            7 => Ok(NavAidType::LeadingLightFront),
205            8 => Ok(NavAidType::LeadingLightRear),
206            9 => Ok(NavAidType::BeaconCardinalNorth),
207            10 => Ok(NavAidType::BeaconCardinalEast),
208            11 => Ok(NavAidType::BeaconCardinalSouth),
209            12 => Ok(NavAidType::BeaconCardinalWest),
210            13 => Ok(NavAidType::BeaconLateralPort),
211            14 => Ok(NavAidType::BeaconLateralStarboard),
212            15 => Ok(NavAidType::BeaconLateralPreferredChannelPort),
213            16 => Ok(NavAidType::BeaconLateralPreferredChannelStarboard),
214            17 => Ok(NavAidType::BeaconIsolatedDanger),
215            18 => Ok(NavAidType::BeaconSafeWater),
216            19 => Ok(NavAidType::BeaconSpecialMark),
217            20 => Ok(NavAidType::CardinalMarkNorth),
218            21 => Ok(NavAidType::CardinalMarkEast),
219            22 => Ok(NavAidType::CardinalMarkSouth),
220            23 => Ok(NavAidType::CardinalMarkWest),
221            24 => Ok(NavAidType::PortHandMark),
222            25 => Ok(NavAidType::StarboardHandMark),
223            26 => Ok(NavAidType::PreferredChannelPort),
224            27 => Ok(NavAidType::PreferredChannelStarboard),
225            28 => Ok(NavAidType::IsolatedDanger),
226            29 => Ok(NavAidType::SafeWater),
227            30 => Ok(NavAidType::SpecialMark),
228            31 => Ok(NavAidType::LightVessel),
229            _ => Err(format!("Unrecognized Nav aid type code: {}", raw).into()),
230        }
231    }
232}
233
234impl Default for NavAidType {
235    fn default() -> NavAidType {
236        NavAidType::NotSpecified
237    }
238}
239
240impl core::fmt::Display for NavAidType {
241    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
242        match self {
243            NavAidType::NotSpecified => write!(f, "not specified"),
244            NavAidType::ReferencePoint => write!(f, "reference point"),
245            NavAidType::Racon => write!(f, "RACON"),
246            NavAidType::FixedStructure => write!(f, "FixedStructure"),
247            NavAidType::Reserved4 => write!(f, "(reserved)"),
248            NavAidType::LightWithoutSectors => write!(f, "light without sectors"),
249            NavAidType::LightWithSectors => write!(f, "light with sectors"),
250            NavAidType::LeadingLightFront => write!(f, "leading light front"),
251            NavAidType::LeadingLightRear => write!(f, "leading light rear"),
252            NavAidType::BeaconCardinalNorth => write!(f, "cardinal beacon, north"),
253            NavAidType::BeaconCardinalEast => write!(f, "cardinal beacon, east"),
254            NavAidType::BeaconCardinalSouth => write!(f, "cardinal beacon, south"),
255            NavAidType::BeaconCardinalWest => write!(f, "cardinal beacon, west"),
256            NavAidType::BeaconLateralPort => write!(f, "lateral beacon, port side"),
257            NavAidType::BeaconLateralStarboard => write!(f, "lateral beacon, starboard side"),
258            NavAidType::BeaconLateralPreferredChannelPort => {
259                write!(f, "lateral beacon, preferred channel, port side")
260            }
261            NavAidType::BeaconLateralPreferredChannelStarboard => {
262                write!(f, "lateral beacon, preferred channel, starboard side")
263            }
264            NavAidType::BeaconIsolatedDanger => write!(f, "isolated danger beacon"),
265            NavAidType::BeaconSafeWater => write!(f, "safe water"),
266            NavAidType::BeaconSpecialMark => write!(f, "special mark"),
267            NavAidType::CardinalMarkNorth => write!(f, "cardinal mark, north"),
268            NavAidType::CardinalMarkEast => write!(f, "cardinal mark, east"),
269            NavAidType::CardinalMarkSouth => write!(f, "cardinal mark, south"),
270            NavAidType::CardinalMarkWest => write!(f, "cardinal mark, west"),
271            NavAidType::PortHandMark => write!(f, "port hand mark"),
272            NavAidType::StarboardHandMark => write!(f, "starboard hand mark"),
273            NavAidType::PreferredChannelPort => write!(f, "preferred channel, port side"),
274            NavAidType::PreferredChannelStarboard => write!(f, "preferred channel, starboard side"),
275            NavAidType::IsolatedDanger => write!(f, "isolated danger"),
276            NavAidType::SafeWater => write!(f, "safe water"),
277            NavAidType::SpecialMark => write!(f, "special mark"),
278            NavAidType::LightVessel => write!(f, "light vessel"),
279        }
280    }
281}
282
283// -------------------------------------------------------------------------------------------------
284
285/// AIS VDM/VDO type 21: Aid-to-Navigation Report
286pub(crate) fn handle(
287    bv: &BitVec,
288    station: Station,
289    own_vessel: bool,
290) -> Result<ParsedMessage, ParseError> {
291    Ok(ParsedMessage::AidToNavigationReport(
292        AidToNavigationReport {
293            own_vessel: { own_vessel },
294            station: { station },
295            mmsi: { pick_u64(bv, 8, 30) as u32 },
296            aid_type: {
297                NavAidType::new(pick_u64(bv, 38, 5) as u8)
298                    .ok()
299                    .unwrap_or(NavAidType::NotSpecified)
300            },
301            name: {
302                let mut s = pick_string(bv, 43, 20);
303                s.push_str(&pick_string(bv, 272, 14));
304                s
305            },
306            high_position_accuracy: { pick_u64(bv, 163, 1) != 0 },
307            latitude: {
308                let lat_raw = pick_i64(bv, 192, 27) as i32;
309                if lat_raw != 0x3412140 {
310                    Some((lat_raw as f64) / 600000.0)
311                } else {
312                    None
313                }
314            },
315            longitude: {
316                let lon_raw = pick_i64(bv, 164, 28) as i32;
317                if lon_raw != 0x6791AC0 {
318                    Some((lon_raw as f64) / 600000.0)
319                } else {
320                    None
321                }
322            },
323            dimension_to_bow: { Some(pick_u64(bv, 219, 9) as u16) },
324            dimension_to_stern: { Some(pick_u64(bv, 228, 9) as u16) },
325            dimension_to_port: { Some(pick_u64(bv, 237, 6) as u16) },
326            dimension_to_starboard: { Some(pick_u64(bv, 243, 6) as u16) },
327            position_fix_type: { Some(PositionFixType::new(pick_u64(bv, 249, 4) as u8)) },
328            timestamp_seconds: { pick_u64(bv, 253, 6) as u8 },
329            off_position_indicator: { pick_u64(bv, 243, 1) != 0 },
330            regional: { pick_u64(bv, 260, 8) as u8 },
331            raim_flag: { pick_u64(bv, 268, 1) != 0 },
332            virtual_aid_flag: { pick_u64(bv, 269, 1) != 0 },
333            assigned_mode_flag: { pick_u64(bv, 270, 1) != 0 },
334        },
335    ))
336}
337
338// -------------------------------------------------------------------------------------------------
339
340#[cfg(test)]
341mod test {
342    use super::*;
343
344    #[test]
345    fn test_parse_vdm_type21() {
346        let mut p = NmeaParser::new();
347        match p.parse_sentence("!AIVDM,2,1,5,B,E1mg=5J1T4W0h97aRh6ba84<h2d;W:Te=eLvH50```q,0*46") {
348            Ok(ps) => match ps {
349                ParsedMessage::Incomplete => {
350                    assert!(true);
351                }
352                _ => {
353                    assert!(false);
354                }
355            },
356            Err(e) => {
357                assert_eq!(e.to_string(), "OK");
358            }
359        }
360
361        match p.parse_sentence("!AIVDM,2,2,5,B,:D44QDlp0C1DU00,2*36") {
362            Ok(ps) => {
363                match ps {
364                    // The expected result
365                    ParsedMessage::AidToNavigationReport(atnr) => {
366                        assert_eq!(atnr.mmsi, 123456789);
367                        assert_eq!(atnr.aid_type, NavAidType::CardinalMarkNorth);
368                        assert_eq!(atnr.name, "CHINA ROSE MURPHY EXPRESS ALERT");
369                        assert!(!atnr.high_position_accuracy);
370                        assert::close(atnr.latitude.unwrap_or(0.0), 47.9206183333, 0.00000001);
371                        assert::close(atnr.longitude.unwrap_or(0.0), -122.698591667, 0.00000001);
372                        assert_eq!(atnr.dimension_to_bow, Some(5));
373                        assert_eq!(atnr.dimension_to_stern, Some(5));
374                        assert_eq!(atnr.dimension_to_port, Some(5));
375                        assert_eq!(atnr.dimension_to_starboard, Some(5));
376                        assert_eq!(atnr.position_fix_type, Some(PositionFixType::GPS));
377                        assert_eq!(atnr.timestamp_seconds, 50);
378                        assert!(!atnr.off_position_indicator);
379                        assert_eq!(atnr.regional, 165);
380                        assert!(!atnr.raim_flag);
381                        assert!(!atnr.virtual_aid_flag);
382                        assert!(!atnr.assigned_mode_flag);
383                    }
384                    ParsedMessage::Incomplete => {
385                        assert!(false);
386                    }
387                    _ => {
388                        assert!(false);
389                    }
390                }
391            }
392            Err(e) => {
393                assert_eq!(e.to_string(), "OK");
394            }
395        }
396    }
397}