diff --git a/lib/xmet/skew_t.py b/lib/xmet/skew_t.py index 7a330f2..92807c0 100644 --- a/lib/xmet/skew_t.py +++ b/lib/xmet/skew_t.py @@ -1,12 +1,16 @@ import math import cairo -from typing import Callable +from typing import Callable +from itertools import product -from xmet.sounding import Sounding +from xmet.list import nearest +from xmet.util import cmp +from xmet.sounding import Sounding, SoundingSample from xmet.thermo import pressure_height, loft_parcel, moist_lapse_rate, \ LAPSE_RATE_DRY, PRESSURE_MAX, PRESSURE_MIN, \ - PRESSURE_STEP + PRESSURE_STEP, mixing_ratio, saturated_mixing_ratio, \ + mixing_ratio_temp PRESSURE_LOG_MAX = math.log(PRESSURE_MAX) PRESSURE_LOG_MIN = math.log(PRESSURE_MIN) @@ -176,6 +180,92 @@ class SkewTGraph(): cr.stroke() cr.restore() + def follow_dry_adiabat(self, temp: float, pressure: float) -> dict: + levels = dict() + + for level in loft_parcel(temp, pressure, lambda t, p: LAPSE_RATE_DRY): + t2, p2 = level + + levels[p2] = t2 + + return levels + + def follow_moist_adiabat(self, temp: float, pressure: float) -> dict: + levels = dict() + + for level in loft_parcel(temp, pressure, moist_lapse_rate): + t2, p2 = level + + levels[p2] = t2 + + return levels + + def follow_saturated_mixing_ratio(self, temp: float, pressure: float) -> dict: + levels = dict() + + ratio = saturated_mixing_ratio(temp, pressure) + + p2 = pressure + + while p2 >= PRESSURE_MIN: + levels[p2] = mixing_ratio_temp(ratio, p2) + + p2 -= 10 + + return levels + + def follow_sounding(self, sounding: Sounding) -> dict: + levels = dict() + + for sample in sounding.samples: + levels[sample.pressure] = sample.temp + + return levels + + def intersect(self, series1: dict, series2: dict): + pairs = nearest(sorted(series1.keys(), reverse=True), + sorted(series2.keys(), reverse=True)) + + sign_last = None + + for pair in pairs: + v1, v2 = series1[pair[0]], series2[pair[1]] + + sign = cmp(v1, v2) + + if sign == 0 or (sign_last is not None and sign_last != sign): + return pair[0] + + sign_last = sign + + def find_lfc(self, temp: float, pressure: float, sounding: Sounding) -> float: + moist_adiabat = self.follow_moist_adiabat(temp, pressure) + temp_line = self.follow_sounding(sounding) + + pairs = nearest(sorted(moist_adiabat.keys(), reverse=True), + sorted(temp_line.keys(), reverse=True)) + + for pair in pairs: + v1, v2 = moist_adiabat[pair[0]], temp_line[pair[1]] + + if v1 > v2: + return pair[0] + + def derive_parameters(self, sounding: Sounding): + dry_adiabat = self.follow_dry_adiabat(sounding.samples[0].temp, + sounding.samples[0].pressure) + + saturated_mr_line = self.follow_saturated_mixing_ratio(sounding.samples[0].dewpoint, + sounding.samples[0].pressure) + + lcl = self.intersect(dry_adiabat, saturated_mr_line) + lfc = self.find_lfc(dry_adiabat[lcl], lcl, sounding) + + return { + 'lcl': lcl, + 'lfc': lfc + } + def draw(self, cr: cairo.Context, x: float,