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
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
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)
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
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())
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")
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
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)
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)