From b96bc126606a68ce41b1b6ec6fc6f4eb12abe4c7 Mon Sep 17 00:00:00 2001 From: XANTRONIX Industrial Date: Sun, 23 Feb 2025 15:26:52 -0500 Subject: [PATCH] Initial shitty commit of skew-T chart rendering --- lib/xmet/skew_t.py | 194 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 lib/xmet/skew_t.py diff --git a/lib/xmet/skew_t.py b/lib/xmet/skew_t.py new file mode 100644 index 0000000..0c1cffc --- /dev/null +++ b/lib/xmet/skew_t.py @@ -0,0 +1,194 @@ +#! /usr/bin/env python3 + +import math +import cairo + +IMAGE_WIDTH = 1366 +IMAGE_HEIGHT = 768 + +GRAPH_HEIGHT = IMAGE_HEIGHT - 64 +GRAPH_WIDTH = (GRAPH_HEIGHT / 10.0) * 18 + +GRAPH_OFFSET_X = (IMAGE_WIDTH - GRAPH_WIDTH) / 2 +GRAPH_OFFSET_Y = 32 + +PRESSURE_MAX = 1050 +PRESSURE_MIN = 100 +PRESSURE_STEP = 50 + +PRESSURE_LOG_MAX = math.log(PRESSURE_MAX) +PRESSURE_LOG_MIN = math.log(PRESSURE_MIN) +PRESSURE_LOG_RANGE = PRESSURE_LOG_MAX - PRESSURE_LOG_MIN + +TEMP_CENTER = 0 # degrees C +TEMP_STEP = 5 +TEMP_STEP_WIDTH = GRAPH_HEIGHT / 10.0 + +ADIABATIC_LAPSE_RATE_DRY = 9.8 / 1000 # degrees C per 1000m +ADIABATIC_LAPSE_RATE_MOIST = 4.0 / 1000 + +def clamp(value, lowest, highest): + if value < lowest: + return lowest + elif value > highest: + return highest + + return value + +def pressure_y(pressure: float) -> float: + if pressure < PRESSURE_MIN: + pressure = PRESSURE_MIN + elif pressure > PRESSURE_MAX: + pressure = PRESSURE_MAX + + log_p = math.log(pressure) + factor = (PRESSURE_LOG_MAX - log_p) / PRESSURE_LOG_RANGE + + return factor * GRAPH_HEIGHT + +def graph_to_screen(x, y) -> tuple: + return ( + GRAPH_OFFSET_X + x, + IMAGE_HEIGHT - GRAPH_OFFSET_Y - y + ) + +def draw_pressure_lines(cr: cairo.Context): + for pressure in range(PRESSURE_MIN, PRESSURE_MAX+1, PRESSURE_STEP): + (x, y) = graph_to_screen(0, pressure_y(pressure)) + + cr.set_source_rgba(0.0, 0.0, 0.0, 0.5) + cr.move_to(x, y) + cr.rel_line_to(GRAPH_WIDTH, 0) + cr.stroke() + +def draw_graph_line(cr: cairo.Context, x1, y1, x2, y2): + (screen_x1, screen_y1) = graph_to_screen(x1, y1) + (screen_x2, screen_y2) = graph_to_screen(x2, y2) + + cr.move_to(screen_x1, screen_y1) + cr.line_to(screen_x2, screen_y2) + cr.stroke() + +ISOTHERM_LINES = ( + (0, 0, 9, 9), (1, 0, 9, 8), (2, 0, 9, 7), (3, 0, 9, 6), + (4, 0, 9, 5), (5, 0, 9, 4), (6, 0, 9, 3), (7, 0, 9, 2), + (8, 0, 9, 1), (9, 0, 9, 0), + + (-1, 0, 9, 10), (-2, 0, 8, 10), (-3, 0, 7, 10), (-4, 0, 6, 10), + (-5, 0, 5, 10), (-6, 0, 4, 10), (-7, 0, 3, 10), (-8, 0, 2, 10), + (-9, 0, 1, 10), + + (-9, 1, 0, 10), (-9, 2, -1, 10), (-9, 3, -2, 10), + (-9, 4, -3, 10), (-9, 5, -4, 10), (-9, 6, -5, 10), + (-9, 7, -6, 10), (-9, 8, -7, 10), (-9, 9, -8, 10), +) + +def isotherm_to_graph(x, y) -> tuple: + return ( + GRAPH_WIDTH / 2 + x, + y + ) + +def draw_isotherms(cr: cairo.Context): + cr.set_source_rgba(0.1, 0.5, 0.1, 0.8) + + for line in ISOTHERM_LINES: + x1 = line[0] * TEMP_STEP_WIDTH + y1 = line[1] * TEMP_STEP_WIDTH + x2 = line[2] * TEMP_STEP_WIDTH + y2 = line[3] * TEMP_STEP_WIDTH + + graph_1 = isotherm_to_graph(x1, y1) + graph_2 = isotherm_to_graph(x2, y2) + + draw_graph_line(cr, graph_1[0], graph_1[1], graph_2[0], graph_2[1]) + +def sample_to_graph(temp: float, pressure: float): + x = (temp / TEMP_STEP) * TEMP_STEP_WIDTH + y = pressure_y(pressure) + + return ( + GRAPH_WIDTH / 2 + x + y, + y + ) + +SAMPLES = ( + ( 101200, -6), + ( 101200, -6), + ( 100000, -12), + ( 99300, -22), + ( 98700, -29), + ( 97500, -39), + ( 95700, -50), + ( 95100, -47), + ( 93900, -43), + ( 92000, -34), + ( 90400, -34), + ( 90000, -34), + ( 89200, -34), + ( 85000, -34), + ( 83700, -34), + ( 82100, -34), + ( 80000, -42), + ( 78600, -46), + ( 77600, -50), + ( 70000, -77), + ( 69100, -80), + ( 60700, -128), + ( 60000, -133), + ( 53100, -186), + ( 50000, -215), + ( 46300, -255), + ( 40200, -337), + ( 40000, -340), + ( 35000, -412), + ( 34900, -413), + ( 30100, -496), + ( 30000, -496), + ( 25700, -565), + ( 25000, -577), + ( 24400, -585), + ( 22100, -510), + ( 21900, -510), + ( 20000, -497), + ( 15000, -466), + ( 10000, -438), + ( 7000, -414), + ( 5000, -393), + ( 3000, -368), + ( 2000, -344), + ( 1500, -337), + ( 1000, -305), + ( 500, -210), + ( 400, -198) +) + +with cairo.SVGSurface('test.svg', IMAGE_WIDTH, IMAGE_HEIGHT) as surface: + cr = cairo.Context(surface) + + cr.rectangle(GRAPH_OFFSET_X, GRAPH_OFFSET_Y, GRAPH_WIDTH, GRAPH_HEIGHT) + cr.stroke() + + draw_pressure_lines(cr) + draw_isotherms(cr) + + cr.set_source_rgb(0, 0, 1) + + first = True + + for sample in SAMPLES: + pressure = sample[0] / 100.0 + temp = sample[1] * 0.1 + + gx, gy = sample_to_graph(temp, pressure) + x, y = graph_to_screen(gx, gy) + + print(f"x {x} y {y} pressure {pressure} temp {temp}") + + if first: + cr.move_to(x, y) + first = False + else: + cr.line_to(x, y) + + cr.stroke()