Begin refactoring RAOB parser

This commit is contained in:
XANTRONIX 2025-03-27 18:44:10 -04:00
parent 3b33faa78b
commit 453a0efe89

View file

@ -60,39 +60,19 @@ class RAOBSounding():
return sounding return sounding
def sample(self, pressure: float): def record(self, sample: SoundingSample):
if pressure in self.samples: dest = SoundingSample()
return self.samples[pressure] dest.pressure_qa = ' '
dest.height_qa = ' '
dest.temp_qa = ' '
dest.pressure = sample.pressure
dest.height = sample.height
dest.temp = sample.temp
dest.dewpoint = sample.dewpoint
dest.wind_speed = sample.wind_speed
dest.wind_dir = sample.wind_dir
sample = SoundingSample() self.samples[sample.pressure] = dest
sample.pressure = pressure
sample.pressure_qa = ' '
sample.height_qa = ' '
sample.temp_qa = ' '
self.samples[pressure] = sample
return sample
def record_height(self, pressure: float, height: float):
sample = self.sample(pressure)
sample.height = height
def record_temp_dewpoint(self,
pressure: float,
temp: float,
dewpoint: float):
sample = self.sample(pressure)
sample.temp = temp
sample.dewpoint = dewpoint
def record_wind_speed_dir(self,
pressure: float,
wind_speed: float,
wind_dir: float):
sample = self.sample(pressure)
sample.wind_speed = wind_speed
sample.wind_dir = wind_dir
class RAOBObs(): class RAOBObs():
DATA_SOURCE = 'UCAR' DATA_SOURCE = 'UCAR'
@ -312,7 +292,7 @@ class RAOBObs():
return sample return sample
def parse_ttaa(self) -> Sounding: def parse_ttaa(self) -> dict:
# #
# Return None if there is no height data up to 100mb. # Return None if there is no height data up to 100mb.
# #
@ -330,16 +310,9 @@ class RAOBObs():
if sample is None: if sample is None:
return return
timestamp = self.parse_timestamp(self.tokens[0]) station = self.tokens[1]
timestamp = self.tokens[0]
sounding = Sounding() samples = list()
sounding.samples = [sample]
sounding.station = self.tokens[1]
sounding.data_source_pressure = self.DATA_SOURCE
sounding.data_source_other = self.DATA_SOURCE
sounding.timestamp_observed = timestamp
sounding.timestamp_released = timestamp - datetime.timedelta(minutes=45)
for i in range(5, len(self.tokens), 3): for i in range(5, len(self.tokens), 3):
if len(self.tokens) < i+3 or self.tokens[i][-1] == '=': if len(self.tokens) < i+3 or self.tokens[i][-1] == '=':
@ -356,9 +329,13 @@ class RAOBObs():
if sample is None: if sample is None:
continue continue
sounding.samples.append(sample) samples.append(sample)
return sounding return {
'station': station,
'timestamp': timestamp,
'samples': samples
}
class RAOBChunk(): class RAOBChunk():
def __init__(self, def __init__(self,
@ -390,15 +367,15 @@ class RAOBChunk():
if obs is not None: if obs is not None:
yield obs yield obs
def each_sounding(self): def each_data(self):
for obs in self.each_obs(): for obs in self.each_obs():
if obs.kind == 'TTAA': if obs.kind == 'TTAA':
sounding = obs.parse_ttaa() data = obs.parse_ttaa()
if sounding is None or len(sounding.samples) == 0: if data is None or len(data['samples']) == 0:
continue continue
yield sounding yield data
class RAOBReader(): class RAOBReader():
""" """
@ -414,6 +391,18 @@ class RAOBReader():
self.fh = fh self.fh = fh
self.soundings = dict() self.soundings = dict()
def sounding(self, station: str, timestamp: str) -> RAOBSounding:
key = station + '.' + timestamp
if key in self.soundings:
return self.soundings[key]
sounding = RAOBSounding(station, timestamp)
self.soundings[key] = sounding
return sounding
def parse_chunk(self, text: str) -> RAOBChunk: def parse_chunk(self, text: str) -> RAOBChunk:
meta = { meta = {
'wfo': None, # NWS forecast office 'wfo': None, # NWS forecast office
@ -474,26 +463,34 @@ class RAOBReader():
tokens.extend(re.split(r'\s+', line)) tokens.extend(re.split(r'\s+', line))
return RAOBChunk(meta['wfo'], return RAOBChunk(meta['wfo'],
meta['product'], meta['product'],
tokens) tokens)
def each_chunk(self): def each_chunk(self):
for text in each_chunk(self.fh, CHUNK_SEP, CHUNK_STRIP_CHARS): for text in each_chunk(self.fh, CHUNK_SEP, CHUNK_STRIP_CHARS):
yield self.parse_chunk(text) yield self.parse_chunk(text)
def each_obs(self): def load_soundings(self):
for chunk in self.each_chunk(): for chunk in self.each_chunk():
yield from chunk.each_obs() for data in chunk.each_data():
station = data['station']
timestamp = data['timestamp']
samples = data['samples']
def each_sounding(self): sounding = self.sounding(station, timestamp)
for chunk in self.each_chunk():
yield from chunk.each_sounding() for sample in samples:
sounding.record(sample)
for key in self.soundings:
yield self.soundings[key].finish()
@staticmethod @staticmethod
def each_sounding_from_fh(fh: io.TextIOBase): def each_sounding_from_fh(fh: io.TextIOBase):
reader = RAOBReader(fh) reader = RAOBReader(fh)
yield from reader.each_sounding() for sounding in reader.load_soundings():
yield sounding
@staticmethod @staticmethod
def each_sounding_from_file(path: str): def each_sounding_from_file(path: str):