AIS-catcher style vessel styling: ship-class colors & icons, callsign prefixes, underway-only filter - #15
Merged
Merged
Conversation
…prefixes, underway filter
New aiscot.shipclass module re-implements AIS-catcher's ship
classification and marker palette from its published behavior (mapping
tables and hex colors only; the marker shapes are redrawn from scratch,
so nothing GPL-licensed is copied):
- SHIPCLASS_COLORS (default True): CoT <color argb> by ship class -
tankers red, cargo spring green, passenger blue, special brown, HSC
yellow, fishing deep pink, sailing/pleasure magenta, other azure.
MMSI rules (SART/AtoN/base station) outrank ship type.
- SHIPCLASS_ICONS (default False): <usericon> from the new bundled
ais-ships-iconset.zip ATAK iconset - dart underway, circle stopped,
diamond for AtoN/base station/SART. COT_ICON still wins. Regenerate
with scripts/build_ais_iconset.py (pure stdlib PNG writer).
- VESSEL_NAME_PREFIX (default True): conventional ship-type callsign
prefixes the way mariners name traffic - T/B Delores, P/V Golden
Gate, M/V, M/T, F/V, S/V, SAR, A/P, L/E. Already-prefixed names and
bare-MMSI callsigns pass verbatim; embedded slashless prefixes
("PV GOLDEN GATE") are normalized instead of stacked.
- UNDERWAY_ONLY (default False): drop parked hulls (SOG < 0.5 kts, or
anchored/moored/aground when SOG is missing) so anchorage clutter
doesn't drown the traffic picture. SOG outranks nav status - crews
leave "Underway" set at the dock and "Moored" set while sailing.
AtoN and USCG SAR/CRS are exempt; vessels reporting neither SOG nor
nav status pass through.
Ship type extraction deliberately ignores the lowercase "type" key,
which is the AIS message type in NMEA-decoded (pyAISm) data, not a
ship type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 tasks
Review fixes for the vessel-styling PR:
- UNDERWAY_ONLY exemptions: never drop SART/EPIRB/MOB distress beacons
(97x MMSIs, drifting at SOG ~0) or operator watch-listed KNOWN_CRAFT
vessels.
- SOG units are now per-feed: pyAISm "speed" is raw 0.1-knot AIS units,
AISHub (format=1) / SeaVision SPEED/SOG are plain knots. Previously
API-feed speeds were 10x too low, so UNDERWAY_ONLY dropped anything
under ~5 knots (and CoT track speeds were wrong).
- Honor the AIS "SOG not available" sentinel (raw 1023 / 102.3 kts):
fall through to nav status instead of reading it as 52.6 m/s.
- Cache Type 5/24 Static & Voyage data per MMSI in the NMEA receiver
and fold it into position reports: ship-class colors and name
prefixes now work on RF feeds, not just API feeds.
- VESSEL_NAME_PREFIX no longer rewrites operator-curated KNOWN_CRAFT
names.
- ais_to_cot track speed and remarks now use the shipclass extractors
(lowercase "type" — the AIS message type — no longer leaks into the
Type remark).
- IGNORE_ATON parses booleans via cfg_bool ("False" was truthy).
- Cleanups: flags parsed once per message, precomputed SHIPCLASS_ARGB,
shared DIAMOND_SHIPCLASSES, MANIFEST.in dead lines removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVibvSyvPsUXRXRYaFsWjY
ampledata
commented
Jul 14, 2026
ampledata
left a comment
Collaborator
Author
There was a problem hiding this comment.
Reviewed for correctness & security — LGTM overall (self-authored, so posting as comment rather than approval). Well-factored: shipclass is pure-function with solid test coverage, UNDERWAY_ONLY is opt-in with sane exemptions, and all AIS-derived strings flow through ElementTree escaping (no injection surface; iconsetpath is built from constants only).
Worth fixing before/at merge:
tests/test_functions.pyhas twodef test_ais_to_cot(sample_data_pyAISm)definitions (119 & 256); the later shadows the earlier, so the earlier one — including the speed assertion this PR updates at :138 — never executes. Rename one (flake8 F811 catches it).shipclass.py:817:970000000 <= num <= 980000000— the inclusive upper bound sweeps the first craft-associated-with-parent-ship MMSI (98xxxxxxx) plus unassigned 975–979 blocks into the SART/EPIRB distress class. Cosmetic consequence (red diamond + underway exemption), but the bound should end at the 974 block.shipclass.py:822: base-station rule0 < num < 9000000misses coast stations 9000000–9999999 (the 00MID block spans everything below 10000000).functions.py:177-183: the remarksType:extraction checks a narrower key set thanshipclass._SHIPTYPE_KEYS(missesship_type/SHIPTYPE/cargo) — a feed supplying only those gets classified/colored but shows no Type in remarks. Reuse theshiptypevalue computed the line above.
Nits: get_nav_status treats non-numeric statuses (e.g. "Moored" as text) as underway, silently fail-open; the T/B prefix normalization can rewrite a vessel genuinely named "TB ..." — both acceptable heuristics, worth a comment.
🤖 Generated with Claude Code
- SART/EPIRB class now stops at the 974 block (975+ unassigned, 98x is craft-associated-with-parent-ship) - Coast-station rule covers the full 00MIDxxxx block (< 10000000) - Remarks Type: falls back to the shipclass shiptype keys (ship_type/ SHIPTYPE/cargo feeds) - Renamed the second test_ais_to_cot to test_cot_to_xml so the first one (shadowed, never ran) executes again Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DVibvSyvPsUXRXRYaFsWjY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Upstreams the AIS visualization enhancements we built for DroneCase into AISCOT proper. Everything is driven by four new config parameters; existing deployments keep working (the only default-on changes are cosmetic).
Ship-class marker colors —
SHIPCLASS_COLORS(defaultTrue)New
aiscot.shipclassmodule collapses MMSI + AIS ship type into an AIS-catcher ship class and emits the matching CoT<color argb>: tankers red, cargo spring green, passenger blue, special craft (pilot/SAR/tug/law) brown, HSC yellow, fishing deep pink, sailing/pleasure magenta, everything else light azure. MMSI rules (SART / AtoN / base station) outrank ship type, same precedence as AIS-catcher. This is the de-facto standard AIS-viewer palette, and it works on any TAK client with no iconset import.Licensing note: only the mapping tables and hex colors are re-implemented from AIS-catcher's published behavior; the marker artwork is redrawn from scratch (see below). Nothing GPL-licensed is copied.
Bundled ATAK iconset —
SHIPCLASS_ICONS(defaultFalse)src/aiscot/data/ais-ships-iconset.zip(9 KB, ships with the pip/deb package) contains 19 redrawn 32×32 markers: solid dart when underway, circle when stopped, diamond for AtoN / base station / SART, colored with the ship-class palette. When enabled, every vessel gets a<usericon iconsetpath>into the set;COT_ICONstill takes precedence. Opt-in because clients must import the zip first (ATAK Settings → Tool Preferences → Point Dropper → Iconset Manager). Regenerate withscripts/build_ais_iconset.py(pure stdlib PNG writer, no Pillow).Conventional callsign prefixes —
VESSEL_NAME_PREFIX(defaultTrue)Vessel callsigns get the conventional ship-type prefix, the way mariners name traffic:
T/B Delores,P/V Golden Gate,M/V,M/T,F/V,S/V,SAR,A/P,L/E. Already-prefixed names pass verbatim, embedded slashless prefixes (PV GOLDEN GATE) get normalized instead of stacked, and bare-MMSI callsigns stay bare.Underway-only filtering —
UNDERWAY_ONLY(defaultFalse)Drops parked hulls (SOG < 0.5 kts, or anchored/moored/aground when SOG is missing) so anchorage/marina clutter doesn't drown the underway traffic picture. SOG outranks nav status — crews leave "Underway" set at the dock and "Moored" set while sailing. AtoN and USCG SAR/CRS are exempt, and vessels reporting neither SOG nor nav status pass through.
Implementation notes
shiptype(pyAISm type 5),TYPE(AISHub),vesselType/veselType(SeaVision), etc. — and deliberately ignores lowercasetype, which is the AIS message type in NMEA-decoded data.cfg_bool()helper parses INI/env booleans correctly (bool("False")isTrue, which would have made string configs a foot-gun)./etc/default/aiscottemplate updated.Test plan
tests/test_shipclass.py+ais_to_cotintegration tests): classification table, signed-ARGB color, iconsetpath layout, prefix normalization, underway gate incl. AtoN exemption,COT_ICONprecedence, zip↔module consistency.🤖 Generated with Claude Code
Review fixes pushed in
3d189be: UNDERWAY_ONLY now exempts SART/EPIRB/MOB distress beacons and KNOWN_CRAFT vessels; per-feed SOG units (pyAISm raw 0.1-kt vs AISHub/SeaVision knots) + AIS "SOG not available" sentinel honored in both track speed and underway logic; the NMEA receiver caches Type 5/24 static data per MMSI so ship-class styling works on RF feeds; KNOWN_CRAFT names are never prefix-rewritten; IGNORE_ATON parses booleans via cfg_bool; assorted hot-path and packaging cleanups. 69 tests pass.