Im Umfeld eines kleinen Forschungsprojekts zu Fiware (fiware.org) ist eine Node-RED Map entstanden die eine lokale Visualisierung der Luftdaten.info Daten entstanden.
Es werden die Daten eines Bereichs über die API von Luftdaten.info gelesen und mittels dem Node-RED Node Worldmap auf einer Map angezeigt, ge nach Sensor Status werden die Pins eingefärbt und im Message Objekt werden dort die jeweiligen Daten in einer Info Box gezeigt.

Zentraler Punkt ist die API Abfrage “Filter Area Südschleswig” in der die Daten via API geholt werden.

In diesem Node wird die URL der Abfrage eingetragen und als UTF 8 Codierte Rückgabe in den Flow getragen.

Aus der Rückgabe wird mittels eines JSON Node ein JSON Objekt erstellt.


Das Ergebnis der Abfrage und Umwandlung ist ein Objekt welches alle im ausgewählten Bereich eingetragenen Sensoren liefert.

Diese beinhaltet nun die ungefilterte und nicht sortierten Daten aller Sensoren. Ein Feinstaubsensor nach Stuttgarter Modell hat neben seinem Feinstaubsensor immer auch einen Wetter Sensore der als eigenständiges Objekt angesehen wird. Die Sensoren haben alle eine Typ ID nach der die unterschiedlichen Sensoren mit ihren unterschiedlichen Rückgabewerten identifiziert werden können. Dadurch können die Sensoren je nach Typ unterschiedlich behandelt werden um die Messwerte dann später auch interpretieren zu können.
Nach dem oben beschriebenen Schritt teilt sich die Verarbeitung der Ergebnisse in 3 Verarbeitungslinien auf:
- CSV für die Fiware Aufbereitung
- Visualisierung als Wetter Map
- Visualisierung als Feinstaub Map

Die CSV Verarbeitung lasse ich an dieser Stelle erst einmal links liegen, hier werden nur die einzelnen Sensoren mit ihren Geo Positionen und den Sensor IDs in eine CSV Datei auf den Server geschrieben um daraus später Fiware Objekte zu erstellen.
Interessanter sind die beiden MAP Linien. Hier werden die Daten in einer JS Funktion so vorbereitet das sie vom Worlsmap Node angenommen und als Karte angezeigt werden können.

Die Wetter Verarbeitung behandelt die Sensor Typen:
- Typ ID 8 = BME180 liefert Temperatur und Luftfeuchte
- Typ ID 9 = DHT22 liefert Temperatur und Luftfeuchte
- Typ ID 17 = BME280 liefert Temperatur, Luftfeuchte und Luftdruck
Die Aufbereitung der Sensordaten für die Map
var NumOfSens = Object.keys(msg.payload).length;
var sensArr=[];
var mapArr=[];
var numfss = 0;
var v = 0;
// Sensoren in ein Array einlesen, den SDS011 (Typ 14) ignorieren
for (var i = 0; i< (NumOfSens); i++){
if (msg.payload[i].sensor.sensor_type.id !== 14) {
sensArr.push(Object.keys(msg.payload)[i]);
numfss++;
} else {}
}
// Sensoren Pins nach Typ eine Farbe zuweisen
for (i = 0; i< numfss; i++){
if (typeof(msg.payload[sensArr[i]].location)!== "undefined"){
message = {
lat: msg.payload[sensArr[i]].location.latitude,
lon: msg.payload[sensArr[i]].location.longitude,
name: msg.payload[sensArr[i]].sensor.id,
};
if (msg.payload[sensArr[i]].sensor.sensor_type.id === "undefined"){
message.iconColor="green";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==14){
message.iconColor="orange";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
message.iconColor="blue";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
message.iconColor="red";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
message.iconColor="black";
}
else {
message.iconColor="green";
}
// Den Hersteller dem Info Objekt zuweisen
message.Hersteller = msg.payload[sensArr[i]].sensor.sensor_type.manufacturer;
// Die Sensor ID als Name festlegen
message.Name = msg.payload[sensArr[i]].sensor.sensor_type.name;
//Messwerte je Sensortyp
//DHT11 (Typ9)
if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C" ;
} else {
message.Humidity = msg.payload[sensArr[i]].sensordatavalues[v].value + " %";
}
}
}
//BME280 (Typ17)
if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C";
}
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="humidity"){
message.Humidity = msg.payload[sensArr[i]].sensordatavalues[v].value + " %";
}
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure"){
message.Luftdruck = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2) + " hPa" ;
}
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure_at_sealevel"){
message.Luftdruck_NN = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2) + " hPa";
}
}
}
//BMP180 (Typ8)
if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C";
}
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure"){
message.Luftdruck = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2) + " hPa" ;
}
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure_at_sealevel"){
message.Luftdruck_NN = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2) + " hPa";
}
}
}
// Den Sensor PIN je nach Temperatur einfärben
if (parseFloat(message.Temperatur) < -5){
message.iconColor="blue";
} else if (parseFloat(message.Temperatur) < 5){
message.iconColor="deepskyblue";
} else if (parseFloat(message.Temperatur) < 15){
message.iconColor="aquamarine";
} else if (parseFloat(message.Temperatur) < 20){
message.iconColor="lightgreen";
} else if (parseFloat(message.Temperatur) < 25){
message.iconColor="greenyellow";
} else if (parseFloat(message.Temperatur) < 28){
message.iconColor="yellow";
} else if (parseFloat(message.Temperatur) < 30){
message.iconColor="orange";
} else if (parseFloat(message.Temperatur) < 33){
message.iconColor="orangered";
} else if (parseFloat(message.Temperatur) < 36){
message.iconColor="firebrick";
} else {
message.iconColor="red";
}
//message.Owner = msg.payload[sensArr[i]].owner;
// Wann wurde der letzte Wert erfasst
message.lastSeen = msg.payload[sensArr[i]].last_seen;
// Den Link zur Grafana Auswertung hinterlegen
message.link = "<a href=" + "https://maps.luftdaten.info/grafana/d/000000004/single-sensor-view?orgId=1&var-node="+ message.name + ">Statistik</a>";
//message.radius = 5000;
mapArr.push(message);
}
}
// Das erstellte MapArray dem Payload übergeben
msg.payload=mapArr;
// Rückgabe an den Flow
return msg;
Hier werden die Sensoren gezählt und einem Array zugefügt, die SDS011 Feinstaubsensoren werden nicht ausgewählt. Danach werden die Messwerte je nach Sensor Typ abgefragt und ab Ende werden die Pins je nach Temperatur eingefärbt. Aus dem Sensor Namen, der die Sensor ID beinhaltet wird der Link zur Langzeitstatistik erzeugt und dann komplett in den Payload geschrieben. Nun sind die Daten so aufbereitet das sie in dem Worldmap Node zur Anzeige übergeben werden können.

Die Konfiguration des Worldmap Node beinhaltet die Auswahl des Map Zentrums, des Zoom Faktors, die hinterlegte Basiskarte so wie der Maplink um die Karte aufzurufen.

Der Web Path ist der Ort an dem die Karte später angezeigt werden soll.
Somit wird aus /wetterssl die Map URL https://node-red.iot-usergroup.de/wetterssl/
Damit dieser Node richtig funktioniert muss noch ein weiterer Node in den Flow eingebaut werden der die Abfrage triggert so das der Besucher auf der Map die aktuellen Daten erhält.

In diesem Trigger Node wird der Web Path eingetragen, sobald ein Nutzer die Karte öffnet wird hier ein Event ausgelöst.

Diese wird an ein Funktions Node weiter gereicht welches ein “connected” liefert mit dem dann die Abfrage der API ausgelöst wird.
if (msg.payload.action === "connected")
return msg;
Mit der selben Logik wird auch die Feinstaub Linie gestartet deren einziger Unterschied das Mapziel ist. Es lautet https://node-red.iot-usergroup.de/fssssl/ und zeigt für den Bereich die Feinstaubsensoren Sensoer Typ ID 14 an.
Der Code im Funktions-Node für die Feinstaublinie ist der folgende:
var NumOfSens = Object.keys(msg.payload).length;
var sensArr=[];
var mapArr=[];
var numfss = 0;
var v = 0;
// Sensoren des Typs 14 in das Array laden
for (var i = 0; i< (NumOfSens); i++){
if (msg.payload[i].sensor.sensor_type.id==14) {
sensArr.push(Object.keys(msg.payload)[i]);
numfss++;
} else {}
}
// Sensor Pins ja nach Typ einfärben
for (i = 0; i< numfss; i++){
if (typeof(msg.payload[sensArr[i]].location)!== "undefined"){
message = {
lat: msg.payload[sensArr[i]].location.latitude,
lon: msg.payload[sensArr[i]].location.longitude,
name: msg.payload[sensArr[i]].sensor.id,
};
if (msg.payload[sensArr[i]].sensor.sensor_type.id === "undefined"){
message.iconColor="green";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==14){
message.iconColor="orange";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
message.iconColor="blue";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
message.iconColor="red";
}
else if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
message.iconColor="black";
}
else {
message.iconColor="green";
}
// Hersteller ermitteln und zuweisen
message.Hersteller = msg.payload[sensArr[i]].sensor.sensor_type.manufacturer;
// Sensor ID als Name festlegen
message.Name = msg.payload[sensArr[i]].sensor.sensor_type.name;
message.Description = "Messwert P1 = PM10 P2 = PM2.5";
// Messwerte abfragen und zuweisen
for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){
if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="P1"){
message.p1 = msg.payload[sensArr[i]].sensordatavalues[v].value;
} else if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="P2"){
message.p2 = msg.payload[sensArr[i]].sensordatavalues[v].value;
}
}
//Letzer Zeit der Messung
message.lastSeen = msg.payload[sensArr[i]].last_seen;
//Statistik Link erstellen
message.link = "<a href=" + "https://maps.luftdaten.info/grafana/d/000000004/single-sensor-view?orgId=1&var-node="+ message.name + ">Statistik</a>";
// Einfärben der Map Pins ja nach Feinstaubwert
if ((parseFloat(message.p1) < 10)){
message.iconColor="green";
} else if ((parseFloat(message.p1) < 25)){
message.iconColor="GreenYellow";
} else if ((parseFloat(message.p1) < 50)) {
message.iconColor="yellow";
} else if ((parseFloat(message.p1) < 75)) {
message.iconColor="orange";
} else if ((parseFloat(message.p1) < 100)) {
message.iconColor="red";
} else {
message.iconColor="magenta";
}
mapArr.push(message);
}
}
msg.payload=mapArr;
return msg;
Viel Spaß mit den Karten, übrigens gibt es diese Karten auch für den Bereich um Kiel: