From 2a06a1ffdf9719cb1a0138b052b78a0ebbfcbc24 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 27 Apr 2021 21:15:12 -0700 Subject: [PATCH] Add proof-of-concept target info kneeboard page. This is extremely rough and just serves as an example of how to use the map projection API. --- game/theater/conflicttheater.py | 60 +++++++++++++++++++++++++++++++++ gen/kneeboard.py | 56 ++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index cedb5b6e..bd98485f 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -40,6 +40,7 @@ from dcs.unitgroup import ( VehicleGroup, ) from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed +from pyproj import CRS, Transformer from shapely import geometry, ops from gen.flights.flight import FlightType @@ -53,6 +54,7 @@ from .controlpoint import ( OffMapSpawn, ) from .landmap import Landmap, load_landmap, poly_contains +from .projections import TransverseMercator from ..point_with_heading import PointWithHeading from ..utils import Distance, meters, nautical_miles, pairwise @@ -473,6 +475,12 @@ class ReferencePoint: image_coordinates: Point +@dataclass(frozen=True) +class LatLon: + latitude: float + longitude: float + + class ConflictTheater: terrain: Terrain @@ -725,6 +733,22 @@ class ConflictTheater: MizCampaignLoader(directory / miz, t).populate_theater() return t + @property + def projection_parameters(self) -> TransverseMercator: + raise NotImplementedError + + def point_to_ll(self, point: Point) -> LatLon: + lat, lon = Transformer.from_crs( + self.projection_parameters.to_crs(), CRS("WGS84") + ).transform(point.x, point.y) + return LatLon(lat, lon) + + def ll_to_point(self, ll: LatLon) -> Point: + x, y = Transformer.from_crs( + CRS("WGS84"), self.projection_parameters.to_crs() + ).transform(ll.latitude, ll.longitude) + return Point(x, y) + class CaucasusTheater(ConflictTheater): terrain = caucasus.Caucasus() @@ -742,6 +766,12 @@ class CaucasusTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .caucasus import PARAMETERS + + return PARAMETERS + class PersianGulfTheater(ConflictTheater): terrain = persiangulf.PersianGulf() @@ -758,6 +788,12 @@ class PersianGulfTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .persiangulf import PARAMETERS + + return PARAMETERS + class NevadaTheater(ConflictTheater): terrain = nevada.Nevada() @@ -774,6 +810,12 @@ class NevadaTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .nevada import PARAMETERS + + return PARAMETERS + class NormandyTheater(ConflictTheater): terrain = normandy.Normandy() @@ -790,6 +832,12 @@ class NormandyTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .normandy import PARAMETERS + + return PARAMETERS + class TheChannelTheater(ConflictTheater): terrain = thechannel.TheChannel() @@ -806,6 +854,12 @@ class TheChannelTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .thechannel import PARAMETERS + + return PARAMETERS + class SyriaTheater(ConflictTheater): terrain = syria.Syria() @@ -822,6 +876,12 @@ class SyriaTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .syria import PARAMETERS + + return PARAMETERS + @dataclass class ComplexFrontLine: diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 8f809148..d1de5456 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -33,6 +33,7 @@ from dcs.mission import Mission from dcs.unittype import FlyingType from tabulate import tabulate +from game.theater import ConflictTheater from game.utils import meters from .aircraft import AIRCRAFT_DATA, FlightData from .airsupportgen import AwacsInfo, TankerInfo @@ -293,7 +294,6 @@ class BriefingPage(KneeboardPage): headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"], ) - flight_plan_builder writer.table( [ [ @@ -415,6 +415,41 @@ class BriefingPage(KneeboardPage): return local_time.strftime(f"%H:%M:%S") +class TargetInfoPage(KneeboardPage): + """A kneeboard page containing target information.""" + + def __init__( + self, + flight: FlightData, + targets: List[FlightWaypoint], + dark_kneeboard: bool, + theater: ConflictTheater, + ) -> None: + self.flight = flight + self.targets = targets + self.dark_kneeboard = dark_kneeboard + self.theater = theater + + def write(self, path: Path) -> None: + writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard) + if self.flight.custom_name is not None: + custom_name_title = ' ("{}")'.format(self.flight.custom_name) + else: + custom_name_title = "" + writer.title(f"{self.flight.callsign} Target Info{custom_name_title}") + + writer.table( + [self.target_info_row(t) for t in self.targets], + headers=["Description", "Location"], + ) + + writer.write(path) + + def target_info_row(self, target: FlightWaypoint) -> List[str]: + ll = self.theater.point_to_ll(target.position) + return [target.pretty_name, f"{ll.latitude} {ll.longitude}"] + + class KneeboardGenerator(MissionInfoGenerator): """Creates kneeboard pages for each client flight in the mission.""" @@ -456,9 +491,21 @@ class KneeboardGenerator(MissionInfoGenerator): ) return all_flights + def generate_target_page(self, flight: FlightData) -> Optional[KneeboardPage]: + target_waypoints = [ + w + for w in flight.waypoints + if w.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC + ] + if not target_waypoints: + return None + return TargetInfoPage( + flight, target_waypoints, self.dark_kneeboard, self.game.theater + ) + def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]: """Returns a list of kneeboard pages for the given flight.""" - return [ + pages: List[KneeboardPage] = [ BriefingPage( flight, self.comms, @@ -469,3 +516,8 @@ class KneeboardGenerator(MissionInfoGenerator): self.dark_kneeboard, ), ] + + if (target_page := self.generate_target_page(flight)) is not None: + pages.append(target_page) + + return pages