From 4e498e6932c31d25196c2c307ba78ac31c7b4af0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 12 May 2021 23:03:53 -0700 Subject: [PATCH] Add waypoint info tooltip to the new map. --- qt_ui/widgets/map/mapmodel.py | 58 +++++++++++++++++++++++++++++++++-- resources/ui/map/map.js | 47 +++++++++++++++++++++------- 2 files changed, 91 insertions(+), 14 deletions(-) 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() {