diff --git a/lib/nexrad/afos.py b/lib/nexrad/afos.py index 03fb515..f8fa079 100644 --- a/lib/nexrad/afos.py +++ b/lib/nexrad/afos.py @@ -3,6 +3,8 @@ import enum import datetime import shapely +from typing import Self + from nexrad.db import DatabaseTable from nexrad.coord import COORD_SYSTEM from nexrad.vtec import VTECEvent @@ -10,13 +12,15 @@ from nexrad.vtec import VTECEvent RE_ID = re.compile(r'^(\d+)$') RE_ISSUANCE = re.compile(r''' - ^ (WF[A-Z]{2}\d{2}) + ^ (W[A-Z]{3}\d{2}) [ ]{1} (?P[A-Z]{4}) [ ]{1} (?P\d{2}) (?P\d{2}) (?P\d{2}) $ ''', re.X) +RE_PRODUCT = re.compile(r'^(?P[A-Z]{3})(?P[A-Z]{3})$') + RE_POLY = re.compile(r'^LAT\.\.\.LON (?P\d+(?: \d+)+)') RE_MOTION = re.compile(r''' @@ -57,16 +61,13 @@ def parse_shape(text: str): return shapely.Polygon(points) class AFOSMessageParserState(enum.Enum): - NONE = 1 - HEADER = enum.auto() - ISSUANCE = enum.auto() - META = enum.auto() - TYPEOFFICE = enum.auto() - VTEC = enum.auto() - BODY_SEP = enum.auto() - BODY = enum.auto() - TAGS = enum.auto() - FOOTER = enum.auto() + NONE = 0 + SERIAL = enum.auto() + ISSUANCE = enum.auto() + PRODUCT = enum.auto() + BODY = enum.auto() + TAGS = enum.auto() + FOOTER = enum.auto() class AFOSMessage(DatabaseTable): __table__ = 'nexrad_afos_messsage' @@ -74,8 +75,9 @@ class AFOSMessage(DatabaseTable): __columns__ = ( 'id', 'timestamp_issued', 'timestamp_start', 'timestamp_end', - 'typeof', 'etn', 'actions', 'wfo', 'phenom', 'sig', 'body', - 'azimuth', 'speed', 'location', 'forecaster', 'poly', + 'serial', 'product', 'vtec_type', 'etn', 'actions', 'wfo', + 'phenom', 'sig', 'text', 'azimuth', 'speed', 'location', + 'forecaster', 'poly', ) __columns_read__ = { @@ -94,73 +96,87 @@ class AFOSMessage(DatabaseTable): 'poly': lambda v: {'poly': shapely.to_wkt(v)} } - id: int + id: int + serial: int + timestamp_issued: datetime.datetime timestamp_start: datetime.datetime timestamp_end: datetime.datetime - typeof: str - actions: str - wfo: str - phenom: str - sig: str - etn: int - body: str - azimuth: int - speed: int - location: shapely.Point - forecaster: str - poly: shapely.Geometry + + product: str + vtec_type: str + actions: str + wfo: str + phenom: str + sig: str + etn: int + body: str + azimuth: int + speed: int + location: shapely.Point + forecaster: str + poly: shapely.Geometry + + def __init__(self): + super().__init__() + self.product = 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 @staticmethod - def parse(text: str): + def parse(text: str) -> Self: event = AFOSMessage() - state = AFOSMessageParserState.NONE - - issuance = None + state = AFOSMessageParserState.SERIAL for line in text.split('\n'): line = line.rstrip() - if state == AFOSMessageParserState.NONE: + if state == AFOSMessageParserState.SERIAL: match = RE_ID.match(line) if match is not None: - event.id = int(match[1]) - state = AFOSMessageParserState.HEADER - elif state == AFOSMessageParserState.HEADER: + event.serial = int(match[1]) + state = AFOSMessageParserState.ISSUANCE + elif state == AFOSMessageParserState.ISSUANCE: match = RE_ISSUANCE.match(line) if match is not None: - issuance = match - state = AFOSMessageParserState.ISSUANCE - elif state == AFOSMessageParserState.ISSUANCE: - state = AFOSMessageParserState.META - elif state == AFOSMessageParserState.META: - vtec = VTECEvent.parse(line) + state = AFOSMessageParserState.PRODUCT + elif state == AFOSMessageParserState.PRODUCT: + match = RE_PRODUCT.match(line) - if vtec is not None: - event.timestamp_start = vtec.timestamp_start - event.timestamp_end = vtec.timestamp_end + if match is not None: + event.product = match['product'] - event.typeof = vtec.typeof - event.actions = vtec.actions - event.wfo = vtec.wfo - event.phenom = vtec.phenom - event.sig = vtec.sig - event.etn = vtec.etn - - 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 == '&&': + if line == '': + continue + elif line[0] == '/': + vtec = VTECEvent.parse(line) + + if vtec is not None: + event.timestamp_start = vtec.timestamp_start + event.timestamp_end = vtec.timestamp_end + + event.vtec_type = vtec.typeof + event.actions = vtec.actions + event.wfo = vtec.wfo + event.phenom = vtec.phenom + event.sig = vtec.sig + event.etn = vtec.etn + elif line == '&&': state = AFOSMessageParserState.TAGS - else: - event.body += '\n' + line elif state == AFOSMessageParserState.TAGS: if line == '$$': state = AFOSMessageParserState.FOOTER