Slowly refactor skew_t.py into reusability

This commit is contained in:
XANTRONIX 2025-02-23 17:00:19 -05:00
parent b96bc12660
commit d5294e111d

View file

@ -1,31 +1,8 @@
#! /usr/bin/env python3
import math import math
import cairo import cairo
IMAGE_WIDTH = 1366 TEMP_CENTER = 0 # degrees C
IMAGE_HEIGHT = 768 TEMP_STEP = 5
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): def clamp(value, lowest, highest):
if value < lowest: if value < lowest:
@ -35,160 +12,50 @@ def clamp(value, lowest, highest):
return value return value
def pressure_y(pressure: float) -> float: class SkewT():
if pressure < PRESSURE_MIN: __slots__ = 'width', 'height', 'temp_step_width',
pressure = PRESSURE_MIN
elif pressure > PRESSURE_MAX:
pressure = PRESSURE_MAX
log_p = math.log(pressure) ISOTHERM_LINES = (
factor = (PRESSURE_LOG_MAX - log_p) / PRESSURE_LOG_RANGE (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),
return factor * GRAPH_HEIGHT (-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),
def graph_to_screen(x, y) -> tuple: (-9, 1, 0, 10), (-9, 2, -1, 10), (-9, 3, -2, 10), (-9, 4, -3, 10),
return ( (-9, 5, -4, 10), (-9, 6, -5, 10), (-9, 7, -6, 10), (-9, 8, -7, 10),
GRAPH_OFFSET_X + x, (-9, 9, -8, 10),
IMAGE_HEIGHT - GRAPH_OFFSET_Y - y
) )
def draw_pressure_lines(cr: cairo.Context): def __init__(self, width: float, height: float):
for pressure in range(PRESSURE_MIN, PRESSURE_MAX+1, PRESSURE_STEP): self.width = width
(x, y) = graph_to_screen(0, pressure_y(pressure)) self.height = height
cr.set_source_rgba(0.0, 0.0, 0.0, 0.5) self.temp_step_width = self.height / 10.0
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): def graph_to_screen(self, x, y) -> tuple:
(screen_x1, screen_y1) = graph_to_screen(x1, y1) return (self.width / 2) + x, self.height - y
(screen_x2, screen_y2) = graph_to_screen(x2, y2)
cr.move_to(screen_x1, screen_y1) def draw_pressure_lines(self, cr: cairo.Context):
cr.line_to(screen_x2, screen_y2) pass
cr.stroke()
ISOTHERM_LINES = ( def draw_isotherms(self, cr: cairo.Context, x: float, y: float):
(0, 0, 9, 9), (1, 0, 9, 8), (2, 0, 9, 7), (3, 0, 9, 6), cr.set_source_rgba(0.1, 0.5, 0.1, 0.8)
(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), for line in self.ISOTHERM_LINES:
(-5, 0, 5, 10), (-6, 0, 4, 10), (-7, 0, 3, 10), (-8, 0, 2, 10), x1 = line[0] * self.temp_step_width
(-9, 0, 1, 10), y1 = line[1] * self.temp_step_width
x2 = line[2] * self.temp_step_width
y2 = line[3] * self.temp_step_width
(-9, 1, 0, 10), (-9, 2, -1, 10), (-9, 3, -2, 10), (screen_x1, screen_y1) = self.graph_to_screen(x1, y1)
(-9, 4, -3, 10), (-9, 5, -4, 10), (-9, 6, -5, 10), (screen_x2, screen_y2) = self.graph_to_screen(x2, y2)
(-9, 7, -6, 10), (-9, 8, -7, 10), (-9, 9, -8, 10),
)
def isotherm_to_graph(x, y) -> tuple: cr.move_to(x + screen_x1, y + screen_y1)
return ( cr.line_to(x + screen_x2, y + screen_y2)
GRAPH_WIDTH / 2 + x, cr.stroke()
y
)
def draw_isotherms(cr: cairo.Context): def draw(self, cr: cairo.Context, x: float, y: float):
cr.set_source_rgba(0.1, 0.5, 0.1, 0.8) self.draw_isotherms(cr, x, y)
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()