"use strict"; const express = require('express'); const router = express.Router(); const moment = require('moment'); const mathe = require('mathjs'); const fs = require('fs'); const util = require('./utilities'); // Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !! // Get the city for given Sensor async function getCity(db, sensorid) { let pcoll = db.collection("properties"); let properties = await pcoll.findOne({_id:sensorid}); let addr = "unKnown"; try { addr = properties.location[0].address.country + " " + properties.location[0].address.plz + " " + properties.location[0].address.city; } catch(e) { // do nothing, just skip } return addr; } /* // API to put data into dBase router.post('/putdata/:what', function(req,res) { let db = req.app.get('dbase'); let cmd = req.query.cmd; let what = req.params.what; if (what=='problems') { putAPIproblemdata(db, cmd, req.body) .then((erg) => { // console.log(erg); res.send(erg); }); } else { res.send ( {error:'wrong call'}) } }); */ //API to read all datas from the database router.get('/getdata', function (req, res) { let db = req.app.get('dbase'); let sid=1; if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) { sid = parseInt(req.query.sensorid); } let avg = req.query.avg; let span = req.query.span; let dt = req.query.datetime; if(isNaN(sid)) { getAPIdataTown(db, req.query.sensorid, avg, span, dt, res) .then(erg => res.json(erg)); } else { getAPIdataSensor(db, sid, avg, span, dt) .then(erg => res.json(erg)); } // if(req.query.sensorid == "all") { // getAPIalldata(db, dt) // .then(erg => res.json(erg)); }); router.get('/getprops', function (req, res) { let db = req.app.get('dbase'); let sid=0; if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) { sid = parseInt(req.query.sensorid); } let dt = "1900-01-01T00:00:00"; if(!((req.query.since === undefined) || (req.query.since ==""))) { dt = req.query.since; } let name = "" if(!((req.query.sensortyp === undefined) || (req.query.sensortyp ==""))) { name = req.query.sensortyp; } getAPIprops(db, sid, name, dt) .then(erg => res.json(erg)); }); router.get('/getmapsensors', function (req, res) { let db = req.app.get('dbase'); // db wird in req übergeben (von app.js) let bounds = {}; bounds.south = parseFloat(req.query.south); bounds.north = parseFloat(req.query.north); bounds.east = parseFloat(req.query.east); bounds.west = parseFloat(req.query.west); bounds.poly = []; if (req.query.poly != undefined) { bounds.poly = JSON.parse(req.query.poly); } let ptype = parseInt(req.query.ptype); let stype = req.query.stype; let st = req.query.start; getApiMapSensors(db, bounds, stype, ptype, st) .then(erg => res.json(erg)); }); router.get('/getcities', (req,res) => { let db = req.app.get('dbase'); // db wird in req übergeben (von app.js) let country = req.query.country; let type = req.query.type; if (country == undefined) { country = 'all'; } if (type == undefined) { type = 'PM'; } getApiCities(db,country.toUpperCase(),type.toUpperCase()) .then(erg => res.json(erg)); }); // Get address from coordinates using OpenStreetMap Nominatim API router.get('/getaddress', function (req, res) { let db = req.app.get('dbase'); let sid = 0; if (!((req.query.sensorid == undefined) || (req.query.sensorid == ""))) { sid = parseInt(req.query.sensorid); } if (sid === 0) { res.json({address: null, err: "No sensorid provided"}); return; } util.getAddress(db, sid) .then(erg => res.json(erg)) .catch(err => { console.log("getaddress error:", err); res.json({address: null, err: err.message}); }); }); // *********************************************************** // putAPIproblemdata - Daten in der DB speichern // // Parameter: // db: Mongo-Database // cmd: 'start', 'end', 'data' // data: JSON string to put into db // // return: // error, if not correctly saved, else null // *********************************************************** /* async function putAPIproblemdata(db, cmd, data) { // console.log("putAPIproblemdata"," Länge: ", data.length); let collection = db.collection('problemsensors'); if(cmd == 'end') { return {error: 'done'}; } if(cmd == 'data') { let inserted; let upd = []; for (let i=0; i< data.length; i++){ let one = { updateOne: { "filter" : { "_id": data[i]._id}, "update": { $set: data[i]}, "upsert": true } }; upd.push(one); } try { inserted = await collection.bulkWrite(upd) // console.log("Modifiziert:", inserted.modifiedCount) } catch(e) { console.log(e) } return {error: "OK"} } return { error: 'wrong command'}; } */ // *********************************************************** // getAPIprobSensors - Get data for problematic sensors // // Parameter: // db: Mongo-Database // // return: // JSON Dokument mit den angefragten Werten // *********************************************************** /* async function getAPIprobSensors(db,pnr,only,withTxt) { let coll = db.collection('problemsensors'); let query = {_id: {$gt: 0}}; let proj = {}; let count = 0; if(withTxt == undefined) { withTxt = true; } if (pnr != 0) { query = { $and: [ {problemNr: pnr}, {_id: {$gt: 0}} ]} ; } if(only) { proj = {_id: 1}; } let docs = await coll.find(query,proj).toArray(); if(docs != null) { count = docs.length; } let texte = {}; if(withTxt) { let tt = await coll.findOne({_id: 0}); if (tt == null) { texte.texte = []; } } let ret; if (only) { ret = {count: count, problemNr: pnr, values: docs, texte: texte.texte}; } else { ret = {count: count, values: docs, texte: texte.texte}; } if(!withTxt) { delete ret.texte; } return ret } */ // *********************************************************** // getAPIdataNew - Get data direct via API for one sensor // // Parameter: // db: Mongo-Database // sid: sensor ID // mavg: time over that to build the average [minutes] // dauer: duration for the data [hours] // start: starting point of 'dauer' // end: end of 'dauer' // // return: // JSON Dokument mit den angefragten Werten // *********************************************************** /* async function getAPIdataNew(db,sid,mavg,dauer,start,end, gstart) { let st = moment(start).startOf('day'); // clone start/end .. let en = moment(end).startOf('day'); // .. and set to start of day let retur = {sid: sid, avg: mavg, span: dauer, start: gstart}; let collection = db.collection('values'); let ergArr = []; let values; for (; st <= en; st.add(1, 'd')) { let id = sid + '_' + st.format('YYYYMMDD'); try { values = await collection.findOne({ _id: id }); } catch (e) { console.log(e); } if(values && (values.values.length != 0)) { ergArr.push(...values.values); } } if (ergArr.length == 0) { retur.count = 0; retur['values'] = []; } else { // Bereich einschränken let v = []; let fnd = ergArr.findIndex(x => x.datetime >= start); if (fnd != -1) { v = ergArr.slice(fnd); ergArr = v; } fnd = ergArr.findIndex(x => x.dateTime > end); if (fnd != -1) { v = ergArr.slice(-fnd); erg.Arr = v; } if ((mavg === undefined) || (mavg == 1)) { retur.count = ergArr.length; retur['values'] = ergArr; } // Mittelwert berechnen let x = util.calcMovingAverage(db, ergArr, mavg, 0, 0, true); fnd = x.findIndex(u => u.dt >= gstart); if((fnd == -1) && (dauer == 0)) { let y = x.slice(-1); x = y; } else { if (fnd != -1) { let y = x.slice(fnd); x = y; } } retur.count = x.length; retur.values = x; } return retur; } */ // ****************************************************************** // getAPITN - Get data direct via API for all sensors in a town // // Parameter: // dbase: Mongo-Database // sensors: array of sensors // mavg: time over that to build the average [minutes] // dauer: duration for the data [hours] // start: starting point of 'dauer' // end: end of 'dauer' // town: name of town // // return: // JSON document with data for ALL sensors in town // // ***** Neue DB-Struktur - Versuch // // ******************************************************************** async function getAPITN (dbase,sensors,mavg,dauer,start,end,gstart,town) { // Fetch for all this sensors let los = moment(); // debug, to time it let erg = {sid:town, avg: mavg, span: dauer, start: gstart, count: 0, sensordata: []}; // prepare object let val; for(let j=0; j u.dt >= gstart); if((fnd == -1) && (dauer == 0)) { let y = x.slice(-1); x = y; } else { if (fnd != -1) { let y = x.slice(fnd); x = y; } } retur.count = x.length; retur.values = x; } return retur; } /* =============================================================== // PM (FEINSTAUB) FUNCTIONS REMOVED - Not needed in new DB // =============================================================== // ********************************************* // Get data direct via API for ALL sensor - WAS PM-SPECIFIC // // Call: // http://feinstaub.rexfue.de/api?sid=all&datetime="2018-06-02T12:00Z" // // mit: // dt: Zeitpunkt, für den die Daten geholt werden // Es werden Daten <= dem Zeitpunkt geholt // // return: // JSON Dokument mit den angefragten Werten // ********************************************* async function getAPIalldata(db,dt) { // REMOVED - Was PM-specific, not applicable to Radiation/THP sensors return { error: 'Function removed - was PM-specific' }; } function isPM(name) { // REMOVED - No longer needed, only Radiation and THP sensors return false; } */ // ********************************************* // Get properties for all sensors // // Call: // http://feinstaub.rexfue.de/api/getprops?sensorid=1234&since=2810-03-23&sensortyp=SDS011 // // mit: // sid: Sensornummer (all -> alle Sensoren) // since: seit dem Datum (incl) // sensortyp: Type des Sensors (z.B. SDS011 oder PM(für alle Feinstaub-Sensoren)) // // params: // db Datenbank // sid Sensor-Nummer oder all // typ Sensor-Typ // dt Datum, ab wann gesucht werden soll // // return: // JSON Dokument mit den angefragten werten // ********************************************* async function getAPIprops(db,sid,typ,dt) { let properties = []; let erg = []; let entry = {}; let pcoll = db.collection("properties"); let query = {}; if(sid == 0) { if(typ == "") { query = {}; // Get all sensors } else if (typ == 'radioactivity') { query = {type: 'radioactivity'}; } else if (typ == 'THP') { query = {type: {$ne: 'radioactivity'}}; // Anything not radioactivity is THP } else { // For specific sensor type names, check in name array query = {'name.name': typ}; } } else { query = { _id:sid }; } properties = await pcoll.find(query).sort({_id: 1}).toArray(); for (let i = 0; i < properties.length; i++) { let loclast = (properties[i].location.length)-1; // Get current name from array let currentName = properties[i].name; if (Array.isArray(properties[i].name)) { currentName = properties[i].name[properties[i].name.length - 1].name; } // Get last_seen from values.timestamp if available let lastSeen = properties[i].last_seen || (properties[i].values && properties[i].values.timestamp); if (!lastSeen) { lastSeen = moment("1900-01-01T00:00Z"); } else if (lastSeen.$date) { lastSeen = moment(lastSeen.$date); } else { lastSeen = moment(lastSeen); } // Build result object let result = { sid: properties[i]._id, typ: currentName, lat: properties[i].location[loclast].loc.coordinates[1], lon: properties[i].location[loclast].loc.coordinates[0], alt: properties[i].location[loclast].altitude, lastSeen: lastSeen.format(), country: properties[i].location[loclast].country }; // Add since date if available if (properties[i].location[loclast].since) { if (properties[i].location[loclast].since.$date) { result.since = moment(properties[i].location[loclast].since.$date).format(); } else { result.since = moment(properties[i].location[loclast].since).format(); } } erg.push(result); } entry.sensortyp = typ =="" ? "all" : typ; entry.count = erg.length; entry.since = dt; entry.values = erg; return entry; } // ******************************************************************* // parseParams - parse the given paramaters // // params: // avg: averegae time in min // span: data range in hours // dt: start date of data range // // return: // object with: // mavg: average time in min (default 1min, max; 1440min)) // dauer: data range in hoiurs (default 24h, max: 720h) // start: start date/time to calculate average // end: end of data range // gstart: start on datarange (without avg-time) // // ********************************************************************** function parseParams(avg, span, dt) { let params = {}; params.mavg = 1; // default average if (avg !== undefined) { // if acg defined .. params.mavg = parseInt(avg); // .. use it } if (params.mavg > 1440) { params.mavg = 1440;} // avgmax = 1 day params.dauer = 24; // span default 1 day if(span !== undefined) { // if defined .. params.dauer = parseInt(span); // .. use it } if (params.dauer > 720) { params.dauer = 720;} // spanmax = 30 days params.start = moment(); // default start -> now params.end = moment(); // define end if(dt != undefined) { // if defined .. params.start = moment(dt); // .. use it .. params.end = moment(dt).add(params.dauer,'h'); // .. and calculate new end } else { // if not defined, calc start .. params.start.subtract(params.dauer, 'h'); // .. from span (= dauer) } params.gstart = moment(params.start); params.start.subtract(params.mavg,'m'); // start earlier to calc right average return params; } // ****************************************************************** // getAPIproblemSensors - Get senosor-IDs of problematic sensor // within map bounds // // Call: // http://feinstaub.rexfue.de/api/getprobsens/?bounds=[bounds]&ptype=1&datetime=2018-08-ß02T20:12:00 // // mit: // bounds: corner coordinates of map or polygone for town border // ptype: type of problem (optional) // datetime: day for which to calculate (optional) // // Parameter: // db: database // bounds: corner coordinates of map or polygone for town border // stype: 'PM' or 'THP' or 'TH' os 'T' or 'H' (default: 'PM') // ptype: type of problem (undefined means ALL problems) // st: date to calculate for // // return: // array wit 24h-Average-Value for the last 24hours for every sensor // // // // ******************************************************************** async function getApiMapSensors(db, bounds, stype, ptype, st) { // fetch list of sensor ids within bounds let slist = []; let collection = db.collection('properties'); let loc; let name = 'PM'; if (stype != undefined) { name = stype; } if (bounds.poly.length != 0) { loc = { 'location.0.loc': { $geoWithin: { $geometry: { type: "Polygon", coordinates: [bounds.poly], } } } } } else { loc = { 'location.0.loc': { $geoWithin: { $box: [ [bounds.west, bounds.south], [bounds.east, bounds.north] ] } } } } let docs = await collection.find(loc, {_id: 1, name:1}).toArray(); // find all data within map borders (box) // .toArray(async function (err, docs) { // console.log(docs); for (let i = docs.length - 1; i >= 0; i--) { if (!((name == 'PM') == isPM(docs[i].name))) { docs.splice(i, 1); continue; } let erg = await getAPIdataSensor(db, docs[i]._id, 1, 24); if (erg.values.length == 0) { docs[i].mean = -1; docs[i].std = 0; docs[i].type = 'noData'; delete docs[i].name; continue; } // if (!erg.values[0].hasOwnProperty('P1')) { // continue; // } // console.log(erg); let result = erg.values.map(x => x.P1); // console.log(result); let mean = mathe.mean(result); let std = mathe.std(result); console.log(mean); docs[i].mean = Math.round(mean); docs[i].std = std; delete docs[i].name; } // console.log(docs); // nun in docs alle Mittelwerte über die letzten 24h // nun sortieren let docs_sorted = docs.sort((a, b) => a.mean - b.mean); let docs_std = docs.sort((a, b) => a.std - b.std); // console.log(docs_sorted); return docs_sorted; } function isPM(name) { let pms = ['SDS011','PMS7003','PMS3003','PMS5003','HPM','SDS021','PPD42NS']; if (pms.findIndex(n => n == name) != -1) { return true; } return false; } // ****************************************************************** // getAPICities - Get all cities, containing sensors // // // Call: // http://feinstaub.rexfue.de/api/getcities/?country=de // // with: // county: 2 character coutrycode to find the city (all or absent => world) // type: PM or THP (or absent => PM) // // Parameter: // db: database // county: 2 character coutrycode to find the citie (all => world) // pm: PM for particulate sensors, THP for temp/hum/press-sensors // // return: // JSON array with cities // // // // ******************************************************************** async function getApiCities(db, country, sensorType) { let slist = []; let collection = db.collection('properties'); let query = {}; // Build country query - in new DB, country is directly in location, not in address if(country != 'ALL') { query = {'location.country': country}; } // Filter by sensor type let matchtype = {}; if (sensorType == 'radioactivity') { matchtype = {type: 'radioactivity'}; } else if (sensorType == 'THP') { matchtype = {type: {$ne: 'radioactivity'}}; // Anything not radioactivity } // If sensorType is not specified or something else, get all let docs = await collection.aggregate([ {$match: matchtype}, { $match: query}, { $project: { _id:1, name:1, location: { '$map': { input: '$location', as: 'm', in: { country: '$$m.address.country', city: '$$m.address.city', plz: '$$m.address.plz' } } } }}, { $unwind: '$location'}, { $group: { sensorids: { $addToSet: '$_id'}, _id: '$location.city', plz: { $addToSet: '$location.plz'}, country: { $first: '$location.country' } }}, { $project: { _id: 0, city: '$_id', sensors: { count: {$size: '$sensorids'}, ids : '$sensorids'}, plz: '$plz', country: '$country', }} ]).toArray(); // console.log(docs); return { count: docs.length, cities: docs }; } module.exports = router; module.exports.api = { getCity };