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:
@@ -37,7 +37,8 @@ die Kartenkacheln). Alle Daten liegen im Ordner `data/` neben der `compose.yaml`
|
|||||||
|
|
||||||
1. **Container anlegen** (Proxmox-Oberfläche → *Create CT*):
|
1. **Container anlegen** (Proxmox-Oberfläche → *Create CT*):
|
||||||
Debian 12 oder 13 Template, unprivileged, 2 Kerne, **4 GB RAM** (pfaedle braucht beim
|
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
|
Unter *Options → Features* **Nesting** und **keyctl** aktivieren – ohne das läuft Docker
|
||||||
im LXC nicht.
|
im LXC nicht.
|
||||||
2. **Installieren** (im Container als root):
|
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
|
bash Trainpulse/deploy/install.sh
|
||||||
```
|
```
|
||||||
Das Skript installiert Docker, falls es fehlt, baut das Image und startet beide Container.
|
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,
|
3. **Echtzeit freischalten:** Auf <https://api-manager.opentransportdata.swiss> registrieren,
|
||||||
eine Applikation anlegen, das Produkt *GTFS-RT* abonnieren und den API-Key in
|
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`.
|
`Trainpulse/.env` bei `TRAINPULSE_RT_KEY=` eintragen. Dann `docker compose up -d`.
|
||||||
|
|||||||
+4
-2
@@ -8,6 +8,8 @@ cd "$(dirname "$0")/.."
|
|||||||
[ "$(id -u)" = 0 ] || { echo "Bitte als root ausführen" >&2; exit 1; }
|
[ "$(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; }
|
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)
|
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
|
[ "$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
|
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
|
cat <<EOF
|
||||||
|
|
||||||
Fertig. Die Karte läuft unter http://$ip:8080
|
Fertig. Die Karte läuft unter http://$ip:8080
|
||||||
Der Updater lädt jetzt Kartenkacheln (~1,2 GB) und den Fahrplan und berechnet die
|
Der Updater lädt jetzt Kartenkacheln (~4,5 GB), OpenStreetMap und die Fahrpläne der Schweiz
|
||||||
Streckenverläufe. Bis die ersten Züge erscheinen, dauert es ca. 15 Minuten.
|
und Deutschlands und berechnet die Streckenverläufe. Beim ersten Start dauert das ca. 30–45 Minuten.
|
||||||
Fortschritt: docker compose logs -f updater
|
Fortschritt: docker compose logs -f updater
|
||||||
Echtzeit: API-Key in $(pwd)/.env eintragen, dann docker compose up -d
|
Echtzeit: API-Key in $(pwd)/.env eintragen, dann docker compose up -d
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -329,8 +329,13 @@ def main() -> None:
|
|||||||
ap.error("--zip braucht --source")
|
ap.error("--zip braucht --source")
|
||||||
|
|
||||||
C.FEEDS_DIR.mkdir(parents=True, exist_ok=True)
|
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
|
for p in C.FEEDS_DIR.iterdir():
|
||||||
if p.is_dir() and p.name not in C.SOURCES:
|
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)
|
shutil.rmtree(p)
|
||||||
if not args.zip:
|
if not args.zip:
|
||||||
ensure_tiles()
|
ensure_tiles()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class Source:
|
|||||||
self.rt_at: float | None = None
|
self.rt_at: float | None = None
|
||||||
self.rt_error: str | None = None
|
self.rt_error: str | None = None
|
||||||
self.rt_tried_at: float | None = None
|
self.rt_tried_at: float | None = None
|
||||||
|
self.on_disk = False # ein aufbereiteter Fahrplan liegt bereit (wird gleich geladen)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_rt(self) -> bool:
|
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:
|
"""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."""
|
die schalten erst um 15 Uhr auf einen neuen Fahrplan um), sonst den neusten."""
|
||||||
today = dt.datetime.now(TZ).date()
|
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():
|
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:
|
if not ready:
|
||||||
continue
|
continue
|
||||||
want = src.rt_version if src.rt_version in ready else ready[-1]
|
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")
|
@app.get("/api/status")
|
||||||
def status():
|
def status():
|
||||||
loaded = [s for s in S.sources.values() if s.feed]
|
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]
|
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]
|
ok = [s for s in rt if not s.rt_error and s.rt_at and time.time() - s.rt_at < 180]
|
||||||
return {
|
return {
|
||||||
"loaded": bool(loaded),
|
"loaded": bool(loaded),
|
||||||
# erst "bereit", wenn Positionen mit (dem ersten Versuch) Echtzeit berechnet sind
|
# 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,
|
"vehicles": S.count,
|
||||||
"rt_ok": bool(ok) and len(ok) == len(loaded),
|
"rt_ok": bool(ok) and len(ok) == len(loaded),
|
||||||
"rt_some": bool(ok),
|
"rt_some": bool(ok),
|
||||||
|
|||||||
+1
-1
@@ -236,7 +236,7 @@ async function refreshStatus() {
|
|||||||
const s = await (await fetch("api/status")).json();
|
const s = await (await fetch("api/status")).json();
|
||||||
if (!s.ready) {
|
if (!s.ready) {
|
||||||
loading.textContent = s.loaded ? "Züge werden geladen …"
|
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;
|
loading.hidden = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user