abstract
This post describes how to implement the Magnus-formulas to calculate dew point, moisture – or absolute humidity – (and its current maximum) as well as the current (and maximal) vapor pressure from given time-series on temperture and humidity. Using InfluxDB’s flux-language, realized within Grafana.
see demo: https://weather.hiveeyes.org/grafana/d/magnus-flux/
demo-screenshot
all series as graphs
references
- title: dew point, absolute moisture & vapor pressure from temperature & humidity with InfluxDB/flux
- author: The Hiveeyes Project (Matthias ‘wetterfrosch/wtf’ Mehldau), 2020
- docs: https://community.hiveeyes.org/t/dew-point-absolute-moisture-vapor-pressure-from-temperature-humidity-with-influxdb-flux/2934
- demo: https://weather.hiveeyes.org/grafana/d/magnus-flux/
- Magnus-formulary: as collected by wetterochs.de
- source for verification: dew point, moisture (thestorff.de)
- source for verification: saturated vapor pressure, absolute moisture (ib-rauch.de)
- InfluxDB-version used: v1.7.9, implying flux-language-version: v0.50.2
- Grafana-version used: v6.4.5, v6.6.0
terms
As stated in the referenced Magnus formulas things are defined as
r = relative Luftfeuchte
T = Temperatur in °C
TK = Temperatur in Kelvin (TK = T + 273.15)
TD = Taupunkttemperatur in °C
DD = Dampfdruck in hPa
SDD = Sättigungsdampfdruck in hPa
parameters
a = 7.5, b = 237.3 für T >= 0
a = 7.6, b = 240.7 für T < 0 über Wasser (Taupunkt)
a = 9.5, b = 265.5 für T < 0 über Eis (Frostpunkt)
R* = 8314.3 J/(kmol*K) (universelle Gaskonstante)
mw = 18.016 kg/kmol (Molekulargewicht des Wasserdampfes)
AF = absolute Feuchte in g Wasserdampf pro m3 Luft
Magnus-formulas
Magnus’ formulas shall eqaute to:
1. SDD(T) = 6.1078 * 10^((a*T)/(b+T))
2. DD(r,T) = r/100 * SDD(T)
3. r(T,TD) = 100 * SDD(TD) / SDD(T)
4. TD(r,T) = b*v/(a-v) mit v(r,T) = log10(DD(r,T)/6.1078)
5. AF(r,TK) = 10^5 * mw/R* * DD(r,T)/TK; AF(TD,TK) = 10^5 * mw/R* * SDD(TD)/TK
operations
Let us summarize queries’ architecture:
- flux’ math-fuctions are loaded
- parameters of Magnus formulas (a, b, R, M_R) are set as variables
- the two main data-series (temperature and humidty) are loaded into seperate tables (T and H)
- both tables (T and H) are joined to are table named “TH”
- both values are now avaiable as r._value_t or r._value_h
- this table “TH” is called for every creation of a product/derivate using the formulas above
the whole query for all calculations
// reference-calculation in flux of
// - dew point
// - vapor pressure: current and saturates
// - absolute moistures: current and possible
// given a temperature and a humidity with InfluxDB/flux
//
// based on the Magnus-formulary as documented by wetterochs.de (german)
// - https://www.wetterochs.de/wetter/feuchte.html
//
// implemented by wetterfrosch/wtf <wetter_ät_hiveeyes.org>
// - docs: https://community.hiveeyes.org/t/dew-point-absolute-moisture-vapor-pressure-from-temperature-humidity-with-influxdb-flux/2934
// - demo: https://weather.hiveeyes.org/grafana/d/magnus-flux/dew-point-moisture-and-vapor-pressure-from-temperature-and-humidity?orgId=1
// - license, if applicable: Do what you want, but think about the omen
//
// partially checked against:
// - http://www.thestorff.de/luftfeuchte-rechner.php
// - https://www.ib-rauch.de/bautens/formel/abs_luftfeucht.html
// ### init ###
// load math-functions
import "math"
// parameters of Magnus-formula for temperatures >= 0.0°C:
a = 7.5
b = 237.3
// parameters of Magnus-formula for temperatures < 0.0°C over water:
// a = 7.6
// b = 240.7
// parameters of Magnus-formula for temperatures < 0.0°C over ice:
// a = 9.5
// b = 265.5
// gas constant, J/(kmol*K)
R = 8314.3 // alias "R*"
// Molecular mass of vaporated water, kg/kmol
M_R = 18.016 // alias "mr"
// ### get and aggregate data ###
// query temperature and store to table "T"
T = from(bucket: "dwd_cdc")
|> range($range)
|> filter(fn: ( r) =>
r._measurement == "dwd_cdc_temp_2m_c" and
r.sta_name =~ /^$STATION$/ and
r.quality_level == "2"
)
|> aggregateWindow(every: $__interval, fn: mean)
|> map(fn: (r) => ({
_time: r._time,
_field: "temperature, °C",
_value: r._value
}))
|> yield(name: "T")
// query humidity and store to table "H"
H = from(bucket: "dwd_cdc")
|> range($range)
|> filter(fn: (r) =>
r._measurement == "dwd_cdc_humidity_2m_percentage" and
r.sta_name =~ /^$STATION$/ and
r.quality_level == "2"
)
|> aggregateWindow(every: $__interval, fn: mean)
|> map(fn: (r) => ({
_time: r._time,
_field: "relative humidity, %",
_value: r._value
}))
|> yield(name: "relative humidity")
// join "T" and "H" to table "TH" to derive calculations from
TH = join(tables: {t: T, h: H}, on: ["_time"])
// let's fill empty values with a dummy-value ...
|> fill(column: "_value_h", value: -999.0)
|> fill(column: "_value_t", value: -999.0)
// ... now let's filter out this dummy-value again ...
|> filter(fn: (r) => r._value_h != -999.0)
|> filter(fn: (r) => r._value_t != -999.0)
// ... and flux is now able to handle null-values. o.O
// yes, this seems insane but necessary, too (as of flux v0.50.2)
// ### calculate desired metrics ###
// saturated vapor pressure, hPa
// SDD(T) = 6.1078 * 10^((a*T)/(b+T))
TH
|> map(fn: (r) => ({
_time: r._time,
_field: "saturated vapor pressure, hPa",
_value: 6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t))
}))
|> yield(name: "saturated vapor pressure")
// vapor pressure, hPa
// DD(r,T) = r/100 * SDD(T)
TH
|> map(fn: (r) => ({
_time: r._time,
_field: "vapor pressure, hPa",
_value: (r._value_h/100.0) * (6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t)))
}))
|> yield(name: "vapor pressure")
// dew point, °C
// TD(r,T) = b*v/(a-v)
// where v(r,T) = log10(DD(r,T)/6.1078)
TH
|> map(fn: (r) => ({
_time: r._time,
_field: "dew point, °C",
_value: b*math.log10(x: (r._value_h/100.0) * (6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t)))/6.1078)/(a-math.log10(x: (r._value_h/100.0) * (6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t)))/6.1078))
}))
|> yield(name: "dew point")
// absolute moisture possible, g/m³
// AF(TD,TK) = 10^5 * mw/R* * SDD(TD)/TK
TH
|> map(fn: (r) => ({
_time: r._time,
_field: "absolute moisture possible, g/m³",
_value: math.pow(x: 10.0, y: 5.0) * (M_R/R) * ( (6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t))) /(r._value_t + 273.15))
}))
|> yield(name: "absolute moisture possible")
// absolute moisture, g/m³
// AF(r,TK) = 10^5 * mw/R* * DD(r,T)/TK
TH
|> map(fn: (r) => ({
_time: r._time,
_field: "absolute moisture, g/m³",
_value: math.pow(x: 10.0, y: 5.0) * (M_R/R) * ((r._value_h/100.0) * (6.1078 * math.pow(x:10.0, y: (a*r._value_t)/(b+r._value_t))))/(r._value_t + 273.15)
}))
|> yield(name: "absolute moisture")