Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions docs/tutorials/tutorial_performance_settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ The range-specific data of an aircraft can be taken into account in Tableview fo
Now we’ll have to select a file called dot json file stored in the MSS database in the syntax of dot json.
The information related to the performance data of the aircraft are stored here.
Click on the "Select" option to select the Json file from the MSS docs database as an example.
Click on show performance for the related data to be shown.
You can see the changes in the columns of the table view regarding the performance parameters of the aircraft.
Giving some examples, things like the change in flight time, fuel, aircraft weight, ceiling altitude,
etc. as the flight takes off from Nagpur and reaches Delhi, can be analysed.
There are options in the panel like take off time, maximum take off weight and aircraft weight without fuel.
There are options in the panel like maximum take off weight and aircraft weight without fuel.
We can change these settings to see the how the performance data changes.
Like, we now changed the maximum take off weight from ninety one thousands to eighty seven thousand kilograms.
And the aircraft weight without fuel from fifty six thousands to forty eight thousands kilograms.
Here you can see we changed the take off time from 19th May 2024 to 19th October 2024.
Then we can uncheck the box of show Performance and then click it again and there you can see the changes
in the performance settings.
It just shows and hides the performance data.
Now we are going to close the Top View.
It asks for confirmation whether we want to close it or not.
We select yes in the confirmation dialogue and it closes.
4 changes: 3 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ 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.
in Table View. The table shows the estimated time (UTC) at each waypoint. Edit the time of any
waypoint to shift the whole flight; all other times follow.
The aircraft performance is specified using tables given in the JSON format.
A basic configuration looks like the following file:

.. literalinclude:: samples/config/msui/performance_simple.json.sample
Expand Down
211 changes: 168 additions & 43 deletions mslib/msui/flighttrack.py

Large diffs are not rendered by default.

79 changes: 55 additions & 24 deletions mslib/msui/mpl_pathinteractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,23 +858,28 @@ def set_waypoints_model(self, waypoints):
# If a model exists, disconnect from the old change() signals.
wpm = self.waypoints_model
if wpm:
wpm.dataChanged.disconnect(self.qt_data_changed_listener)
wpm.rowsInserted.disconnect(self.qt_insert_remove_point_listener)
wpm.rowsRemoved.disconnect(self.qt_insert_remove_point_listener)
for signal, slot in self._model_connections(wpm):
signal.disconnect(slot)
# Set the new waypoints model.
self.waypoints_model = waypoints
# Connect to the new model's signals.
wpm = self.waypoints_model
wpm.dataChanged.connect(self.qt_data_changed_listener)
wpm.rowsInserted.connect(self.qt_insert_remove_point_listener)
wpm.rowsRemoved.connect(self.qt_insert_remove_point_listener)
for signal, slot in self._model_connections(wpm):
signal.connect(slot)
# Redraw.
self.plotter.update_from_waypoints(wpm.all_waypoint_data())
self.redraw_figure()

def qt_insert_remove_point_listener(self, index, first, last):
"""Listens to rowsInserted() and rowsRemoved() signals emitted
by the flight track data model. The view can thus react to
def _model_connections(self, wpm):
return ((wpm.dataChanged, self.qt_data_changed_listener),
(wpm.rowsInserted, self.qt_insert_remove_point_listener),
(wpm.rowsRemoved, self.qt_insert_remove_point_listener),
(wpm.modelReset, self.qt_insert_remove_point_listener),
(wpm.performanceDataChanged, self.qt_performance_data_changed_listener))

def qt_insert_remove_point_listener(self, *args):
"""Listens to rowsInserted(), rowsRemoved() and modelReset() signals
emitted by the flight track data model. The view can thus react to
data changes induced by another view (table, side view).
"""
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
Expand All @@ -888,6 +893,13 @@ def qt_data_changed_listener(self, index1, index2):
# REIMPLEMENT IN SUBCLASSES.
pass

def qt_performance_data_changed_listener(self):
"""Listens to performanceDataChanged() signals emitted by the flight
track data model, i.e. to changed times of the waypoints.
"""
# REIMPLEMENT IN SUBCLASSES that show times.
pass

def get_ind_under_point(self, event):
"""Get the index of the waypoint vertex under the point
specified by event within epsilon tolerance.
Expand Down Expand Up @@ -1088,20 +1100,30 @@ def qt_data_changed_listener(self, index1, index2):
data model. The side view can thus react to data changes
induced by another view (table, top view).
"""
# If the altitude of a point has changed, only the plotted flight
# profile needs to be redrawn (redraw_path()). If the horizontal
# position of a waypoint has changed, the entire figure needs to be
# redrawn, as this affects the x-position of all points.
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
if index1.column() in [ft.FLIGHTLEVEL, ft.PRESSURE, ft.LOCATION]:
# If the altitude of a point has changed, the plotted flight profile
# needs to be redrawn (redraw_path()), and the x-axis, as the flight
# times and the aircraft weight (ceiling altitude) depend on the
# altitude. If the horizontal position of a waypoint has changed, the
# entire figure needs to be redrawn, as this affects the x-position of
# all points.
columns = ft.changed_columns(index1, index2)
if columns & {ft.LAT, ft.LON}:
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
self.redraw_figure()
elif columns & {ft.FLIGHTLEVEL, ft.PRESSURE, ft.LOCATION}:
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
self.plotter.redraw_path(
self.plotter.pathpatch.get_path().vertices, self.waypoints_model.all_waypoint_data())
elif index1.column() in [ft.LAT, ft.LON]:
self.redraw_figure()
elif index1.column() in [ft.TIME_UTC]:
if self.redraw_xaxis is not None:
if columns & {ft.FLIGHTLEVEL, ft.PRESSURE} and self.redraw_xaxis is not None:
self.redraw_xaxis(self.plotter.path.ilats, self.plotter.path.ilons, self.plotter.path.itimes)

def qt_performance_data_changed_listener(self):
"""The times of the waypoints label the x-axis.
"""
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
if self.redraw_xaxis is not None:
self.redraw_xaxis(self.plotter.path.ilats, self.plotter.path.ilons, self.plotter.path.itimes)


class LPathInteractor(PathInteractor):
"""
Expand Down Expand Up @@ -1152,8 +1174,11 @@ def qt_data_changed_listener(self, index1, index2):
data model. The linear view can thus react to data changes
induced by another view (table, top view, side view).
"""
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
self.redraw_figure()
# A new section is requested only for a changed path, not e.g. for a
# changed name or comment.
if ft.changed_columns(index1, index2) & {ft.LAT, ft.LON, ft.FLIGHTLEVEL, ft.PRESSURE}:
self.plotter.update_from_waypoints(self.waypoints_model.all_waypoint_data())
self.redraw_figure()


class HPathInteractor(PathInteractor):
Expand Down Expand Up @@ -1326,9 +1351,15 @@ def qt_data_changed_listener(self, index1, index2):
data model. The top view can thus react to data changes
induced by another view (table, side view).
"""
# Update the top view if the horizontal position of any point has been
# changed.
if index1.column() in [ft.LOCATION, ft.LAT, ft.LON, ft.FLIGHTLEVEL]:
# Update the top view if the horizontal position or the name of any
# point has been changed, or its flight level (tangent and solar lines).
if ft.changed_columns(index1, index2) & {ft.LOCATION, ft.LAT, ft.LON, ft.FLIGHTLEVEL, ft.PRESSURE}:
self.update()

def qt_performance_data_changed_listener(self):
"""Only the solar angle lines depend on the times of the waypoints.
"""
if self.plotter.show_solar_angle is not None:
self.update()

def update(self):
Expand Down
15 changes: 4 additions & 11 deletions mslib/msui/mpl_qtwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,7 @@ def redraw_xaxis(self, lats, lons, times):
a vertical section image if one exists, as it is invalid after
a path change.
"""
times_visible = False
if self.waypoints_model is not None:
times_visible = self.waypoints_model.performance_settings["visible"]
self.plotter.redraw_xaxis(lats, lons, times, times_visible)
self.plotter.redraw_xaxis(lats, lons, times, self.waypoints_model is not None)

for _line in self.ceiling_alt:
_line.remove()
Expand All @@ -488,8 +485,7 @@ def redraw_xaxis(self, lats, lons, times):
xs, thermolib.flightlevel2pressure(np.asarray(ys) * units.hft).magnitude,
color="k", ls="--")
self.update_ceiling(
self.plotter.get_settings()["draw_ceiling"] and
self.waypoints_model.performance_settings["visible"],
self.plotter.get_settings()["draw_ceiling"],
self.plotter.get_settings()["colour_ceiling"])
highlight = [[wp.lat, wp.lon] for wp in self.waypoints_model.waypoints]
self.plotter.draw_vertical_lines(highlight, lats, lons)
Expand Down Expand Up @@ -560,11 +556,8 @@ def set_settings(self, settings, save=False):
settings = self.plotter.get_settings()
self.set_flight_levels(settings["flightlevels"])
self.set_flight_levels_visible(settings["draw_flightlevels"])
self.update_ceiling(
settings["draw_ceiling"] and (
self.waypoints_model is not None and
self.waypoints_model.performance_settings["visible"]),
settings["colour_ceiling"])
self.update_ceiling(settings["draw_ceiling"] and self.waypoints_model is not None,
settings["colour_ceiling"])
self.update_vertical_extent_from_settings()

if self.waypoints_interactor is not None:
Expand Down
30 changes: 23 additions & 7 deletions mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,7 @@ def create_local_operation_file(self, local_dir):
def reload_local_wp(self):
self.waypoints_model = ft.WaypointsTableModel(filename=self.local_ftml_file, data_dir=self.data_dir)
self.waypoints_model.changeMessageSignal.connect(self.handle_change_message)
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.connect_waypoints_model()
self.reload_view_windows()

def operation_category_handler(self, update_operations=True):
Expand Down Expand Up @@ -1162,7 +1162,7 @@ def fetch_wp_mscolab(self):
self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
self.waypoints_model.changeMessageSignal.connect(self.handle_change_message)
self.waypoints_model.save_to_ftml(self.local_ftml_file)
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.connect_waypoints_model()
self.reload_view_windows()
show_popup(self.ui, "Success", "New Waypoints Fetched To Local File!", icon=1)
self.merge_dialog.close()
Expand All @@ -1181,7 +1181,7 @@ def save_wp_mscolab(self, comment=None):
self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
self.waypoints_model.changeMessageSignal.connect(self.handle_change_message)
self.waypoints_model.save_to_ftml(self.local_ftml_file)
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.connect_waypoints_model()
self.reload_view_windows()
show_popup(self.ui, "Success", "New Waypoints Saved To Server!", icon=1)
self.merge_dialog.close()
Expand Down Expand Up @@ -1630,7 +1630,7 @@ def load_wps_from_server(self):
self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
self.waypoints_model.changeMessageSignal.connect(self.handle_change_message)
self.waypoints_model.name = self.active_operation_name
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.connect_waypoints_model()

def reload_operations(self):
logging.debug('reload_operations')
Expand All @@ -1647,13 +1647,29 @@ def reload_wps_from_server(self):
self.load_wps_from_server()
self.reload_view_windows()

def _flight_plan_signals(self):
"""
The signals of a change of the flight plan of the active operation, which is sent to the server or written
to the local file. Only the flight plan is stored, not the times and fuel (performanceDataChanged).
"""
model = self.waypoints_model
return model.dataChanged, model.rowsInserted, model.rowsRemoved, model.modelReset

def connect_waypoints_model(self):
for signal in self._flight_plan_signals():
signal.connect(self.handle_waypoints_changed)

def disconnect_waypoints_model(self):
for signal in self._flight_plan_signals():
signal.disconnect(self.handle_waypoints_changed)

def handle_waypoints_changed(self, _1=None, _2=None, _3=None, version_name=None):
logging.debug("handle_waypoints_changed")
if self.ui.workLocallyCheckbox.isChecked():
self.waypoints_model.save_to_ftml(self.local_ftml_file)
else:
xml_content = self.waypoints_model.get_xml_content()
# one change emits dataChanged twice, e.g. from setData and from update_distances; a reloaded flight
# skip content that was sent already, e.g. a cell committed with its old value; a reloaded flight
# track is a new model, so after it the same content is sent again
sent = (self.waypoints_model, self.active_op_id, xml_content)
if version_name is None and self.last_sent_flight_track is not None and \
Expand Down Expand Up @@ -1707,12 +1723,12 @@ def handle_import_msc(self, file_path, extension, function, pickertype):
if not verify_waypoint_data(xml_content):
show_popup(self.ui, "Import Failed", f"The file - {file_name}, was not imported!", 0)
return
self.waypoints_model.dataChanged.disconnect(self.handle_waypoints_changed)
self.disconnect_waypoints_model()
self.waypoints_model = model
self.waypoints_model.changeMessageSignal.connect(self.handle_change_message)
# the server refuses longer version names
self.handle_waypoints_changed(version_name=file_name[:MAX_VERSION_NAME_LENGTH])
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.connect_waypoints_model()
self.reload_view_windows()
show_popup(self.ui, "Import Success", f"The file - {file_name}, was imported successfully!", 1)

Expand Down
41 changes: 22 additions & 19 deletions mslib/msui/performance_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@

DEFAULT_PERFORMANCE = {
"aircraft": aircraft.SimpleAircraft(aircraft.AIRCRAFT_DUMMY),
"visible": False,
"takeoff_weight": 0,
"takeoff_weight": aircraft.AIRCRAFT_DUMMY["takeoff_weight"],
"takeoff_time": QtCore.QDateTime.currentDateTimeUtc(),
"empty_weight": 0,
"empty_weight": aircraft.AIRCRAFT_DUMMY["empty_weight"],
"ceiling_alt": [410],
}

Expand All @@ -66,16 +65,14 @@ def __init__(self, parent=None, view=None, settings_dict=None):
settings_dict = DEFAULT_PERFORMANCE
self.aircraft = settings_dict["aircraft"]
self.lbAircraftName.setText(self.aircraft.name)
self.cbShowPerformance.setChecked(settings_dict["visible"])
self.dsbTakeoffWeight.setValue(settings_dict["takeoff_weight"])
self.dsbEmptyWeight.setValue(
settings_dict.get("empty_weight", settings_dict["takeoff_weight"] - settings_dict.get("fuel", 0)))
self.dteTakeoffTime.setDateTime(settings_dict["takeoff_time"])

# Connecting signals
# Connecting signals. Each update recomputes and redraws the flight
# track, the spin boxes have no keyboard tracking, so that typing a
# value emits valueChanged once, not on every key stroke.
self.pbLoadPerformance.clicked.connect(self.load_performance)
self.cbShowPerformance.stateChanged.connect(self.update_parent_performance)
self.dteTakeoffTime.dateTimeChanged.connect(self.update_parent_performance)
self.dsbTakeoffWeight.valueChanged.connect(self.update_parent_performance)
self.dsbEmptyWeight.valueChanged.connect(self.update_parent_performance)

Expand All @@ -88,8 +85,8 @@ def get_settings(self):
"""
settings_dict = {
"aircraft": self.aircraft,
"visible": self.cbShowPerformance.isChecked(),
"takeoff_time": self.dteTakeoffTime.dateTime(),
# The takeoff time is set by editing the time column of the table view.
"takeoff_time": self.parent.waypoints_model.performance_settings["takeoff_time"],
"takeoff_weight": self.dsbTakeoffWeight.value(),
"empty_weight": self.dsbEmptyWeight.value()
}
Expand All @@ -111,15 +108,21 @@ def load_performance(self):
performance = json.load(tf)
self.aircraft = aircraft.SimpleAircraft(performance)
self.lbAircraftName.setText(self.aircraft.name)
self.dsbTakeoffWeight.setValue(self.aircraft.takeoff_weight)
if not any(hasattr(self.aircraft, _x) for _x in ("fuel", "empty_weight")):
raise KeyError("empty_weight")
if hasattr(self.aircraft, "empty_weight"):
self.dsbEmptyWeight.setValue(self.aircraft.empty_weight)
else:
self.dsbEmptyWeight.setValue(self.aircraft.takeoff_weight - self.aircraft.fuel)

self.update_parent_performance()
# Apply the new aircraft once, not for each changed weight.
self.dsbTakeoffWeight.blockSignals(True)
self.dsbEmptyWeight.blockSignals(True)
try:
self.dsbTakeoffWeight.setValue(self.aircraft.takeoff_weight)
if not any(hasattr(self.aircraft, _x) for _x in ("fuel", "empty_weight")):
raise KeyError("empty_weight")
if hasattr(self.aircraft, "empty_weight"):
self.dsbEmptyWeight.setValue(self.aircraft.empty_weight)
else:
self.dsbEmptyWeight.setValue(self.aircraft.takeoff_weight - self.aircraft.fuel)
finally:
self.dsbTakeoffWeight.blockSignals(False)
self.dsbEmptyWeight.blockSignals(False)
self.update_parent_performance()

except KeyError as ex:
QtWidgets.QMessageBox.critical(self, self.tr("Performance JSON Load"),
Expand Down
Loading
Loading