예제 #1
0
 def test_taf_line(self):
     """Tests converting TAF line data into into a single spoken string"""
     units = structs.Units(**static.core.NA_UNITS)
     line = {
         "altimeter": parse_altimeter("2992"),
         "clouds": [core.make_cloud("BKN015CB")],
         "end_time": core.make_timestamp("1206"),
         "icing": ["611005"],
         "other": [],
         "start_time": core.make_timestamp("1202"),
         "transition_start": None,
         "turbulence": ["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"),
         "wx_codes": get_wx_codes(["+RA"])[1],
     }
     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 2000ft 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_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)
예제 #3
0
def parse_na(report: str) -> (MetarData, Units):
    """
    Parser for the North American METAR variant
    """
    units = Units(**NA_UNITS)
    wxresp = {"raw": report}
    clean = core.sanitize_report_string(report)
    wxdata, wxresp["remarks"] = get_remarks(clean)
    wxdata = core.dedupe(wxdata)
    wxdata = core.sanitize_report_list(wxdata)
    wxresp["sanitized"] = " ".join(wxdata + [wxresp["remarks"]])
    wxdata, wxresp["station"], wxresp["time"] = core.get_station_and_time(
        wxdata)
    wxdata, wxresp["runway_visibility"] = get_runway_visibility(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"] = get_altimeter(wxdata, units, "NA")
    wxdata, wxresp["visibility"] = core.get_visibility(wxdata, units)
    wxdata, wxresp["temperature"], wxresp["dewpoint"] = get_temp_and_dew(
        wxdata)
    condition = core.get_flight_rules(wxresp["visibility"],
                                      core.get_ceiling(wxresp["clouds"]))
    wxresp["other"], wxresp["wx_codes"] = get_wx_codes(wxdata)
    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 = 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
            if prob:
                parsed_line[
                    "sanitized"] = prob + " " + parsed_line["sanitized"]
            prob = ""
            parsed_lines.append(parsed_line)
        lines.pop(0)
    return parsed_lines
예제 #5
0
def parse_in(report: str, issued: date = None) -> (MetarData, Units):
    """
    Parser for the International METAR variant
    """
    units = Units(**IN_UNITS)
    resp = {"raw": report}
    resp["sanitized"], resp["remarks"], data = sanitize(report)
    data, resp["station"], resp["time"] = core.get_station_and_time(data)
    data, resp["runway_visibility"] = get_runway_visibility(data)
    if "CAVOK" not in data:
        data, resp["clouds"] = core.get_clouds(data)
    (
        data,
        resp["wind_direction"],
        resp["wind_speed"],
        resp["wind_gust"],
        resp["wind_variable_direction"],
    ) = core.get_wind(data, units)
    data, resp["altimeter"] = get_altimeter(data, units, "IN")
    if "CAVOK" in data:
        resp["visibility"] = core.make_number("CAVOK")
        resp["clouds"] = []
        data.remove("CAVOK")
    else:
        data, resp["visibility"] = core.get_visibility(data, units)
    data, resp["temperature"], resp["dewpoint"] = get_temp_and_dew(data)
    condition = core.get_flight_rules(resp["visibility"],
                                      core.get_ceiling(resp["clouds"]))
    resp["other"], resp["wx_codes"] = get_wx_codes(data)
    resp["flight_rules"] = FLIGHT_RULES[condition]
    resp["remarks_info"] = remarks.parse(resp["remarks"])
    resp["time"] = core.make_timestamp(resp["time"], target_date=issued)
    return MetarData(**resp), units
예제 #6
0
def parse_na(report: str, issued: date = None) -> Tuple[MetarData, Units]:
    """Parser for the North American METAR variant"""
    units = Units(**NA_UNITS)
    resp = {"raw": report}
    resp["sanitized"], resp["remarks"], data = sanitize(report)
    data, resp["station"], resp["time"] = core.get_station_and_time(data)
    data, resp["runway_visibility"] = get_runway_visibility(data)
    data, resp["clouds"] = core.get_clouds(data)
    (
        data,
        resp["wind_direction"],
        resp["wind_speed"],
        resp["wind_gust"],
        resp["wind_variable_direction"],
    ) = core.get_wind(data, units)
    data, resp["altimeter"] = get_altimeter(data, units, "NA")
    data, resp["visibility"] = core.get_visibility(data, units)
    data, resp["temperature"], resp["dewpoint"] = get_temp_and_dew(data)
    condition = core.get_flight_rules(
        resp["visibility"], core.get_ceiling(resp["clouds"])
    )
    resp["other"], resp["wx_codes"] = get_wx_codes(data)
    resp["flight_rules"] = FLIGHT_RULES[condition]
    resp["remarks_info"] = remarks.parse(resp["remarks"])
    resp["time"] = core.make_timestamp(resp["time"], target_date=issued)
    return MetarData(**resp), units
예제 #7
0
 def test_make_timestamp(self):
     """
     Tests that a report timestamp is converted into a Timestamp dataclass
     """
     today = datetime.now(tz=timezone.utc)
     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)
예제 #8
0
 def test_make_timestamp(self):
     """Tests that a report timestamp is converted into a Timestamp dataclass"""
     for dt, fmt, target in (
         (datetime.now(tz=timezone.utc), r"%d%HZ", False),
         (datetime.now(tz=timezone.utc), r"%d%H%MZ", False),
         (datetime(2010, 2, 2, 2, 2, tzinfo=timezone.utc), r"%d%HZ", True),
         (datetime(2010, 2, 2, 2, 2,
                   tzinfo=timezone.utc), r"%d%H%MZ", True),
     ):
         dt_repr = dt.strftime(fmt)
         target = dt.date() if target else None
         dt = dt.replace(second=0, microsecond=0)
         if "%M" not in fmt:
             dt = dt.replace(minute=0)
         ts = core.make_timestamp(dt_repr, target_date=target)
         self.assert_timestamp(ts, dt_repr, dt)
예제 #9
0
 def test_find_missing_taf_times(self):
     """
     Tests that missing forecast times can be interpretted by 
     """
     good_lines = [
         {
             "type": "FROM",
             "transition_start": None,
             "start_time": "3021",
             "end_time": "3023",
         },
         {
             "type": "FROM",
             "transition_start": None,
             "start_time": "3023",
             "end_time": "0105",
         },
         {
             "type": "BECMG",
             "transition_start": "0105",
             "start_time": "0107",
             "end_time": "0108",
         },
         {
             "type": "FROM",
             "transition_start": None,
             "start_time": "0108",
             "end_time": "0114",
         },
     ]
     for line in good_lines:
         for key in ("start_time", "end_time", "transition_start"):
             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[1]["end_time"] = None
     bad_lines[2]["end_time"] = None  # This None implies normal parsing
     bad_lines[3]["end_time"] = None
     start, end = good_lines[0]["start_time"], good_lines[-1]["end_time"]
     self.assertEqual(taf.find_missing_taf_times(bad_lines, start, end),
                      good_lines)
예제 #10
0
 def test_taf(self):
     """
     Tests converting a TafData report into a single spoken string
     """
     units = structs.Units(**static.core.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)
예제 #11
0
def parse(station: str, report: str) -> (TafData, Units):
    """
    Returns TafData and Units dataclasses with parsed data and their associated units
    """
    if not report:
        return None, None
    valid_station(station)
    while len(report) > 3 and report[:4] in ("TAF ", "AMD ", "COR "):
        report = report[4:]
    retwx = {
        "end_time": None,
        "raw": report,
        "remarks": None,
        "start_time": None
    }
    report = core.sanitize_report_string(report)
    _, station, time = core.get_station_and_time(report[:20].split())
    retwx["station"] = station
    retwx["time"] = core.make_timestamp(time)
    report = report.replace(station, "")
    if time:
        report = report.replace(time, "").strip()
    if 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"] = get_taf_remarks(report)
    # Split and parse each line
    lines = 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"],
        ) = 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"],
            ) = 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 = find_missing_taf_times(parsed_lines, start, end)
        parsed_lines = get_taf_flight_rules(parsed_lines)
    # Extract Oceania-specific data
    if retwx["station"][0] == "A":
        (
            parsed_lines[-1]["other"],
            retwx["alts"],
            retwx["temps"],
        ) = get_oceania_temp_and_alt(parsed_lines[-1]["other"])
    # Convert wx codes
    for i, line in enumerate(parsed_lines):
        parsed_lines[i]["other"], parsed_lines[i]["wx_codes"] = get_wx_codes(
            line["other"])
    # Convert to dataclass
    retwx["forecast"] = [TafLineData(**line) for line in parsed_lines]
    return TafData(**retwx), units
예제 #12
0
def _time(item: str, target: date = None) -> Timestamp:
    """
    Convert a time element to a Timestamp
    """
    return core.make_timestamp(item, time_only=True, target_date=target)
예제 #13
0
def _time(item: str) -> Timestamp:
    """
    Convert a time element to a Timestamp
    """
    return core.make_timestamp(item, time_only=True)