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.
This commit is contained in:
Dan Albert 2021-04-27 21:15:12 -07:00
parent 8a01209ded
commit 2a06a1ffdf
2 changed files with 114 additions and 2 deletions

View File

@ -40,6 +40,7 @@ from dcs.unitgroup import (
VehicleGroup, VehicleGroup,
) )
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from pyproj import CRS, Transformer
from shapely import geometry, ops from shapely import geometry, ops
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
@ -53,6 +54,7 @@ from .controlpoint import (
OffMapSpawn, OffMapSpawn,
) )
from .landmap import Landmap, load_landmap, poly_contains from .landmap import Landmap, load_landmap, poly_contains
from .projections import TransverseMercator
from ..point_with_heading import PointWithHeading from ..point_with_heading import PointWithHeading
from ..utils import Distance, meters, nautical_miles, pairwise from ..utils import Distance, meters, nautical_miles, pairwise
@ -473,6 +475,12 @@ class ReferencePoint:
image_coordinates: Point image_coordinates: Point
@dataclass(frozen=True)
class LatLon:
latitude: float
longitude: float
class ConflictTheater: class ConflictTheater:
terrain: Terrain terrain: Terrain
@ -725,6 +733,22 @@ class ConflictTheater:
MizCampaignLoader(directory / miz, t).populate_theater() MizCampaignLoader(directory / miz, t).populate_theater()
return t 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): class CaucasusTheater(ConflictTheater):
terrain = caucasus.Caucasus() terrain = caucasus.Caucasus()
@ -742,6 +766,12 @@ class CaucasusTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .caucasus import PARAMETERS
return PARAMETERS
class PersianGulfTheater(ConflictTheater): class PersianGulfTheater(ConflictTheater):
terrain = persiangulf.PersianGulf() terrain = persiangulf.PersianGulf()
@ -758,6 +788,12 @@ class PersianGulfTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .persiangulf import PARAMETERS
return PARAMETERS
class NevadaTheater(ConflictTheater): class NevadaTheater(ConflictTheater):
terrain = nevada.Nevada() terrain = nevada.Nevada()
@ -774,6 +810,12 @@ class NevadaTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .nevada import PARAMETERS
return PARAMETERS
class NormandyTheater(ConflictTheater): class NormandyTheater(ConflictTheater):
terrain = normandy.Normandy() terrain = normandy.Normandy()
@ -790,6 +832,12 @@ class NormandyTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .normandy import PARAMETERS
return PARAMETERS
class TheChannelTheater(ConflictTheater): class TheChannelTheater(ConflictTheater):
terrain = thechannel.TheChannel() terrain = thechannel.TheChannel()
@ -806,6 +854,12 @@ class TheChannelTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .thechannel import PARAMETERS
return PARAMETERS
class SyriaTheater(ConflictTheater): class SyriaTheater(ConflictTheater):
terrain = syria.Syria() terrain = syria.Syria()
@ -822,6 +876,12 @@ class SyriaTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
@property
def projection_parameters(self) -> TransverseMercator:
from .syria import PARAMETERS
return PARAMETERS
@dataclass @dataclass
class ComplexFrontLine: class ComplexFrontLine:

View File

@ -33,6 +33,7 @@ from dcs.mission import Mission
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
from tabulate import tabulate from tabulate import tabulate
from game.theater import ConflictTheater
from game.utils import meters from game.utils import meters
from .aircraft import AIRCRAFT_DATA, FlightData from .aircraft import AIRCRAFT_DATA, FlightData
from .airsupportgen import AwacsInfo, TankerInfo from .airsupportgen import AwacsInfo, TankerInfo
@ -293,7 +294,6 @@ class BriefingPage(KneeboardPage):
headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"], headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"],
) )
flight_plan_builder
writer.table( writer.table(
[ [
[ [
@ -415,6 +415,41 @@ class BriefingPage(KneeboardPage):
return local_time.strftime(f"%H:%M:%S") 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): class KneeboardGenerator(MissionInfoGenerator):
"""Creates kneeboard pages for each client flight in the mission.""" """Creates kneeboard pages for each client flight in the mission."""
@ -456,9 +491,21 @@ class KneeboardGenerator(MissionInfoGenerator):
) )
return all_flights 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]: def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
"""Returns a list of kneeboard pages for the given flight.""" """Returns a list of kneeboard pages for the given flight."""
return [ pages: List[KneeboardPage] = [
BriefingPage( BriefingPage(
flight, flight,
self.comms, self.comms,
@ -469,3 +516,8 @@ class KneeboardGenerator(MissionInfoGenerator):
self.dark_kneeboard, self.dark_kneeboard,
), ),
] ]
if (target_page := self.generate_target_page(flight)) is not None:
pages.append(target_page)
return pages