Fahrtenliste zeigt Orte aus dem Zwischenspeicher (2026.9.1.23)

Was die Einzelfahrt einmal aufgeloest hat, zeigt die Liste umsonst mit:
ortAusCache() liest nur den Zwischenspeicher und fragt nie das Netz. Ein Abruf
je Zeile waere genau der Vorratsabruf, um dessen Unterlassung Nominatim bittet.

In der Liste steht nur der ORT, nicht die Anschrift (Wunsch des Eigentuemers):
eine Zeile traegt Datum, Art, Strecke und Verbrauch, zwei volle Anschriften
passen dort nicht. Auf der Einzelfahrt bleibt die vollstaendige Anschrift.

Der Ortsname wird beim Aufloesen separat gemerkt ("ort:"-Schluessel), nicht aus
der Anschrift geschnitten - faellt Nominatim auf display_name zurueck, stuende
hinter dem letzten Komma das LAND.

Der Rueckfall war trotzdem noetig, und das Ausliefern hat es gezeigt: die Liste
stand weiter voller Anschriften, weil der Ortsname nur bei einem frischen Abruf
entsteht und die Adressen laengst im Speicher lagen. stadtAusAnschrift()
schneidet ihn deshalb aus einer gespeicherten Anschrift - aber nur, wenn hinter
dem letzten Komma eine Postleitzahl steht. Damit greift es bei unserem Format
"<Strasse>, <PLZ> <Ort>" und niemals bei display_name.

VERIFIZIERT: 7 Faelle gegen den Schnitt (mit/ohne Strasse, benannter Platz,
ohne PLZ, display_name-Form, null), Panel als Modul, tsc --noEmit und
vite build sauber, 165/165 Tests gruen. Im Browser: "Eichstaett -> Adelschlag"
in der Liste, volle Anschrift auf der Einzelfahrt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 20:36:24 +02:00
parent 0f680780a4
commit ce287ccac2
7 changed files with 193 additions and 8 deletions
+65 -1
View File
@@ -77,6 +77,67 @@ function cacheSchreiben(suche: string, treffer: Koordinate | null): void {
* Koordinatenpaar. Auf drei Nachkommastellen gerundet (~110 m), damit ein
* paar Meter Abweichung keinen neuen Abruf auslösen.
*/
/** Ein bereits aufgeloester Ort aus dem Zwischenspeicher - OHNE Netzabruf.
*
* Die Einzelfahrt loest Koordinaten bei Bedarf auf und legt das Ergebnis im
* selben Fach ab wie die Standortansicht. Was einmal dort steht, kann die
* Liste umsonst mitbenutzen: kein zusaetzlicher Abruf, keine Wartezeit, kein
* Flackern beim Zeichnen.
*
* Bewusst NUR aus dem Speicher. Ein Abruf je Zeile waere genau der
* Vorratsabruf, um dessen Unterlassung Nominatim in seinen
* Nutzungsbedingungen bittet - bei zweihundert Fahrten waeren das
* vierhundert Anfragen fuer einen Blick auf die Liste.
*
* Schluessel wie bei der Aufloesung: drei Nachkommastellen (~110 m). */
/** Der Ortsname allein, fuer die Fahrtenliste.
*
* In der Liste steht eine Fahrt in einer Zeile neben Datum, Art, Strecke und
* Verbrauch. Zwei volle Anschriften passen dort nicht - "Schottenau, 85072
* Eichstaett -> Am Anger 7a, 85111 Adelschlag" ist laenger als der Rest der
* Zeile zusammen. Auf der Einzelfahrt bleibt die vollstaendige Anschrift.
*
* Der Ort wird beim Aufloesen SEPARAT gemerkt (Schluessel "ort:"), nicht
* nachtraeglich aus der Anschrift geschnitten. Das Zerlegen der eigenen
* Ausgabe waere fast immer richtig und in einem Fall falsch: faellt Nominatim
* auf display_name zurueck, steht hinter dem letzten Komma das LAND. Dann
* stuende in der Liste "Deutschland -> Deutschland". */
/** Der Ortsname aus einer bereits gespeicherten Anschrift.
*
* Rueckfall fuer alles, was VOR dem Merken des Ortsnamens aufgeloest wurde:
* dort liegt nur die volle Anschrift, und ein neuer Netzabruf, nur um den Ort
* zu erfahren, waere Verschwendung.
*
* Geschnitten wird ausschliesslich, wenn hinter dem letzten Komma eine
* Postleitzahl steht - unser eigenes Format ist "<Strasse>, <PLZ> <Ort>".
* Faellt Nominatim auf display_name zurueck, fehlt die PLZ und hinter dem
* letzten Komma stuende das LAND; dann lieber die volle Anschrift zeigen als
* "Deutschland". */
export function stadtAusAnschrift(anschrift: string | null | undefined): string | null {
if (!anschrift) return null
const teil = String(anschrift).split(", ").pop()?.trim() ?? ""
const treffer = teil.match(/^\d{4,5}\s+(.+)$/)
return treffer && treffer[1] ? treffer[1].trim() : null
}
export function stadtAusCache(
lat: number | null | undefined,
lon: number | null | undefined,
): string | null {
if (lat === null || lat === undefined || lon === null || lon === undefined) return null
const wert = cacheLesen()[`ort:${lat.toFixed(3)},${lon.toFixed(3)}`]
return typeof wert === "string" && wert ? wert : null
}
export function ortAusCache(
lat: number | null | undefined,
lon: number | null | undefined,
): string | null {
if (lat === null || lat === undefined || lon === null || lon === undefined) return null
const wert = cacheLesen()[`rev:${lat.toFixed(3)},${lon.toFixed(3)}`]
return typeof wert === "string" ? wert : null
}
export async function koordinatenAufloesen(lat: number, lon: number): Promise<string | null> {
const schluessel = `rev:${lat.toFixed(3)},${lon.toFixed(3)}`
const cache = cacheLesen()
@@ -96,7 +157,8 @@ export async function koordinatenAufloesen(lat: number, lon: number): Promise<st
}
const a = daten.address ?? {}
const strasse = [a["road"], a["house_number"]].filter(Boolean).join(" ")
const ort = [a["postcode"], a["city"] ?? a["town"] ?? a["village"]].filter(Boolean).join(" ")
const stadt = (a["city"] ?? a["town"] ?? a["village"] ?? "").trim()
const ort = [a["postcode"], stadt].filter(Boolean).join(" ")
/* Steht das Auto auf einem benannten Platz — Parkplatz, Parkhaus,
Rastplatz, Ladesäule —, sagt dessen Name mehr als die nächstgelegene
Hausnummer: "Parkhaus Westentor" statt "Rathausstraße 4". Nominatim
@@ -111,6 +173,8 @@ export async function koordinatenAufloesen(lat: number, lon: number): Promise<st
// Nur echte Treffer merken: eine gedrosselte Antwort ist kein "hier gibt
// es keine Adresse", die soll beim nächsten Mal erneut versucht werden.
if (adresse) adresseMerken(schluessel, adresse)
// Der reine Ortsname zusätzlich, für die Fahrtenliste — siehe stadtAusCache().
if (stadt) adresseMerken(`ort:${lat.toFixed(3)},${lon.toFixed(3)}`, stadt)
return adresse
} catch {
return null
+26 -2
View File
@@ -9,6 +9,7 @@ import { Accordion, ActionButton, Feld, Tile } from "@audi-dash/ui"
import { useDaten } from "../daten/DatenKontext"
import { nachJahrUndMonat } from "../daten/statistik"
import { ortAusCache, stadtAusAnschrift, stadtAusCache } from "../daten/geokodierung"
import { datum, de, streckeText, summe } from "../format"
import type { SeitenName } from "../navigation"
import { Blattzeile, Leerzustand } from "./bausteine"
@@ -20,8 +21,31 @@ import { Zeilenmenue } from "./Zeilenmenue"
"Fahrt ohne Ortsangabe". Trug bisher in der Liste hier gar nichts bei
(nur auf der Einzelfahrt-Seite sichtbar) - im Panel ist das die
führende Information jeder Fahrtzeile. */
function strecke(fahrt: { start_address?: string | null; end_address?: string | null }): string | null {
const a = fahrt.start_address, b = fahrt.end_address
function strecke(fahrt: {
start_address?: string | null
end_address?: string | null
start_lat?: number | null
start_lon?: number | null
end_lat?: number | null
end_lon?: number | null
}): string | null {
// Was die Einzelfahrt schon einmal aufgeloest hat, zeigt die Liste umsonst
// mit - siehe ortAusCache(), kein zusaetzlicher Abruf.
// Ortsname bevorzugt; ohne ihn die volle Anschrift, damit eine vor dieser
// Aenderung aufgeloeste Fahrt nicht ploetzlich wieder ortlos dasteht.
const ortsname = (
adresse: string | null | undefined,
lat: number | null | undefined,
lon: number | null | undefined,
): string | null => {
if (adresse) return adresse
const gemerkt = stadtAusCache(lat, lon)
if (gemerkt) return gemerkt
const voll = ortAusCache(lat, lon)
return stadtAusAnschrift(voll) ?? voll
}
const a = ortsname(fahrt.start_address, fahrt.start_lat, fahrt.start_lon)
const b = ortsname(fahrt.end_address, fahrt.end_lat, fahrt.end_lon)
if (!a && !b) return "Fahrt ohne Ortsangabe"
if (!b) return a ?? null
if (!a) return b