Skip to content

AIS-catcher style vessel styling: ship-class colors & icons, callsign prefixes, underway-only filter - #15

Merged
ampledata merged 3 commits into
mainfrom
ais-catcher-styling
Jul 15, 2026
Merged

AIS-catcher style vessel styling: ship-class colors & icons, callsign prefixes, underway-only filter#15
ampledata merged 3 commits into
mainfrom
ais-catcher-styling

Conversation

@ampledata

@ampledata ampledata commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

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 (default True)

New aiscot.shipclass module 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 (default False)

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_ICON still takes precedence. Opt-in because clients must import the zip first (ATAK Settings → Tool Preferences → Point Dropper → Iconset Manager). Regenerate with scripts/build_ais_iconset.py (pure stdlib PNG writer, no Pillow).

Conventional callsign prefixes — VESSEL_NAME_PREFIX (default True)

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 (default False)

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

  • Ship type extraction checks shiptype (pyAISm type 5), TYPE (AISHub), vesselType/veselType (SeaVision), etc. — and deliberately ignores lowercase type, which is the AIS message type in NMEA-decoded data.
  • New cfg_bool() helper parses INI/env booleans correctly (bool("False") is True, which would have made string configs a foot-gun).
  • Version bumped to 7.3.0; docs, example config, and /etc/default/aiscot template updated.

Test plan

  • 24 new tests (tests/test_shipclass.py + ais_to_cot integration tests): classification table, signed-ARGB color, iconsetpath layout, prefix normalization, underway gate incl. AtoN exemption, COT_ICON precedence, zip↔module consistency.
  • Full suite: 61 passed.
  • flake8 clean on new/changed lines.
  • The same classification/palette/iconset code has been running in production in DroneCase since 2026-07-09.

🤖 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.

…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>
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 ampledata left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. tests/test_functions.py has two def 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).
  2. 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.
  3. shipclass.py:822: base-station rule 0 < num < 9000000 misses coast stations 9000000–9999999 (the 00MID block spans everything below 10000000).
  4. functions.py:177-183: the remarks Type: extraction checks a narrower key set than shipclass._SHIPTYPE_KEYS (misses ship_type/SHIPTYPE/cargo) — a feed supplying only those gets classified/colored but shows no Type in remarks. Reuse the shiptype value 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

https://claude.ai/code/session_01DVibvSyvPsUXRXRYaFsWjY

- 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
@ampledata
ampledata merged commit 7624961 into main Jul 15, 2026
4 checks passed
@ampledata
ampledata deleted the ais-catcher-styling branch July 15, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant