diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py
index 061d4f830..9cf2d17e6 100644
--- a/mslib/msui/flighttrack.py
+++ b/mslib/msui/flighttrack.py
@@ -50,7 +50,6 @@
from mslib.utils.coordinate import path_points, get_distance
from mslib.utils.find_location import find_location
from mslib.utils import thermolib
-from mslib.utils.verify_waypoint_data import verify_waypoint_data
from mslib.utils.config import config_loader, save_settings_qsettings, load_settings_qsettings
from mslib.utils.qt import variant_to_string, variant_to_float
from mslib.msui.performance_settings import DEFAULT_PERFORMANCE
@@ -63,6 +62,17 @@
TIME_UTC = 9
+class Revision:
+ """
+ Represents a revision of a flight track.
+ ID is mandatory, name is optional.
+ """
+
+ def __init__(self, revision_id, name=None):
+ self.id = revision_id
+ self.name = name
+
+
def seconds_to_string(seconds):
"""
Format a time given in seconds to a string HH:MM:SS. Used for the
@@ -132,12 +142,16 @@ class Waypoint:
def __init__(self, lat=0., lon=0., flightlevel=0., location="", comments=""):
self.location = location
+
+ # Prefer explicitly provided coordinates (e.g. from XML/server)
+ self.lat = lat
+ self.lon = lon
+
+ # Only resolve from configured locations if coordinates were not meaningfully provided
locations = config_loader(dataset='locations')
- if location in locations:
+ if location in locations and (lat == 0.0 and lon == 0.0):
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.
@@ -186,6 +200,7 @@ def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False,
data_dir=config_loader(dataset="mss_dir"),
xml_content=None):
super().__init__()
+ self.revision = None
self.name = name # a name for this flight track
self.filename = filename # filename for store/load
self.data_dir = data_dir
@@ -639,12 +654,24 @@ def save_to_ftml(self, filename=None):
def get_xml_doc(self):
doc = xml.dom.minidom.Document() # nosec, we take care of writing correct XML
+
ft_el = doc.createElement("FlightTrack")
ft_el.setAttribute("version", __version__)
doc.appendChild(ft_el)
# The list of waypoint elements.
+
+ # Write revision information
+ if self.revision is not None:
+ rev_el = doc.createElement("Revision")
+ rev_el.setAttribute("id", str(self.revision.id))
+ if self.revision.name:
+ rev_el.setAttribute("name", self.revision.name)
+ ft_el.appendChild(rev_el)
+
+ # List of waypoints
wp_el = doc.createElement("ListOfWaypoints")
ft_el.appendChild(wp_el)
+
for wp in self.waypoints:
element = doc.createElement("Waypoint")
wp_el.appendChild(element)
@@ -652,9 +679,11 @@ def get_xml_doc(self):
element.setAttribute("lat", str(wp.lat))
element.setAttribute("lon", str(wp.lon))
element.setAttribute("flightlevel", str(wp.flightlevel))
+
comments = doc.createElement("Comments")
comments.appendChild(doc.createTextNode(str(wp.comments)))
element.appendChild(comments)
+
return doc
def get_xml_content(self):
@@ -671,16 +700,35 @@ def load_from_ftml(self, filename):
def load_from_xml_data(self, xml_content, name="Flight track"):
self.name = name
- if verify_waypoint_data(xml_content):
- _waypoints_list = load_from_xml_data(xml_content, name)
- self.replace_waypoints(_waypoints_list)
+
+ try:
+ doc = defusedxml.minidom.parseString(xml_content)
+ except DefusedXmlException as ex:
+ raise SyntaxError(str(ex))
+
+ ft_el = doc.getElementsByTagName("FlightTrack")[0]
+
+ # Load revision
+ revision_nodes = ft_el.getElementsByTagName("Revision")
+ if revision_nodes:
+ rev_el = revision_nodes[0]
+ revision_id = int(rev_el.getAttribute("id"))
+ revision_name = rev_el.getAttribute("name") or None
+ self.revision = Revision(revision_id, revision_name)
else:
- raise SyntaxError(f"Invalid flight track filename: {name}")
+ # Backward compatibility: create deterministic default revision
+ self.revision = Revision(
+ revision_id=0,
+ name=None
+ )
+
+ # Validate only waypoint structure, revision is optional metadata
+ _waypoints_list = load_from_xml_data(xml_content, name)
+ self.replace_waypoints(_waypoints_list)
def get_filename(self):
return self.filename
-
#
# CLASS WaypointDelegate
#
diff --git a/mslib/msui/viewwindows.py b/mslib/msui/viewwindows.py
index eb1f936ca..5bbbb612a 100644
--- a/mslib/msui/viewwindows.py
+++ b/mslib/msui/viewwindows.py
@@ -34,6 +34,23 @@
from mslib.utils.config import save_settings_qsettings
+def format_operation_with_revision(model):
+ """
+ Return operation name including revision id and optional revision name.
+ """
+ if model is None:
+ return ""
+
+ rev = getattr(model, "revision", None)
+ if rev is None:
+ return model.name
+
+ label = f"{model.name} [rev: {rev.id}]"
+ if getattr(rev, "name", None):
+ label += f" ({rev.name})"
+ return label
+
+
class MSUIViewWindow(QtWidgets.QMainWindow):
"""
Derives QMainWindow to provide some common functionality to all
@@ -109,7 +126,9 @@ def setFlightTrackModel(self, model):
"""
# Update title flighttrack name
if self.waypoints_model:
- self.setWindowTitle(self.windowTitle().replace(self.waypoints_model.name, model.name))
+ old_label = format_operation_with_revision(self.waypoints_model)
+ new_label = format_operation_with_revision(model)
+ self.setWindowTitle(self.windowTitle().replace(old_label, new_label))
self.waypoints_model = model
@@ -274,11 +293,15 @@ def setFlightTrackModel(self, model):
# Update Top View flighttrack name
if hasattr(self.mpl.canvas, "map"):
- self.mpl.canvas.map.ax.figure.suptitle(f"{model.name}", x=0.95, ha='right')
+ title = format_operation_with_revision(model)
+ self.mpl.canvas.map.ax.figure.suptitle(title, x=0.95, ha='right')
+
self.mpl.canvas.map.ax.figure.canvas.draw()
elif hasattr(self.mpl.canvas, 'plotter'):
- self.mpl.canvas.plotter.fig.suptitle(f"{model.name}", x=0.95, ha='right')
+ title = format_operation_with_revision(model)
+ self.mpl.canvas.plotter.fig.suptitle(title, x=0.95, ha='right')
+
self.mpl.canvas.plotter.fig.canvas.draw()
def getView(self):
diff --git a/tests/_test_msui/test_flighttrack.py b/tests/_test_msui/test_flighttrack.py
index 5a8cc1476..44a6009fc 100644
--- a/tests/_test_msui/test_flighttrack.py
+++ b/tests/_test_msui/test_flighttrack.py
@@ -114,3 +114,38 @@ def test_isinstance_check_with_various_types(self):
"Dict should pass isinstance dict check"
assert isinstance({}, dict), \
"Empty dict should pass isinstance dict check"
+
+ def test_load_ftml_with_revision(self):
+ xml_content = """
+
+
+
+
+
+
+
+
+ """
+
+ model = WaypointsTableModel(xml_content=xml_content, name="test_track")
+
+ assert model.revision is not None
+ assert model.revision.id == 42
+ assert model.revision.name == "draft"
+
+ def test_load_ftml_without_revision_creates_default_revision(self):
+ xml_content = """
+
+
+
+
+
+
+
+ """
+
+ model = WaypointsTableModel(xml_content=xml_content, name="legacy_track")
+
+ assert model.revision is not None
+ assert isinstance(model.revision.id, int)
+ assert model.revision.name is None