Deutschland: Karte erst bereit wenn beide Länder geladen, alte Schweizer Daten übernehmen, Hardware-Hinweise

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
This commit is contained in:
Claude
2026-10-21 21:35:09 +02:00
parent 2a4962c379
commit c07a309045
5 changed files with 24 additions and 8 deletions
+4 -1
View File
@@ -37,7 +37,8 @@ die Kartenkacheln). Alle Daten liegen im Ordner `data/` neben der `compose.yaml`
1. **Container anlegen** (Proxmox-Oberfläche → *Create CT*):
Debian 12 oder 13 Template, unprivileged, 2 Kerne, **4 GB RAM** (pfaedle braucht beim
Aufbereiten bis ~2 GB, der Server ~0,5 GB), **20 GB Disk**, Netz per DHCP.
Aufbereiten bis ~2 GB, der Server ~1 GB), **30 GB Disk** (Kartenkacheln ~4,5 GB, beim
monatlichen OSM-Update kurzzeitig ~5 GB Download für Deutschland), Netz per DHCP.
Unter *Options → Features* **Nesting** und **keyctl** aktivieren – ohne das läuft Docker
im LXC nicht.
2. **Installieren** (im Container als root):
@@ -47,6 +48,8 @@ die Kartenkacheln). Alle Daten liegen im Ordner `data/` neben der `compose.yaml`
bash Trainpulse/deploy/install.sh
```
Das Skript installiert Docker, falls es fehlt, baut das Image und startet beide Container.
Der erste Start dauert ca. 30–45 Minuten: Kartenkacheln (~4,5 GB) und OpenStreetMap für
Deutschland (~5 GB) herunterladen, dann beide Fahrpläne aufbereiten.
3. **Echtzeit freischalten:** Auf <https://api-manager.opentransportdata.swiss> registrieren,
eine Applikation anlegen, das Produkt *GTFS-RT* abonnieren und den API-Key in
`Trainpulse/.env` bei `TRAINPULSE_RT_KEY=` eintragen. Dann `docker compose up -d`.
+4 -2
View File
@@ -8,6 +8,8 @@ cd "$(dirname "$0")/.."
[ "$(id -u)" = 0 ] || { echo "Bitte als root ausführen" >&2; exit 1; }
command -v apt-get >/dev/null || { echo "Nur für Debian/Ubuntu (apt) gedacht" >&2; exit 1; }
mem=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
disk=$(df -Pm . | awk 'NR==2 {print $4}')
[ "$disk" -ge 25000 ] || echo "WARNUNG: nur ${disk} MB frei – empfohlen sind ~30 GB" >&2
[ "$mem" -ge 3500 ] || echo "WARNUNG: nur ${mem} MB RAM – für die Fahrplan-Aufbereitung werden ~4 GB empfohlen" >&2
if ! command -v docker >/dev/null || ! docker compose version >/dev/null 2>&1; then
@@ -35,8 +37,8 @@ ip=$(hostname -I | awk '{print $1}')
cat <<EOF
Fertig. Die Karte läuft unter http://$ip:8080
Der Updater lädt jetzt Kartenkacheln (~1,2 GB) und den Fahrplan und berechnet die
Streckenverläufe. Bis die ersten Züge erscheinen, dauert es ca. 15 Minuten.
Der Updater lädt jetzt Kartenkacheln (~4,5 GB), OpenStreetMap und die Fahrpläne der Schweiz
und Deutschlands und berechnet die Streckenverläufe. Beim ersten Start dauert das ca. 30–45 Minuten.
Fortschritt: docker compose logs -f updater
Echtzeit: API-Key in $(pwd)/.env eintragen, dann docker compose up -d
EOF
+7 -2
View File
@@ -329,8 +329,13 @@ def main() -> None:
ap.error("--zip braucht --source")
C.FEEDS_DIR.mkdir(parents=True, exist_ok=True)
for p in C.FEEDS_DIR.iterdir(): # Ordner der alten Version (feeds/<version>) und abgeschaltete Quellen
if p.is_dir() and p.name not in C.SOURCES:
for p in C.FEEDS_DIR.iterdir():
if not p.is_dir() or p.name in C.SOURCES:
continue
if p.name.isdigit() and (p / "meta.json").exists(): # alte Version: feeds/<version> war die Schweiz
(C.FEEDS_DIR / "ch").mkdir(exist_ok=True)
p.rename(C.FEEDS_DIR / "ch" / p.name)
else:
shutil.rmtree(p)
if not args.zip:
ensure_tiles()
+8 -2
View File
@@ -34,6 +34,7 @@ class Source:
self.rt_at: float | None = None
self.rt_error: str | None = None
self.rt_tried_at: float | None = None
self.on_disk = False # ein aufbereiteter Fahrplan liegt bereit (wird gleich geladen)
@property
def has_rt(self) -> bool:
@@ -67,8 +68,12 @@ def watch_feeds():
"""Lädt je Quelle den passenden Fahrplan: den, auf den sich die Echtzeitdaten beziehen (Schweiz:
die schalten erst um 15 Uhr auf einen neuen Fahrplan um), sonst den neusten."""
today = dt.datetime.now(TZ).date()
found = {}
for src in S.sources.values(): # erst alle markieren, damit die Karte nicht mit einem Land allein startet
found[src.name] = sorted(m.parent.name for m in (C.FEEDS_DIR / src.name).glob("*/meta.json"))
src.on_disk = bool(found[src.name])
for src in S.sources.values():
ready = sorted(m.parent.name for m in (C.FEEDS_DIR / src.name).glob("*/meta.json"))
ready = found[src.name]
if not ready:
continue
want = src.rt_version if src.rt_version in ready else ready[-1]
@@ -171,12 +176,13 @@ def trip(source: str, date: str, ti: int):
@app.get("/api/status")
def status():
loaded = [s for s in S.sources.values() if s.feed]
pending = [s for s in S.sources.values() if s.on_disk and not s.feed] # nicht halbe Karte zeigen
rt = [s for s in loaded if s.has_rt]
ok = [s for s in rt if not s.rt_error and s.rt_at and time.time() - s.rt_at < 180]
return {
"loaded": bool(loaded),
# erst "bereit", wenn Positionen mit (dem ersten Versuch) Echtzeit berechnet sind
"ready": bool(loaded and S.computed_at and all(s.rt_tried_at and S.computed_at >= s.rt_tried_at for s in rt)),
"ready": bool(loaded and not pending and S.computed_at and all(s.rt_tried_at and S.computed_at >= s.rt_tried_at for s in rt)),
"vehicles": S.count,
"rt_ok": bool(ok) and len(ok) == len(loaded),
"rt_some": bool(ok),
+1 -1
View File
@@ -236,7 +236,7 @@ async function refreshStatus() {
const s = await (await fetch("api/status")).json();
if (!s.ready) {
loading.textContent = s.loaded ? "Züge werden geladen …"
: "Fahrplan wird vorbereitet … beim ersten Start dauert das 15–20 Minuten.";
: "Fahrplan wird vorbereitet … beim ersten Start dauert das 30–45 Minuten.";
loading.hidden = false;
return;
}