Fix multi-line polygons
This commit is contained in:
		
							parent
							
								
									77804f5e1d
								
							
						
					
					
						commit
						584642b61c
					
				
					 2 changed files with 68 additions and 30 deletions
				
			
		| 
						 | 
					@ -30,6 +30,8 @@ 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_POLY_CONT = re.compile(r'^\s+(?P<coords>\d+(?: \d+)+)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RE_MOTION = re.compile(r'''
 | 
					RE_MOTION = re.compile(r'''
 | 
				
			||||||
    ^ TIME
 | 
					    ^ TIME
 | 
				
			||||||
    \.\.\. MOT
 | 
					    \.\.\. MOT
 | 
				
			||||||
| 
						 | 
					@ -58,37 +60,30 @@ TIMEZONES = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_lon(text: str):
 | 
					def parse_lon(text: str):
 | 
				
			||||||
    size = len(text)
 | 
					    size = len(text)
 | 
				
			||||||
    return 0 - float(text[0:size-2]) + (float(text[size-2:size]) / 100)
 | 
					
 | 
				
			||||||
 | 
					    return 0 - float(text[0:size-2] + '.' + text[size-2:size])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_lat(text: str):
 | 
					def parse_lat(text: str):
 | 
				
			||||||
    size = len(text)
 | 
					    size = len(text)
 | 
				
			||||||
    return float(text[0:size-2]) + (float(text[size-2:size]) / 100)
 | 
					    return float(text[0:size-2] + '.' + text[size-2:size])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_location(lon: str, lat: str):
 | 
					def parse_location(lon: str, lat: str):
 | 
				
			||||||
    return shapely.Point(parse_lon(lon), parse_lat(lat))
 | 
					    return shapely.Point(parse_lon(lon), parse_lat(lat))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_poly(text: str):
 | 
					def parse_poly_coords(text: str) -> list:
 | 
				
			||||||
    points = list()
 | 
					    coords = list()
 | 
				
			||||||
    coords = text.split(' ')
 | 
					    items  = text.split(' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for i in range(0, len(coords), 2):
 | 
					    for i in range(0, len(items), 2):
 | 
				
			||||||
        lat = coords[i]
 | 
					        lat = items[i]
 | 
				
			||||||
        lon = coords[i+1]
 | 
					        lon = items[i+1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        points.append([parse_lon(lon), parse_lat(lat)])
 | 
					        coords.append([parse_lon(lon), parse_lat(lat)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    points.append([parse_lon(coords[1]), parse_lat(coords[0])])
 | 
					    return coords
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return shapely.Polygon(points)
 | 
					def poly_from_coords(coords: list) -> shapely.Polygon:
 | 
				
			||||||
 | 
					    return shapely.Polygon([*coords, [coords[0][0], coords[0][1]]])
 | 
				
			||||||
class AFOSMessageParserState(enum.Enum):
 | 
					 | 
				
			||||||
    NONE     = 0
 | 
					 | 
				
			||||||
    SERIAL   = enum.auto()
 | 
					 | 
				
			||||||
    ISSUANCE = enum.auto()
 | 
					 | 
				
			||||||
    PRODUCT  = enum.auto()
 | 
					 | 
				
			||||||
    BODY     = enum.auto()
 | 
					 | 
				
			||||||
    TAGS     = enum.auto()
 | 
					 | 
				
			||||||
    FOOTER   = enum.auto()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AFOSMessage(DatabaseTable):
 | 
					class AFOSMessage(DatabaseTable):
 | 
				
			||||||
    __table__ = 'nexrad_afos_message'
 | 
					    __table__ = 'nexrad_afos_message'
 | 
				
			||||||
| 
						 | 
					@ -171,14 +166,32 @@ class AFOSMessage(DatabaseTable):
 | 
				
			||||||
    def is_warning(self):
 | 
					    def is_warning(self):
 | 
				
			||||||
        return self.sig is not None and self.sig == 'W'
 | 
					        return self.sig is not None and self.sig == 'W'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AFOSMessageParserState(enum.Enum):
 | 
				
			||||||
 | 
					    NONE      = 0
 | 
				
			||||||
 | 
					    SERIAL    = enum.auto()
 | 
				
			||||||
 | 
					    ISSUANCE  = enum.auto()
 | 
				
			||||||
 | 
					    PRODUCT   = enum.auto()
 | 
				
			||||||
 | 
					    BODY      = enum.auto()
 | 
				
			||||||
 | 
					    TAGS      = enum.auto()
 | 
				
			||||||
 | 
					    TAGS_POLY = enum.auto()
 | 
				
			||||||
 | 
					    FOOTER    = enum.auto()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AFOSMessageParser():
 | 
					class AFOSMessageParser():
 | 
				
			||||||
    __slots__ = 'message', 'state', 'issuance', 'timestamp',
 | 
					    __slots__ = (
 | 
				
			||||||
 | 
					        'message', 'state', 'issuance', 'timestamp', 'poly_coords'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message:     AFOSMessage
 | 
				
			||||||
 | 
					    state:       AFOSMessageParserState
 | 
				
			||||||
 | 
					    timestamp:   datetime.datetime
 | 
				
			||||||
 | 
					    poly_coords: list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.message   = None
 | 
					        self.message     = None
 | 
				
			||||||
        self.state     = None
 | 
					        self.state       = None
 | 
				
			||||||
        self.issuance  = None
 | 
					        self.issuance    = None
 | 
				
			||||||
        self.timestamp = None
 | 
					        self.timestamp   = None
 | 
				
			||||||
 | 
					        self.poly_coords = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse_vtec(self, line: str):
 | 
					    def parse_vtec(self, line: str):
 | 
				
			||||||
        vtec = VTECEvent.parse(line)
 | 
					        vtec = VTECEvent.parse(line)
 | 
				
			||||||
| 
						 | 
					@ -241,14 +254,25 @@ class AFOSMessageParser():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse_tags(self, line: str):
 | 
					    def parse_tags(self, line: str):
 | 
				
			||||||
        if line == '$$':
 | 
					        if line == '$$':
 | 
				
			||||||
 | 
					            if len(self.poly_coords) > 0:
 | 
				
			||||||
 | 
					                self.message.poly = shapely.Polygon(self.poly_coords)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.state = AFOSMessageParserState.FOOTER
 | 
					            self.state = AFOSMessageParserState.FOOTER
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # Parsing for "LAT...LON"
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
        match = RE_POLY.match(line)
 | 
					        match = RE_POLY.match(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if match is not None:
 | 
					        if match is not None:
 | 
				
			||||||
            self.message.poly = parse_poly(match['coords'])
 | 
					            self.poly_coords.extend(parse_poly_coords(match['coords']))
 | 
				
			||||||
 | 
					            self.state = AFOSMessageParserState.TAGS_POLY
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # Parsing for "TIME...MOT...LOC"
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
        match = RE_MOTION.match(line)
 | 
					        match = RE_MOTION.match(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if match is not None:
 | 
					        if match is not None:
 | 
				
			||||||
| 
						 | 
					@ -256,6 +280,15 @@ class AFOSMessageParser():
 | 
				
			||||||
            self.message.speed    = int(match['speed'])
 | 
					            self.message.speed    = int(match['speed'])
 | 
				
			||||||
            self.message.location = parse_location(match['lon'], match['lat'])
 | 
					            self.message.location = parse_location(match['lon'], match['lat'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_tags_poly(self, line: str):
 | 
				
			||||||
 | 
					        match = RE_POLY_CONT.match(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if match is None:
 | 
				
			||||||
 | 
					            self.state = AFOSMessageParserState.TAGS
 | 
				
			||||||
 | 
					            return self.parse_tags(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.poly_coords.extend(parse_poly_coords(match['coords']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse_footer(self, line: str):
 | 
					    def parse_footer(self, line: str):
 | 
				
			||||||
        self.message.forecaster = line
 | 
					        self.message.forecaster = line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -280,6 +313,8 @@ class AFOSMessageParser():
 | 
				
			||||||
            self.parse_body(line)
 | 
					            self.parse_body(line)
 | 
				
			||||||
        elif self.state == AFOSMessageParserState.TAGS:
 | 
					        elif self.state == AFOSMessageParserState.TAGS:
 | 
				
			||||||
            self.parse_tags(line)
 | 
					            self.parse_tags(line)
 | 
				
			||||||
 | 
					        elif self.state == AFOSMessageParserState.TAGS_POLY:
 | 
				
			||||||
 | 
					            self.parse_tags_poly(line)
 | 
				
			||||||
        elif self.state == AFOSMessageParserState.FOOTER:
 | 
					        elif self.state == AFOSMessageParserState.FOOTER:
 | 
				
			||||||
            self.parse_footer(line)
 | 
					            self.parse_footer(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -301,10 +336,13 @@ class AFOSMessageParser():
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse(self, text: str):
 | 
					    def parse(self, text: str):
 | 
				
			||||||
        self.message   = AFOSMessage()
 | 
					        self.message          = AFOSMessage()
 | 
				
			||||||
        self.state     = AFOSMessageParserState.SERIAL
 | 
					        self.message.text_raw = text
 | 
				
			||||||
        self.issuance  = None
 | 
					
 | 
				
			||||||
        self.timestamp = None
 | 
					        self.poly_coords = list()
 | 
				
			||||||
 | 
					        self.state       = AFOSMessageParserState.SERIAL
 | 
				
			||||||
 | 
					        self.issuance    = None
 | 
				
			||||||
 | 
					        self.timestamp   = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for line in text.split('\n'):
 | 
					        for line in text.split('\n'):
 | 
				
			||||||
            self.parse_line(line.rstrip())
 | 
					            self.parse_line(line.rstrip())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue