diff --git a/km_berechnen/.gitignore b/km_berechnen/.gitignore new file mode 100644 index 0000000..0e754e3 --- /dev/null +++ b/km_berechnen/.gitignore @@ -0,0 +1,2 @@ +.env +geocode-cache.json diff --git a/km_berechnen/README.md b/km_berechnen/README.md new file mode 100644 index 0000000..7e5d1f8 --- /dev/null +++ b/km_berechnen/README.md @@ -0,0 +1,57 @@ +# km_berechnen + +Berechnet die **Straßenentfernung (km)** zwischen der fixen **Sternwarte Welzheim** +und dem Wohnort jedes BEOs (Tabelle `beos`) und schreibt das Ergebnis in die +vorhandene Spalte `beos.km`. + +Der DB-Zugriff läuft über den **PHP-Proxy** `DB4js_all.php`, da die echte +Sternwarte-Datenbank nicht direkt erreichbar ist: + +- Lesen: `cmd = LB_GET_BEO_ADRESSEN` (BEOs mit Adresse + km) +- Schreiben: `cmd = LB_UPDATE_BEO_KM` `{ id, km }` + +Distanzberechnung: + +- **Geocoding:** OpenStreetMap [Nominatim](https://nominatim.openstreetmap.org) (kostenlos, ohne API-Key) +- **Routing:** öffentlicher [OSRM](https://project-osrm.org)-Server (kostenlos, ohne API-Key) + +## Einrichtung + +1. `.env` öffnen und `PHP_DB_URL` auf den DB4js-Endpunkt setzen + (lokal: `http://localhost:8080/DB4js_all.php`). Falls der Proxy Basic-Auth + verlangt, zusätzlich `API_USER` / `API_PASS` eintragen. +2. Optional den exakten Referenzpunkt der Sternwarte setzen + (`STERNWARTE_LAT` / `STERNWARTE_LON`) — sonst wird er per Nominatim ermittelt, + mit Ortsmitte Welzheim als Fallback. + +Es werden nur Node-Bordmittel verwendet (globales `fetch`) — kein +`npm install` nötig (Node ≥ 18). + +## Ausführen + +```bash +cd km_berechnen +node --env-file=.env berechne-km.mjs # berechnen und in beos.km schreiben +node --env-file=.env berechne-km.mjs --dry-run # nur berechnen/anzeigen, DB unverändert +``` + +## Hinweise + +- Geocoding-Ergebnisse werden in `geocode-cache.json` zwischengespeichert; ein + zweiter Lauf ist dadurch deutlich schneller. Zum Neu-Geocodieren die Datei löschen. +- Nominatim erlaubt max. 1 Anfrage/Sekunde — das Skript hält dies automatisch ein. +- BEOs ohne (vollständige) Adresse bzw. ohne Geocoding-/Routing-Treffer erhalten + `km = NULL` und werden am Ende aufgelistet. +- Die öffentlichen Nominatim-/OSRM-Server sind „Fair Use"; für die ~20 BEOs + unproblematisch. +- Die beiden Proxy-Kommandos (`LB_GET_BEO_ADRESSEN`, `LB_UPDATE_BEO_KM`) sind in + `DB4js_all.php` implementiert. + +## Ergebnis prüfen + +Per Proxy: + +```bash +curl -s -X POST "$PHP_DB_URL" -H 'Content-Type: application/json' \ + -d '{"cmd":"LB_GET_BEO_ADRESSEN"}' +``` diff --git a/km_berechnen/beo-entfernungen.html b/km_berechnen/beo-entfernungen.html new file mode 100644 index 0000000..3660a07 --- /dev/null +++ b/km_berechnen/beo-entfernungen.html @@ -0,0 +1,151 @@ + +BEO-Entfernungen + + +

Entfernungen der BEOs zur Sternwarte Welzheim

+

Einfache Straßenentfernung in Kilometern · Stand: 10. Juli 2026

+

+ Die folgende Tabelle listet die einfache Straßenentfernung (Auto) von deinem + Wohnort zur Sternwarte Welzheim. Die Werte wurden automatisch über + OpenStreetMap / OSRM ermittelt. Bitte prüfe deinen Wert und melde + ihn in jedem Fall zurück – auch wenn er stimmt. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#NameWohnortkm
1Ralf BernhardStuttgart53.0
2Steffen BrücknerWeil im Schönbuch68.6
3Hans DschidaUrbach15.3
4Andrea EssRemshalden23.8
5Ulrich FörnzlerStuttgart39.3
6Reinhard FuerstStuttgart43.3
7Martin GertzWeinstadt26.8
8Hubert GräberRemshalden19.1
9Marc GüssmannBacknang22.3
10Rudolf IdlerKernen29.3
11Hans-Ulrich KellerStuttgart40.8
12Olaf Meyer-HammeWinterbach19.0
13Xeno NastosAlfdorf14.7
14Jörg NikolaizigLudwigsburg46.9
15Christoph RiedlAichwald33.7
16Eva SchneiderWelzheim8.1
17Bernd SchulerSchorndorf14.5
18Christoph WeishaarWaiblingen-Bittenfeld43.0
19Matthias ZollerSindelfingen60.6
+

19 Beobachter · Sternwarte Welzheim · automatisch erzeugt

+ \ No newline at end of file diff --git a/km_berechnen/beo-entfernungen.pdf b/km_berechnen/beo-entfernungen.pdf new file mode 100644 index 0000000..bef6018 Binary files /dev/null and b/km_berechnen/beo-entfernungen.pdf differ diff --git a/km_berechnen/berechne-km.mjs b/km_berechnen/berechne-km.mjs new file mode 100644 index 0000000..6433e49 --- /dev/null +++ b/km_berechnen/berechne-km.mjs @@ -0,0 +1,201 @@ +// Berechnet die Straßenentfernung (km) zwischen der fixen Sternwarte Welzheim +// und dem Wohnort jedes BEOs und schreibt das Ergebnis in die Spalte beos.km. +// +// Der DB-Zugriff läuft über den PHP-Proxy (DB4js_all.php), da die echte +// Sternwarte-Datenbank nicht direkt erreichbar ist: +// Lesen: cmd LB_GET_BEO_ADRESSEN +// Schreiben: cmd LB_UPDATE_BEO_KM { id, km } +// +// Geocoding: OpenStreetMap Nominatim | Routing: öffentlicher OSRM-Server. +// Beide kostenlos, ohne API-Key. Ausführen mit: +// node --env-file=.env berechne-km.mjs +// +// Optionen: +// --dry-run nichts in die DB schreiben, nur berechnen und anzeigen + +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const CACHE_FILE = join(__dirname, 'geocode-cache.json'); +const USER_AGENT = 'sternwarte-welzheim-km-berechnen/1.0 (rxf@gmx.de)'; +const DRY_RUN = process.argv.includes('--dry-run'); + +const PHP_DB_URL = process.env.PHP_DB_URL || 'http://localhost:8080/DB4js_all.php'; + +// Fallback-Referenzpunkt: Ortsmitte Welzheim (nur Näherung, per .env überschreibbar) +const DEFAULT_STERNWARTE = { lat: 48.8756, lon: 9.6377 }; + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +// ---- PHP-Proxy (DB4js) ---- + +async function callProxy(cmd, params = {}) { + const headers = { 'Content-Type': 'application/json' }; + if (process.env.API_USER && process.env.API_PASS) { + const token = Buffer.from(`${process.env.API_USER}:${process.env.API_PASS}`).toString('base64'); + headers.Authorization = `Basic ${token}`; + } + const res = await fetch(PHP_DB_URL, { + method: 'POST', + headers, + body: JSON.stringify({ cmd, ...params }), + }); + const text = await res.text(); + let json; + try { + json = JSON.parse(text); + } catch { + throw new Error(`DB4js ${cmd} HTTP ${res.status}: ${text.slice(0, 200)}`); + } + if (json && typeof json === 'object' && !Array.isArray(json) && 'error' in json) { + throw new Error(`DB4js ${cmd}: ${json.error}`); + } + if (!res.ok) throw new Error(`DB4js ${cmd} HTTP ${res.status}`); + return json; +} + +// ---- Geocode-Cache (Adresse -> {lat, lon} | null) ---- + +async function loadCache() { + try { + return JSON.parse(await readFile(CACHE_FILE, 'utf8')); + } catch { + return {}; + } +} + +async function saveCache(cache) { + await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf8'); +} + +// ---- Nominatim Geocoding (max. 1 Anfrage/Sekunde) ---- + +async function geocode(query, cache) { + if (Object.prototype.hasOwnProperty.call(cache, query)) { + return cache[query]; // Treffer (auch gecachte Fehlschläge = null) + } + const url = + 'https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' + + encodeURIComponent(query); + const res = await fetch(url, { headers: { 'User-Agent': USER_AGENT } }); + if (!res.ok) throw new Error(`Nominatim HTTP ${res.status}`); + const json = await res.json(); + const hit = Array.isArray(json) && json[0] + ? { lat: parseFloat(json[0].lat), lon: parseFloat(json[0].lon) } + : null; + cache[query] = hit; + await saveCache(cache); + await sleep(1100); // Rate-Limit einhalten + return hit; +} + +// ---- OSRM Straßenrouting -> Distanz in km ---- + +async function roadKm(from, to) { + const url = + `https://router.project-osrm.org/route/v1/driving/` + + `${from.lon},${from.lat};${to.lon},${to.lat}?overview=false`; + const res = await fetch(url, { headers: { 'User-Agent': USER_AGENT } }); + if (!res.ok) throw new Error(`OSRM HTTP ${res.status}`); + const json = await res.json(); + if (json.code !== 'Ok' || !json.routes?.[0]) return null; + return Math.round((json.routes[0].distance / 1000) * 10) / 10; +} + +function buildAddress(row) { + const parts = [row.Adresse, [row.Plz, row.Ort].filter(Boolean).join(' ').trim()] + .map((s) => (s || '').trim()) + .filter(Boolean); + return parts.length ? parts.join(', ') + ', Deutschland' : ''; +} + +async function resolveSternwarte(cache) { + const envLat = parseFloat(process.env.STERNWARTE_LAT ?? ''); + const envLon = parseFloat(process.env.STERNWARTE_LON ?? ''); + if (Number.isFinite(envLat) && Number.isFinite(envLon)) { + return { lat: envLat, lon: envLon, source: '.env' }; + } + const hit = await geocode('Sternwarte, Welzheim, Deutschland', cache); + if (hit) return { ...hit, source: 'Nominatim' }; + return { ...DEFAULT_STERNWARTE, source: 'Fallback (Ortsmitte Welzheim)' }; +} + +async function main() { + const cache = await loadCache(); + + const ref = await resolveSternwarte(cache); + console.log(`Proxy: ${PHP_DB_URL}`); + console.log( + `Referenzpunkt Sternwarte Welzheim: ${ref.lat}, ${ref.lon} [${ref.source}]\n` + ); + + const rows = await callProxy('LB_GET_BEO_ADRESSEN'); + + const done = []; + const skipped = []; + + for (const row of rows) { + const address = buildAddress(row); + if (!address) { + skipped.push({ row, reason: 'keine Adresse' }); + if (!DRY_RUN) await callProxy('LB_UPDATE_BEO_KM', { id: row.ID, km: null }); + continue; + } + + let km = null; + let reason = ''; + try { + const geo = await geocode(address, cache); + if (!geo) { + reason = 'Geocoding ohne Treffer'; + } else { + km = await roadKm(ref, geo); + if (km == null) reason = 'keine Route gefunden'; + } + } catch (err) { + reason = err.message; + } + + if (!DRY_RUN) await callProxy('LB_UPDATE_BEO_KM', { id: row.ID, km }); + + if (km == null) skipped.push({ row, reason }); + else done.push({ row, km }); + } + + // ---- Zusammenfassung ---- + console.log('Berechnete Entfernungen:'); + console.log('Kürzel Name Ort km'); + console.log('─'.repeat(66)); + done + .sort((a, b) => a.km - b.km) + .forEach(({ row, km }) => { + console.log( + (row.Kuerzel || '').padEnd(7) + + ' ' + + (row.Name || '').padEnd(24) + + ' ' + + (row.Ort || '').padEnd(24) + + ' ' + + String(km).padStart(6) + ); + }); + + if (skipped.length) { + console.log('\nÜbersprungen / ohne Ergebnis (km = NULL):'); + skipped.forEach(({ row, reason }) => + console.log(` ${(row.Kuerzel || '?').padEnd(7)} ${(row.Name || '').padEnd(24)} — ${reason}`) + ); + } + + console.log( + `\nFertig. ${done.length} berechnet, ${skipped.length} übersprungen.` + + (DRY_RUN ? ' (DRY-RUN: nichts in die DB geschrieben)' : ' In Spalte beos.km gespeichert.') + ); +} + +main().catch((err) => { + console.error('Fehler:', err.message); + process.exit(1); +}); diff --git a/km_berechnen/erzeuge-pdf.mjs b/km_berechnen/erzeuge-pdf.mjs new file mode 100644 index 0000000..4fb58d3 --- /dev/null +++ b/km_berechnen/erzeuge-pdf.mjs @@ -0,0 +1,112 @@ +// Erzeugt eine PDF-Tabelle aller BEOs mit Wohnort und Straßenentfernung (km) +// zur Sternwarte Welzheim — zum Versand an die BEOs zur Verifizierung. +// +// Daten kommen über den PHP-Proxy (cmd GET_BEOS). Das HTML wird mit Headless- +// Chrome nach PDF gerendert. +// node --env-file=.env erzeuge-pdf.mjs +// +// Ergebnis: beo-entfernungen.html und beo-entfernungen.pdf + +import { writeFile } from 'node:fs/promises'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const execFileP = promisify(execFile); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const HTML_FILE = join(__dirname, 'beo-entfernungen.html'); +const PDF_FILE = join(__dirname, 'beo-entfernungen.pdf'); + +const PHP_DB_URL = process.env.PHP_DB_URL || 'http://localhost:8080/DB4js_all.php'; +const CHROME = process.env.CHROME_PATH || + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; + +async function callProxy(cmd, params = {}) { + const headers = { 'Content-Type': 'application/json' }; + if (process.env.API_USER && process.env.API_PASS) { + const token = Buffer.from(`${process.env.API_USER}:${process.env.API_PASS}`).toString('base64'); + headers.Authorization = `Basic ${token}`; + } + const res = await fetch(PHP_DB_URL, { + method: 'POST', headers, body: JSON.stringify({ cmd, ...params }), + }); + const json = await res.json(); + if (json && !Array.isArray(json) && json.error) throw new Error(`DB4js ${cmd}: ${json.error}`); + return json; +} + +const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); + +function fullName(r) { + return [r.vorname, r.name].map((x) => (x || '').trim()).filter(Boolean).join(' '); +} + +function buildHtml(rows) { + const stand = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: 'long', year: 'numeric' }); + const body = rows.map((r, i) => ` + + ${i + 1} + ${esc(fullName(r))} + ${esc(r.ort)} + ${r.km == null || r.km === '' ? '—' : esc(r.km)} + `).join(''); + + return ` +BEO-Entfernungen + + +

Entfernungen der BEOs zur Sternwarte Welzheim

+

Einfache Straßenentfernung in Kilometern · Stand: ${stand}

+

+ Die folgende Tabelle listet die einfache Straßenentfernung (Auto) von deinem + Wohnort zur Sternwarte Welzheim. Die Werte wurden automatisch über + OpenStreetMap / OSRM ermittelt. Bitte prüfe deinen Wert und melde + ihn in jedem Fall zurück – auch wenn er stimmt. +

+ + + + + ${body} + +
#NameWohnortkm
+

${rows.length} Beobachter · Sternwarte Welzheim · automatisch erzeugt

+`; +} + +async function main() { + const rows = await callProxy('GET_BEOS', { what: 'id,vorname,name,ort,km' }); + rows.sort((a, b) => (a.name || '').localeCompare(b.name || '', 'de')); + + const html = buildHtml(rows); + await writeFile(HTML_FILE, html, 'utf8'); + + await execFileP(CHROME, [ + '--headless=new', + '--disable-gpu', + '--no-pdf-header-footer', + `--print-to-pdf=${PDF_FILE}`, + HTML_FILE, + ]); + + console.log(`PDF erzeugt: ${PDF_FILE} (${rows.length} BEOs)`); +} + +main().catch((err) => { console.error('Fehler:', err.message); process.exit(1); });