Militaerschiff-Datenbank mit Bildern + Klassifizierung
Neue Datei milship_db.py: - MMSI-Laenderzuordnung (200+ Laendercodes) - Schiffsklassen-Datenbank (Nimitz, Arleigh-Burke, Gorshkov, Type 052D, Queen Elizabeth, F125, Charles de Gaulle) - Bilder aus Wikimedia Commons (frei lizenziert) - Klassifizierung nach MMSI-Prefix + Schiffsname Klick auf Militaerschiff zeigt: - Foto der Schiffsklasse (wenn verfuegbar) - Klassenname und Schiffstyp - Herkunftsland (aus MMSI) - MMSI, SOG, COG, Heading API: GET /api/ships/military liefert alle Militaerschiffe mit Klassifizierung und Bild-URLs.
Dieser Commit ist enthalten in:
@@ -7,6 +7,7 @@ import time
|
||||
|
||||
import websockets
|
||||
from fastapi import APIRouter
|
||||
from milship_db import get_country_from_mmsi, classify_military_ship
|
||||
|
||||
logger = logging.getLogger("globe.ships")
|
||||
router = APIRouter()
|
||||
@@ -135,6 +136,25 @@ async def get_ships():
|
||||
ships_out.append(ship)
|
||||
return {"ships": ships_out, "total": len(ships_out), "connected": _connected}
|
||||
|
||||
@router.get("/ships/military")
|
||||
async def get_military_ships():
|
||||
"""Alle Militaerschiffe mit Klassifizierung und Bildern."""
|
||||
mil_ships = []
|
||||
for s in _store.values():
|
||||
if s.get("ship_type") == 35 or (isinstance(s.get("ship_type"), int) and 30 <= s["ship_type"] <= 39):
|
||||
info = classify_military_ship(s.get("mmsi"), s.get("name",""))
|
||||
mil_ships.append({
|
||||
"mmsi": s["mmsi"],
|
||||
"name": s.get("name", ""),
|
||||
"lat": s["lat"], "lon": s["lon"],
|
||||
"sog": s["sog"], "cog": s["cog"],
|
||||
"country": info["country"],
|
||||
"ship_class": info["class"],
|
||||
"ship_type_detail": info["type"],
|
||||
"image": info["image"],
|
||||
})
|
||||
return {"ships": mil_ships, "total": len(mil_ships)}
|
||||
|
||||
@router.get("/ships/dark")
|
||||
async def get_dark_ships():
|
||||
dark = _detect_dark_ships()
|
||||
|
||||
202
src/milship_db.py
Normale Datei
202
src/milship_db.py
Normale Datei
@@ -0,0 +1,202 @@
|
||||
"""Militaerschiff-Datenbank: MMSI-Zuordnung, Klassifizierung, Bilder."""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger("globe.milships")
|
||||
|
||||
# MMSI-Laendercodes (MID = Maritime Identification Digits, erste 3 Ziffern)
|
||||
MID_COUNTRIES = {
|
||||
"201": "Albanien", "202": "Andorra", "203": "Oesterreich", "204": "Azoren",
|
||||
"205": "Belgien", "206": "Belarus", "207": "Bulgarien", "208": "Vatikan",
|
||||
"209": "Zypern", "210": "Zypern", "211": "Deutschland", "212": "Zypern",
|
||||
"213": "Georgien", "214": "Moldawien", "215": "Malta", "216": "Armenien",
|
||||
"218": "Deutschland", "219": "Daenemark", "220": "Daenemark",
|
||||
"224": "Spanien", "225": "Spanien", "226": "Frankreich", "227": "Frankreich",
|
||||
"228": "Frankreich", "229": "Malta", "230": "Finnland", "231": "Faeroeer",
|
||||
"232": "UK", "233": "UK", "234": "UK", "235": "UK",
|
||||
"236": "Gibraltar", "237": "Griechenland", "238": "Kroatien", "239": "Griechenland",
|
||||
"240": "Griechenland", "241": "Griechenland", "242": "Marokko", "243": "Ungarn",
|
||||
"244": "Niederlande", "245": "Niederlande", "246": "Niederlande",
|
||||
"247": "Italien", "248": "Malta", "249": "Malta", "250": "Irland",
|
||||
"251": "Island", "252": "Liechtenstein", "253": "Luxemburg", "254": "Madeira",
|
||||
"255": "Portugal", "256": "Malta", "257": "Norwegen", "258": "Norwegen",
|
||||
"259": "Norwegen", "261": "Polen", "263": "Portugal", "264": "Rumaenien",
|
||||
"265": "Schweden", "266": "Schweden", "267": "Schweden", "268": "Schweiz",
|
||||
"269": "Tschechien", "270": "Tschechien", "271": "Tuerkei", "272": "Ukraine",
|
||||
"273": "Russland", "274": "Nordmazedonien", "275": "Lettland", "276": "Estland",
|
||||
"277": "Litauen", "278": "Slowenien", "279": "Serbien",
|
||||
"301": "Anguilla", "303": "Alaska", "304": "Antigua", "305": "Antigua",
|
||||
"306": "Curacao", "307": "Aruba", "308": "Bahamas", "309": "Bahamas",
|
||||
"310": "Bermuda", "311": "Bahamas", "312": "Belize",
|
||||
"316": "Kanada", "319": "Cayman Islands",
|
||||
"330": "Grenada", "331": "Groenland", "332": "Guatemala",
|
||||
"338": "USA", "339": "Jamaica", "341": "St. Kitts",
|
||||
"345": "Mexiko", "347": "Martinique",
|
||||
"351": "Peru", "352": "Panama", "353": "Panama", "354": "Panama",
|
||||
"355": "Panama", "356": "Panama", "357": "Panama",
|
||||
"366": "USA", "367": "USA", "368": "USA", "369": "USA",
|
||||
"370": "Panama", "371": "Panama", "372": "Panama", "373": "Panama",
|
||||
"374": "Panama", "375": "Dominikanische Republik",
|
||||
"376": "Bolivien", "377": "Trinidad",
|
||||
"378": "Haiti", "379": "Honduras",
|
||||
"401": "Afghanistan", "403": "Saudi-Arabien",
|
||||
"405": "Bangladesch", "408": "Bahrain",
|
||||
"410": "Bhutan", "412": "China", "413": "China", "414": "China",
|
||||
"416": "Taiwan", "417": "Sri Lanka",
|
||||
"419": "Indien", "422": "Iran", "423": "Aserbaidschan",
|
||||
"425": "Irak", "428": "Israel",
|
||||
"431": "Japan", "432": "Japan",
|
||||
"434": "Turkmenistan", "436": "Kasachstan",
|
||||
"437": "Usbekistan", "438": "Jordanien",
|
||||
"440": "Suedkorea", "441": "Suedkorea",
|
||||
"443": "Palaestina", "445": "Nordkorea",
|
||||
"447": "Kuwait", "450": "Libanon",
|
||||
"451": "Kirgistan", "453": "Macau",
|
||||
"455": "Malediven", "457": "Mongolei",
|
||||
"459": "Nepal", "461": "Oman",
|
||||
"463": "Pakistan", "466": "Katar",
|
||||
"468": "Syrien", "470": "VAE", "471": "VAE",
|
||||
"472": "Tadschikistan", "473": "Jemen",
|
||||
"477": "Hongkong",
|
||||
"501": "Antarktis (FR)", "503": "Australien",
|
||||
"506": "Myanmar", "508": "Brunei",
|
||||
"510": "Mikronesien", "511": "Palau",
|
||||
"512": "Neuseeland", "514": "Kambodscha",
|
||||
"515": "Kambodscha", "516": "Weihnachtsinsel",
|
||||
"518": "Cookinseln",
|
||||
"520": "Fidschi", "523": "Cocos",
|
||||
"525": "Indonesien", "529": "Kiribati",
|
||||
"533": "Malaysia", "536": "Nordliche Marianen",
|
||||
"538": "Marshallinseln",
|
||||
"540": "Neukaledonien", "542": "Niue",
|
||||
"544": "Nauru", "546": "Franzoesisch-Polynesien",
|
||||
"548": "Philippinen",
|
||||
"553": "Papua-Neuguinea", "555": "Pitcairn",
|
||||
"557": "Salomonen",
|
||||
"559": "Amerikanisch-Samoa", "561": "Samoa",
|
||||
"563": "Singapur", "564": "Singapur", "565": "Singapur", "566": "Singapur",
|
||||
"567": "Thailand", "570": "Tonga",
|
||||
"572": "Tuvalu", "574": "Vietnam",
|
||||
"576": "Vanuatu", "577": "Vanuatu",
|
||||
"578": "Wallis",
|
||||
"601": "Suedafrika", "603": "Angola",
|
||||
"605": "Algerien", "607": "Mauritius",
|
||||
"609": "Burundi", "610": "Benin",
|
||||
"611": "Botsuana", "612": "Zentralafrikanische Republik",
|
||||
"613": "Kamerun", "615": "Kongo",
|
||||
"616": "Komoren", "617": "Kap Verde",
|
||||
"618": "Antarktis",
|
||||
"619": "Elfenbeinkueste", "620": "Komoren",
|
||||
"621": "Dschibuti", "622": "Aegypten",
|
||||
"624": "Aethiopien", "625": "Eritrea",
|
||||
"626": "Gabun", "627": "Ghana",
|
||||
"629": "Gambia", "630": "Guinea-Bissau",
|
||||
"631": "Aequatorialguinea", "632": "Guinea",
|
||||
"633": "Burkina Faso", "634": "Kenia",
|
||||
"635": "Kerguelen",
|
||||
"636": "Liberia", "637": "Liberia",
|
||||
"638": "Suedsudan", "642": "Libyen",
|
||||
"644": "Lesotho", "645": "Mauritius",
|
||||
"647": "Madagaskar", "649": "Mali",
|
||||
"650": "Mosambik", "654": "Mauretanien",
|
||||
"655": "Malawi", "656": "Niger",
|
||||
"657": "Nigeria", "659": "Namibia",
|
||||
"660": "Reunion", "661": "Ruanda",
|
||||
"662": "Sudan", "663": "Senegal",
|
||||
"664": "Seychellen", "665": "St. Helena",
|
||||
"666": "Somalia", "667": "Sierra Leone",
|
||||
"668": "Sao Tome", "669": "Eswatini",
|
||||
"670": "Tschad", "671": "Togo",
|
||||
"672": "Tunesien", "674": "Tansania",
|
||||
"675": "Uganda", "676": "Kongo",
|
||||
"677": "Tansania", "678": "Sambia",
|
||||
"679": "Simbabwe",
|
||||
}
|
||||
|
||||
# Bekannte Militaerschiff-Klassen mit Bildern (Wikimedia Commons)
|
||||
MILITARY_SHIP_DB = {
|
||||
# Format: MMSI-Prefix oder exakte MMSI -> Schiffsdaten
|
||||
# USA (MMSI 338*, 366-369*)
|
||||
"_class_usa_carrier": {
|
||||
"class": "Nimitz/Gerald R. Ford-Klasse",
|
||||
"type": "Flugzeugtraeger",
|
||||
"country": "USA",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/USS_Abraham_Lincoln_%28CVN-72%29_closeup.jpg/640px-USS_Abraham_Lincoln_%28CVN-72%29_closeup.jpg",
|
||||
},
|
||||
"_class_usa_destroyer": {
|
||||
"class": "Arleigh-Burke-Klasse",
|
||||
"type": "Zerstoerer",
|
||||
"country": "USA",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/US_Navy_050715-N-8163B-003.jpg/640px-US_Navy_050715-N-8163B-003.jpg",
|
||||
},
|
||||
"_class_russia_frigate": {
|
||||
"class": "Admiral Gorshkov-Klasse",
|
||||
"type": "Fregatte",
|
||||
"country": "Russland",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Admiral_Gorshkov_frigate.jpg/640px-Admiral_Gorshkov_frigate.jpg",
|
||||
},
|
||||
"_class_china_destroyer": {
|
||||
"class": "Type 052D",
|
||||
"type": "Zerstoerer",
|
||||
"country": "China",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/90/Type_052D_Kunming.jpg/640px-Type_052D_Kunming.jpg",
|
||||
},
|
||||
"_class_uk_carrier": {
|
||||
"class": "Queen Elizabeth-Klasse",
|
||||
"type": "Flugzeugtraeger",
|
||||
"country": "UK",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/HMS_Queen_Elizabeth_in_Gibraltar_-_2018_%2828386226189%29.jpg/640px-HMS_Queen_Elizabeth_in_Gibraltar_-_2018_%2828386226189%29.jpg",
|
||||
},
|
||||
"_class_germany_frigate": {
|
||||
"class": "Baden-Wuerttemberg-Klasse (F125)",
|
||||
"type": "Fregatte",
|
||||
"country": "Deutschland",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Fregatte_Baden-W%C3%BCrttemberg_F222_%28cropped%29.jpg/640px-Fregatte_Baden-W%C3%BCrttemberg_F222_%28cropped%29.jpg",
|
||||
},
|
||||
"_class_france_carrier": {
|
||||
"class": "Charles de Gaulle (R91)",
|
||||
"type": "Flugzeugtraeger",
|
||||
"country": "Frankreich",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Charles_De_Gaulle_PEO.jpg/640px-Charles_De_Gaulle_PEO.jpg",
|
||||
},
|
||||
"_class_default_military": {
|
||||
"class": "Militaerschiff",
|
||||
"type": "Unbekannt",
|
||||
"country": "",
|
||||
"image": "",
|
||||
},
|
||||
}
|
||||
|
||||
def get_country_from_mmsi(mmsi):
|
||||
"""Bestimmt das Land anhand der MMSI (erste 3 Ziffern)."""
|
||||
if not mmsi:
|
||||
return "Unbekannt"
|
||||
mid = str(mmsi)[:3]
|
||||
return MID_COUNTRIES.get(mid, "Unbekannt")
|
||||
|
||||
def classify_military_ship(mmsi, name=""):
|
||||
"""Versucht ein Militaerschiff zu klassifizieren."""
|
||||
country = get_country_from_mmsi(mmsi)
|
||||
mid = str(mmsi)[:3] if mmsi else ""
|
||||
name_upper = (name or "").upper()
|
||||
|
||||
# Laender-basierte Klassenzuordnung
|
||||
if mid in ("338","366","367","368","369"): # USA
|
||||
if any(w in name_upper for w in ("CVN","CARRIER","ENTERPRISE","FORD","NIMITZ","LINCOLN","WASHINGTON","REAGAN","BUSH","EISENHOWER","TRUMAN","STENNIS","VINSON")):
|
||||
return {**MILITARY_SHIP_DB["_class_usa_carrier"], "country": country}
|
||||
return {**MILITARY_SHIP_DB["_class_usa_destroyer"], "country": country}
|
||||
if mid in ("273",): # Russland
|
||||
return {**MILITARY_SHIP_DB["_class_russia_frigate"], "country": country}
|
||||
if mid in ("412","413","414"): # China
|
||||
return {**MILITARY_SHIP_DB["_class_china_destroyer"], "country": country}
|
||||
if mid in ("232","233","234","235"): # UK
|
||||
if any(w in name_upper for w in ("QUEEN","ELIZABETH","PRINCE","WALES")):
|
||||
return {**MILITARY_SHIP_DB["_class_uk_carrier"], "country": country}
|
||||
if mid in ("211","218"): # Deutschland
|
||||
return {**MILITARY_SHIP_DB["_class_germany_frigate"], "country": country}
|
||||
if mid in ("226","227","228"): # Frankreich
|
||||
if any(w in name_upper for w in ("CHARLES","GAULLE")):
|
||||
return {**MILITARY_SHIP_DB["_class_france_carrier"], "country": country}
|
||||
|
||||
return {"class": "Militaerschiff", "type": "Unbekannt", "country": country, "image": ""}
|
||||
@@ -175,15 +175,31 @@ const ShipsLayer = {
|
||||
var name = best.name || ('MMSI ' + best.mmsi);
|
||||
var catLabels = { tanker:'Tanker', cargo:'Frachter', passenger:'Passagier', fishing:'Fischerei', military:'Militaer', other:'Sonstige' };
|
||||
this._viewer.trackedEntity = undefined;
|
||||
this._viewer.selectedEntity = new Cesium.Entity({
|
||||
name: name,
|
||||
description: '<div style="font-family:monospace;font-size:12px;padding:8px;color:' + this._getColor(best.category) + '">' +
|
||||
var baseHtml = '<div style="font-family:monospace;font-size:12px;padding:8px;color:' + this._getColor(best.category) + '">' +
|
||||
'<strong>' + name + '</strong><br>' +
|
||||
'MMSI: ' + (best.mmsi||'?') + '<br>' +
|
||||
'Typ: ' + (catLabels[best.category] || best.category || '?') + '<br>' +
|
||||
'SOG: ' + (best.sog||0).toFixed(1) + ' kn | COG: ' + Math.round(best.cog||0) + '°<br>' +
|
||||
'HDG: ' + (best.heading||'?') + '°</div>',
|
||||
});
|
||||
'HDG: ' + (best.heading||'?') + '°</div>';
|
||||
this._viewer.selectedEntity = new Cesium.Entity({ name: name, description: baseHtml });
|
||||
// Militaerschiff: Bild + Klassifizierung nachladen
|
||||
if (best.category === 'military') {
|
||||
var viewer = this._viewer;
|
||||
fetch('/api/ships/military').then(function(r){return r.json()}).then(function(data){
|
||||
var mil = (data.ships||[]).find(function(m){return m.mmsi===best.mmsi});
|
||||
if (mil && viewer.selectedEntity) {
|
||||
var imgHtml = mil.image ? '<img src="' + mil.image + '" style="width:100%;max-height:180px;object-fit:cover;border-radius:6px;margin-bottom:8px">' : '';
|
||||
viewer.selectedEntity.description = '<div style="font-family:monospace;font-size:12px;padding:8px">' +
|
||||
imgHtml +
|
||||
'<strong style="color:#ff44ff;font-size:14px">' + (mil.name||name) + '</strong><br>' +
|
||||
'<span style="color:#ccc">' + mil.ship_class + '</span><br>' +
|
||||
'<span style="color:#aaa">' + mil.ship_type_detail + '</span><br>' +
|
||||
'<span style="color:#888">Land: ' + mil.country + '</span><br>' +
|
||||
'<span style="color:#888">MMSI: ' + best.mmsi + '</span><br>' +
|
||||
'SOG: ' + (best.sog||0).toFixed(1) + ' kn | COG: ' + Math.round(best.cog||0) + '°</div>';
|
||||
}
|
||||
}).catch(function(){});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren