diff --git a/src/data_ships.py b/src/data_ships.py
index af855c9..d9a2a91 100644
--- a/src/data_ships.py
+++ b/src/data_ships.py
@@ -51,6 +51,12 @@ async def _listener():
lon = meta.get("longitude") or pos.get("Longitude")
if not lat or not lon or not (-90 <= lat <= 90 and -180 <= lon <= 180):
continue
+ ship_type = meta.get("ShipType", 0)
+ # Vorherige Position fuer Dark-Ship-Erkennung merken
+ prev = _store.get(mmsi)
+ prev_positions = (prev.get("track", []) if prev else [])[-10:]
+ prev_positions.append({"lat": round(lat, 5), "lon": round(lon, 5), "ts": time.time()})
+
_store[mmsi] = {
"mmsi": mmsi,
"lat": round(lat, 5),
@@ -59,7 +65,9 @@ async def _listener():
"cog": round(pos.get("Cog", 0), 1),
"heading": pos.get("TrueHeading", 0),
"name": (meta.get("ShipName") or "").strip(),
+ "ship_type": ship_type,
"ts": time.time(),
+ "track": prev_positions,
}
# Stale-Cleanup alle 1000 Updates
if len(_store) % 1000 == 0:
@@ -84,6 +92,50 @@ def start_ais_collector():
logger.info("AIS collector gestartet")
+def _classify_ship(ship_type):
+ """AIS Ship Type zu Kategorie."""
+ if not ship_type:
+ return "unknown"
+ t = int(ship_type)
+ if 20 <= t <= 29: return "wing_in_ground"
+ if 30 <= t <= 39: return "fishing" if t == 30 else "towing" if t in (31,32) else "military" if t == 35 else "sailing" if t == 36 else "other"
+ if 40 <= t <= 49: return "hsc"
+ if 50 <= t <= 59: return "pilot" if t == 50 else "sar" if t == 51 else "tug" if t == 52 else "port" if t == 53 else "medical" if t == 58 else "other"
+ if 60 <= t <= 69: return "passenger"
+ if 70 <= t <= 79: return "cargo"
+ if 80 <= t <= 89: return "tanker"
+ return "other"
+
+def _detect_dark_ships():
+ """Schiffe die laenger als 10 Minuten kein Update hatten aber vorher aktiv waren."""
+ now = time.time()
+ dark = []
+ for mmsi, s in _store.items():
+ age = now - s["ts"]
+ track = s.get("track", [])
+ # Aktiv gewesen (>3 Positionen) aber seit >10min kein Update und SOG war > 1
+ if age > 600 and len(track) >= 3 and s.get("sog", 0) > 0.5:
+ dark.append({
+ "mmsi": mmsi, "name": s.get("name", ""),
+ "last_lat": s["lat"], "last_lon": s["lon"],
+ "last_sog": s["sog"], "last_cog": s["cog"],
+ "silent_minutes": round(age / 60, 1),
+ "ship_type": _classify_ship(s.get("ship_type")),
+ })
+ return dark
+
@router.get("/ships")
async def get_ships():
- return {"ships": list(_store.values()), "total": len(_store), "connected": _connected}
+ ships_out = []
+ for s in _store.values():
+ ship = dict(s)
+ ship["category"] = _classify_ship(s.get("ship_type"))
+ # Track auf letzte 5 Positionen kuerzen fuer API
+ ship["track"] = ship.get("track", [])[-5:]
+ ships_out.append(ship)
+ return {"ships": ships_out, "total": len(ships_out), "connected": _connected}
+
+@router.get("/ships/dark")
+async def get_dark_ships():
+ dark = _detect_dark_ships()
+ return {"dark_ships": dark, "total": len(dark)}
diff --git a/static/css/globe.css b/static/css/globe.css
index c46370b..f961e1d 100644
--- a/static/css/globe.css
+++ b/static/css/globe.css
@@ -577,3 +577,9 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo
.dot-terminator { background: #ff8800; }
.dot-timezones { background: #8888ff; }
+
+/* === Ship Filters === */
+.ship-filters {
+ padding: 4px 12px 6px;
+ border-top: 1px solid rgba(255,255,255,0.04);
+}
diff --git a/static/index.html b/static/index.html
index 12d822a..f0988c1 100644
--- a/static/index.html
+++ b/static/index.html
@@ -74,6 +74,7 @@
+