Add fallback locations for join zones.

It's rare with the current 5NM buffer around the origin, but if we use
the hold distance as the buffer like we maybe should it's possible for
the preferred join locations to fall entirely within the home zone. In
that case, fall back to a location within the max-turn-zone that's
outside the home zone and is nearest the IP.
This commit is contained in:
Dan Albert 2021-07-18 14:41:22 -07:00
parent 2580fe6b79
commit e22e8669e1
4 changed files with 59 additions and 32 deletions

View File

@ -11,7 +11,6 @@ from shapely.geometry import (
MultiLineString,
)
from game.theater import ConflictTheater
from game.utils import nautical_miles
if TYPE_CHECKING:
@ -26,12 +25,7 @@ class JoinZoneGeometry:
"""
def __init__(
self,
target: Point,
home: Point,
ip: Point,
coalition: Coalition,
theater: ConflictTheater,
self, target: Point, home: Point, ip: Point, coalition: Coalition
) -> None:
# Normal join placement is based on the path from home to the IP. If no path is
# found it means that the target is on a direct path. In that case we instead
@ -82,14 +76,28 @@ class JoinZoneGeometry:
]
)
permissible_lines = ip_direction_limit_wedge.intersection(
permissible_zones = ip_direction_limit_wedge.difference(
self.excluded_zones
).difference(self.home_bubble)
if permissible_zones.is_empty:
permissible_zones = MultiPolygon([])
if not isinstance(permissible_zones, MultiPolygon):
permissible_zones = MultiPolygon([permissible_zones])
self.permissible_zones = permissible_zones
preferred_lines = ip_direction_limit_wedge.intersection(
self.excluded_zones.boundary
).difference(self.home_bubble)
if not isinstance(permissible_lines, MultiLineString):
permissible_lines = MultiLineString([permissible_lines])
self.permissible_lines = permissible_lines
if preferred_lines.is_empty:
preferred_lines = MultiLineString([])
if not isinstance(preferred_lines, MultiLineString):
preferred_lines = MultiLineString([preferred_lines])
self.preferred_lines = preferred_lines
def find_best_join_point(self) -> Point:
join, _ = shapely.ops.nearest_points(self.permissible_lines, self.home)
if self.preferred_lines.is_empty:
join, _ = shapely.ops.nearest_points(self.permissible_zones, self.ip)
else:
join, _ = shapely.ops.nearest_points(self.preferred_lines, self.home)
return Point(join.x, join.y)

View File

@ -975,7 +975,6 @@ class FlightPlanBuilder:
package_airfield.position,
ingress_point,
self.coalition,
self.theater,
).find_best_join_point()
# And the split point based on the best route from the IP. Since that's no

View File

@ -869,7 +869,8 @@ class JoinZonesJs(QObject):
targetBubbleChanged = Signal()
ipBubbleChanged = Signal()
excludedZonesChanged = Signal()
permissibleLinesChanged = Signal()
permissibleZonesChanged = Signal()
preferredLinesChanged = Signal()
def __init__(
self,
@ -877,14 +878,16 @@ class JoinZonesJs(QObject):
target_bubble: LeafletPoly,
ip_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_lines: list[list[LeafletLatLon]],
permissible_zones: list[LeafletPoly],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._target_bubble = target_bubble
self._ip_bubble = ip_bubble
self._excluded_zones = excluded_zones
self._permissible_lines = permissible_lines
self._permissible_zones = permissible_zones
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
@ -902,13 +905,17 @@ class JoinZonesJs(QObject):
def excludedZones(self) -> list[LeafletPoly]:
return self._excluded_zones
@Property(list, notify=permissibleLinesChanged)
def permissibleLines(self) -> list[list[LeafletLatLon]]:
return self._permissible_lines
@Property(list, notify=permissibleZonesChanged)
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
@classmethod
def empty(cls) -> JoinZonesJs:
return JoinZonesJs([], [], [], [], [])
return JoinZonesJs([], [], [], [], [], [])
@classmethod
def for_flight(cls, flight: Flight, game: Game) -> JoinZonesJs:
@ -919,15 +926,14 @@ class JoinZonesJs(QObject):
if flight.package.waypoints is None:
return JoinZonesJs.empty()
ip = flight.package.waypoints.ingress
geometry = JoinZoneGeometry(
target.position, home.position, ip, game.blue, game.theater
)
geometry = JoinZoneGeometry(target.position, home.position, ip, game.blue)
return JoinZonesJs(
shapely_poly_to_leaflet_points(geometry.home_bubble, game.theater),
shapely_poly_to_leaflet_points(geometry.target_bubble, game.theater),
shapely_poly_to_leaflet_points(geometry.ip_bubble, game.theater),
shapely_to_leaflet_polys(geometry.excluded_zones, game.theater),
shapely_lines_to_leaflet_points(geometry.permissible_lines, game.theater),
shapely_to_leaflet_polys(geometry.permissible_zones, game.theater),
shapely_lines_to_leaflet_points(geometry.preferred_lines, game.theater),
)
@ -937,7 +943,7 @@ class HoldZonesJs(QObject):
joinBubbleChanged = Signal()
excludedZonesChanged = Signal()
permissibleZonesChanged = Signal()
permissibleLinesChanged = Signal()
preferredLinesChanged = Signal()
def __init__(
self,
@ -946,7 +952,7 @@ class HoldZonesJs(QObject):
join_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_zones: list[LeafletPoly],
permissible_lines: list[list[LeafletLatLon]],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
@ -954,7 +960,7 @@ class HoldZonesJs(QObject):
self._join_bubble = join_bubble
self._excluded_zones = excluded_zones
self._permissible_zones = permissible_zones
self._permissible_lines = permissible_lines
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
@ -976,9 +982,9 @@ class HoldZonesJs(QObject):
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=permissibleLinesChanged)
def permissibleLines(self) -> list[list[LeafletLatLon]]:
return self._permissible_lines
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
@classmethod
def empty(cls) -> HoldZonesJs:
@ -1003,7 +1009,7 @@ class HoldZonesJs(QObject):
shapely_poly_to_leaflet_points(geometry.join_bubble, game.theater),
shapely_to_leaflet_polys(geometry.excluded_zones, game.theater),
shapely_to_leaflet_polys(geometry.permissible_zones, game.theater),
[], # shapely_to_leaflet_polys(geometry.permissible_lines, game.theater),
shapely_lines_to_leaflet_points(geometry.preferred_lines, game.theater),
)

View File

@ -1066,7 +1066,14 @@ function drawJoinZones() {
}).addTo(joinZones);
}
for (const line of game.joinZones.permissibleLines) {
for (const zone of game.joinZones.permissibleZones) {
L.polygon(zone, {
color: Colors.Green,
interactive: false,
}).addTo(joinZones);
}
for (const line of game.joinZones.preferredLines) {
L.polyline(line, {
color: Colors.Green,
interactive: false,
@ -1114,6 +1121,13 @@ function drawHoldZones() {
interactive: false,
}).addTo(holdZones);
}
for (const line of game.holdZones.preferredLines) {
L.polyline(line, {
color: Colors.Green,
interactive: false,
}).addTo(holdZones);
}
}
function drawInitialMap() {