Exemple #1
0
def load_from_txt(filename):
    name = os.path.basename(filename.replace(".txt", "").strip())
    waypoints = []
    _dirname, _name = os.path.split(filename)
    _fs = open_fs(_dirname)
    with _fs.open(_name, "r") as in_file:
        pos = []
        for line in in_file:
            if line.startswith("#"):
                continue
            if line.startswith("Index"):
                pos.append(0)  # 0
                pos.append(line.find("Location"))  # 1
                pos.append(line.find("Lat (+-90)"))  # 2
                pos.append(line.find("Lon (+-180)"))  # 3
                pos.append(line.find("Flightlevel"))  # 4
                pos.append(line.find("Pressure (hPa)"))  # 5
                pos.append(line.find("Leg dist. (km)"))  # 6
                pos.append(line.find("Cum. dist. (km)"))  # 7
                pos.append(line.find("Comments"))  # 8
                continue
            if line.startswith("Track name: "):
                continue

            if len(pos) == 0:
                raise SyntaxError(
                    "TXT Import could not parse column headings.")
            if len(line) < max(pos):
                raise SyntaxError(f"TXT Import could not parse line: '{line}'")

            wp = ft.Waypoint()
            attr_names = [
                "location", "lat", "lon", "flightlevel", "pressure",
                "distance_to_prev", "distance_total", "comments"
            ]
            setattr(wp, attr_names[0], line[pos[1]:pos[2]].strip())
            setattr(wp, attr_names[7], line[pos[8]:].strip())
            for i in range(2, len(pos) - 1):
                if pos[i] >= 0:
                    if i == 5:
                        setattr(wp, attr_names[i - 1],
                                float(line[pos[i]:pos[i + 1]].strip()) * 100.)
                    else:
                        setattr(wp, attr_names[i - 1],
                                float(line[pos[i]:pos[i + 1]].strip()))
                else:
                    if i == 5:
                        logging.debug('calculate pressure from FL ' + str(
                            thermolib.flightlevel2pressure(
                                float(wp.flightlevel) * units.hft).magnitude))
                        setattr(
                            wp, attr_names[i - 1],
                            thermolib.flightlevel2pressure(
                                float(wp.flightlevel) * units.hft).magnitude)
            waypoints.append(wp)
    return name, waypoints
Exemple #2
0
    def __init__(self, lat=0, lon=0, flightlevel=0, location="", comments=""):
        self.location = location
        locations = config_loader(dataset='locations')
        if location in locations:
            self.lat, self.lon = locations[location]
        else:
            self.lat = lat
            self.lon = lon
        self.flightlevel = flightlevel
        self.pressure = thermolib.flightlevel2pressure(flightlevel *
                                                       units.hft).magnitude
        self.distance_to_prev = 0.
        self.distance_total = 0.
        self.comments = comments

        # Performance fields (for values read from the flight performance
        # service).
        self.leg_time = None  # time from previous waypoint
        self.cum_time = None  # total time of flight
        self.utc_time = None  # time in UTC since given takeoff time
        self.leg_fuel = None  # fuel consumption since previous waypoint
        self.rem_fuel = None  # total fuel consumption
        self.weight = None  # aircraft gross weight
        self.ceiling_alt = None  # aircraft ceiling altitude
        self.ascent_rate = None  # aircraft ascent rate

        self.wpnumber_major = None
        self.wpnumber_minor = None
Exemple #3
0
def test_flightlevel2pressure():
    assert thermolib.flightlevel2pressure(182.8850 * units.hft).magnitude == pytest.approx(50000)
    assert thermolib.flightlevel2pressure(530.8279 * units.hft).magnitude == pytest.approx(10000)
    assert thermolib.flightlevel2pressure(782.4335 * units.hft).magnitude == pytest.approx(3000)
    assert thermolib.flightlevel2pressure(1151.9583 * units.hft).magnitude == pytest.approx(550)
    assert thermolib.flightlevel2pressure(1626.8966 * units.hft).magnitude == pytest.approx(80)
    assert thermolib.flightlevel2pressure(1804.2727 * units.hft).magnitude == pytest.approx(40)
    with pytest.raises(ValueError):
        thermolib.flightlevel2pressure(72000 * units.m)
Exemple #4
0
def load_from_flitestar(filename):
    waypoints = []
    _dirname, _name = os.path.split(filename)
    _fs = open_fs(_dirname)
    with _fs.open(_name, 'r') as f:
        firstline = f.readline()
        if not firstline.startswith(
                "# FliteStar/FliteMap generated flight plan."):
            raise SyntaxError("The file does not seem to be a FliteStar file!")
        for line in f:

            if line.startswith('FWP'):
                line = line.split()
                if len(line) < 10:
                    raise SyntaxError(f"Line {line} has less than 9 fields.")
                alt = round(float(line[-1]) / 100., 2)
                if line[4] == 'N':
                    NS = 1.
                elif line[4] == 'S':
                    NS = -1.
                else:
                    NS = np.nan
                lat = round((float(line[5]) + (float(line[6]) / 60.)) * NS, 3)
                if line[7] == 'E':
                    EW = 1.
                elif line[7] == 'W':
                    EW = -1.
                else:
                    EW = np.nan
                lon = round((float(line[8]) + (float(line[9]) / 60.)) * EW, 3)

                wp = ft.Waypoint()
                wp.location = line[3]
                wp.lat = float(lat)
                wp.lon = float(lon)
                wp.flightlevel = float(alt)
                wp.pressure = thermolib.flightlevel2pressure(
                    float(wp.flightlevel) * units.hft).magnitude
                waypoints.append(wp)

    name = os.path.basename(filename).strip('.txt')
    return name, waypoints
Exemple #5
0
 def verticalunitsclicked(self, index):
     new_unit = self._suffixes[index]
     old_unit = self.sbPbot.suffix().strip()
     if new_unit == old_unit:
         return
     self.setBotTopLimits("maximum")
     for sb in (self.sbPbot, self.sbPtop):
         sb.setSuffix(" " + new_unit)
         if new_unit == "hPa":
             sb.setValue(
                 thermolib.flightlevel2pressure(
                     convert_to(sb.value(), old_unit, "hft", 1) *
                     units.hft).to(units.hPa).magnitude)
         elif old_unit == "hPa":
             sb.setValue(
                 convert_to(
                     thermolib.pressure2flightlevel(
                         sb.value() * units.hPa).magnitude, "hft",
                     new_unit))
         else:
             sb.setValue(convert_to(sb.value(), old_unit, new_unit, 1))
     self.setBotTopLimits(self.cbVerticalAxis.currentText())
Exemple #6
0
def main():
    parser = argparse.ArgumentParser(description="""
       This script automatically retrieves and stores a set of plots for the
       configured flights. The configuration is placed within the normal
       MSS frontend JSON file. E.g.

       "automated_plotting": {
           "flights": [
               ["ST25", "01 SADPAP (stereo)", "500,50",
                "ST25-joern.ftml",
                "2019-07-01T00:00:00Z", "2019-09-01T12:00:00Z"]
           ],
           "hsecs": [
               ["https://mss-server/campaigns2019",
                "ecmwf.PVTropo01", "default", "4.0"],
               ["https://mss-server/campaigns2019",
                "ecmwf.ertel_potential_vorticity_pl", "ertel_potential_vorticity_bh", "200.0"]
           ],
           "vsecs": [
               ["https://mss-server/campaigns2019",
                "ecmwf.VS_ertel_potential_vorticity_ml", "ertel_potential_vorticity_bh"],
               ["https://mss-server/campaigns2019",
                "ecmwf.TroposphereInversionLayer", ""]
           ]
       }

       will plot flight "ST25" with configured map section "01 SADPAP (stereo)" and
       vertical range 500hPa to 50hPa from the given FTML file for init time
       "2019-07-01T00:00:00Z" and valid time "2019-09-01T12:00:00Z". The plots
       are defined in the hsecs (horizontal cross-sections) and vsecs (vertical
       cross-sections) entries given each the URL of the server, the layer name, the style,
       and, for hsec only, the elevation to plot (if necessary).
    """)
    parser.add_argument("-v",
                        "--version",
                        help="show version",
                        action="store_true",
                        default=False)
    parser.add_argument("--debug",
                        help="show debugging log messages on console",
                        action="store_true",
                        default=False)
    parser.add_argument(
        "--logfile",
        help="Specify logfile location. Set to empty string to disable.",
        action="store",
        default=os.path.join(mslib.msui.constants.MSUI_CONFIG_PATH,
                             "msui.log"))
    args = parser.parse_args()

    if args.version:
        print(
            "***********************************************************************"
        )
        print("\n            Mission Support System (mss_retriever)\n")
        print(
            "***********************************************************************"
        )
        print("Documentation: http://mss.rtfd.io")
        print("Version:", mslib.__version__)
        sys.exit()

    mslib.utils.setup_logging(args)
    read_config_file(path=mslib.msui.constants.MSUI_SETTINGS)
    config = config_loader()
    num_interpolation_points = config["num_interpolation_points"]
    num_labels = config["num_labels"]
    tick_index_step = num_interpolation_points // num_labels

    fig = plt.figure()
    for flight, section, vertical, filename, init_time, time in \
            config["automated_plotting"]["flights"]:
        params = mslib.utils.coordinate.get_projection_params(
            config["predefined_map_sections"][section]["CRS"].lower())
        params["basemap"].update(
            config["predefined_map_sections"][section]["map"])
        wps = load_from_ftml(filename)
        wp_lats, wp_lons, wp_locs = [[x[i] for x in wps] for i in [0, 1, 3]]
        wp_presss = [
            thermolib.flightlevel2pressure(wp[2] * units.hft).magnitude
            for wp in wps
        ]
        for url, layer, style, elevation in config["automated_plotting"][
                "hsecs"]:
            fig.clear()
            ax = fig.add_subplot(111, zorder=99)
            bm = mslib.msui.mpl_map.MapCanvas(ax=ax, **(params["basemap"]))

            # plot path and labels
            bm.plot(wp_lons,
                    wp_lats,
                    color="blue",
                    marker="o",
                    linewidth=2,
                    markerfacecolor="red",
                    latlon=True,
                    markersize=4,
                    zorder=100)
            for i, (lon, lat, loc) in enumerate(zip(wp_lons, wp_lats,
                                                    wp_locs)):
                textlabel = f"{loc if loc else str(i)}   "
                x, y = bm(lon, lat)
                plt.text(x, y, textlabel, **TEXT_CONFIG)
            plt.tight_layout()

            # retrieve and draw WMS image
            ax_bounds = plt.gca().bbox.bounds
            width, height = int(round(ax_bounds[2])), int(round(ax_bounds[3]))
            bbox = params['basemap']
            req = requests.get(
                url,
                auth=tuple(config["WMS_login"][url]),
                params={
                    "version":
                    "1.3.0",
                    "request":
                    "GetMap",
                    "format":
                    "image/png",
                    "exceptions":
                    "XML",
                    "crs":
                    config["predefined_map_sections"][section]["CRS"],
                    "layers":
                    layer,
                    "styles":
                    style,
                    "elevation":
                    elevation,
                    "dim_init_time":
                    init_time,
                    "time":
                    time,
                    "width":
                    width,
                    "height":
                    height,
                    "bbox":
                    f"{bbox['llcrnrlat']},{bbox['llcrnrlon']},{bbox['urcrnrlat']},{bbox['urcrnrlon']}"
                })
            if req.headers['Content-Type'] == "text/xml":
                print(flight, section, vertical, filename, init_time, time)
                print(url, layer, style, elevation)
                print("WMS Error:")
                print(req.text)
                exit(1)
            image_io = io.BytesIO(req.content)
            img = PIL.Image.open(image_io)
            bm.imshow(img, interpolation="nearest", origin="upper")
            bm.drawcoastlines()
            bm.drawcountries()

            fig.savefig(f"{flight}_{layer}.png")

        # prepare vsec plots
        path = [(wp[0], wp[1], datetime.datetime.now()) for wp in wps]
        lats, lons = mslib.utils.coordinate.path_points(
            [_x[0] for _x in path], [_x[1] for _x in path],
            numpoints=num_interpolation_points + 1,
            connection="greatcircle")
        intermediate_indexes = []
        ipoint = 0
        for i, (lat, lon) in enumerate(zip(lats, lons)):
            if abs(lat - wps[ipoint][0]) < 1E-10 and abs(
                    lon - wps[ipoint][1]) < 1E-10:
                intermediate_indexes.append(i)
                ipoint += 1
            if ipoint >= len(wps):
                break

        for url, layer, style in config["automated_plotting"]["vsecs"]:
            fig.clear()

            # setup ticks and labels
            ax = fig.add_subplot(111, zorder=99)
            ax.set_yscale("log")
            p_bot, p_top = [float(x) * 100 for x in vertical.split(",")]
            bbox = ",".join(
                str(x) for x in (num_interpolation_points, p_bot / 100,
                                 num_labels, p_top / 100))
            ax.grid(visible=True)
            ax.patch.set_facecolor("None")
            pres_maj = mslib.msui.mpl_qtwidget.MplSideViewCanvas._pres_maj
            pres_min = mslib.msui.mpl_qtwidget.MplSideViewCanvas._pres_min
            major_ticks = pres_maj[(pres_maj <= p_bot) & (pres_maj >= p_top)]
            minor_ticks = pres_min[(pres_min <= p_bot) & (pres_min >= p_top)]
            labels = [
                f"{int(_mt / 100)}" if
                (_mt / 100.) - int(_mt / 100.) == 0 else f"{float(_mt / 100)}"
                for _mt in major_ticks
            ]
            if len(labels) > 20:
                labels = [
                    "" if _x.split(".")[-1][0] in "975" else _x
                    for _x in labels
                ]
            elif len(labels) > 10:
                labels = [
                    "" if _x.split(".")[-1][0] in "9" else _x for _x in labels
                ]
            ax.set_ylabel("pressure (hPa)")
            ax.set_yticks(minor_ticks, minor=True)
            ax.set_yticks(major_ticks, minor=False)
            ax.set_yticklabels([], minor=True, fontsize=10)
            ax.set_yticklabels(labels, minor=False, fontsize=10)
            ax.set_ylim(p_bot, p_top)
            ax.set_xlim(0, num_interpolation_points)
            ax.set_xticks(range(0, num_interpolation_points, tick_index_step))
            ax.set_xticklabels([
                f"{x[0]:2.1f}, {x[1]:2.1f}"
                for x in zip(lats[::tick_index_step], lons[::tick_index_step])
            ],
                               rotation=25,
                               fontsize=10,
                               horizontalalignment="right")
            ax.set_xlabel("lat/lon")

            # plot path and waypoint labels
            ax.plot(intermediate_indexes,
                    wp_presss,
                    color="blue",
                    marker="o",
                    linewidth=2,
                    markerfacecolor="red",
                    markersize=4)
            for i, (idx, press, loc) in enumerate(
                    zip(intermediate_indexes, wp_presss, wp_locs)):
                textlabel = f"{loc if loc else str(i)} "
                plt.text(idx + 1, press, textlabel, rotation=90, **TEXT_CONFIG)
            plt.tight_layout()

            # retrieve and draw WMS image
            ax_bounds = plt.gca().bbox.bounds
            width, height = int(round(ax_bounds[2])), int(round(ax_bounds[3]))
            req = requests.get(url,
                               auth=tuple(config["WMS_login"][url]),
                               params={
                                   "version":
                                   "1.3.0",
                                   "request":
                                   "GetMap",
                                   "format":
                                   "image/png",
                                   "exceptions":
                                   "XML",
                                   "crs":
                                   "VERT:LOGP",
                                   "layers":
                                   layer,
                                   "styles":
                                   style,
                                   "dim_init_time":
                                   init_time,
                                   "time":
                                   time,
                                   "width":
                                   width,
                                   "height":
                                   height,
                                   "path":
                                   ",".join(f"{wp[0]:.2f},{wp[1]:.2f}"
                                            for wp in wps),
                                   "bbox":
                                   bbox
                               })

            if req.headers['Content-Type'] == "text/xml":
                print(flight, section, vertical, filename, init_time, time)
                print(url, layer, style)
                print("WMS Error:")
                print(req.text)
                exit(1)
            image_io = io.BytesIO(req.content)
            img = PIL.Image.open(image_io)
            imgax = fig.add_axes(ax.get_position(),
                                 frameon=True,
                                 xticks=[],
                                 yticks=[],
                                 label="ax2",
                                 zorder=0)
            imgax.imshow(img,
                         interpolation="nearest",
                         aspect="auto",
                         origin="upper")
            imgax.set_xlim(0, img.size[0] - 1)
            imgax.set_ylim(img.size[1] - 1, 0)

            plt.savefig(f"{flight}_{layer}.png")
Exemple #7
0
    def setData(self, index, value, role=QtCore.Qt.EditRole, update=True):
        """
        Change a data element of the flight track; overrides the
        corresponding QAbstractTableModel method.

        NOTE: Performance computations loose their validity if a change is made.
        """
        if index.isValid() and 0 <= index.row() < len(self.waypoints):
            waypoint = self.waypoints[index.row()]
            column = index.column()
            index2 = index  # in most cases only one field is being changed
            if column == LOCATION:
                waypoint.location = variant_to_string(value)
            elif column == LAT:
                try:
                    # The table fields accept basically any input.
                    # If the string cannot be converted to "float" (raises ValueError), the user input is discarded.
                    value = variant_to_float(value)
                except TypeError as ex:
                    logging.error("unexpected error: %s %s %s %s", type(ex),
                                  ex, type(value), value)
                except ValueError as ex:
                    logging.error("%s", ex)
                else:
                    waypoint.lat = value
                    waypoint.location = ""
                    loc = find_location(waypoint.lat, waypoint.lon, 1e-3)
                    if loc is not None:
                        waypoint.lat, waypoint.lon = loc[0]
                        waypoint.location = loc[1]
                    # A change of position requires an update of the distances.
                    if update:
                        self.update_distances(index.row())
                    # Notify the views that items between the edited item and
                    # the distance item of the corresponding waypoint have been
                    # changed.
                    # Delete the location name -- it won't be valid anymore
                    # after its coordinates have been changed.
                    index2 = self.createIndex(index.row(), LOCATION)
            elif column == LON:
                try:
                    # The table fields accept basically any input.
                    # If the string cannot be converted to "float" (raises ValueError), the user input is discarded.
                    value = variant_to_float(value)
                except TypeError as ex:
                    logging.error("unexpected error: %s %s %s %s", type(ex),
                                  ex, type(value), value)
                except ValueError as ex:
                    logging.error("%s", ex)
                else:
                    waypoint.lon = value
                    waypoint.location = ""
                    loc = find_location(waypoint.lat, waypoint.lon, 1e-3)
                    if loc is not None:
                        waypoint.lat, waypoint.lon = loc[0]
                        waypoint.location = loc[1]
                    if update:
                        self.update_distances(index.row())
                    index2 = self.createIndex(index.row(), LOCATION)
            elif column == FLIGHTLEVEL:
                try:
                    # The table fields accept basically any input.
                    # If the string cannot be converted to "float" (raises ValueError), the user input is discarded.
                    flightlevel = variant_to_float(value)
                    pressure = float(
                        thermolib.flightlevel2pressure(flightlevel *
                                                       units.hft).magnitude)
                except TypeError as ex:
                    logging.error("unexpected error: %s %s %s %s", type(ex),
                                  ex, type(value), value)
                except ValueError as ex:
                    logging.error("%s", ex)
                else:
                    waypoint.flightlevel = flightlevel
                    waypoint.pressure = pressure
                    if update:
                        self.update_distances(index.row())
                    # need to notify view of the second item that has been
                    # changed as well.
                    index2 = self.createIndex(index.row(), PRESSURE)
            elif column == PRESSURE:
                try:
                    # The table fields accept basically any input.
                    # If the string cannot be converted to "float" (raises ValueError), the user input is discarded.
                    pressure = variant_to_float(
                        value) * 100  # convert hPa to Pa
                    if pressure > 200000:
                        raise ValueError
                    flightlevel = float(
                        round(
                            thermolib.pressure2flightlevel(
                                pressure * units.Pa).magnitude))
                    pressure = float(
                        thermolib.flightlevel2pressure(flightlevel *
                                                       units.hft).magnitude)
                except TypeError as ex:
                    logging.error("unexpected error: %s %s %s %s", type(ex),
                                  ex, type(value), value)
                except ValueError as ex:
                    logging.error("%s", ex)
                else:
                    waypoint.pressure = pressure
                    waypoint.flightlevel = flightlevel
                    if update:
                        self.update_distances(index.row())
                    index2 = self.createIndex(index.row(), FLIGHTLEVEL)
            else:
                waypoint.comments = variant_to_string(value)
            self.modified = True
            # Performance computations loose their validity if a change is made.
            if update:
                self.dataChanged.emit(index, index2)
            return True
        return False
Exemple #8
0
def test_pressure2flightlevel2pressure():
    ps = np.arange(5, 105000, 1.)[::-1] * units.Pa
    fs = thermolib.pressure2flightlevel(ps)
    ps_p = thermolib.flightlevel2pressure(fs).magnitude
    assert ps.magnitude == pytest.approx(ps_p)
Exemple #9
0
def test_flightlevel2pressure2flightlevel():
    fs = (np.arange(1, 71000, 1000.) * units.m).to(units.hft)
    ps = thermolib.flightlevel2pressure(fs)
    fs_p = thermolib.pressure2flightlevel(ps).magnitude
    assert fs.magnitude == pytest.approx(fs_p)