Alte Version abgeändert auf neue Datebank /-Struktur).

This commit is contained in:
rxf
2026-03-27 17:26:46 +01:00
commit b7736413d4
87 changed files with 54060 additions and 0 deletions

860
routes/apidata.js Normal file
View File

@@ -0,0 +1,860 @@
"use strict";
const express = require('express');
const router = express.Router();
const moment = require('moment');
const mathe = require('mathjs');
const fs = require('fs');
const $ = require('jquery');
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<sensors.length; j++) { // loop thru array of sensors
try {
val = await getAPIdata(dbase,sensors[j],mavg,dauer,start,end,gstart); // get data for obe sensor
if(val.count != 0) { // if there is data
delete val.avg; // delete unnecessary elements
delete val.span;
delete val.start;
erg.sensordata.push(val); // and push data to result array
}
}
catch(e) {
console.log(e);
}
}
console.log("Zeit in getAPIdataTown:",(moment()-los)/1000,'[sec]'); // time it
console.log('Daten für',erg.sensordata.length,' Sensoren gelesen');
erg.count = erg.sensordata.length; // save count
return erg; // and return all data
}
// ******************************************************************
// getAPIdataTown - Get data direct via API for all sensors in a town
//
// Call:
// http://feinstaub.rexfue.de/api/getdata/?sensorid=stuttgart&avg=5&span=12
//
// mit:
// sensorid: Name der Stadt
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
// dt: Startzeitpunkt
//
// Parameter:
// db: Mongo-Database
// town: name of town
// avg: time over that to build the average [minutes]
// span: duration for the data [hours]
// dt: starting point of 'span'
// res: http-object to send result
//
// return:
// nothing; JSON document will be sent back
//
//
// For every town, there has to be an JSON-file with the
// sensornumbers of ervery sensor living in that town.
//
// ***** Neue DB-Struktur - Versuch
//
// ********************************************************************
async function getAPIdataTown(db, town, avg, span, dt, res) {
// get sensors for the town as array of ids
let p = parseParams(avg, span, dt);
// get sensor numbers from town-sensor-file
let sensors = [];
let tw = town.toLowerCase();
let data = fs.readFileSync(tw+'.txt');
sensors = JSON.parse(data);
return getAPITN (db,sensors,p.mavg,p.dauer,p.start,p.end,p.gstart, town);
}
// ******************************************************************
// getAPIdataSenssor - Get data direct via API for all sensors in a town
//
// Call:
// http://feinstaub.rexfue.de/api/getdata/?sensorid=140&avg=5&span=12&datetime=2018-08-ß02T20:12:00
//
// mit:
// sensorid: ID des gewümschten Sensors
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
// datetime: Startzeitpunkt
//
// Parameter:
// db: Datenbank
// sid: ID of sensor
// avg: time over that to build the average [minutes]
// span: duration for the data [hours]
// dt: starting point of 'span'
//
// return:
// nothing; JSON document will be sent back
//
//
// ***** Neue DB-Struktur - Versuch
//
// ********************************************************************
async function getAPIdataSensor(db, sid, avg, span, dt) {
let p = parseParams(avg, span, dt);
return getAPIdata(db,sid,p.mavg,p.dauer,p.start,p.end,p.gstart)
}
// *********************************************
// Get data direct via API for one sensor
//
// Call:
// http://feinstaub.rexfue.de/api?sid=1234&avg=5&span=24
//
// mit:
// sid: Sensornummer
// avg: Mittelwert-Bildung über xxx Minuten
// span: Zeitraum für die Mittelwertbildung in Stunden
//
// return:
// JSON Dokument mit den angefragten Werten
// *********************************************
async function getAPIdata(db, sid, mavg, dauer, start, end, gstart) {
let values = [];
let retur = {sid: sid, avg: mavg, span: dauer, start: gstart};
// First, determine sensor type from properties
let pcoll = db.collection("properties");
let props = await pcoll.findOne({_id: sid});
if (!props) {
retur.count = 0;
retur['values'] = [];
retur.error = 'Sensor not found';
return retur;
}
// Determine collection based on type
let collectionName;
if (props.type === 'radioactivity') {
collectionName = 'radioactivity_sensors';
} else {
// Assume THP for any other type
collectionName = 'thp_sensors';
}
let collection = db.collection(collectionName);
try {
values = await collection.find(
{
sensorid: sid,
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
},
{
projection: {_id: 0},
sort: {datetime: 1}
}
).toArray()
} catch (e) {
console.log(e);
}
if(values.length == 0) {
retur.count = 0;
retur['values'] = [];
} else {
if((mavg===undefined) || (mavg == 1)) {
retur.count = values.length;
retur['values'] = values;
}
let x = await util.calcMovingAverage(db, sid, values, mavg, true);
let 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;
}
/* ===============================================================
// 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 };

134
routes/data.js Normal file
View File

@@ -0,0 +1,134 @@
var express = require('express');
var router = express.Router();
var moment = require('moment');
// Get latest 10 readings out from the database
router.get('/latest10', function(req,res) {
var collection = req.strom;
collection.find({},{limit:5, sort: { date: -1}},function(e,docs) {
res.json(docs);
});
});
// Get readings for the last hour out from the database
router.get('/onehour', function(req,res) {
var db = req.db;
var collection = db.get('strom');
var st = req.query.start;
st = st.substring(0,st.length-1);
var start = moment(st);
var end = moment(st);
start.subtract(1,'h');
collection.find({date: { $gte: new Date(start), $lt: new Date(end)}},{sort: { date: 1} },function(e,docs) {
// console.dir(docs);
res.json(docs);
});
});
router.get('/oneyear', function(req,res) {
var year = req.query.year;
if(year == undefined) {
year = moment().year();
}
var st = req.query.start;
st = st.substring(0,10);
// console.log("st: " + st);
var start = moment(st);
var db = req.db;
var collection = db.get('stromDay');
var sDat = moment("01-01-"+year, "MM-DD-YYYY"); // Start-Datum 1.1.year
var curDat = moment();
var erg = []; // hier die Werte sammeln
var last = { date: 0, wert: 0, cnt: 0};
var first=0;
// console.log("Start " + start.format());
start.subtract(30,'days'); // Daten für 30 Tage holen
// console.log("Start-30 " + start.format());
collection.find( { date: { $gte: new Date(start) }}, {sort: { date: 1} },function(err,docs){
if(err) {
console.log("StromM-Error: ");
console.dir(err);
} else {
var lang = (docs.length > 31) ? 31 : docs.length;
for(var i=0; i<lang; i++) {
// console.log(i + " " + docs[i].date);
if(last.date != 0) {
// console.log("last.date: " + last.date);
erg.push({ date: last.date, wert: (docs[i].zstand - last.wert)});
if( (docs[i].zstand - last.wert) != 0) {
last.cnt++;
}
} else {
first = docs[i].zstand;
}
last.date = docs[i].date;
last.wert = docs[i].zstand;
}
}
// console.log(erg);
var avg = (last.wert - first)/last.cnt;
res.send ({ average: avg, erg: erg });
});
/*
async.whilst(
function() { return sDat < curDat; },
function (callback) {
collection.findOne( { date: { $gte: new Date(sDat) }}, {sort: { date: 1} }, function(err,data) {
if(err) {
console.log("StromM-Error: " + err);
callback(err);
} else {
if(last.date != 0) {
erg.push({ date: last.date, wert: (data.all - last.wert)});
if( (data.all - last.wert) != 0) {
last.cnt++;
}
} else {
first = data.all;
}
last.date = data.date;
last.wert = data.all;
}
sDat.add(1, 'days'); // add 1 day of seconds
callback();
});
},
function (err) {
var avg = (last.wert - first)/last.cnt;
res.send ({ average: avg, erg: erg });
}
);
*/
});
//Get readings for the last hour out from the database
router.get('/latest10I', function(req,res) {
var collection = req.strom;
var start = moment();
// console.log("Start=" + start);
start.subtract(20,'s');
collection.find({date: { $gte: new Date(start)}},{sort: { date: 1} },function(e,docs) {
res.json(docs);
});
});
//Get the reading of the 1. of January for the current year
router.get('/zstand', function(req,res) {
var db = req.db;
var curYear = moment().year();
var collection = db.get('zaehler');
collection.findOne({'year' : curYear},function(e,docs) {
res.json(docs);
});
});
module.exports = router;

482
routes/fsdata.js Normal file
View File

@@ -0,0 +1,482 @@
"use strict";
const express = require('express');
const router = express.Router();
const moment = require('moment');
const mathe = require('mathjs');
const util = require('./utilities');
// Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !!
let sv_factor = {'SBM-20': 1 / 2.47, 'SBM-19': 1 / 9.81888, 'Si22G': 0.081438};
//Get readings for all data out ot the database
router.get('/getfs/:week', function (req, res) {
let week = req.params.week;
let db = req.app.get('dbase');
let st = req.query.start;
let sid = parseInt(req.query.sensorid);
let sname = req.query.sensorname;
let avg = req.query.avgTime;
let live = (req.query.live == 'true');
let movingAvg = (req.query.moving=='true');
let longAVG = req.query.longAVG;
let system = req.query.os;
if (week == 'oneday') {
console.log(`Operating System = ${system}`);
getDWMData(db, sid, st, avg, live, movingAvg, 1, longAVG)
.then(erg => res.json(erg));
} else if (week == 'oneweek') {
getDWMData(db, sid, st, avg, live, movingAvg, 7)
.then(erg => res.json(erg));
} else if (week == 'onemonth') {
getDWMData(db, sid, st, 1440, false, false, 31)
.then(erg => res.json(erg));
} else if (week == 'korr') {
getSensorProperties(db,sid)
.then(erg => res.json(erg));
} else {
res.json({'error': 'MIST VERDAMMTER!!'});
}
});
// fetch name of given sensor-id
function getSensorName(db,sid) {
const p = new Promise((resolve,reject) => {
let coll = db.collection('properties');
coll.findOne({_id: parseInt(sid)})
.then(erg => {
// name is now an array, get the current (last) entry
if (erg && erg.name && Array.isArray(erg.name)) {
resolve(erg.name[erg.name.length - 1].name);
} else {
resolve(erg ? erg.name : null);
}
})
.catch(err => {
console.log('getSensorName',err);
reject(err);
});
});
return p
}
// fetch the properties for the given sensor
async function getSensorProperties(db,sid) {
let start = new Date();
console.log("Get properties for", sid,"from DB");
let sensorEntries = [{'sid':sid}];
let coll = db.collection('properties');
let properties;
try {
properties = await coll.findOne({_id: sid});
}
catch(e) {
console.log("getSensorProperties",e);
return {};
}
console.log("got properties - time:", new Date() - start);
if(properties == null) return null;
// name is now an array, get the current (last) entry
let currentName = properties.name;
if (Array.isArray(properties.name)) {
currentName = properties.name[properties.name.length - 1].name;
}
// Overwrite name field with string for frontend compatibility
properties.name = currentName;
sensorEntries[0]['name'] = currentName;
// Check if othersensors exists (might not in new DB structure)
if (!properties.othersensors || properties.othersensors.length === 0) {
properties.othersensors = sensorEntries;
return properties;
}
let mustbeobject = false;
for(let i = 0, j=1; i<properties.othersensors.length; i++) {
let es = properties.othersensors[i];
let e = {};
if (es != null) {
if ( typeof es === 'object') {
mustbeobject=true;
e.sid = es.id;
e.name = es.name;
} else {
if(mustbeobject) { continue; }
e.sid = es;
e.name = await getSensorName(db, es);
}
}
sensorEntries[j] = e;
j++;
}
sensorEntries.sort(function (a, b) {
if (a.sid < b.sid) {
return -1;
}
if (a.sid > b.sid) {
return 1;
}
return 0;
});
properties.othersensors = sensorEntries;
return properties;
}
async function readRadiationMovingAverage(db, sid, start, end, average, factor) {
let docs = [];
let collection = db.collection('radioactivity_sensors');
try {
docs = await collection.find({
sensorid: sid,
datetime: {
$gte: new Date(start),
$lt: new Date(end)
}
}, {sort: {datetime: 1}}).toArray();
} catch (e) {
console.log('readRadiationMovingAverage',e);
return [];
}
if (docs.length == 0) {
return [];
} else {
let d = await util.calcMovingAverage(db, sid, docs, average , false, factor);
return d.RAD;
}
}
async function readRadiationAverages(db, sid, start, end, average, factor) {
let docs = [];
let collection = db.collection('radioactivity_sensors');
try {
docs = await collection.aggregate([
{$match: {sensorid: sid, datetime: {$gte: new Date(start), $lt: new Date(end)}}},
{$sort: {datetime: 1}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, 1000 * 60 * average]} // aggregate every average minutes
]
}
},
cpmAvg: {$avg: '$values.counts_per_minute'},
cpmSum: {$sum: '$values.counts_per_minute'},
count: {$sum: 1}
}
},
{ $addFields: { uSvphAvg: { $multiply: ["$cpmAvg", factor]}}},
{$sort: {_id: 1}}
]).toArray();
}
catch(e) {
console.log('readRadiationAverages', e);
return [];
}
return docs;
}
async function readClimateAverages(db, sid, start, end, average) {
let docs = [];
let collection = db.collection('thp_sensors');
try {
docs = await collection.aggregate([
{$match: {sensorid: sid, datetime: {$gte: new Date(start), $lt: new Date(end)}}},
{$sort: {datetime: 1}},
{
$group: {
_id: {
$toDate: {
$subtract: [
{$toLong: '$datetime'},
{$mod: [{$toLong: '$datetime'}, 1000 * 60 * average]} // aggregate every average min
]
}
},
tempAvg: {$avg: '$values.temperature'}, // average over every 10min
humiAvg: {$avg: '$values.humidity'},
pressSeaAvg: {$avg: '$values.pressure_at_sealevel'},
count: {$sum: 1}
}
},
{$sort: {_id: 1}}
]).toArray();
}
catch(e) {
console.log('readClimateAverage', e);
return [];
}
return docs;
}
function calcTimeRange(st, range, live, avg) {
let start = moment(st);
let end = moment(st);
if(range == 1) { // one day
if (live == true) {
start.subtract(24, 'h');
start.subtract(avg, 'm');
} else {
start.subtract(avg, 'm');
end.add(24, 'h');
}
} else if (range == 7) { // one week (7 days)
if (live == true) {
start.subtract(24 * 8, 'h');
} else {
start.subtract(24 * 8, 'h');
// end.add(24 * 7, 'h');
console.log(start.format(), end.format());
}
} else if (range == 31) { // one month (31 days)
start=start.startOf('day');
end = end.startOf('day');
start.subtract(33, 'd');
} else if (range >= 48) { // 48 hours
if(live == true) {
start.subtract(range, 'h')
}
}
return { start: start, end: end };
}
// get data for one day, one week or one month from the database
async function getDWMData(db, sensorid, st, avg, live, doMoving, span, longAVG) {
let docs = [];
let ret = {radiation: [], climate: []};
// first fetch properties for this sensor
let properties = await getSensorProperties(db,sensorid);
// calculate time range
let timerange = calcTimeRange(st, span, live, avg);
for (let n = 0; n<properties.othersensors.length; n++) {
let sid = properties.othersensors[n].sid;
let sname = properties.othersensors[n].name;
try {
if (sname.startsWith("Radiation")) {
let factor = sv_factor[sname.substring(10)] / 60;
if(doMoving) {
docs = await readRadiationMovingAverage(db, sid, timerange.start, timerange.end, avg, factor);
} else {
docs = await readRadiationAverages(db, sid, timerange.start, timerange.end, avg, factor);
}
let avg48 = null;
if (longAVG != undefined) {
let tr = calcTimeRange(st, longAVG, true, avg);
let avg48_docs = await readRadiationAverages(db, sid, tr.start, tr.end, longAVG*60, factor);
avg48 = avg48_docs[0];
}
ret['radiation'] = {sid: sid, sname: sname, avg48: avg48, values: docs};
} else if (sname == "BME280") {
docs = await readClimateAverages(db, sid, timerange.start, timerange.end, 10);
if (docs.length != 0) {
ret['climate'] = {sid: sid, sname: sname, values: docs};
}
} else {
ret['error'] = "Sensor not of right type (unknown)";
}
} catch (e) {
console.log('getDayData', e);
}
}
return ret;
}
// für die Wochenanzeige die Daten als gleitenden Mittelwert über 24h durchrechnen
// ===============================================================
// PM (FEINSTAUB) FUNCTIONS REMOVED - Not needed in new DB
// ===============================================================
/*
// und in einem neuen Array übergeben
function movAvgSDSWeek(data) {
var neuData = [];
const oneDay = 3600*24;
// first convert date to timestam (in secs)
for (var i=0; i<data.length; i++) {
data[i].datetime = ( new Date(data[i].datetime)) / 1000; // the math does the convertion
}
// now calculate the moving average over 24 hours
let left=0, roll_sum=0, nd = [];
for (let right =0; right < data.length; right++) {
// if(right == 200) {
// console.log("right = 200");
// }
if(data[right].P1 != undefined) {
roll_sum += data[right].P1;
}
if(data[right].P10 != undefined) {
roll_sum += data[right].P10;
}
while( data[left].datetime <= data[right].datetime - oneDay) {
if(data[left].P1 != undefined) {
roll_sum -= data[left].P1;
}
if(data[left].P10 != undefined) {
roll_sum -= data[left].P10;
}
left += 1;
}
nd[right] = { 'P1024': roll_sum/ (right-left+1)+5};
}
for (var i=1, j=0; i< data.length; i++, j++) {
var sum1=0, sum2 = 0, cnt1 = 0, cnt2 = 0;
var di = data[i].datetime;
for (var k=i; k>=0 ; k--) {
if (data[k].datetime+oneDay < di) {
break;
}
if (data[k].P1 !== undefined) {
sum1 += data[k].P1;
cnt1++;
}
if (data[k].P2 !== undefined) {
sum2 += data[k].P2;
cnt2++;
}
if (data[k].P10 !== undefined) {
sum1 += data[k].P10;
cnt1++;
}
if (data[k].P2_5 !== undefined) {
sum2 += data[k].P2_5;
cnt2++;
}
}
neuData[j] = {'P10': sum1 / cnt1, 'P2_5': sum2 / cnt2, 'date': data[i].datetime} ;
}
// finally shrink datasize, so that max. 1000 values will be returned
var neu1 = [];
var step = Math.round(neuData.length / 500);
// if (step == 0) step = 1;
step = 1;
for (var i = 0, j=0; i < neuData.length; i+=step, j++) {
var d = neuData.slice(i,i+step)
var p1=0, p2=0;
for(var k=0; k<d.length; k++) {
if (d[k].P10 > p1) {
p1 = d[k].P10;
}
if (d[k].P2_5 > p2) {
p2 = d[k].P2_5;
}
}
neu1[j] = {'P10': p1, 'P2_5': p2, 'date': neuData[i].date*1000} ;
}
return { 'ndold': neu1, 'ndnew': nd };
}
function calcMinMaxAvgSDS(data,isp10) {
var p1=[], p2=[];
for (var i = 0; i < data.length; i++) {
if (data[i].P10 != undefined) {
p1.push(data[i].P10);
}
if (data[i].P2_5 != undefined) {
p2.push(data[i].P2_5);
}
if (data[i].P1 != undefined) {
p1.push(data[i].P1);
}
if (data[i].P2 != undefined) {
p2.push(data[i].P2);
}
}
return {
'P10_max': mathe.max(p1),
'P2_5_max': mathe.max(p2),
'P10_min': mathe.min(p1),
'P2_5_min': mathe.min(p2),
'P10_avg' : mathe.mean(p1),
'P2_5_avg' : mathe.mean(p2) };
}
*/
function calcMinMaxAvgDHT(data) {
var t=[], h=[];
for (var i=0; i<data.length; i++) {
if(data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if(data[i].humidity != undefined) {
h.push(data[i].humidity);
}
}
return {
'temp_max': mathe.max(t),
'humi_max': mathe.max(h),
'temp_min': mathe.min(t),
'humi_min': mathe.min(h),
'temp_avg' : mathe.mean(t),
'humi_avg' : mathe.mean(h) };
}
async function calcMinMaxAvgBMP(db, sid, data) {
var t = [], p = [];
for (var i = 0; i < data.length; i++) {
if (data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if (data[i].pressure != undefined) {
p.push(data[i].pressure);
}
}
let altitude = await util.getAltitude(db, sid);
p = util.calcSealevelPressure(p, '', altitude);
return {
'temp_max': mathe.max(t), 'press_max': mathe.max(p),
'temp_min': mathe.min(t), 'press_min': mathe.min(p),
'temp_avg': mathe.mean(t), 'press_avg': mathe.mean(p)
};
}
async function calcMinMaxAvgBME(db, sid, data) {
var t = [], h = [], p = [], sumt = 0;
for (var i = 0; i < data.length; i++) {
if (data[i].temperature != undefined) {
t.push(data[i].temperature);
}
if (data[i].humidity != undefined) {
h.push(data[i].humidity);
}
if (data[i].pressure != undefined) {
p.push(data[i].pressure);
}
}
let altitude = await util.getAltitude(db, sid);
p = util.calcSealevelPressure(p, '', altitude);
return {
'temp_max': mathe.max(t), 'humi_max': mathe.max(h), 'press_max': mathe.max(p),
'temp_min': mathe.min(t), 'humi_min': mathe.min(h), 'press_min': mathe.min(p),
'temp_avg': mathe.mean(t), 'humi_avg': mathe.mean(h), 'press_avg': mathe.mean(p)
};
}
/* PM (FEINSTAUB) FUNCTIONS REMOVED
function isPM(name) {
if ((name == "SDS011") || (name.startsWith("PPD")) || (name.startsWith("PMS"))) {
return true;
}
return false;
}
// Statistiken für den Sensor holen und übergeben - PM only
async function getStatistics(db,sensorid) {
// REMOVED - Was PM-specific
return { error: 'PM statistics removed in new DB' };
}
*/
module.exports = router;

59
routes/index.js Normal file
View File

@@ -0,0 +1,59 @@
var express = require('express');
var router = express.Router();
//Version
var pkg = require('../package.json');
var idx = 'index';
var mapit = 'mapit';
var tit = 'Geiger';
const MAINT = (process.env.MAINTENANCE=="true");
if (MAINT==true) {
idx = 'maintenance';
mapit = 'maintenance';
}
// GET home page.
router.get('/', function(req, res, next) {
res.render(mapit, {
title: tit+'-Map',
version: pkg.version,
date: pkg.date,
name: 'map',
param: req.query.addr,
stday: req.query.stday,
showAllMap: req.query.mall,
csensor: req.query.sid,
zoom:req.query.zoom,
city:req.query.city,
stype: tit,
splash: req.query.splash == 'true',
});
});
router.get('/:nam', function(req, res, next) {
var name = req.params.nam;
if(name == 'index') {
idx = 'index';
name = '140';
title += '_I';
} else {
res.render(mapit, {
title: tit+'-Map',
version : pkg.version,
date : pkg.date,
param: req.query.addr,
stday: req.query.stday,
zoom:req.query.zoom,
city:req.query.city,
csensor: name,
splash: req.query.splash == 'true',
name: 'map'});
}
});
module.exports = router;

294
routes/mapdata.js Executable file
View File

@@ -0,0 +1,294 @@
"use strict";
var express = require('express');
var router = express.Router();
var moment = require('moment');
const axios = require('axios');
let $ = require('jquery');
var fs = require('fs');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// URL to get coordinates for cities
const NOMINATIM_URL="https://nominatim.openstreetmap.org/search?format=json&limit=3&q=";
// Mongo wird in app.js geöffnet und verbunden und bleibt immer verbunden !!
// Fetch the actual out of the dbase
router.get('/getaktdata/', async function (req, res) {
var db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let box = req.query.box;
let poly = [];
var collection = db.collection('properties'); // Using properties collection now
var aktData = []; // hier die daten sammeln
var now = moment(); // akt. Uhrzeit
var lastDate = 0;
let south=null,north=null,east=null,west=null;
let loc = {};
if(req.query.poly != undefined) {
poly = JSON.parse(req.query.poly);
}
if (!((box == "") || (box == undefined))) {
south = parseFloat(box[0][1]);
north = parseFloat(box[1][1]);
east = parseFloat(box[1][0]);
west = parseFloat(box[0][0]);
console.log("getaktdata: S=", south, " N=", north, " E=", east, " W=", west)
}
console.log("getaktdata: now fetching data from DB");
// Build geo query - location is now in location array (last element)
if(poly.length != 0) {
loc = {
'location.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [poly],
}
}
},
type: 'radioactivity' // Only radiation sensors for map
}
} else if (south !== null) {
loc = {
'location.loc': {
$geoWithin: {
$box: [
[west, south],
[east, north]
]
}
},
type: 'radioactivity' // Only radiation sensors for map
}
} else {
loc = {
type: 'radioactivity' // Only radiation sensors for map
}
}
try {
let docs = await collection.find(loc).toArray();
if (docs == null) {
console.log("getaktdata: docs==null");
res.json({"avgs": [], "lastDate": null});
return;
}
console.log("getaktdata: data fetched, length=",docs.length);
for (var i = 0; i < docs.length; i++) {
var item = docs[i];
// Skip if no values or no location
if (!item.values || !item.location || item.location.length === 0) {
continue;
}
// Get current location (last in array)
let currentLoc = item.location[item.location.length - 1];
// Get current name (last in array)
let currentName = item.name;
if (Array.isArray(item.name)) {
currentName = item.name[item.name.length - 1].name;
}
var oneAktData = {};
oneAktData['location'] = currentLoc.loc.coordinates;
oneAktData['id'] = item._id;
// Handle timestamp - might be in $date format
let timestamp = item.values.timestamp;
if (timestamp && timestamp.$date) {
timestamp = new Date(timestamp.$date);
} else {
timestamp = new Date(timestamp);
}
oneAktData['lastSeen'] = timestamp;
// Extract sensor type from name (e.g., "Radiation SBM-20" -> "SBM-20")
oneAktData['name'] = currentName.replace('Radiation ', '');
// indoor is now a number (0 or 1)
oneAktData['indoor'] = currentLoc.indoor === 1;
var dt = timestamp;
if ((now - dt) >= (7 * 24 * 3600 * 1000)) { // älter als 1 Woche ->
oneAktData['cpm'] = -2; // -2 zurückgeben
} else if ((now - dt) >= (2 * 3600 * 1000)) { // älter als 2 Stunde ->
oneAktData['cpm'] = -1; // -1 zurückgeben
} else {
oneAktData['cpm'] = -5; // bedeutet -> nicht anzeigen
if (item.values.hasOwnProperty('counts_per_minute')) {
oneAktData['cpm'] = item.values.counts_per_minute.toFixed(0); // und merken
}
if (dt > lastDate) {
lastDate = dt;
}
}
aktData.push(oneAktData); // dies ganzen Werte nun in das Array
}
res.json({"avgs": aktData, "lastDate": lastDate}); // alles bearbeitet -> Array senden
}
catch(e) {
console.log("Problem mit getaktdata", e);
res.json({"avgs": [], "lastDate": null});
return;
}
});
/* ===============================================================
// AKW (Nuclear Power Plant) FUNCTIONS - COMMENTED OUT FOR NOW
// Will be needed later, so not deleting
// ===============================================================
// Fetch all akw data out of the dbase
router.get('/getakwdata/', async function (req, res) {
const db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
let collection = db.collection('akws'); // die 'korrelation' verwenden
let erg = [];
let docs = [];
console.log("getakwdata: now fetching data from DB");
try {
docs = await collection.find().toArray(); // find all
if (docs == null) {
console.log("getakwdata: docs==null");
res.json(erg);
return;
}
console.log("getawkdata: data fetched from akws, length=",docs.length);
for (var i = 0; i < docs.length; i++) {
var item = docs[i];
var oneAktData = {};
oneAktData['location'] = {
type: 'Point',
coordinates: [item.lon, item.lat]
};
oneAktData['name'] = item.Name;
oneAktData['active'] = item.Status == 'aktiv';
oneAktData['start'] = item.Baujahr;
oneAktData['end'] = item.Stillgeleg;
oneAktData['type'] = item.Status === 'aktiv' ? 'akw_a' : 'akw_s';
oneAktData['link'] = item.Wiki_Link;
erg.push(oneAktData); // dies ganzen Werte nun in das Array
}
collection = db.collection('th1_akws');
docs = await collection.find().toArray();
if (docs == null) {
console.log("getakwdata: docs==null");
res.json(erg);
return;
}
console.log("getawkdata: data fetched from th_akws, length=", docs.length);
for (let i = 0; i < docs.length; i++) {
const item = docs[i];
let oneAktData = {};
let loc = item.geo.substr(6).split(' ');
let lon = parseFloat(loc[0]);
let lat = parseFloat(loc[1]);
oneAktData['location'] = {
type: 'Point',
coordinates: [lon, lat]
};
oneAktData['name'] = item.name;
oneAktData['typeText'] = item.types;
oneAktData['type'] = item.types == "Nuclear power plant" ? 'akw_a' : 'other';
oneAktData['link'] = item.item;
if (item.itemServiceretirement != undefined) {
oneAktData['ende'] = item.itemServiceretirement.substr(0,4);
}
if (item.itemServiceentry != undefined) {
oneAktData['begin'] = item.itemServiceentry.substr(0,4);
}
// Push only NOT 'Nuclear Power Plants' into data array
// if(item.types != 'Nuclear power plant') {
erg.push(oneAktData);
// }
}
res.json(erg);
}
catch(e) {
console.log("Problem mit getakwdata", e);
res.json({"akws": [], "research": [], "fusion": [], "waste": [],});
return;
}
});
*/
router.get('/getStuttgart/', function (req, res) {
fs.readFile('public/Stuttgart.gpx',function(err,data) {
res.send(data);
})
});
router.get('/getcoord/', function (req, res) {
getCoordinates(req.query.city)
.then(erg => res.json(erg));
});
router.get('/getIcon/:col', function (req, res) {
let color = req.params.col;
// fs.readFile('public/radioak4_30.png',function(err,data) {
fs.readFile('public/nuclear-'+color+'.svg',function(err,data) {
res.send(data);
})
});
router.get('/regionSensors/', function (req, res) {
var db = req.app.get('dbase'); // db wird in req übergeben (von app.js)
var spoints = JSON.parse(req.query.points);
getRegionSensors(db,spoints)
.then(erg => res.json(erg));
});
async function getRegionSensors(db,p) {
let properties = [];
let pcoll = db.collection("properties");
properties = await pcoll.find({
'location.loc': {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [ p ],
}
}
},
type: 'radioactivity' // Only radiation sensors
},{_id: 1, name: 1}
).toArray();
let sids = [];
properties.forEach(x => {
sids.push(x._id);
});
console.log('Anzahl gefundene Sensoren:',sids.length);
return sids;
}
router.get('/storeSensors/', function (req, res) {
let data = req.query.sensors;
fs.writeFile('stuttgart.txt',data,(err) => {
if (err) throw(err);
console.log("Sensoren gespeichert");
});
});
async function getCoordinates(city) {
let start = moment()
let url = NOMINATIM_URL + city;
const response = await axios.get(encodeURI(url));
const data = response.data;
if(data.length !== 0) {
console.log(`Fetching of city ${city} needs ${(moment() - start) / 1000} seconds.`)
return data[0];
} else {
console.log(`City ${city} not found` )
return {lat: 0, lon: 0}
}
}
module.exports = router;

245
routes/utilities.js Normal file
View File

@@ -0,0 +1,245 @@
"use strict";
const moment = require('moment');
// *********************************************
// Calculate moving average over the data array.
//
// params:
// data: array of data
// mav: time in minutes to average
// name: name of sensor
// api: default=false, true = API -> no akt. values
//
// return:
// array with averaged values
// TODO <----- die ersten Einträge in newData mit 0 füllen bis zum Beginn des average
// *********************************************
async function calcMovingAverage(db, sid, data, mav, api, factor) {
var newDataT = [], newDataR = [];
var avgTime = mav*60; // average time in sec
let havepressure = false; // true: we have pressure
if (avgTime === 0) { // if there's nothing to average, then
avgTime = 1;
}
// first convert date to timestamp (in secs)
for (var i=0; i<data.length; i++) {
// Handle both old datetime and new datetime.$date format
if (data[i].datetime && data[i].datetime.$date) {
data[i].datetime = (new Date(data[i].datetime.$date)) / 1000;
} else {
data[i].datetime = (new Date(data[i].datetime)) / 1000;
}
}
let left=0, roll_sum3=0, roll_sum4=0, roll_sum5=0, roll_sum6=0;
// Check if we have pressure data
if(data[0] && data[0].values && data[0].values.pressure != undefined) {
havepressure = true;
} else if (data[0] && data[0].pressure != undefined) {
havepressure = true;
}
for (let right =0; right < data.length; right++) {
// Handle values object (new structure)
let temperature = data[right].values ? data[right].values.temperature : data[right].temperature;
let humidity = data[right].values ? data[right].values.humidity : data[right].humidity;
let pressure = data[right].values ? data[right].values.pressure : data[right].pressure;
let counts_per_minute = data[right].values ? data[right].values.counts_per_minute : data[right].counts_per_minute;
if (temperature != undefined) {
roll_sum3 += temperature;
}
if (humidity != undefined) {
roll_sum4 += humidity;
}
if (pressure != undefined) {
roll_sum5 += pressure;
}
if (counts_per_minute != undefined) {
roll_sum6 += counts_per_minute;
}
while (data[left].datetime <= data[right].datetime - avgTime) {
let temp_left = data[left].values ? data[left].values.temperature : data[left].temperature;
let humi_left = data[left].values ? data[left].values.humidity : data[left].humidity;
let press_left = data[left].values ? data[left].values.pressure : data[left].pressure;
let cpm_left = data[left].values ? data[left].values.counts_per_minute : data[left].counts_per_minute;
if (temp_left != undefined) {
roll_sum3 -= temp_left;
}
if (humi_left != undefined) {
roll_sum4 -= humi_left;
}
if (press_left != undefined) {
roll_sum5 -= press_left;
}
if (cpm_left != undefined) {
roll_sum6 -= cpm_left;
}
left += 1;
}
if (api == true) {
newDataT[right] = {'dt': moment.unix(data[right].datetime)};
if (roll_sum3 != 0) newDataT[right]['T'] = (roll_sum3 / (right - left + 1)).toFixed(1);
if (roll_sum4 != 0) newDataT[right]['H'] = (roll_sum4 / (right - left + 1)).toFixed(0);
if (roll_sum5 != 0) newDataT[right]['P'] = (roll_sum5 / (right - left + 1)).toFixed(2);
newDataR[right] = {'date': data[right].datetime * 1000};
if (roll_sum6 != 0) newDataR[right]['cpm'] = roll_sum6 / (right - left + 1);
} else {
newDataT[right] = {'date': data[right].datetime * 1000};
if (roll_sum3 != 0) newDataT[right]['temp_mav'] = roll_sum3 / (right - left + 1);
if (roll_sum4 != 0) newDataT[right]['humi_mav'] = roll_sum4 / (right - left + 1);
if (roll_sum5 != 0) newDataT[right]['press_mav'] = roll_sum5 / (right - left + 1);
newDataR[right] = {'_id': moment.unix(data[right].datetime).toDate()};
if (roll_sum6 != 0) {
let val = roll_sum6 / (right - left + 1);
newDataR[right]['cpmAvg'] = val;
newDataR[right]['uSvphAvg'] = val * factor;
}
}
}
if (havepressure == true) {
let altitude = await getAltitude(db, sid);
if (api == true) {
newDataT = calcSealevelPressure(newDataT, 'P', altitude);
for (let i = 0; i < newDataT.length; i++) {
newDataT[i].P = (newDataT[i].P / 100).toFixed(0);
}
} else {
newDataT = calcSealevelPressure(newDataT, 'press_mav', altitude);
}
}
if (api == true) {
// Return THP or Radiation data depending on what's available
return (roll_sum3 != 0 || roll_sum4 != 0 || roll_sum5 != 0) ? newDataT : newDataR;
}
return { 'THP' : newDataT , 'RAD' : newDataR};
}
// Berechnung des barometrischen Druckes auf Seehöhe
//
// Formel (lt. WikiPedia):
//
// p[0] = p[h] * ((T[h] / (T[h] + 0,0065 * h) ) ^-5.255)
//
// mit
// p[0] Druck auf NN (in hPa)
// p[h] gemessener Druck auf Höhe h (in m)
// T[h] gemessene Temperatur auf Höhe h in K (== t+273,15)
// h Höhe über NN in m
//
// press -> aktuelle Druck am Ort
// temp -> aktuelle Temperatur
// alti -> Höhe über NN im m
//
// NEU NEU NEU
// Formel aus dem BMP180 Datenblatt
//
// p0 = ph / pow(1.0 - (altitude/44330.0), 5.255);
//
//
//
// Rückgabe: normierter Druck auf Sehhhöhe
//
function calcSealevelPressure(data, p, alti) {
if (!((alti == 0) || (alti == undefined))) {
for (let i = 0; i < data.length; i++) {
if (p=='') {
data[i] = data[i] / Math.pow(1.0 - (alti / 44330.0), 5.255);
} else {
data[i][p] = data[i][p] / Math.pow(1.0 - (alti / 44330.0), 5.255);
}
}
}
return data
}
// Aus der 'properties'-collection die altitude für die
// übergebene sid rausholen
async function getAltitude(db,sid) {
let collection = db.collection('properties');
try {
let values = await collection.findOne({"_id":sid});
return values.location[values.location.length-1].altitude;
}
catch(e) {
console.log("GetAltitude Error",e);
return 0
}
}
// Get address from coordinates using OpenStreetMap Nominatim API
// params: db, sensorid
// returns: {address: {street, plz, city}, err}
async function getAddress(db, sid) {
const axios = require('axios');
let ret = {address: {street: "", plz: "", city: ""}, err: null};
// Get sensor properties to extract coordinates
let collection = db.collection('properties');
let props;
try {
props = await collection.findOne({"_id": sid});
if (!props || !props.location || props.location.length === 0) {
return {address: ret.address, err: "No location found for sensor"};
}
} catch(e) {
console.log("getAddress - DB Error:", e);
return {address: ret.address, err: e.message};
}
// Get coordinates from last location entry
let coord = props.location[props.location.length - 1].loc.coordinates;
let lat = coord[1];
let lon = coord[0];
// Call Nominatim API for reverse geocoding
let url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`;
try {
const response = await axios(encodeURI(url), {
headers: {
'User-Agent': 'MultiGeiger-Web/2.9.6' // Nominatim requires User-Agent
}
});
if (response.status !== 200) {
return {address: ret.address, err: `Nominatim API returned status ${response.status}`};
}
let akt = response.data.address;
// Try to find city name in various fields
const CITY = ['city', 'town', 'village', 'suburb', 'county'];
let city = "unknown";
for (let c of CITY) {
if(akt[c] !== undefined) {
city = akt[c];
break;
}
}
ret.address = {
street: (akt.road ? akt.road : ""),
plz: (akt.postcode ? akt.postcode : ""),
city: city
};
} catch (e) {
console.log("getAddress - API Error:", e.message);
return {address: ret.address, err: e.message};
}
return ret;
}
module.exports.calcMovingAverage = calcMovingAverage;
module.exports.calcSealevelPressure = calcSealevelPressure;
module.exports.getAltitude = getAltitude;
module.exports.getAddress = getAddress;