Files
Paddy d418f156fa German date display + optional street address for precise geocoding
- Date pickers (entry form + edit dialog) now display TT.MM.JJJJ instead of
  ISO (DATE_DISPLAY_FORMAT). Storage stays YYYY-MM-DD; parse_date() already
  accepted both formats, so existing data and the self-updater are unaffected.
- New optional "Straße" field (street + house number) in the entry form and
  edit dialog, backed by a new `street` CSV column. geocode_city() and
  GeocoderWorker.enqueue() gained a street parameter: when set, a full-address
  query is tried first for a much more precise map point, falling back
  automatically to the existing city/PLZ search if it doesn't resolve.
- Tab 2 and the entry queue show a Straße column; Tab 2 search now also
  matches on street.
- Fix: pandas turns a blank CSV cell into NaN even for a dtype=str column, so
  every existing (blank-street) row would have shown literal "nan" in Tab 2.
  DataStore._load now does street.fillna("") after every read.

Verified with a non-GUI test suite (date parsing, query construction, CSV
round-trip incl. the NaN case) and a full GUI build/drive test on Python
3.12/Tk 9. Docs updated (changelog, overview, architecture, data-model,
dev-notes, improvements).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 11:15:37 +02:00

6.4 KiB
Raw Permalink Blame History

Overview what the app does

There is a Hilfe menu with "Nach Update suchen" (self-update via git, see setup.md) and "Version…".

The app is a single window: a DRK-red header bar, two tabs, and a colour-coded status bar at the bottom (grey = neutral, green = success, orange = warning, red = error; transient messages fade back to neutral after ~8 s). The window title shows the total entry count, and the window remembers its size and position between sessions (data/window.json). The UI font is enlarged for readability (UI_FONT_SIZE).

Tab 1 "Neuer Eintrag" (new entry)

Workflow: build up a queue of entries, let them geocode in the background, then save the whole batch at once.

  1. Entry form date (a calendar picker, ttkbootstrap.DateEntry, pre-filled with today and displayed as TT.MM.JJJJ, e.g. 12.09.2026; typed input also accepts JJJJ-MM-TT and is validated an invalid date is rejected with a dialog), city (autocomplete combobox), postal code (optional, digits only), and Straße (street + house number, optional, second row of the form) — see Geocoding behaviour for why it's worth filling in. Pressing Return in any field, or the + Hinzufügen button, adds the row to the queue.
  2. Queue table shows date, city, PLZ, street, and a live coordinate column that updates from ⏳ wird gesucht… to either lat / lon or ⚠ nicht gefunden as the background geocoder works through the queue.
    • Select a row and press Delete, use the "Auswahl entfernen" button, or click the cell.
    • Right-click a row for Entfernen / Koordinaten erneut suchen.
  3. Action bar
    • Alle speichern appends every queued row to orte.csv. If any row looks like a duplicate (same date + city + PLZ), a modal lists them and asks whether to save anyway.
    • Warteschlange leeren discards the queue (asks for confirmation).
    • Karte öffnen regenerates karte.html and opens it in the browser.

Tab 2 "Einträge verwalten" (manage entries)

A table view of everything in orte.csv.

  • Columns: Datum, Ort, PLZ, Straße, and Karte a status column showing when the row has coordinates or a red fehlt when it doesn't (the whole row is red too). The raw lat/lon numbers live in the edit dialog, not this table.
  • Search box live filter across date, city, PLZ, and street (substring match).
  • Sortable columns click a header to sort; clicking again reverses. The active column shows a ▲/▼ arrow. Default sort is by date, newest first. Sorting by Karte groups the rows without coordinates together.
  • "Nur ohne Koordinaten" toggle filters to entries that have no coordinates yet. The hint line always shows the total count, plus how many are missing coordinates.
  • Edit double-click a row (or Bearbeiten) opens a modal dialog to change date (calendar picker, TT.MM.JJJJ, validated) / city / PLZ / Straße. Two ways to fix coordinates: a "Koordinaten automatisch neu suchen" toggle re-runs geocoding after saving (now using the street if one is set), or the Breitengrad / Längengrad fields let you type them in by hand (both empty = no map marker).
  • Right-click → Koordinaten suchen runs geocoding for that one row (handy for rows that failed the first time).
  • Delete Löschen removes the selected row(s) after a confirmation dialog. This is irreversible, but a timestamped copy of the file is written to data/backups/ before every change (see data-model.md).
  • Karte öffnen same as on Tab 1.

The map (karte.html)

Generated by generate_map():

  • Base layer: Esri "World Light Gray" (MAP_TILES + a labels overlay) a light, muted basemap that needs no API key and no Referer. Initial view centred on [49.0, 9.0], zoom 8; auto-fits to the markers when there is more than one.
  • One CircleMarker per unique (city, lat, lon) group. Radius scales linearly with the visit count for that location (5 + count / max_count * 20 px), colour is DRK red (#CC0000).
  • Popup shows the city and the visit count; tooltip shows the city.
  • Rows with missing/blank coordinates are silently excluded.
  • If there are no usable coordinates at all, a dialog says so (and the status bar shows a warning).

The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, and the map tiles from server.arcgisonline.com, so the map needs an internet connection to render even though the data is local. To change the look, edit MAP_TILES / MAP_TILES_LABELS near the top of app.py — the comment there lists other no-key Esri layers (Street, Topo). tile.openstreetmap.org was tried first but its tile-usage policy 403s pages opened from file:// (no Referer); CartoDB positron needs an API key.

Geocoding behaviour

geocode_city(city, postal_code, street="") tries a series of queries in order and returns the first hit:

  1. "<street>, <PLZ> <city>, Germany" — only if a street was given (with PLZ if there is one, without it if not). This is what the optional Straße field is for: a full address geocodes far more precisely than a city name alone (a point in the right town vs. the right building).
  2. "<PLZ> <city>, Germany" (only if a PLZ was given)
  3. "<city>, Baden-Württemberg, Germany"
  4. "<city>, Hessen, Germany"
  5. "<city>, Germany"

If the street-level query (1) doesn't match anything in Nominatim (typo, house number Nominatim doesn't know, etc.), it falls back automatically to the same city/PLZ-level searches used when no street is given — a bad street never makes geocoding worse than before, only better when it resolves.

The regional bias is the GEOCODE_REGIONS constant (Baden-Württemberg, Hessen) near the top of app.py. Results are rounded to five decimal places. Nominatim's usage policy (max 1 req/s, identifying user_agent) is respected by a manual time.sleep(1) in the worker loop.

If the geocoding service is unreachable (no internet), entries still save without coordinates and after a few consecutive failures a one-time dialog explains that the coordinates can be added later via Bearbeiten. The worker thread logs failures to data/app.log and keeps running.