Wegwichtel/README.md
2026-06-16 16:45:59 +02:00

180 lines
10 KiB
Markdown

# Wegwichtel Next
Neuaufbau des früheren schiffsbezogenen Ansagesystems als mobile Lernweg-Anwendung für Schulen. GPX-Strecken werden serverseitig verwaltet; POIs können Bilder und eine Audioansage enthalten. Der Client schlägt anhand der aktuellen Position nahe Routen vor und aktiviert POIs während einer Wanderung über einen GPS-Watcher.
## Architektur
- **Server:** Node.js 22.13+, Express 5, `node:sqlite`, Multer und `fast-xml-parser`
- **Client:** klassisches JavaScript, jQuery 4.0.0 und jQuery UI 1.14.2 mit lokalem Base-Theme
- **Medien:** getrennte Verzeichnisse für aktive und zum Löschen markierte Strecken
- **Löschmodell:** Soft Delete in SQLite plus atomisches Verschieben des gesamten Streckenordners in `storage/trash/routes`
- **Wiederherstellung:** `POST /api/routes/:id/restore` verschiebt die Daten zurück und korrigiert alle gespeicherten Pfade
## Schnellstart
```bash
cp .env.example .env
npm install
npm run init-db
npm start
```
Danach lauscht Wegwichtel ausschließlich auf dem lokalen Socket `127.0.0.1:47145` und ist unter `http://127.0.0.1:47145` erreichbar. Die offiziellen, exakt versionierten Distributionsdateien sind bereits unter `public/vendor` enthalten. `npm install` installiert zusätzlich die Pakete `jquery@4.0.0` und `jquery-ui@1.14.2` und synchronisiert daraus JavaScript, Base-Theme, Themebilder und Lizenzdateien erneut in das Vendor-Verzeichnis. Der Browser lädt keine Bibliotheken von einem CDN.
## Server-Socket
Die Standardwerte stehen in `.env.example` und werden auch verwendet, wenn keine `.env` vorhanden ist:
```dotenv
HOST=127.0.0.1
PORT=47145
```
`HOST` ist die tatsächliche Bind-Adresse des Node.js-Servers. Der Health-Endpunkt meldet beispielsweise:
```json
{
"ok": true,
"service": "wegwichtel",
"socket": "127.0.0.1:47145"
}
```
## Initialisierungsablauf des Clients
1. statischer Initialisierungsbildschirm erscheint ohne Bibliotheksabhängigkeit,
2. lokale jQuery-Datei wird geladen und auf Version 4.0.0 geprüft,
3. lokales jQuery UI 1.14.2 samt Base-Theme wird geladen und über `jQuery.ui.version` geprüft,
4. die Clientmodule werden sequenziell geladen,
5. `/api/health` prüft Server und SQLite,
6. der Client lädt die Routenliste,
7. anschließend wird die Position ermittelt; daraus entstehen nahe Empfehlungen, während die Gesamtliste vollständig erhalten bleibt,
8. die responsive Routenansicht wird freigeschaltet.
Scheitert ein Schritt, bleibt der Initialisierungsbildschirm mit einer konkreten Fehlermeldung und einem Wiederholungsbutton sichtbar.
## Lokale UI-Abhängigkeiten
Im Vendor-Verzeichnis liegen die zur Laufzeit verwendeten Dateien vollständig lokal:
```text
public/vendor/jquery/jquery-4.0.0.min.js
public/vendor/jquery/LICENSE.txt
public/vendor/jquery-ui/jquery-ui-1.14.2.min.js
public/vendor/jquery-ui/jquery-ui-1.14.2.min.css
public/vendor/jquery-ui/images/*.png
```
Die Anwendung verwendet aus jQuery UI insbesondere die Widgets **Button** und **Controlgroup**. Das offizielle vollständige jQuery-UI-Bundle bleibt lokal verfügbar, damit weitere aktuelle Widgets ohne erneuten CDN-Bezug ergänzt werden können. Die frühere jQuery-Mobile-Seitensteuerung wurde durch eine eigene, History-API-basierte Navigation ersetzt.
## REST-API
| Methode | Pfad | Zweck |
|---|---|---|
| GET | `/api/health` | Server- und SQLite-Selbsttest |
| GET | `/api/routes?lat=…&lon=…&radiusKm=25` | aktive, optional nahe Routen |
| GET | `/api/routes/:id` | Route, Trackpunkte, POIs und Medien-URLs |
| GET | `/api/routes/:id/pois` | POIs einer Route |
| GET | `/api/pois/:id` | einzelner POI samt Bild- und Audio-URLs |
| POST | `/api/routes` | neue Route; `multipart/form-data`, Feld `gpx` |
| PUT | `/api/routes/:id` | Metadaten und optional GPX vollständig ersetzen |
| POST | `/api/routes/:id/append` | zusätzliche GPX-Trackpunkte anhängen |
| POST | `/api/routes/:id/pois` | POI anlegen; Felder `audio` und `images` |
| PUT | `/api/pois/:id` | POI ändern und weitere Medien ergänzen |
| DELETE | `/api/routes/:id` | Route samt Medien in den Papierkorb verschieben |
| POST | `/api/routes/:id/restore` | Route aus dem Papierkorb wiederherstellen |
### Route anlegen
```bash
curl -X POST http://127.0.0.1:47145/api/routes \
-F 'name=Schulwald-Runde' \
-F 'schoolName=Beispielschule' \
-F 'description=Naturkundlicher Rundweg' \
-F 'gpx=@examples/sample-route.gpx;type=application/gpx+xml'
```
### POI mit Bildern und Audio anlegen
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/pois \
-F 'title=Die alte Eiche' \
-F 'description=Hier wird das Alter der Eiche erklärt.' \
-F 'lat=52.5208' -F 'lon=13.4070' -F 'triggerRadiusM=60' \
-F 'audio=@ansage.mp3;type=audio/mpeg' \
-F 'images=@eiche-1.jpg;type=image/jpeg' \
-F 'images=@eiche-2.jpg;type=image/jpeg'
```
## Dateistruktur
```text
public/ mobiler Client
index.html reduzierte semantische Seitenstruktur
js/bootstrap-loader.js lädt und prüft lokale Bibliotheken
js/orientation.js Kompassausrichtung für den Navigationspfeil
vendor/ lokale jQuery-/jQuery-UI-Dateien samt Themebildern
src/routes/ REST-Routing
src/services/ GPX-, Geodaten-, Speicher- und Fachlogik
database/schema.sql SQLite-Schema
data/ lokale SQLite-Datei
storage/active/routes/ aktive GPX-, Bild- und Audiodateien
storage/trash/routes/ zum Löschen markierte Strecken
examples/ Beispiel-GPX
test/ Basistests
```
## Technische Hinweise
- Medienpfade werden relativ zu `storage/` gespeichert. So bleibt das Projekt verschiebbar.
- Dateiverschiebung und Datenbankänderung sind durch eine kompensierende Rückverschiebung gekoppelt: Schlägt die SQL-Transaktion fehl, wird das Verzeichnis an seinen vorherigen Ort zurückbewegt.
- GPX-Erweiterungen ergänzen die Trackpunkte in SQLite. Das Original-GPX bleibt im Skelett unverändert; ein späterer Exportdienst sollte aus den Datenbankpunkten eine konsolidierte GPX-Datei generieren.
- Schreibzugriffe sind noch nicht authentifiziert. Vor einem öffentlichen Einsatz sind Rollen, Login, CSRF-Schutz, Rate-Limits, Dateisignaturprüfung und ein Moderationsworkflow zwingend zu ergänzen.
- Der Server bindet standardmäßig nur an `127.0.0.1`; Zugriffe von anderen Geräten sind damit bewusst ausgeschlossen.
- Anwendungsaktionen verwenden Pointer Events für Touch, Stift und Maus. Enter und Leertaste bleiben als Tastaturbedienung erhalten.
- Bei gestarteter Route zeigt ein fixierter Footer die Entfernung zum nächsten GPX-Trackpunkt auf ganze Meter und dreht einen SVG-Pfeil relativ zur Geräteausrichtung. Ohne Kompassdaten wird die Peilung mit Norden oben dargestellt.
- Auf iPhone und iPad wird die Freigabe der Geräteausrichtung beim Start der Route innerhalb der Benutzeraktion angefordert.
- Für die Geolokalisierung sollte der Client lokal über `http://127.0.0.1:47145` oder online ausschließlich über HTTPS geöffnet werden. Das betrifft ebenso den Gerätekompass.
## Nächste Ausbaustufen
- Administrationsoberfläche zum Zeichnen/Importieren von Routen und Platzieren der POIs
- Benutzer-, Schul- und Projektzuordnung mit Rollenmodell
- Offline-Cache/PWA für Wanderungen ohne Mobilfunkempfang
- Kartenansicht, GPX-Visualisierung und Abweichungswarnung
- Bildunterschriften, Sortierung und gezieltes Entfernen einzelner Medien
- Hintergrundbereinigung des Papierkorbs nach einer konfigurierbaren Aufbewahrungsfrist
- Integritätsjournal für Dateiverschiebungen und Wiederherstellungen
## Routenauswahl
Die Startseite zeigt standortbasierte Empfehlungen und darunter alle aktiven Routen. Die Gesamtliste bleibt auch bei verweigertem oder nicht verfügbarem GPS auswählbar und kann nach Name, Schule oder Beschreibung durchsucht werden.
## Mobile Bedienung und Routennavigation
Das HTML verwendet nur IDs, die von den Clientmodulen tatsächlich angesprochen werden. Die wenigen Klassen bilden wiederverwendete Layoutbausteine wie Seiten, Inhalte, Hinweise, Routenlisten und Routendaten ab. Automatisierte Tests gleichen diese Verwendungen ab.
Alle selbst implementierten Schaltaktionen reagieren primär auf `pointerup`; dadurch funktionieren dieselben Handler mit Touchscreen, Eingabestift und Maus. Die Diashow kann zusätzlich horizontal gewischt werden. Für die Tastatur werden Enter und Leertaste separat behandelt.
Beim Start der Route wird der aktuellen Position nächstgelegene GPX-Trackpunkt gesucht. Als Navigationsziel dient der folgende Trackpunkt. Der Fortschritt läuft nur vorwärts und wird in einem lokalen Fenster entlang der Punktfolge nachgeführt, um Rücksprünge durch GPS-Schwankungen zu vermeiden. Der Footer zeigt:
- die auf ganze Meter gerundete Luftlinienentfernung zum nächsten Trackpunkt,
- einen frei rotierenden SVG-Pfeil,
- die relative Richtung zum Ziel, sofern Kompass- oder Bewegungsrichtung verfügbar ist,
- andernfalls die absolute Peilung bei Norden oben,
- und `Ziel der Route erreicht`, sobald der letzte Trackpunkt innerhalb der GPS-Toleranz liegt.
## Routensteuerung und GPS-Tracking
Die Routenansicht besitzt drei nebeneinanderliegende, semantische Schaltflächen mit lokalen SVG-Symbolen:
- **Route starten beziehungsweise fortsetzen:** aktiviert `navigator.geolocation.watchPosition()` und die Geräteausrichtung.
- **Route pausieren:** beendet den aktiven GPS-Watcher und den Kompass-Listener, behält aber Routenfortschritt und bereits ausgelöste POIs bei.
- **Route beenden:** beendet alle Sensor-Listener, setzt den Fortschritt zurück und leert die Liste bereits ausgelöster POIs.
Während die Route läuft, werden Positionsänderungen fortlaufend verarbeitet. Eine Browser-Webanwendung ist jedoch kein nativer Hintergrunddienst: Betriebssystem und Browser können die Aktualisierung bei gesperrtem Bildschirm, Energiesparmodus oder im Hintergrund drosseln beziehungsweise anhalten.
Die Symbole liegen unter `public/images/icons/`. Sie befinden sich innerhalb nativer `<button>`-Elemente. Die Bilder selbst sind dekorativ (`alt=""`); der zugängliche Name wird über `aria-label` und `title` am jeweiligen Button bereitgestellt.