Files
geiger2/routes/utilities.js

246 lines
8.8 KiB
JavaScript

"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;