Fix additional ASOS parsing issues

This commit is contained in:
XANTRONIX Industrial 2025-02-19 21:48:54 -05:00
parent 04c5692df1
commit c0257863ee

View file

@ -3,6 +3,8 @@ import enum
import datetime import datetime
import shapely import shapely
from typing import Self
from nexrad.db import DatabaseTable from nexrad.db import DatabaseTable
from nexrad.coord import COORD_SYSTEM from nexrad.coord import COORD_SYSTEM
from nexrad.vtec import VTECEvent from nexrad.vtec import VTECEvent
@ -10,13 +12,15 @@ from nexrad.vtec import VTECEvent
RE_ID = re.compile(r'^(\d+)$') RE_ID = re.compile(r'^(\d+)$')
RE_ISSUANCE = re.compile(r''' RE_ISSUANCE = re.compile(r'''
^ (WF[A-Z]{2}\d{2}) ^ (W[A-Z]{3}\d{2})
[ ]{1} (?P<wfo>[A-Z]{4}) [ ]{1} (?P<wfo>[A-Z]{4})
[ ]{1} (?P<day>\d{2}) [ ]{1} (?P<day>\d{2})
(?P<hour>\d{2}) (?P<minute>\d{2}) (?P<hour>\d{2}) (?P<minute>\d{2})
$ $
''', re.X) ''', re.X)
RE_PRODUCT = re.compile(r'^(?P<product>[A-Z]{3})(?P<wfo>[A-Z]{3})$')
RE_POLY = re.compile(r'^LAT\.\.\.LON (?P<coords>\d+(?: \d+)+)') RE_POLY = re.compile(r'^LAT\.\.\.LON (?P<coords>\d+(?: \d+)+)')
RE_MOTION = re.compile(r''' RE_MOTION = re.compile(r'''
@ -57,13 +61,10 @@ def parse_shape(text: str):
return shapely.Polygon(points) return shapely.Polygon(points)
class AFOSMessageParserState(enum.Enum): class AFOSMessageParserState(enum.Enum):
NONE = 1 NONE = 0
HEADER = enum.auto() SERIAL = enum.auto()
ISSUANCE = enum.auto() ISSUANCE = enum.auto()
META = enum.auto() PRODUCT = enum.auto()
TYPEOFFICE = enum.auto()
VTEC = enum.auto()
BODY_SEP = enum.auto()
BODY = enum.auto() BODY = enum.auto()
TAGS = enum.auto() TAGS = enum.auto()
FOOTER = enum.auto() FOOTER = enum.auto()
@ -74,8 +75,9 @@ class AFOSMessage(DatabaseTable):
__columns__ = ( __columns__ = (
'id', 'timestamp_issued', 'timestamp_start', 'timestamp_end', 'id', 'timestamp_issued', 'timestamp_start', 'timestamp_end',
'typeof', 'etn', 'actions', 'wfo', 'phenom', 'sig', 'body', 'serial', 'product', 'vtec_type', 'etn', 'actions', 'wfo',
'azimuth', 'speed', 'location', 'forecaster', 'poly', 'phenom', 'sig', 'text', 'azimuth', 'speed', 'location',
'forecaster', 'poly',
) )
__columns_read__ = { __columns_read__ = {
@ -95,10 +97,14 @@ class AFOSMessage(DatabaseTable):
} }
id: int id: int
serial: int
timestamp_issued: datetime.datetime timestamp_issued: datetime.datetime
timestamp_start: datetime.datetime timestamp_start: datetime.datetime
timestamp_end: datetime.datetime timestamp_end: datetime.datetime
typeof: str
product: str
vtec_type: str
actions: str actions: str
wfo: str wfo: str
phenom: str phenom: str
@ -111,56 +117,66 @@ class AFOSMessage(DatabaseTable):
forecaster: str forecaster: str
poly: shapely.Geometry poly: shapely.Geometry
@staticmethod def __init__(self):
def parse(text: str): super().__init__()
event = AFOSMessage() self.product = None
state = AFOSMessageParserState.NONE self.vtec_type = None
self.actions = None
self.wfo = None
self.phenom = None
self.sig = None
self.etn = None
self.body = None
self.azimuth = None
self.speed = None
self.location = None
self.forecaster = None
self.poly = None
issuance = None @staticmethod
def parse(text: str) -> Self:
event = AFOSMessage()
state = AFOSMessageParserState.SERIAL
for line in text.split('\n'): for line in text.split('\n'):
line = line.rstrip() line = line.rstrip()
if state == AFOSMessageParserState.NONE: if state == AFOSMessageParserState.SERIAL:
match = RE_ID.match(line) match = RE_ID.match(line)
if match is not None: if match is not None:
event.id = int(match[1]) event.serial = int(match[1])
state = AFOSMessageParserState.HEADER state = AFOSMessageParserState.ISSUANCE
elif state == AFOSMessageParserState.HEADER: elif state == AFOSMessageParserState.ISSUANCE:
match = RE_ISSUANCE.match(line) match = RE_ISSUANCE.match(line)
if match is not None: if match is not None:
issuance = match state = AFOSMessageParserState.PRODUCT
state = AFOSMessageParserState.ISSUANCE elif state == AFOSMessageParserState.PRODUCT:
elif state == AFOSMessageParserState.ISSUANCE: match = RE_PRODUCT.match(line)
state = AFOSMessageParserState.META
elif state == AFOSMessageParserState.META: if match is not None:
event.product = match['product']
state = AFOSMessageParserState.BODY
elif state == AFOSMessageParserState.BODY:
if line == '':
continue
elif line[0] == '/':
vtec = VTECEvent.parse(line) vtec = VTECEvent.parse(line)
if vtec is not None: if vtec is not None:
event.timestamp_start = vtec.timestamp_start event.timestamp_start = vtec.timestamp_start
event.timestamp_end = vtec.timestamp_end event.timestamp_end = vtec.timestamp_end
event.typeof = vtec.typeof event.vtec_type = vtec.typeof
event.actions = vtec.actions event.actions = vtec.actions
event.wfo = vtec.wfo event.wfo = vtec.wfo
event.phenom = vtec.phenom event.phenom = vtec.phenom
event.sig = vtec.sig event.sig = vtec.sig
event.etn = vtec.etn event.etn = vtec.etn
elif line == '&&':
state = AFOSMessageParserState.VTEC
elif state == AFOSMessageParserState.VTEC:
if line == '':
state = AFOSMessageParserState.BODY_SEP
elif state == AFOSMessageParserState.BODY_SEP:
event.body = line
state = AFOSMessageParserState.BODY
elif state == AFOSMessageParserState.BODY:
if line == '&&':
state = AFOSMessageParserState.TAGS state = AFOSMessageParserState.TAGS
else:
event.body += '\n' + line
elif state == AFOSMessageParserState.TAGS: elif state == AFOSMessageParserState.TAGS:
if line == '$$': if line == '$$':
state = AFOSMessageParserState.FOOTER state = AFOSMessageParserState.FOOTER