From 10cd964ede157284e5b9717db8a48a211f29b07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Mon, 3 Aug 2026 15:03:27 +0000 Subject: [PATCH] Stadtsuche in den Einstellungen repariert (graue Karte) Wer auf dem Kartenreiter die Einstellungen oeffnete und eine Stadt eingab, bekam danach nur noch ein graues Fenster. Drei Fehler wirkten zusammen: 1. getCityCoords() rief Nominatim ohne eigenen User-Agent auf. Nominatim beantwortet den axios-Default mit 403, und zwar fuer jede Stadt. Die Schwesterfunktion getAddress() setzt seit jeher einen User-Agent - deshalb funktionierten die Adressen an den Sensoren, nur die Stadtsuche nicht. Beide nutzen jetzt dieselbe Konstante. 2. cityCoords() lieferte im Fehlerfall den String "no coordinates". Der landete als Kartenzentrum in L.map().setView(), Leaflet warf "Invalid LatLng object" - und weil showMap() die alte Karte vorher schon entfernt hat, blieb der Container leer. Daher grau statt Fehlermeldung. cityCoords() liefert jetzt {coords, err}; schlaegt die Suche fehl, bleibt das bisherige Zentrum stehen und der Text wird angezeigt. 3. Der Proxy in noise/routes/api.js kodierte die bereits prozentkodierte req.originalUrl ein zweites Mal. 'Goettingen' kam als 'G%c3%b6ttingen' an. Das fiel bisher nicht auf, weil schon Punkt 1 alles abfing. Ausserdem: Tippfehler getCityCoord.name, fehlendes encodeURI bei der Stadtsuche, Koordinaten als Zahl statt String, und eine eigene Meldung (ENOCITY), wenn Nominatim den Ort nicht kennt. Geprueft ueber die volle Kette noise -> sensorapi -> Nominatim: Stuttgart, Goettingen (Umlaut), Bad Cannstatt (Leerzeichen) liefern Koordinaten, ein erfundener Ort eine saubere Meldung. Co-Authored-By: Claude Opus 5 --- noise/public/javascripts/chart_utilities.js | 11 +++++++---- noise/public/javascripts/global.js | 18 +++++++++++++++--- noise/routes/api.js | 5 ++++- sensorapi/actions/getaddress.js | 18 ++++++++++++++---- sensorapi/locales/de/translation.json | 3 ++- sensorapi/locales/en/translation.json | 3 ++- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/noise/public/javascripts/chart_utilities.js b/noise/public/javascripts/chart_utilities.js index aa4ce2b..40770f4 100644 --- a/noise/public/javascripts/chart_utilities.js +++ b/noise/public/javascripts/chart_utilities.js @@ -255,14 +255,17 @@ export const addAddress = async (sid) => { } } +// liefert {coords: [lat, lon], err: null} oder {coords: null, err: }. +// Kein Ersatzwert wie frueher "no coordinates": der Aufrufer muss den Fehlschlag +// erkennen koennen, sonst landet ein ungueltiges Zentrum in der Karte und +// Leaflet baut sie nicht mehr auf (graues Fenster). export const cityCoords = async (city) => { let url = `/srv/getcitycoords?city=${city}` let erg = await fetchfromserver(url) - if (!erg.err) { - return erg.coords - } else { - return "no coordinates" + if (erg.err || !Array.isArray(erg.coords) || erg.coords.length !== 2) { + return {coords: null, err: erg.err || `'${city}' ?`} } + return {coords: erg.coords, err: null} } // table to distribute the different charts diff --git a/noise/public/javascripts/global.js b/noise/public/javascripts/global.js index d7edd60..28345f0 100644 --- a/noise/public/javascripts/global.js +++ b/noise/public/javascripts/global.js @@ -86,6 +86,7 @@ import * as spin from './spinner.js' // Button 'Save' pressed document.querySelector('#btnSave').addEventListener('click', async () => { let curtab = getCurrentTab() + let cityErr = null // Meldung erst nach dem Schliessen des Dialogs zeigen for(let i = 0; i < setting.length; i++) { if(setting[i].typ === getCurrentTab()) { for(let j = 0; j < setting[i].show.length; j++) { @@ -115,9 +116,17 @@ import * as spin from './spinner.js' params.datetime = starttime } else { const oldCoords = params.center.coords - params.center.coords = await cityCoords(params.center.name) - checkNewCenter(oldCoords, params.center.coords) - localStorage.setItem('centercity',JSON.stringify(params.center)) + const erg = await cityCoords(params.center.name) + if (erg.coords === null) { + // Ort nicht aufloesbar: altes Zentrum behalten. Ohne das landet + // ein ungueltiger Wert in setView() und die Karte bleibt grau. + cityErr = erg.err + params.center = JSON.parse(localStorage.getItem('centercity')) + } else { + params.center.coords = erg.coords + checkNewCenter(oldCoords, params.center.coords) + localStorage.setItem('centercity',JSON.stringify(params.center)) + } } let newlng = document.querySelector('#sellan input:checked').id let oldlng = localStorage.getItem('curlang') @@ -129,6 +138,9 @@ import * as spin from './spinner.js' spin.spinner.spin(spin.spindiv) await loadAll(params,0, curtab) spin.spinner.stop() + if (cityErr) { + showError(cityErr) + } // ToDo: // Load ALL incl. MAP und LIVE hier, d.h. an loadAll einen zusätzlichen Parameter übergeben // if(params.sid !== undefined) { diff --git a/noise/routes/api.js b/noise/routes/api.js index 24c9202..c3df57f 100644 --- a/noise/routes/api.js +++ b/noise/routes/api.js @@ -29,7 +29,10 @@ router.get('/:cmd', async function(req, res, next) { let url = APIHOST + '/api' + req.originalUrl.slice(4) + `&lng=${lng}` try { - const response = await axios.get(encodeURI(url) , { + // kein encodeURI: req.originalUrl ist bereits prozentkodiert. Ein zweiter + // Durchlauf macht aus '%C3%B6' ein '%25C3%25B6', und die Gegenseite sieht + // dann den Text 'G%c3%b6ttingen' statt 'Göttingen'. + const response = await axios.get(url , { headers: {'X-API-Key': API_KEY} }); if (response.status !== 200) { diff --git a/sensorapi/actions/getaddress.js b/sensorapi/actions/getaddress.js index eecf128..84e4e40 100644 --- a/sensorapi/actions/getaddress.js +++ b/sensorapi/actions/getaddress.js @@ -6,17 +6,27 @@ import { response } from "express" const NOMINATIM_URL = `https://nominatim.openstreetmap.org/reverse?lat=${'xx'}&lon=${'yy'}&format=json` const NOMINATIM_CITY_URL = `https://nominatim.openstreetmap.org/?q="${'xx'}"&format=json` +// Nominatim verlangt laut Nutzungsbedingungen einen eigenen User-Agent und +// beantwortet den Default von axios ('axios/1.x') mit 403. +const NOMINATIM_UA = 'laermsensor-stack (rxf@gmx.de)' export const getCityCoords = async (params) => { let ret = {coords: [], city: params.city, err: null} let url = NOMINATIM_CITY_URL.replace('xx', params.city) // let url = 'https://nominatim.openstreetmap.org/?q="K%C3%B6ln"&format=json' try { - const response = await axios(url) + const response = await axios(encodeURI(url), { + headers: { + 'User-Agent': NOMINATIM_UA + } + }) if (response.status !== 200) { - return returnOnError(ret, 'RESPSTATUS', getCityCoord.name, response.status) + return returnOnError(ret, 'RESPSTATUS', getCityCoords.name, response.status) } - ret.coords = [response.data[0].lat,response.data[0].lon] + if (!response.data.length) { // Stadt nicht gefunden + return returnOnError(ret, 'ENOCITY', getCityCoords.name, params.city) + } + ret.coords = [parseFloat(response.data[0].lat), parseFloat(response.data[0].lon)] logit(JSON.stringify(ret.coords)) } catch (e) { return returnOnError(ret, e, getCityCoords.name) @@ -35,7 +45,7 @@ export const getAddress = async (params) => { try { const response = await axios(encodeURI(url),{ headers: { - 'User-Agent': 'Firefox 132.0.1' + 'User-Agent': NOMINATIM_UA } }); if (response.status !== 200) { diff --git a/sensorapi/locales/de/translation.json b/sensorapi/locales/de/translation.json index bd7b225..ffa9399 100644 --- a/sensorapi/locales/de/translation.json +++ b/sensorapi/locales/de/translation.json @@ -12,5 +12,6 @@ "SYNTAXURL": "Syntax Fehler beim Aufruf der URL", "WRONGTYPE": "Sensor xxx ist kein yyy Sensor", "NOLASTDATES": "Probleme bein abholen der letzten Daten", - "NOPROPSFOUND": "Properties Collection nicht gefunden" + "NOPROPSFOUND": "Properties Collection nicht gefunden", + "ENOCITY": "Ort \"xxx\" nicht gefunden" } diff --git a/sensorapi/locales/en/translation.json b/sensorapi/locales/en/translation.json index d701d6d..f4d9bd0 100644 --- a/sensorapi/locales/en/translation.json +++ b/sensorapi/locales/en/translation.json @@ -12,5 +12,6 @@ "SYNTAXURL": "Syntax error in calling url!", "WRONGTYPE": "Sensor xxx is not of type yyy", "NOLASTDATES": "Problems fetching last dates from database", - "NOPROPSFOUND": "Properties collection not found" + "NOPROPSFOUND": "Properties collection not found", + "ENOCITY": "City 'xxx' not found" }