Refactor strike formation timing calculations.

This commit is contained in:
Dan Albert 2020-11-02 01:34:58 -08:00
parent 85491dca20
commit ed05f995b5

View File

@ -7,7 +7,6 @@ generating the waypoints for the mission.
"""
from __future__ import annotations
import math
from datetime import timedelta
from functools import cached_property
import logging
@ -52,77 +51,6 @@ class InvalidObjectiveLocation(PlanningError):
)
@dataclass(frozen=True)
class PackageWaypointTiming:
#: The package being scheduled.
package: Package
#: The package join time.
join: timedelta
#: The ingress waypoint TOT.
ingress: timedelta
#: The egress waypoint TOT.
egress: timedelta
#: The package split time.
split: timedelta
@property
def target(self) -> timedelta:
"""The package time over target."""
return self.package.time_over_target
@classmethod
def for_package(cls, package: Package) -> Optional[PackageWaypointTiming]:
"""Computes and returns the timings for package formation waypoints.
Package waypoint timing depends on the composition of the package.
Whenever flights are added or removed they must be recomputed since the
mission speed may have changed.
If the package contains no flights with formation flight plans, this
returns None.
"""
assert package.waypoints is not None
if not package.flights:
raise ValueError("Cannot plan TOT for package with no flights")
group_ground_speed = package.formation_speed
if group_ground_speed is None:
return None
# Round each waypoint TOT since DCS doesn't support sub-second timing
# and it's not interesting to the user.
ingress = timedelta(seconds=math.floor(
(package.time_over_target - TravelTime.between_points(
package.waypoints.ingress,
package.target.position,
group_ground_speed)).total_seconds()))
join = timedelta(seconds=math.floor(
(ingress - TravelTime.between_points(
package.waypoints.join,
package.waypoints.ingress,
group_ground_speed)).total_seconds()))
egress = timedelta(seconds=math.floor(
(package.time_over_target + TravelTime.between_points(
package.target.position,
package.waypoints.egress,
group_ground_speed)).total_seconds()))
split = timedelta(seconds=math.floor(
(egress + TravelTime.between_points(
package.waypoints.egress,
package.waypoints.split,
group_ground_speed)).total_seconds()))
return cls(package, join, ingress, egress, split)
@dataclass(frozen=True)
class FlightPlan:
package: Package
@ -179,9 +107,8 @@ class FlightPlan:
self, destination: FlightWaypoint) -> timedelta:
total = timedelta()
for previous_waypoint, waypoint in self.edges:
total += TravelTime.between_points(
previous_waypoint.position, waypoint.position,
self.speed_between_waypoints(previous_waypoint, waypoint))
total += self.travel_time_between_waypoints(previous_waypoint,
waypoint)
if waypoint == destination:
break
else:
@ -190,6 +117,11 @@ class FlightPlan:
f"waypoints for {self.flight}")
return total
def travel_time_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> timedelta:
return TravelTime.between_points(a.position, b.position,
self.speed_between_waypoints(a, b))
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
raise NotImplementedError
@ -210,14 +142,6 @@ class FormationFlightPlan(FlightPlan):
join: FlightWaypoint
split: FlightWaypoint
@property
def package_timing(self) -> PackageWaypointTiming:
timing = PackageWaypointTiming.for_package(self.package)
# Should be able to create a PackageWaypointTiming for any package with
# a FormationFlightPlan flight.
assert timing is not None
return timing
@property
def waypoints(self) -> List[FlightWaypoint]:
raise NotImplementedError
@ -266,23 +190,19 @@ class FormationFlightPlan(FlightPlan):
"""The estimated time between the first waypoint and the join point."""
return self._travel_time_to_waypoint(self.join)
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
target_types = (
FlightWaypointType.TARGET_GROUP_LOC,
FlightWaypointType.TARGET_POINT,
FlightWaypointType.TARGET_SHIP,
)
@property
def join_time(self) -> timedelta:
raise NotImplementedError
if waypoint.waypoint_type == FlightWaypointType.JOIN:
return self.package_timing.join
elif waypoint.waypoint_type in INGRESS_TYPES:
return self.package_timing.ingress
elif waypoint.waypoint_type in target_types:
return self.package_timing.target
elif waypoint.waypoint_type == FlightWaypointType.EGRESS:
return self.package_timing.egress
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
return self.package_timing.split
@property
def split_time(self) -> timedelta:
raise NotImplementedError
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.join:
return self.join_time
elif waypoint == self.split:
return self.split_time
return None
def depart_time_for_waypoint(
@ -293,7 +213,7 @@ class FormationFlightPlan(FlightPlan):
@property
def push_time(self) -> timedelta:
return self.package_timing.join - TravelTime.between_points(
return self.join_time - TravelTime.between_points(
self.hold.position,
self.join.position,
GroundSpeed.for_flight(self.flight, self.hold.alt)
@ -472,6 +392,43 @@ class StrikeFlightPlan(FormationFlightPlan):
def mission_speed(self) -> int:
return GroundSpeed.for_flight(self.flight, self.ingress.alt)
@property
def join_time(self) -> timedelta:
ingress_time = self.tot_for_waypoint(self.ingress)
travel_time = self.travel_time_between_waypoints(
self.join, self.ingress)
return ingress_time - travel_time
@property
def split_time(self) -> timedelta:
egress_time = self.tot_for_waypoint(self.egress)
travel_time = self.travel_time_between_waypoints(
self.egress, self.split)
return egress_time + travel_time
@property
def ingress_time(self) -> timedelta:
tot = self.package.time_over_target
travel_time = self.travel_time_between_waypoints(
self.ingress, self.tot_waypoint)
return tot - travel_time
@property
def egress_time(self) -> timedelta:
tot = self.package.time_over_target
travel_time = self.travel_time_between_waypoints(
self.tot_waypoint, self.egress)
return tot + travel_time
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.ingress:
return self.ingress_time
elif waypoint == self.egress:
return self.egress_time
elif waypoint in self.targets:
return self.package.time_over_target
return super().tot_for_waypoint(waypoint)
@dataclass(frozen=True)
class CustomFlightPlan(FlightPlan):