예제 #1
0
 def test_taf_line(self):
     """
     Tests converting TAF line data into into a single spoken string
     """
     units = structs.Units(**static.NA_UNITS)
     line = {
         'altimeter': core.make_number('2992'),
         'clouds': [core.make_cloud('BKN015CB')],
         'end_time': core.make_timestamp('1206'),
         'icing': ['611005'],
         'other': ['+RA'],
         'start_time': core.make_timestamp('1202'),
         'turbulance': ['540553'],
         'type': 'FROM',
         'visibility': core.make_number('3'),
         'wind_direction': core.make_number('360'),
         'wind_gust': core.make_number('20'),
         'wind_shear': 'WS020/07040KT',
         'wind_speed': core.make_number('12'),
     }
     line.update({
         k: None
         for k in ('flight_rules', 'probability', 'raw', 'sanitized')
     })
     line = structs.TafLineData(**line)
     spoken = (
         'From 2 to 6 zulu, Winds three six zero at 12kt gusting to 20kt. '
         'Wind shear 2000inHg from zero seven zero at 40kt. Visibility three miles. '
         'Altimeter two nine point nine two. Heavy Rain. '
         'Broken layer at 1500ft (Cumulonimbus). '
         'Occasional moderate turbulence in clouds from 5500ft to 8500ft. '
         'Light icing from 10000ft to 15000ft')
     ret = speech.taf_line(line, units)
     self.assertIsInstance(ret, str)
     self.assertEqual(ret, spoken)
예제 #2
0
 def test_taf_line(self):
     """
     Tests converting TAF line data into into a single spoken string
     """
     units = structs.Units(**static.NA_UNITS)
     line = {
         'altimeter': core.make_number('2992'),
         'clouds': [core.make_cloud('BKN015CB')],
         'end_time': core.make_timestamp('1206'),
         'icing': ['611005'],
         'other': ['+RA'],
         'start_time': core.make_timestamp('1202'),
         'turbulance': ['540553'],
         'type': 'FROM',
         'visibility': core.make_number('3'),
         'wind_direction': core.make_number('360'),
         'wind_gust': core.make_number('20'),
         'wind_shear': 'WS020/07040KT',
         'wind_speed': core.make_number('12'),
     }
     line.update({k: None for k in ('flight_rules', 'probability', 'raw', 'sanitized')})
     line = structs.TafLineData(**line)
     spoken = ('From 2 to 6 zulu, Winds three six zero at 12kt gusting to 20kt. '
               'Wind shear 2000inHg from zero seven zero at 40kt. Visibility three miles. '
               'Altimeter two nine point nine two. Heavy Rain. '
               'Broken layer at 1500ft (Cumulonimbus). '
               'Occasional moderate turbulence in clouds from 5500ft to 8500ft. '
               'Light icing from 10000ft to 15000ft')
     ret = speech.taf_line(line, units)
     self.assertIsInstance(ret, str)
     self.assertEqual(ret, spoken)
예제 #3
0
def parse_in(txt: str) -> (MetarData, Units):
    """
    Parser for the International METAR variant
    """
    units = Units(**IN_UNITS)
    clean = core.sanitize_report_string(txt)
    wxresp = {'raw': txt, 'sanitized': clean}
    wxdata, wxresp['remarks'] = core.get_remarks(clean)
    wxdata, wxresp['runway_visibility'], _ = core.sanitize_report_list(wxdata)
    wxdata, wxresp['station'], wxresp['time'] = core.get_station_and_time(
        wxdata)
    if 'CAVOK' not in wxdata:
        wxdata, wxresp['clouds'] = core.get_clouds(wxdata)
    wxdata, wxresp['wind_direction'], wxresp['wind_speed'], \
        wxresp['wind_gust'], wxresp['wind_variable_direction'] = core.get_wind(wxdata, units)
    wxdata, wxresp['altimeter'] = core.get_altimeter(wxdata, units, 'IN')
    if 'CAVOK' in wxdata:
        wxresp['visibility'] = core.make_number('CAVOK')
        wxresp['clouds'] = []
        wxdata.remove('CAVOK')
    else:
        wxdata, wxresp['visibility'] = core.get_visibility(wxdata, units)
    wxresp['other'], wxresp['temperature'], wxresp[
        'dewpoint'] = core.get_temp_and_dew(wxdata)
    condition = core.get_flight_rules(wxresp['visibility'],
                                      core.get_ceiling(wxresp['clouds']))
    wxresp['flight_rules'] = FLIGHT_RULES[condition]
    wxresp['remarks_info'] = remarks.parse(wxresp['remarks'])
    wxresp['time'] = core.make_timestamp(wxresp['time'])
    return MetarData(**wxresp), units
예제 #4
0
def parse_lines(lines: [str], units: Units, use_na: bool = True) -> [dict]:
    """
    Returns a list of parsed line dictionaries
    """
    parsed_lines = []
    prob = ''
    while lines:
        raw_line = lines[0].strip()
        line = core.sanitize_line(raw_line)
        # Remove prob from the beginning of a line
        if line.startswith('PROB'):
            # Add standalone prob to next line
            if len(line) == 6:
                prob = line
                line = ''
            # Add to current line
            elif len(line) > 6:
                prob = line[:6]
                line = line[6:].strip()
        if line:
            parsed_line = (parse_na_line if use_na else parse_in_line)(line, units)
            for key in ('start_time', 'end_time'):
                parsed_line[key] = core.make_timestamp(parsed_line[key])
            parsed_line['probability'] = core.make_number(prob[4:])
            parsed_line['raw'] = raw_line
            parsed_line['sanitized'] = prob + ' ' + line if prob else line
            prob = ''
            parsed_lines.append(parsed_line)
        lines.pop(0)
    return parsed_lines
예제 #5
0
 def test_find_missing_taf_times(self):
     """
     Tests that missing forecast times can be interpretted by 
     """
     good_lines = [{
         'type': 'FROM',
         'start_time': '3021',
         'end_time': '3023'
     }, {
         'type': 'FROM',
         'start_time': '3023',
         'end_time': '0105'
     }, {
         'type': 'FROM',
         'start_time': '0105',
         'end_time': '0108'
     }, {
         'type': 'FROM',
         'start_time': '0108',
         'end_time': '0114'
     }]
     for line in good_lines:
         for key in ('start_time', 'end_time'):
             line[key] = core.make_timestamp(line[key])
     bad_lines = deepcopy(good_lines)
     bad_lines[0]['start_time'] = None
     bad_lines[1]['start_time'] = None
     bad_lines[2]['end_time'] = None
     bad_lines[3]['end_time'] = None
     start, end = good_lines[0]['start_time'], good_lines[-1]['end_time']
     self.assertEqual(core.find_missing_taf_times(bad_lines, start, end),
                      good_lines)
예제 #6
0
def parse_na(report: str) -> (MetarData, Units):
    """
    Parser for the North American METAR variant
    """
    units = Units(**NA_UNITS)
    clean = core.sanitize_report_string(report)
    wxresp = {'raw': report, 'sanitized': clean}
    wxdata, wxresp['remarks'] = core.get_remarks(clean)
    wxdata = core.dedupe(wxdata)
    wxdata, wxresp['runway_visibility'], _ = core.sanitize_report_list(wxdata)
    wxdata, wxresp['station'], wxresp['time'] = core.get_station_and_time(
        wxdata)
    wxdata, wxresp['clouds'] = core.get_clouds(wxdata)
    wxdata, wxresp['wind_direction'], wxresp['wind_speed'], \
        wxresp['wind_gust'], wxresp['wind_variable_direction'] = core.get_wind(wxdata, units)
    wxdata, wxresp['altimeter'] = core.get_altimeter(wxdata, units, 'NA')
    wxdata, wxresp['visibility'] = core.get_visibility(wxdata, units)
    wxresp['other'], wxresp['temperature'], wxresp[
        'dewpoint'] = core.get_temp_and_dew(wxdata)
    condition = core.get_flight_rules(wxresp['visibility'],
                                      core.get_ceiling(wxresp['clouds']))
    wxresp['flight_rules'] = FLIGHT_RULES[condition]
    wxresp['remarks_info'] = remarks.parse(wxresp['remarks'])
    wxresp['time'] = core.make_timestamp(wxresp['time'])
    return MetarData(**wxresp), units
예제 #7
0
def parse_lines(lines: [str], units: Units, use_na: bool = True) -> [dict]:
    """
    Returns a list of parsed line dictionaries
    """
    parsed_lines = []
    prob = ''
    while lines:
        raw_line = lines[0].strip()
        line = core.sanitize_line(raw_line)
        # Remove prob from the beginning of a line
        if line.startswith('PROB'):
            # Add standalone prob to next line
            if len(line) == 6:
                prob = line
                line = ''
            # Add to current line
            elif len(line) > 6:
                prob = line[:6]
                line = line[6:].strip()
        if line:
            parsed_line = (parse_na_line if use_na else parse_in_line)(line, units)
            for key in ('start_time', 'end_time'):
                parsed_line[key] = core.make_timestamp(parsed_line[key])
            parsed_line['probability'] = core.make_number(prob[4:])
            parsed_line['raw'] = raw_line
            parsed_line['sanitized'] = prob + ' ' + line if prob else line
            prob = ''
            parsed_lines.append(parsed_line)
        lines.pop(0)
    return parsed_lines
예제 #8
0
 def test_make_timestamp(self):
     """
     Tests that a report timestamp is converted into a Timestamp dataclass
     """
     today = datetime.utcnow()
     rts = today.strftime(r'%d%HZ')
     date = core.make_timestamp(rts)
     self.assertIsInstance(date, structs.Timestamp)
     self.assertEqual(date.repr, rts)
     self.assertEqual(date.dt.day, today.day)
     self.assertEqual(date.dt.hour, today.hour)
예제 #9
0
 def test_make_timestamp(self):
     """
     Tests that a report timestamp is converted into a Timestamp dataclass
     """
     today = datetime.utcnow()
     rts = today.strftime(r'%d%HZ')
     date = core.make_timestamp(rts)
     self.assertIsInstance(date, structs.Timestamp)
     self.assertEqual(date.repr, rts)
     self.assertEqual(date.dt.day, today.day)
     self.assertEqual(date.dt.hour, today.hour)
예제 #10
0
def parse(station: str, report: str) -> TafData:
    """
    Returns TafData and Units dataclasses with parsed data and their associated units
    """
    if not report:
        return None, None
    core.valid_station(station)
    while len(report) > 3 and report[:4] in ('TAF ', 'AMD ', 'COR '):
        report = report[4:]
    _, station, time = core.get_station_and_time(report[:20].split())
    retwx = {
        'end_time': None,
        'raw': report,
        'remarks': None,
        'start_time': None,
        'station': station,
        'time': core.make_timestamp(time)
    }
    report = report.replace(station, '')
    report = report.replace(time, '').strip()
    if core.uses_na_format(station):
        use_na = True
        units = Units(**NA_UNITS)
    else:
        use_na = False
        units = Units(**IN_UNITS)
    # Find and remove remarks
    report, retwx['remarks'] = core.get_taf_remarks(report)
    # Split and parse each line
    lines = core.split_taf(report)
    parsed_lines = parse_lines(lines, units, use_na)
    # Perform additional info extract and corrections
    if parsed_lines:
        parsed_lines[-1]['other'], retwx['max_temp'], retwx['min_temp'] \
            = core.get_temp_min_and_max(parsed_lines[-1]['other'])
        if not (retwx['max_temp'] or retwx['min_temp']):
            parsed_lines[0]['other'], retwx['max_temp'], retwx['min_temp'] \
                = core.get_temp_min_and_max(parsed_lines[0]['other'])
        # Set start and end times based on the first line
        start, end = parsed_lines[0]['start_time'], parsed_lines[0]['end_time']
        parsed_lines[0]['end_time'] = None
        retwx['start_time'], retwx['end_time'] = start, end
        parsed_lines = core.find_missing_taf_times(parsed_lines, start, end)
        parsed_lines = core.get_taf_flight_rules(parsed_lines)
    # Extract Oceania-specific data
    if retwx['station'][0] == 'A':
        parsed_lines[-1]['other'], retwx['alts'], retwx['temps'] \
            = core.get_oceania_temp_and_alt(parsed_lines[-1]['other'])
    # Convert to dataclass
    retwx['forecast'] = [TafLineData(**line) for line in parsed_lines]
    return TafData(**retwx), units
예제 #11
0
def parse(station: str, report: str) -> TafData:
    """
    Returns TafData and Units dataclasses with parsed data and their associated units
    """
    if not report:
        return None, None
    core.valid_station(station)
    while len(report) > 3 and report[:4] in ('TAF ', 'AMD ', 'COR '):
        report = report[4:]
    _, station, time = core.get_station_and_time(report[:20].split())
    retwx = {
        'end_time': None,
        'raw': report,
        'remarks': None,
        'start_time': None,
        'station': station,
        'time': core.make_timestamp(time)
    }
    report = report.replace(station, '')
    report = report.replace(time, '').strip()
    if core.uses_na_format(station):
        use_na = True
        units = Units(**NA_UNITS)
    else:
        use_na = False
        units = Units(**IN_UNITS)
    # Find and remove remarks
    report, retwx['remarks'] = core.get_taf_remarks(report)
    # Split and parse each line
    lines = core.split_taf(report)
    parsed_lines = parse_lines(lines, units, use_na)
    # Perform additional info extract and corrections
    if parsed_lines:
        parsed_lines[-1]['other'], retwx['max_temp'], retwx['min_temp'] \
            = core.get_temp_min_and_max(parsed_lines[-1]['other'])
        if not (retwx['max_temp'] or retwx['min_temp']):
            parsed_lines[0]['other'], retwx['max_temp'], retwx['min_temp'] \
                = core.get_temp_min_and_max(parsed_lines[0]['other'])
        # Set start and end times based on the first line
        start, end = parsed_lines[0]['start_time'], parsed_lines[0]['end_time']
        parsed_lines[0]['end_time'] = None
        retwx['start_time'], retwx['end_time'] = start, end
        parsed_lines = core.find_missing_taf_times(parsed_lines, start, end)
        parsed_lines = core.get_taf_flight_rules(parsed_lines)
    # Extract Oceania-specific data
    if retwx['station'][0] == 'A':
        parsed_lines[-1]['other'], retwx['alts'], retwx['temps'] \
            = core.get_oceania_temp_and_alt(parsed_lines[-1]['other'])
    # Convert to dataclass
    retwx['forecast'] = [TafLineData(**line) for line in parsed_lines]
    return TafData(**retwx), units
예제 #12
0
 def test_type_and_times(self):
     """
     Tests line start from type, time, and probability values
     """
     for type, *times, prob, spoken in (
         (None, None, None, None, ''),
         ('FROM', '2808', '2815', None, 'From 8 to 15 zulu,'),
         ('FROM', '2822', '2903', None, 'From 22 to 3 zulu,'),
         ('BECMG', '3010', None, None, 'At 10 zulu becoming'),
         ('PROB', '1303', '1305', '30', r"From 3 to 5 zulu, there's a 30% chance for"),
         ('INTER', '1303', '1305', '45', r"From 3 to 5 zulu, there's a 45% chance for intermittent"),
         ('INTER', '2423', '2500', None, 'From 23 to midnight zulu, intermittent'),
         ('TEMPO', '0102', '0103', None, 'From 2 to 3 zulu, temporary'),
     ):
         times = [core.make_timestamp(time) for time in times]
         if prob is not None:
             prob = core.make_number(prob)
         ret = speech.type_and_times(type, *times, prob)
         self.assertIsInstance(ret, str)
         self.assertEqual(ret, spoken)
예제 #13
0
 def test_find_missing_taf_times(self):
     """
     Tests that missing forecast times can be interpretted by 
     """
     good_lines = [
         {'type': 'FROM', 'start_time': '3021', 'end_time': '3023'},
         {'type': 'FROM', 'start_time': '3023', 'end_time': '0105'},
         {'type': 'FROM', 'start_time': '0105', 'end_time': '0108'},
         {'type': 'FROM', 'start_time': '0108', 'end_time': '0114'}
     ]
     for line in good_lines:
         for key in ('start_time', 'end_time'):
             line[key] = core.make_timestamp(line[key])
     bad_lines = deepcopy(good_lines)
     bad_lines[0]['start_time'] = None
     bad_lines[1]['start_time'] = None
     bad_lines[2]['end_time'] = None
     bad_lines[3]['end_time'] = None
     start, end = good_lines[0]['start_time'], good_lines[-1]['end_time']
     self.assertEqual(core.find_missing_taf_times(bad_lines, start, end), good_lines)
예제 #14
0
 def test_type_and_times(self):
     """
     Tests line start from type, time, and probability values
     """
     for type, *times, prob, spoken in (
         (None, None, None, None, ''),
         ('FROM', '2808', '2815', None, 'From 8 to 15 zulu,'),
         ('FROM', '2822', '2903', None, 'From 22 to 3 zulu,'),
         ('BECMG', '3010', None, None, 'At 10 zulu becoming'),
         ('PROB', '1303', '1305', '30', r"From 3 to 5 zulu, there's a 30% chance for"),
         ('INTER', '1303', '1305', '45', r"From 3 to 5 zulu, there's a 45% chance for intermittent"),
         ('INTER', '2423', '2500', None, 'From 23 to midnight zulu, intermittent'),
         ('TEMPO', '0102', '0103', None, 'From 2 to 3 zulu, temporary'),
     ):
         times = [core.make_timestamp(time) for time in times]
         if prob is not None:
             prob = core.make_number(prob)
         ret = speech.type_and_times(type, *times, prob)
         self.assertIsInstance(ret, str)
         self.assertEqual(ret, spoken)
예제 #15
0
 def test_taf(self):
     """
     Tests converting a TafData report into a single spoken string
     """
     units = structs.Units(**static.NA_UNITS)
     empty_line = {
         k: None
         for k in structs.TafLineData.__dataclass_fields__.keys()
     }
     forecast = [
         structs.TafLineData(**{
             **empty_line,
             **line
         }) for line in (
             {
                 'type': 'FROM',
                 'start_time': core.make_timestamp('0410Z'),
                 'end_time': core.make_timestamp('0414Z'),
                 'visibility': core.make_number('3'),
                 'wind_direction': core.make_number('360'),
                 'wind_gust': core.make_number('20'),
                 'wind_speed': core.make_number('12'),
             },
             {
                 'type': 'PROB',
                 'probability': core.make_number('45'),
                 'start_time': core.make_timestamp('0412Z'),
                 'end_time': core.make_timestamp('0414Z'),
                 'visibility': core.make_number('M1/4'),
             },
         )
     ]
     taf = structs.TafData(raw=None,
                           remarks=None,
                           station=None,
                           time=None,
                           forecast=forecast,
                           start_time=core.make_timestamp('0410Z'),
                           end_time=core.make_timestamp('0414Z'))
     ret = speech.taf(taf, units)
     spoken = (
         f"Starting on {taf.start_time.dt.strftime('%B')} 4th - From 10 to 14 zulu, "
         "Winds three six zero at 12kt gusting to 20kt. Visibility three miles. "
         r"From 12 to 14 zulu, there's a 45% chance for Visibility "
         "less than one quarter of a mile")
     self.assertIsInstance(ret, str)
     self.assertEqual(ret, spoken)
예제 #16
0
 def test_taf(self):
     """
     Tests converting a TafData report into a single spoken string
     """
     units = structs.Units(**static.NA_UNITS)
     empty_line = {k: None for k in structs.TafLineData.__dataclass_fields__.keys()}
     forecast = [structs.TafLineData(**{**empty_line, **line}) for line in (
         {
             'type': 'FROM',
             'start_time': core.make_timestamp('0410Z'),
             'end_time': core.make_timestamp('0414Z'),
             'visibility': core.make_number('3'),
             'wind_direction': core.make_number('360'),
             'wind_gust': core.make_number('20'),
             'wind_speed': core.make_number('12'),
         },
         {
             'type': 'PROB',
             'probability': core.make_number('45'),
             'start_time': core.make_timestamp('0412Z'),
             'end_time': core.make_timestamp('0414Z'),
             'visibility': core.make_number('M1/4'),
         },
     )]
     taf = structs.TafData(
         raw=None, remarks=None, station=None, time=None,
         forecast=forecast,
         start_time=core.make_timestamp('0410Z'),
         end_time=core.make_timestamp('0414Z')
     )
     ret = speech.taf(taf, units)
     spoken = (f"Starting on {taf.start_time.dt.strftime('%B')} 4th - From 10 to 14 zulu, "
               "Winds three six zero at 12kt gusting to 20kt. Visibility three miles. "
               r"From 12 to 14 zulu, there's a 45% chance for Visibility "
               "less than one quarter of a mile")
     self.assertIsInstance(ret, str)
     self.assertEqual(ret, spoken)
예제 #17
0
def _time(item: str) -> Timestamp:
    """
    Convert a time element to a Timestamp
    """
    return core.make_timestamp(item, time_only=True)
예제 #18
0
def _time(item: str) -> Timestamp:
    """
    Convert a time element to a Timestamp
    """
    return core.make_timestamp(item, time_only=True)