diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py
index 726b9105..18e76b26 100644
--- a/qt_ui/widgets/map/mapmodel.py
+++ b/qt_ui/widgets/map/mapmodel.py
@@ -1,4 +1,5 @@
import logging
+from datetime import timedelta
from typing import List, Optional, Tuple
from PySide2.QtCore import Property, QObject, Signal, Slot
@@ -11,8 +12,10 @@ from game.theater import (
ControlPoint,
TheaterGroundObject,
)
+from game.utils import meters
from gen.ato import AirTaskingOrder
-from gen.flights.flight import Flight, FlightWaypoint
+from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
+from gen.flights.flightplan import FlightPlan
from qt_ui.models import GameModel
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
@@ -102,16 +105,55 @@ class SupplyRouteJs(QObject):
class WaypointJs(QObject):
- def __init__(self, waypoint: FlightWaypoint, theater: ConflictTheater) -> None:
+ def __init__(
+ self,
+ waypoint: FlightWaypoint,
+ number: int,
+ flight_plan: FlightPlan,
+ theater: ConflictTheater,
+ ) -> None:
super().__init__()
self.waypoint = waypoint
+ self._number = number
+ self.flight_plan = flight_plan
self.theater = theater
+ @Property(int)
+ def number(self) -> int:
+ return self._number
+
@Property(list)
def position(self) -> LeafletLatLon:
ll = self.theater.point_to_ll(self.waypoint.position)
return [ll.latitude, ll.longitude]
+ @Property(int)
+ def altitudeFt(self) -> int:
+ return int(self.waypoint.alt.feet)
+
+ @Property(str)
+ def altitudeReference(self) -> str:
+ return "AGL" if self.waypoint.alt_type == "RADIO" else "MSL"
+
+ @Property(str)
+ def name(self) -> str:
+ return self.waypoint.name
+
+ @Property(str)
+ def timing(self) -> str:
+ prefix = "TOT"
+ time = self.flight_plan.tot_for_waypoint(self.waypoint)
+ if time is None:
+ prefix = "Depart"
+ time = self.flight_plan.depart_time_for_waypoint(self.waypoint)
+ if time is None:
+ return ""
+ return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
+
+ @Property(bool)
+ def isDivert(self) -> bool:
+ return self.waypoint.waypoint_type is FlightWaypointType.DIVERT
+
class FlightJs(QObject):
flightPlanChanged = Signal()
@@ -127,7 +169,17 @@ class FlightJs(QObject):
self.reset_waypoints()
def reset_waypoints(self) -> None:
- self._waypoints = [WaypointJs(p, self.theater) for p in self.flight.points]
+ departure = FlightWaypoint(
+ FlightWaypointType.TAKEOFF,
+ self.flight.departure.position.x,
+ self.flight.departure.position.y,
+ meters(0),
+ )
+ departure.alt_type = "RADIO"
+ self._waypoints = [
+ WaypointJs(p, i, self.flight.flight_plan, self.theater)
+ for i, p in enumerate([departure] + self.flight.points)
+ ]
self.flightPlanChanged.emit()
@Property(list, notify=flightPlanChanged)
diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js
index 6cad0155..381c79f8 100644
--- a/resources/ui/map/map.js
+++ b/resources/ui/map/map.js
@@ -8,7 +8,6 @@
* - Time of day/weather themeing
* - Exclusion zones
* - Commit ranges
- * - Waypoint info
* - Supply route status
* - Front line
* - Debug flight plan drawing
@@ -61,7 +60,7 @@ L.control
"Enemy SAM detection range": redSamDetectionLayer,
},
"Flight Plans": {
- "Hide": L.layerGroup(),
+ Hide: L.layerGroup(),
"Show selected blue": selectedFlightPlansLayer,
"Show all blue": blueFlightPlansLayer,
"Show all red": redFlightPlansLayer,
@@ -178,19 +177,32 @@ function drawFlightPlan(flight) {
var layer = flight.blue ? blueFlightPlansLayer : redFlightPlansLayer;
var color = flight.blue ? Colors.Blue : Colors.Red;
var highlight = "#ffff00";
- var points = [];
- flight.flightPlan.forEach((waypoint) => {
- points.push(waypoint.position);
- L.circle(waypoint.position, { radius: 50, color: color }).addTo(layer);
+ // We don't need a marker for the departure waypoint (and it's likely
+ // coincident with the landing waypoint, so hard to see). We do want to draw
+ // the path from it though.
+ var points = [flight.flightPlan[0].position];
+ flight.flightPlan.slice(1).forEach((waypoint) => {
+ if (!waypoint.isDivert) {
+ points.push(waypoint.position);
+ }
+
if (flight.selected) {
- L.circle(waypoint.position, { radius: 50, color: highlight }).addTo(
- selectedFlightPlansLayer
- );
+ L.marker(waypoint.position)
+ .bindTooltip(
+ `${waypoint.number} ${waypoint.name}
` +
+ `${waypoint.altitudeFt} ft ${waypoint.altitudeReference}
` +
+ `${waypoint.timing}`,
+ { permanent: true }
+ )
+ .addTo(layer)
+ .addTo(selectedFlightPlansLayer);
}
});
- L.polyline(points, { color: color }).addTo(layer);
if (flight.selected) {
L.polyline(points, { color: highlight }).addTo(selectedFlightPlansLayer);
+ L.polyline(points, { color: highlight }).addTo(layer);
+ } else {
+ L.polyline(points, { color: color }).addTo(layer);
}
}
@@ -198,9 +210,22 @@ function drawFlightPlans() {
blueFlightPlansLayer.clearLayers();
redFlightPlansLayer.clearLayers();
selectedFlightPlansLayer.clearLayers();
+ var selected = null;
game.flights.forEach((flight) => {
- drawFlightPlan(flight);
+ // Draw the selected waypoint last so it's on top. bringToFront only brings
+ // it to the front of the *extant* elements, so any flights drawn later will
+ // be drawn on top. We could fight with manual Z-indexes but leaflet does a
+ // lot of that automatically so it'd be error prone.
+ if (flight.selected) {
+ selected = flight;
+ } else {
+ drawFlightPlan(flight);
+ }
});
+
+ if (selected != null) {
+ drawFlightPlan(selected);
+ }
}
function drawInitialMap() {