MSUI (Mission Support User Interface)
The executable for the user interface application is “msui”. A short description of how to start the program is given by the –help option. The program should open the main window of the user interface, from which you can open further windows, including top view, side view and so on.
Configuration for the user interface is located in “msui_settings.json”. In this file, you can specify, for instance, the default WMS URLs for the WMS client, the size of the local image cache (the MSUI caches retrieved WMS images to accelerate repeated retrievals), or the predefined locations that the user can select in the table view.
A few options influencing the appearance of the displayed plots and flight tracks (colours etc.) can be set directly in the user interface (top view and side view).
Configuration of MSUI
The settings file msui_settings file includes configuration settings central to the entire Mission Support User Interface (msui). Among others, define
available map projections
vertical section interpolation options
the lists of predefined web service URLs
predefined waypoints for the table view
If you don’t have a msui_settings.json then default configuration is in place.
Store this msui_settings.json in a path, e.g. “$HOME/.config/mss”
The file could be loaded by the File Configuration dialog or by the environment variable msui_settings pointing to your msui_settings.json.
/$HOME/.config/msui/msui_settings.json
{
"data_dir": "~/mssdata",
"filepicker_default": "default",
"import_plugins": {
"FliteStar": ["fls", "mslib.plugins.io.flitestar", "load_from_flitestar"],
"Text": ["txt", "mslib.plugins.io.text", "load_from_txt"]
},
"export_plugins": {
"Text": ["txt", "mslib.plugins.io.text", "save_to_txt"],
"KML": ["kml", "mslib.plugins.io.kml", "save_to_kml"],
"GPX": ["gpx", "mslib.plugins.io.gpx", "save_to_gpx"]
},
"layout": {
"topview": [963, 702],
"sideview": [913, 557],
"tableview": [1236, 424],
"immutable": false
},
"locations": {
"EDMO": [48.08, 11.28],
"Hannover": [52.37, 9.74],
"Hamburg": [53.55, 9.99],
"Juelich": [50.92, 6.36],
"Leipzig": [51.34, 12.37],
"Muenchen": [48.14, 11.57],
"Stuttgart": [48.78, 9.18],
"Wien": [48.20833, 16.373064],
"Zugspitze": [47.42, 10.98],
"Kiruna": [67.821, 20.336],
"Ny-Alesund": [78.928, 11.986]
},
"predefined_map_sections": {
"01 Europe (cyl)": {"CRS": "EPSG:4326",
"map": {"llcrnrlon": -15.0, "llcrnrlat": 35.0,
"urcrnrlon": 30.0, "urcrnrlat": 65.0}},
"02 Germany (cyl)": {"CRS": "EPSG:4326",
"map": {"llcrnrlon": 5.0, "llcrnrlat": 45.0,
"urcrnrlon": 15.0, "urcrnrlat": 57.0}},
"03 Global (cyl)": {"CRS": "EPSG:4326",
"map": {"llcrnrlon": -180.0, "llcrnrlat": -90.0,
"urcrnrlon": 180.0, "urcrnrlat": 90.0}},
"04 Shannon (stereo)": {"CRS": "EPSG:77752350",
"map": {"llcrnrlon": -45.0, "llcrnrlat": 22.0,
"urcrnrlon": 45.0, "urcrnrlat": 63.0}},
"05 Northern Hemisphere (stereo)": {"CRS": "EPSG:77790000",
"map": {"llcrnrlon": -45.0, "llcrnrlat": 0.0,
"urcrnrlon": 135.0, "urcrnrlat": 0.0}},
"06 Southern Hemisphere (stereo)": {"CRS": "EPSG:77890000",
"map": {"llcrnrlon": 45.0, "llcrnrlat": 0.0,
"urcrnrlon": -135.0, "urcrnrlat": 0.0}}
},
"new_flighttrack_template": ["Kiruna", "Ny-Alesund"],
"new_flighttrack_flightlevel": 250,
"num_interpolation_points": 201,
"num_labels": 10,
"WMS_request_timeout": 30,
"default_WMS": ["http://www.your-server.de/forecasts"],
"default_VSEC_WMS": ["http://www.your-server.de/forecasts"],
"default_LSEC_WMS": ["http://www.your-server.de/forecasts"],
"default_MSCOLAB": ["http://www.your-mscolab-server.de/"],
"WMS_login": {
"http://www.your-server.de/forecasts" : ["youruser", "yourpassword"]
},
"MSC_login": {
"http://www.your-mscolab-server.de" : ["youruser", "yourpassword"]
},
"MSCOLAB_mailid": "",
"MSCOLAB_password": ""
}
File I/O
For storage capabilities mss uses the PyFilesystem2 approach. The default data dir is predefined as a directory: ~/mssdata which is the same as osfs://~/mssdata.
PyFilesystem can open a filesystem via an FS URL, which is similar to a URL you might enter in to a browser. FS URLs are useful if you want to specify a filesystem dynamically, such as in a conf file or from the command line.
We have internally implemented PyFilesystem2
FS URLs are formatted in the following way:
<protocol>://<username>:<password>@<resource>
The components are as follows:
<protocol>
Identifies the type of filesystem to create. e.g.osfs
,ftp
.<username>
Optional username.<password>
Optional password.<resource>
A resource, which may be a domain, path, or both.
Here are a few examples:
osfs://~/projects
osfs://c://system32
ftp://ftp.example.org/pub
mem://
ftp://[user[:password]@]host[:port]/[directory]
webdav://[user[:password]@]host[:port]/[directory]
ssh://[user[:password]@]host[:port]/[directory]
File picker dialogue
MSS supports the use of a general file picker to access locations on remote machines facilitating collaboration on campaigns. To enable this feature apply
"filepicker_default": "fs",
to your configuration file. The allowed values are “qt” for QT-based dialogues, “fs” for fs_file_picker-based dialogues supporting remote locations, or “default” for the default dialogues. The default is currently identical to “qt”, but may change in upcoming releases.
With using the “filepicker_default”: “fs” setting you can enable any implemented PyFilesystem2 fs url. Additional to the builtin fs urls we have added optional the webdavfs and sshfs service.
With setting the option “filepicker_default”: “default” you can only access local storages.
"data_dir": "~/mssdata",
MSUI Flight track import/export plugins
MSS currently offers several import/export filters in the mslib.plugins.io module, which may serve as an example for the definition of own plugins. Take care that added plugins use different file extensions. They are listed below. The CSV plugin is enabled by default. Enabling the experimental FliteStar text import plugin would require those lines in the UI settings file:
"import_plugins": {
"FliteStar": ["fls", "mslib.plugins.io.flitestar", "load_from_flitestar"]
},
More details about Plugins on Flight track import/export.
Web Proxy
If you are in an area with a very low bandwidth you may consider to use a squid web proxy and add those lines in your msui_settings pointing to the proxy server.
"proxies": {
"http": "http://yoursquidproxy:3128",
"https": "http://yoursquidproxy:3128"
}
Caching
For changing the default cache directory and behaviour to a named directory you can use these parameters. If you use shared directories you may have to solve access rights.
"wms_cache": "/tmp/.cache/.mss/msui/wms_cache",
"wms_cache_max_size_bytes": 20971520,
"wms_cache_max_age_seconds": 432000,
Docking Widgets Configurations
Performance
MSS may also roughly estimate the fuel consumption and thus range of the aircraft neglecting weather conditions given a proper configuration file specifying the aircraft performance. Such a file may be loaded using the ‘performance settings’ button in Table View. The aircraft performance is specified using tables given in the JSON format. A basic configuration looks like the following file:
{
"name": "DUMMY",
"takeoff_weight": 91000,
"empty_weight": 56000,
"climb": [[0.00, 0.0, 0.0, 0.0, 0.0]],
"descent": [[0.00, 0.0, 0.0, 0.0, 0.0]],
"cruise": [[0.00, 0.00, 400, 2900.00]],
"ceiling": [410],
"_comment1": "_comment fields are just for self-documentation!",
"_comment2": "takeoff_weight: maximum weight for takeoff (lbs)",
"_comment3": "empty_weight: aircraft weight without fuel (lbs)",
"_comment4": "climb/descent: weight(lbs), altitude(ft), duration(min), distance(nm), fuel(lbs)",
"_comment5": "cruise: weight(lbs), altitude(ft), total air speed(nm/h), fuel flow(lbs/h)",
"_comment6": "ceiling: polynomial coeficients for relating weight (lbs) with peak flightlevel (hft). Leftmost coefficient is the intercept."
}
This example file assumes a constant speed of 400 nm/h and a constant fuel consumption of 2900 lbs/h irrespective of flight level changes. The aircraft weight and available fuel are also given, but these may also be adjusted in the GUI after loading.
The columns of the cruise table are aircraft weight (lbs), aircraft altitude (feet), speed (nm/h), and fuel consumption (lbs/h). MSS bilinearily interpolates in aircraft weight and altitude and extrapolates assuming a constant behaviour outside the given data. The climb table specifies the aircraft performance when climbing up from 0 feet altitude, while the descent table specifies the behaviour when descending down to 0 feet altitude. The column headers are aircraft weight (lbs), aircraft altitude (feet), time spent (minutes), distance required (nm), and fuel consumed (lbs). To compute the required data for a flight level change, a bilinear interpolation in the table for current aircraft weight and the two involved altitudes is performed and the difference of the resulting value is used in the calculation.
Satellite Track Docking Widget
The TopView has a docking widget allowing the visualisation of satellite tracks.
A web site to generate the data for
such tracks is operated by NASA. The data can be downloaded as ASCII file that can be open by the docking
widget. An example file is located at
docs/samples/satellite_tracks/satellite_predictor.txt
.
KML Overlay Docking Widget
The TopView has a docking widget that allows the visualization of KML files on top of the map.
This feature supports all essential elements of KML relevant to MSS’ usage namely:
Placemarks (present in Folder/ Document or otherwise)
Style (LineStyle & PolyStyle)
Geometries defined in KML such as
Point
LineString
LinearRing
Polygon (Inner and Outer Rings)
MultiGeometries (MultiPoint, MultiLineString, MultiPolygon)
Geometry Collection (combination of various types of MultiGeometries)
The KML Support has been enhanced to parse all legal KML Files without crashing, and a clear visualization on the map, with the relevant geometries and styles.
The KML Interface now supports display of multiple KML Files simultaneously, with easy to use Buttons such as ‘Add KML Files’, ‘Remove File’, ‘Select/ Unselect All Files’ for the user’s benefit.
A Check/ Uncheck feature allows users to display/hide individual plots on the map, at the User’s leisure.
A KML Customize Option improves the User Experience by allowing user to customize the colour & linewidth of each of the KML Files displayed, realtime. This allows for better understanding of the map and the plots. (The Customize Option can be accessed for each file, by double clicking on the name of that file in the list.)
The ‘Merge KML Files’ Button allows users to combine all the displayed plotted files, to be combined into a single KML File ‘output.kml’, which will be present in the last working directory of the user.
Have to head out somewhere? Important KML Files open? Close the software with ease of mind. Next time you open your software, all your work will be present, right where you left it! KML Overlay supports Saving Open files so that you can jump back in, anytime!
KML Examples
Curious to test out some KML Files? We have a vibrant sample collection ready just for this!
Example KML Files are located at :
Displays LineString
docs/samples/kml/line.kml
Displays Point & Polygon
docs/samples/kml/folder.kml
Displays Polygon
docs/samples/kml/color.kml
Displays Style (The green blob with the Airport)
docs/samples/kml/style.kml
Displays Area in South America (Points, LineStrings, Polygons)
docs/samples/kml/features.kml
Displays the World Map (MultiPolygon)
docs/samples/kml/World_Map.kml
Displays Square Fractal plot in North America (Polygon Rings)
docs/samples/kml/polygon_inner.kml
Displays Fork like pattern in Ireland (MultiLineStrings)
docs/samples/kml/Multilinestrings.kml
Displays Geometry Collection in Adelaide, Australia
docs/samples/kml/geometry_collection.kml
Remote sensing Docking Widget
The TopView has a docking widget that allows the visualization of remote sensing related features. It may visualize the position of tangent points of limb sounders and can overlay the flight path with colours according to the relative position of sun, moon, and some planets (to either avoid or seek out alignments). Upon first starting the widget, it is thus necessary to download astronomic positional data (see here for more information). This is automatically performed by the skyfield python package, retrieving the data from public sources of JPL and other US services. The data is stored in the MSS configuration directory and may need to update irregularly.
publicly accessible WMS Servers
Some examples for publicly accessible WMS Servers
Automation using the WMS API
Besides using the MSS UI we can use the API of the WMS sercer by a script to create for instance a number of the same plots or several flights or several forecast steps.
The retriever is an example which needs tweaked before using it
# -*- coding: utf-8 -*-
"""
mslib.retriever
~~~~~~~~~~~~~~~~~~~~
automation within msui to create for instance a number of the same plots
for several flights or several forecast steps
This file is part of MSS.
:copyright: Copyright 2020 Joern Ungermann
:copyright: Copyright 2020-2023 by the MSS team, see AUTHORS.
:license: APACHE-2.0, see LICENSE for details.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import sys
import argparse
import datetime
import io
import os
import xml
import requests
from fs import open_fs
import PIL.Image
import matplotlib.pyplot as plt
import mslib
import mslib.utils
from mslib.utils import thermolib
from mslib.utils.config import config_loader, read_config_file
from mslib.utils.units import units
import mslib.msui
import mslib.msui.mpl_map
import mslib.utils.qt
TEXT_CONFIG = {
"bbox": dict(boxstyle="round", facecolor="white", alpha=0.5, edgecolor="none"),
"fontweight": "bold", "zorder": 4, "fontsize": 6, "clip_on": True}
def load_from_ftml(filename):
"""Load a flight track from an XML file at <filename>.
"""
_dirname, _name = os.path.split(filename)
_fs = open_fs(_dirname)
datasource = _fs.open(_name)
try:
doc = xml.dom.minidom.parse(datasource)
except xml.parsers.expat.ExpatError as ex:
raise SyntaxError(str(ex))
ft_el = doc.getElementsByTagName("FlightTrack")[0]
waypoints_list = []
for wp_el in ft_el.getElementsByTagName("Waypoint"):
location = wp_el.getAttribute("location")
lat = float(wp_el.getAttribute("lat"))
lon = float(wp_el.getAttribute("lon"))
flightlevel = float(wp_el.getAttribute("flightlevel"))
comments = wp_el.getElementsByTagName("Comments")[0]
# If num of comments is 0(null comment), then return ''
if len(comments.childNodes):
comments = comments.childNodes[0].data.strip()
else:
comments = ''
waypoints_list.append((lat, lon, flightlevel, location, comments))
return waypoints_list
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")
if __name__ == "__main__":
main()