Dew point, absolute moisture & vapor pressure from temperature & humidity with InfluxDB/flux

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

image

all series as graphs

image

references

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")
4 Likes

Thanks, @wtf!


N.B.: When we started with Grafana for hiveeyes in 2016, we used a simplified approach for calculating the dew point published (initially?) by Mark G. Lawrence (abstract: https://journals.ametsoc.org/doi/abs/10.1175/BAMS-86-2-225 ; pdf download on that page), which gives acceptable approximated results:

However, there is a very simple rule of thumb that I have found to be quite useful for approximating the conversion for moist air (RH > 50%), which does not appear to be widely known by the meteorological community: td decreases by about 1°C for every 5% decrease in RH (starting at td = t, the dry-bulb temperature, when RH = 100%):

t_{d} ~~ t - ((100 - rH) / 5)

used in ‘old’ influx query language:

SELECT median("airtemperature_outside") - ((100 - median("airhumidity_outside"))/5)  FROM [...]

As the author notes, this is more accurate for moist air (rH>50%) than for lower moisture values.

1 Like