diff --git a/.env.example b/.env.example index f2feb98..7a3a0e5 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ HOST=127.0.0.1 PORT=47145 DATA_DIR=./data STORAGE_DIR=./storage +GPX_CLUSTER_METERS=20 MAX_UPLOAD_MB=50 DEFAULT_ROUTE_RADIUS_KM=25 DEFAULT_POI_TRIGGER_METERS=80 diff --git a/AGENTS.md b/AGENTS.md index 954373d..d05849e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,9 @@ # Wegwichtel: Sprache der App +GPX-Änderungen berücksichtigen `GPX-Aufzeichnung.md`: Stationen sind `wpt`, +Aufzeichnungen sind `trkpt`, der Server normalisiert zu `rtept`. Fehlende +Koordinaten bleiben gespeichert und bearbeitbar; keine Ersatzkoordinaten erfinden. + Diese Vorgabe des Nutzers gilt für alle künftigen Änderungen an der Oberfläche. Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntnisse. @@ -29,6 +33,10 @@ Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntni - Alle eigenen Bedienungstexte liegen zentral in der Klasse `WegwichtelTexts` in `public/js/texts-de.js`. Keine neuen sichtbaren Texte direkt in HTML oder Funktionscode schreiben. +- Ausnahme: Impressum und Datenschutzerklärung bleiben ausschließlich statisch + auf Deutsch in `public/index.html` und tragen `lang="de"`. Ihre Texte und + Kontaktangaben gehören nicht in Sprachdateien und erhalten keine i18n-Attribute. + Auch ihre Dialogüberschriften bleiben beim Sprachwechsel auf Deutsch. - Das HTML enthält daraus erzeugte deutsche Standardtexte, damit Beschriftungen vor dem Laden der Skripte vorhanden sind. Nach Änderungen am Textkatalog `node scripts/localize-html.js` ausführen und `public/index.html` mit ablegen. diff --git a/GPX-Aufzeichnung.md b/GPX-Aufzeichnung.md new file mode 100644 index 0000000..0ee9a41 --- /dev/null +++ b/GPX-Aufzeichnung.md @@ -0,0 +1,80 @@ +# GPX und Wegaufzeichnung + +## Stationen und Weg getrennt speichern + +- Stationen mit Koordinaten stehen als `wpt` unmittelbar unter `gpx`. +- Beim ersten Speichern eines neuen Entwurfs startet eine unabhängige Aufzeichnung: + sofort eine Position, danach alle 30 Sekunden eine neue GPS-Abfrage. + Das Schließen des Bearbeitungsfensters beendet die Aufzeichnung nicht. +- Die sichtbaren Schaltflächen erlauben Pausieren, Fortsetzen und Beenden. + Beim Löschen des Entwurfs endet die Aufzeichnung ebenfalls. +- Tatsächliche Positionen stehen als `trk/trkseg/trkpt` mit Zeitstempel und, + falls vorhanden, Höhe in der Datei des Entwurfs. +- Ohne Standort entsteht eine Erweiterung `wegwichtel:gpsGap` mit Zeitstempel. + Eine Station ohne Standort steht als `wegwichtel:pendingWaypoint` in den + Erweiterungen. Es werden keine Koordinaten wie `0,0` erfunden. Leere GPX- + `wpt` oder `trkpt` wären ungültig, daher werden Erweiterungen verwendet. + +Die Aufzeichnung speichert immer die neueste Fassung des Entwurfs, damit +gleichzeitige Änderungen an Stationen erhalten bleiben. Auch eine manuelle +Bearbeitung ergänzt die neueste Aufzeichnung und bewahrt inzwischen hinzugekommene +Positionen. Speicherfehler pausieren die Aufzeichnung und zeigen einen Hinweis. + +## Später Koordinaten ergänzen + +Entwürfe und Stationen dürfen ohne GPS erstellt und gespeichert werden. +In der Streckenbearbeitung können unter „Orte auf dem Weg ergänzen“ fehlende +Positionen ergänzt, vorhandene geändert und weitere hinzugefügt werden. +Breiten- und Längengrad müssen gemeinsam angegeben werden oder beide leer bleiben. +Stationen haben eigene Felder unter „Ort ergänzen“. + +Die Serverdatenbank erlaubt `NULL` in `pois.lat` und `pois.lon`. +`route_points` enthält ausschließlich gültige Koordinaten. Die Änderung ist in +`database/schema.sql` enthalten; eine bestehende Datenbank bekommt sie nicht +allein durch das Kopieren des Codes. Das Projekt verwendet beim Deployment +die vereinbarte Neuerstellung der Datenbank, keine automatische Migration. + +## Normalisierung auf dem Server + +Beim Einreichen oder Ersetzen einer GPX-Datei normalisiert der Node-Dienst den +aufgezeichneten Weg zu `rte/rtept`. Bereits vorhandene Routenpunkte haben Vorrang. +Aufeinanderfolgende Trackpunkte innerhalb des eingestellten Abstands werden +zusammengefasst. Der erste Punkt bleibt der Vertreter; der nächste wird erst +übernommen, wenn er weiter entfernt liegt. So werden kleine Schwankungen +während einer Pause nicht fortlaufend zur Weglänge addiert. + +`GPX_CLUSTER_METERS` in `.env` steuert den Abstand, standardmäßig 20 Meter. +Das Verfahren ist eine geometrische Vereinfachung ohne externen Routingdienst. +Es berechnet keine Wege auf einer Straßenkarte. GPS-Lücken werden bei der +Routenberechnung übersprungen; zwischen gültigen Nachbarpunkten entsteht eine +direkte Verbindung. Die Lücken bleiben für spätere Bearbeitung als Erweiterungen +erhalten. Gibt es keine gültigen Wegpositionen, bleiben Weglänge und Punktzahl +bei 0 Metern und 0 Punkten; Stationen bleiben trotzdem gespeichert. + +Die gespeicherte Serverdatei enthält den normalisierten Weg. Änderungen an +Stationen synchronisieren die `wpt` und die noch unvollständigen Stationen in +dieser Datei. GPX verwendet unverändert den Namespace +`http://www.topografix.com/GPX/1/1`; das ist eine Kennung, kein Netzwerkabruf. + +## Anzeige beim Abspielen + +Zwei Pfeile zeigen gleichzeitig die Richtung: + +- zum nächsten Routenpunkt, ohne Entfernungsangabe; +- zur nächsten noch offenen Station mit Koordinaten, mit Metern. + +Bei einer Strecke mit ausschließlich Stationen fehlt nur die Wegführung. +Stationen ohne Koordinaten lösen keine Standort-Erkennung aus. Während einer +Pause werden die Pfeile verborgen. + +## Grenzen und Prüfung + +Die Aufzeichnung läuft nur im geöffneten App-Fenster. Neuladen beendet sie; +bereits gespeicherte Positionen bleiben erhalten. Browser und Betriebssystem +können Zeitgeber und Standort-Abfragen im Hintergrund oder bei gesperrtem +Bildschirm verzögern. Ein verlässlicher Hintergrunddienst wird nicht zugesichert. +Bei verweigerter Standort-Erlaubnis bleiben die Einträge leer und bearbeitbar. + +Die Tests prüfen Aufzeichnung, Pause/Fortsetzen/Ende, verspätete GPS-Antworten, +Speicherfehler, GPS-Lücken, manuelle Ergänzungen, getrennte Pfeile, +Servernormalisierung sowie Stationen ohne Koordinaten über die API. diff --git a/I18N.md b/I18N.md index e7ee354..ba8b64b 100644 --- a/I18N.md +++ b/I18N.md @@ -35,6 +35,13 @@ HTML verwendet `data-i18n`, `data-i18n-title`, `data-i18n-aria-label`, stehen Beschriftungen bereits vor dem Laden der Skripte bereit. Nach Änderungen die erzeugte `public/index.html` mit ins Repository übernehmen. +Impressum und Datenschutzerklärung sind davon ausgenommen. Ihre rechtlichen +Texte und Kontaktangaben stehen ausschließlich statisch auf Deutsch in +`public/index.html`, ohne i18n-Attribute und mit `lang="de"` an beiden Artikeln. +Änderungen an diesen Angaben direkt im HTML vornehmen. Auch die zugehörigen +Dialogüberschriften bleiben auf Deutsch. Die Menüeinträge und die übrige +Bedienoberfläche werden weiterhin übersetzt. + Zum gezielten Prüfen kann `Wegwichtel.I18n.setLanguage('en')` aufgerufen werden. Das aktualisiert feste Beschriftungen. Bereits aufgebaute dynamische Ansichten müssen neu aufgebaut werden; die automatische Startauswahl erfolgt vor ihrem diff --git a/README.md b/README.md index 4b78b27..5bf3f28 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Neuaufbau des früheren schiffsbezogenen Ansagesystems als mobile Lernweg-Anwend ## Architektur +Die Trennung von Stationen und Weg, die Aufzeichnung alle 30 Sekunden und das +nachträgliche Ergänzen fehlender Koordinaten sind in +[`GPX-Aufzeichnung.md`](GPX-Aufzeichnung.md) beschrieben. + - **Server:** Node.js 22.13+, Express 5, `node:sqlite`, Multer, `fast-xml-parser`, `file-type` und `@file-type/av` - **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 diff --git a/database/schema.sql b/database/schema.sql index 3c959b3..24b4485 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -50,8 +50,8 @@ CREATE TABLE IF NOT EXISTS pois ( route_id INTEGER NOT NULL REFERENCES routes(id), title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', - lat REAL NOT NULL, - lon REAL NOT NULL, + lat REAL, + lon REAL, trigger_radius_m REAL NOT NULL DEFAULT 80, sequence INTEGER NOT NULL DEFAULT 0, audio_path TEXT, diff --git a/public/css/app.css b/public/css/app.css index 1eaef60..06f70a2 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -456,7 +456,7 @@ button, left: 0; z-index: 40; display: grid; - grid-template-columns: 3.75rem minmax(0, 1fr); + grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: center; min-height: var(--footer-height); box-sizing: border-box; @@ -468,7 +468,8 @@ button, box-shadow: 0 -.25rem .8rem rgba(0, 0, 0, .2); backdrop-filter: blur(.4rem); } -#navigation-arrow { +#navigation-arrow, +#route-navigation-arrow { width: 3.5rem; height: 3.5rem; fill: currentColor; @@ -484,7 +485,8 @@ button, margin: 0; } -#navigation-mode { +#navigation-mode, +#route-navigation-mode { overflow: hidden; font-size: .78rem; text-overflow: ellipsis; @@ -845,3 +847,25 @@ button, padding: .65rem; } } + +.navigation-target { + display: grid; + grid-template-columns: 2.8rem minmax(0, 1fr); + align-items: center; + gap: .35rem; + min-width: 0; +} +.navigation-target svg { width: 2.7rem; height: 2.7rem; } +#route-recording-control { + position: fixed; + top: calc(env(safe-area-inset-top) + var(--icon-button-size) + 1rem); + left: max(.5rem, env(safe-area-inset-left)); + right: max(.5rem, env(safe-area-inset-right)); + z-index: 45; + padding: .5rem; + border-radius: .7rem; + background: #f8f5eb; + box-shadow: 0 .25rem 1rem rgba(0, 0, 0, .2); +} +#route-recording-status { display: block; margin-bottom: .35rem; } +#route-recording-control button { min-height: 2.75rem; } diff --git a/public/css/editor.css b/public/css/editor.css index a26889d..235df78 100644 --- a/public/css/editor.css +++ b/public/css/editor.css @@ -58,6 +58,17 @@ color: #233326; box-shadow: 0 .8rem 2rem rgba(20, 32, 22, .28); touch-action: pan-y; + transform-origin: top right; + transform: scaleY(0); + opacity: 0; + pointer-events: none; + transition: transform 180ms ease-out, opacity 180ms ease-out; +} + +.route-control-dropdown.is-open { + transform: scaleY(1); + opacity: 1; + pointer-events: auto; } .route-control-dropdown section + section { diff --git a/public/index.html b/public/index.html index 4fbdf07..64908fe 100644 --- a/public/index.html +++ b/public/index.html @@ -94,7 +94,7 @@
Hier gibt es keine Werbung. Wir verfolgen nicht, welche anderen Seiten du besuchst.
+Hier gibt es keine Werbung. Wir verfolgen nicht, welche anderen Seiten du besuchst.