10cd964ede
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 <noreply@anthropic.com>
280 lines
8.1 KiB
JavaScript
280 lines
8.1 KiB
JavaScript
// Utility routine for plotting the data
|
|
|
|
import {DateTime} from './luxon.min.js'
|
|
export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'};
|
|
|
|
// Defaults für die Mittelwertbildungen
|
|
let txtMeldung = false; // falls keine Daten da sind, Text melden
|
|
const avgTime = 30; // defaul average time für particulate matter
|
|
let doUpdate = true; // update every 5 min
|
|
let optSidsArray = []; // Arrray der letzten 5 Einträge
|
|
let nbrofdaysforavg = 8; // Stundenmittel Anzahl Tagte (default = 5)
|
|
let nbrofdaysfordaynight = 30; // use 30 days or day/night graphic
|
|
let stucols = 1; // Anzeige nur Balken (StundenMittel)
|
|
let activeTab = 'maptab'; // active TAB ID
|
|
|
|
export const noise_ymin = 30; // lowest value on y-axis for noise
|
|
export const noise_ymax = 120; // highest value on y-axis for noise
|
|
export let peaklim = 70; // threshold for peak count
|
|
|
|
export const url = '/srv'
|
|
|
|
export function createGlobObtions() {
|
|
// Options, die für alle Plots identisch sind
|
|
let globObject = {
|
|
chart: {
|
|
accessibility: {
|
|
enabled: false
|
|
},
|
|
height: 600,
|
|
// width: 1000,
|
|
spacingRight: 20,
|
|
spacingLeft: 20,
|
|
spacingTop: 25,
|
|
backgroundColor: {
|
|
linearGradient: [0, 400, 0, 0],
|
|
stops: [
|
|
[0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'],
|
|
[1, '#fff']
|
|
]
|
|
},
|
|
type: 'line',
|
|
borderWidth: '2',
|
|
useHTML: true,
|
|
events: {
|
|
selection: function (event) {
|
|
if (event.xAxis) {
|
|
doUpdate = false;
|
|
} else {
|
|
doUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
title: {
|
|
// text: 'Feinstaub über 1 Tag',
|
|
align: 'left',
|
|
style: {'fontSize': '25px'},
|
|
useHTML: true,
|
|
},
|
|
subtitle: {
|
|
// text: 'Gemessene Werte und ' + avgTime + 'min-gleitende Mittelwerte',
|
|
align: 'left',
|
|
},
|
|
tooltip: {
|
|
valueDecimals: 1,
|
|
backgroundColor: 0,
|
|
borderWidth: 0,
|
|
borderRadius: 0,
|
|
useHTML: true,
|
|
},
|
|
labels: {
|
|
useHTML: true
|
|
},
|
|
xAxis: {
|
|
type: 'datetime',
|
|
title: {
|
|
text: 'date/time',
|
|
},
|
|
gridLineWidth: 2,
|
|
labels: {
|
|
formatter: function () {
|
|
let v = this.axis.defaultLabelFormatter.call(this);
|
|
if (v.indexOf(':') == -1) {
|
|
return '<span style="font-weight:bold;color:red">' + v + '<span>';
|
|
} else {
|
|
return v;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
legend: {
|
|
enabled: true,
|
|
layout: 'horizontal',
|
|
// verticalAlign: 'top',
|
|
borderWidth: 1,
|
|
align: 'center',
|
|
},
|
|
plotOptions: {
|
|
series: {
|
|
animation: false,
|
|
turboThreshold: 0,
|
|
marker: {
|
|
enabled: false,
|
|
},
|
|
},
|
|
}
|
|
};
|
|
return globObject;
|
|
}
|
|
|
|
export function calcWeekends(data, isyear) {
|
|
let weekend = [];
|
|
let oldDay = 8;
|
|
for (let i = 0; i < data.length; i++) {
|
|
let mom = moment(data[i].date);
|
|
if (isyear) {
|
|
mom = moment(data[i]._id)
|
|
}
|
|
let day = mom.day();
|
|
let st = mom.startOf('day');
|
|
if (day != oldDay) {
|
|
if (day == 6) {
|
|
weekend.push({
|
|
color: 'rgba(169,235,158,0.4)',
|
|
from: st.valueOf(),
|
|
to: st.add(1, 'days').valueOf(),
|
|
zIndex: 0
|
|
})
|
|
} else if (day == 0) {
|
|
weekend.push({
|
|
color: 'rgba(169,235,158,0.4)',
|
|
from: st.valueOf(),
|
|
to: st.add(1, 'days').valueOf(),
|
|
zIndex: 0
|
|
})
|
|
}
|
|
oldDay = day;
|
|
}
|
|
}
|
|
return weekend;
|
|
}
|
|
|
|
export function calcDays(data, isyear) {
|
|
let days = [];
|
|
if (data.length == 0) {
|
|
return days
|
|
}
|
|
let oldday = moment(data[0].date).day();
|
|
if (isyear) {
|
|
oldday = moment(data[0]._id).day();
|
|
}
|
|
for (let i = 0; i < data.length; i++) {
|
|
let m = moment(data[i].date);
|
|
if (isyear) {
|
|
m = moment(data[i]._id);
|
|
}
|
|
let tag = m.day()
|
|
if (tag != oldday) {
|
|
m.startOf('day');
|
|
days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2});
|
|
oldday = tag;
|
|
}
|
|
}
|
|
return days;
|
|
};
|
|
|
|
export async function addSensorID2chart(chart, sensor, width) {
|
|
let indoor = sensor.indoor || false
|
|
let addr = await addAddress(sensor.sid)
|
|
chart.renderer.label(
|
|
`Sensor: <span class="bigger">${sensor.sid}</span> ${indoor ? ' (indoor)' : ''}<br /><br />${addr.street}<br />${addr.plz} ${addr.city}`,
|
|
width / 2 - 150, 70)
|
|
.css({
|
|
fontSize: '14pt',
|
|
'font-weight': 'bold',
|
|
})
|
|
.attr({
|
|
zIndex: 3,
|
|
}).add();
|
|
}
|
|
|
|
export const showError = (err) => {
|
|
const dialog = document.getElementById('dialogError')
|
|
const body = dialog.querySelector('.dialog-body')
|
|
body.innerHTML = err
|
|
dialog.showModal()
|
|
}
|
|
|
|
export const showReset = () => {
|
|
const dialog = document.getElementById('dialogReset')
|
|
dialog.showModal()
|
|
}
|
|
|
|
// Setup dialog close buttons
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const errorClose = document.querySelector('#dialogError .dialog-close')
|
|
if (errorClose) {
|
|
errorClose.addEventListener('click', () => {
|
|
document.getElementById('dialogError').close()
|
|
})
|
|
}
|
|
|
|
const resetClose = document.querySelector('#dialogReset .dialog-close')
|
|
if (resetClose) {
|
|
resetClose.addEventListener('click', () => {
|
|
document.getElementById('dialogReset').close()
|
|
})
|
|
}
|
|
|
|
const resetOk = document.querySelector('#btnResetOk')
|
|
if (resetOk) {
|
|
resetOk.addEventListener('click', () => {
|
|
document.getElementById('dialogReset').close()
|
|
})
|
|
}
|
|
})
|
|
|
|
// remove the taps (if shownig the map)
|
|
export const removeTabs = () => {
|
|
document.querySelector('#navi').style.display = 'none'
|
|
}
|
|
|
|
// show thw tabs again
|
|
export const showTabs = () => {
|
|
if (activeTab !== 'maptab') {
|
|
document.querySelector('#navi').style.display = 'block'
|
|
}
|
|
}
|
|
|
|
export const setCurrentTab = (tab) => {
|
|
activeTab = tab
|
|
}
|
|
|
|
export const getCurrentTab = () => {
|
|
return activeTab
|
|
}
|
|
|
|
export const fetchfromserver = async (url) => {
|
|
const ret = await fetch(encodeURI(url))
|
|
.catch(e => {
|
|
showError(e)
|
|
});
|
|
// return await ret.json()
|
|
let x = await ret.json()
|
|
return x
|
|
}
|
|
|
|
export const addAddress = async (sid) => {
|
|
let url = `/srv/getaddress?sensorid=${sid}`
|
|
let erg = await fetchfromserver(url)
|
|
if (!erg.err) {
|
|
return erg.address
|
|
} else {
|
|
return "no address"
|
|
}
|
|
}
|
|
|
|
// liefert {coords: [lat, lon], err: null} oder {coords: null, err: <Meldung>}.
|
|
// 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 || !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
|
|
export const ttIndex = Object.freeze({
|
|
map: 0,
|
|
live: 1,
|
|
havg: 2,
|
|
davg: 3,
|
|
daynight: 4,
|
|
lden: 5
|
|
})
|