Transponders
Transponder message decoding: AIS (Automatic Identification System).
Transponder message decoding: AIS (Automatic Identification System).
Named after the MATLAB TCL’s Transponders/ directory, whose
decodeAISString wraps libais; pytcl.transponders.ais plays the
same role here over pyais (the ais extra). Importing this package never
requires pyais to be installed – pyais is imported lazily inside
decode_ais / ais_position_reports, raising
DependencyError if it is missing when one of
them is actually called.
Examples
>>> import pytcl.transponders as transponders
>>> vdm = "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C"
>>> msgs = transponders.decode_ais(vdm)
>>> msgs[0].msg_type
1
AIS
AIS NMEA sentence decoding and position-report extraction via pyais
(ais extra).
AIS NMEA decoding via pyais, with position-report extraction.
pyais is an optional dependency (the ais extra). It is imported lazily
inside _import_pyais, so importing this module never requires pyais to be
installed; calling decode_ais or ais_position_reports without it raises
DependencyError. This mirrors the guard
pattern in pytcl.io.dataframes / pytcl.io.readers.
This is the Python port’s counterpart to the MATLAB TCL’s
Transponders/decodeAISString, which wraps libais; here pyais plays that
role.
Sentinel handling (ITU-R M.1371) is applied in ais_position_reports rather than left to pyais: empirically, pyais 3.2.1 does not normalize “not available” sentinels itself – it returns them unchanged (lat 91 deg, lon 181 deg, speed-over-ground 102.3 kn (1023 decideci-knots), course-over-ground 360.0 deg (3600 decidegrees), heading 511) – so this module detects the raw sentinel values coming back from pyais and converts each to NaN.
- pytcl.transponders.ais.nmea_checksum(sentence)[source]
Compute an NMEA sentence’s
*hhchecksum.The checksum is the XOR of every character strictly between the leading
!/$and the trailing*, rendered as two uppercase hex digits.- Parameters:
sentence (str) – A full sentence, with or without its trailing
*hh.- Returns:
Two uppercase hex digits.
- Return type:
Examples
>>> from pytcl.transponders.ais import nmea_checksum >>> nmea_checksum("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C") '5C'
- class pytcl.transponders.ais.AISMessage(msg_type, mmsi, fields)[source]
Bases:
NamedTupleOne decoded AIS message.
- Variables:
msg_type (int) – ITU-R M.1371 message type (1-27).
mmsi (int) – Maritime Mobile Service Identity of the transmitting station.
fields (dict) – The pyais payload, normalized via its own
asdict()– every field the message type carries, including type-specific ones (e.g.shipnamefor type 5) not surfaced by PositionReports.
- class pytcl.transponders.ais.PositionReports(mmsi, t, lat, lon, sog, cog, heading)[source]
Bases:
NamedTuplePosition reports (msg types 1, 2, 3, 18, 19) as parallel arrays.
- Variables:
mmsi (ndarray of int64, shape (n,))
t (ndarray of float64, shape (n,)) – Receiver timestamp per report, from times when given to ais_position_reports; NaN otherwise.
lat (ndarray of float64, shape (n,)) – Latitude, radians. NaN where pyais reports the ITU-R M.1371 “not available” sentinel (91 deg).
lon (ndarray of float64, shape (n,)) – Longitude, radians. NaN where pyais reports the sentinel (181 deg).
sog (ndarray of float64, shape (n,)) – Speed over ground, m/s (pyais reports knots; converted here). NaN where pyais reports the sentinel (102.3 kn).
cog (ndarray of float64, shape (n,)) – Course over ground, radians. NaN where pyais reports the sentinel (360.0 deg).
heading (ndarray of float64, shape (n,)) – True heading, radians. NaN where pyais reports the sentinel (511).
- pytcl.transponders.ais.decode_ais(nmea_text, validate_checksum=True)[source]
Decode AIS NMEA sentences (one or more, newline-separated) to messages.
Multipart messages (e.g. type 5, split across two
!AIVDMsentences with the same sequence id) are reassembled automatically – pyais’s own ~pyais.stream.IterMessages groups fragments by sequence id, channel, talker and fragment count before assembling and decoding them, so a fragment is only turned into an AISMessage once every part of it has arrived.Lines that are not valid AIS sentences, or whose payload pyais cannot decode (e.g. an unsupported message type), are skipped rather than raising – this is a batch decode over potentially noisy logs, not a single-message parse. The number skipped is logged at DEBUG (site
"transponders") when diagnostics are enabled.- Parameters:
nmea_text (str) – One or more
!AIVDM/!AIVDOsentences, one per line.validate_checksum (bool, optional) – Reject sentences whose trailing
*hhdoes not match the XOR of their body, and sentences carrying no*hhat all. Default True. Rejected lines are skipped and counted like any other undecodable line. Set False only to ingest a feed known to carry bad checksums, accepting that a corrupted position report may decode to a plausible but wrong latitude and longitude.
- Returns:
One entry per successfully decoded (and, where applicable, reassembled) message, in the order completed.
- Return type:
list of AISMessage
- Raises:
DependencyError – If pyais is not installed.
Examples
>>> from pytcl.transponders.ais import decode_ais >>> vdm = "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C" >>> msgs = decode_ais(vdm) >>> msgs[0].msg_type 1 >>> msgs[0].mmsi 366053209
- pytcl.transponders.ais.ais_position_reports(nmea_text_or_messages, times=None, validate_checksum=True)[source]
Extract position reports (types 1, 2, 3, 18, 19) as parallel arrays.
- Parameters:
nmea_text_or_messages (str or sequence of AISMessage) – Either raw NMEA text (decoded internally via decode_ais) or an already-decoded message list, e.g. from a prior decode_ais call.
times (sequence of float, optional) – One receiver timestamp per entry of nmea_text_or_messages (after decoding, if text was given) –
times[i]is the timestamp for the i-th decoded message, not the i-th position report. When given, its length must equal the number of decoded messages. Entries whose message is not a position report are dropped along with that message. When omitted, PositionReports.t is all NaN.validate_checksum (bool, optional) – Forwarded to decode_ais when raw text is given; ignored when an already-decoded message list is passed. Default True.
- Returns:
One row per position-report message, in decode order. Units: lat/lon/cog/heading radians, sog m/s.
- Return type:
- Raises:
ValueError – If times is given and its length does not match the number of decoded messages.
DependencyError – If pyais is not installed.
Examples
>>> from pytcl.transponders.ais import ais_position_reports >>> vdm = "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C" >>> rep = ais_position_reports(vdm) >>> rep.mmsi[0] 366053209 >>> bool(rep.lat[0] > 0) # northern hemisphere True