Use shapely projection instead of brute force.

Converts the landmap to use MultiPolygon instead of a collection of
polygons, since Shapely has explicit support for this.

Because we've done that, we can use a single projection from a line
instead of brute forcing the extent of the front line.

This makes turn processing ~66% faster (3 seconds to 1.8).

There are probably other places this should be used.
This commit is contained in:
Dan Albert 2020-12-24 00:54:57 -08:00
parent 2a65916f7c
commit b9138acbc8
11 changed files with 42 additions and 23 deletions

View File

@ -447,11 +447,11 @@ class ConflictTheater:
if self.is_on_land(point):
return False
for exclusion_zone in self.landmap[1]:
for exclusion_zone in self.landmap.exclusion_zones:
if poly_contains(point.x, point.y, exclusion_zone):
return False
for sea in self.landmap[2]:
for sea in self.landmap.sea_zones:
if poly_contains(point.x, point.y, sea):
return True
@ -462,14 +462,13 @@ class ConflictTheater:
return True
is_point_included = False
for inclusion_zone in self.landmap[0]:
if poly_contains(point.x, point.y, inclusion_zone):
is_point_included = True
if poly_contains(point.x, point.y, self.landmap.inclusion_zones):
is_point_included = True
if not is_point_included:
return False
for exclusion_zone in self.landmap[1]:
for exclusion_zone in self.landmap.exclusion_zones:
if poly_contains(point.x, point.y, exclusion_zone):
return False
@ -484,7 +483,7 @@ class ConflictTheater:
nearest_points = []
if not self.landmap:
raise RuntimeError("Landmap not initialized")
for inclusion_zone in self.landmap[0]:
for inclusion_zone in self.landmap.inclusion_zones:
nearest_pair = ops.nearest_points(point, inclusion_zone)
nearest_points.append(nearest_pair[1])
min_distance = point.distance(nearest_points[0]) # type: geometry.Point

View File

@ -1,11 +1,17 @@
from dataclasses import dataclass
import pickle
from typing import Collection, Optional, Tuple
from typing import Optional, Tuple, Union
import logging
from shapely import geometry
from shapely.geometry import MultiPolygon, Polygon
Zone = Collection[Tuple[float, float]]
Landmap = Tuple[Collection[geometry.Polygon], Collection[geometry.Polygon], Collection[geometry.Polygon]]
@dataclass(frozen=True)
class Landmap:
inclusion_zones: MultiPolygon
exclusion_zones: MultiPolygon
sea_zones: MultiPolygon
def load_landmap(filename: str) -> Optional[Landmap]:
@ -17,7 +23,7 @@ def load_landmap(filename: str) -> Optional[Landmap]:
return None
def poly_contains(x, y, poly:geometry.Polygon):
def poly_contains(x, y, poly: Union[MultiPolygon, Polygon]):
return poly.contains(geometry.Point(x, y))

View File

@ -1,9 +1,9 @@
import logging
import random
from typing import Tuple, Optional
from dcs.country import Country
from dcs.mapping import Point
from shapely.geometry import LineString, Point as ShapelyPoint
from game.theater.conflicttheater import ConflictTheater, FrontLine
from game.theater.controlpoint import ControlPoint
@ -56,7 +56,7 @@ class Conflict:
"""
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
left_heading = heading_sum(heading, -90)
right_heading = heading_sum(heading, 90)
right_heading = heading_sum(heading, 90)
left_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater)
right_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
distance = int(left_position.distance_to_point(right_position))
@ -83,12 +83,21 @@ class Conflict:
@classmethod
def extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
"""Finds the first intersection with an exclusion zone in one heading from an initial point up to max_distance"""
pos = initial
for distance in range(0, int(max_distance), 100):
pos = initial.point_from_heading(heading, distance)
if not theater.is_on_land(pos):
return initial.point_from_heading(heading, distance - 100)
return pos
extended = initial.point_from_heading(heading, max_distance)
p0 = ShapelyPoint(initial.x, initial.y)
p1 = ShapelyPoint(extended.x, extended.y)
line = LineString([p0, p1])
intersection = line.intersection(
theater.landmap.inclusion_zones.boundary)
if intersection.is_empty:
# Max extent does not intersect with the boundary of the inclusion
# zone, so the full front line is usable. This does assume that the
# front line was centered on a valid location.
return extended
# Otherwise extend the front line only up to the intersection.
return initial.point_from_heading(heading, p0.distance(intersection))
@classmethod
def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater, coerce=True) -> Optional[Point]:

View File

@ -945,7 +945,7 @@ class QLiberationMap(QGraphicsView):
# Polygon display mode
if self.game.theater.landmap is not None:
for sea_zone in self.game.theater.landmap[2]:
for sea_zone in self.game.theater.landmap.sea_zones:
print(sea_zone)
poly = QPolygonF([QPointF(*self._transform_point(Point(point[0], point[1]))) for point in sea_zone.exterior.coords])
if self.reference_point_setup_mode:
@ -954,14 +954,14 @@ class QLiberationMap(QGraphicsView):
color = "sea_blue"
scene.addPolygon(poly, CONST.COLORS[color], CONST.COLORS[color])
for inclusion_zone in self.game.theater.landmap[0]:
for inclusion_zone in self.game.theater.landmap.inclusion_zones:
poly = QPolygonF([QPointF(*self._transform_point(Point(point[0], point[1]))) for point in inclusion_zone.exterior.coords])
if self.reference_point_setup_mode:
scene.addPolygon(poly, CONST.COLORS["grey_transparent"], CONST.COLORS["dark_grey_transparent"])
else:
scene.addPolygon(poly, CONST.COLORS["grey"], CONST.COLORS["dark_grey"])
for exclusion_zone in self.game.theater.landmap[1]:
for exclusion_zone in self.game.theater.landmap.exclusion_zones:
poly = QPolygonF([QPointF(*self._transform_point(Point(point[0], point[1]))) for point in exclusion_zone.exterior.coords])
if self.reference_point_setup_mode:
scene.addPolygon(poly, CONST.COLORS["grey_transparent"], CONST.COLORS["dark_dark_grey_transparent"])

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,6 +2,9 @@ import pickle
from dcs.mission import Mission
from shapely import geometry
from shapely.geometry import MultiPolygon
from game.theater.landmap import Landmap
for terrain in ["cau", "nev", "syria", "channel", "normandy", "gulf"]:
print("Terrain " + terrain)
@ -29,4 +32,6 @@ for terrain in ["cau", "nev", "syria", "channel", "normandy", "gulf"]:
with open("../{}landmap.p".format(terrain), "wb") as f:
print(len(inclusion_zones), len(exclusion_zones), len(seas_zones))
pickle.dump((inclusion_zones, exclusion_zones, seas_zones), f)
pickle.dump(Landmap(MultiPolygon(inclusion_zones),
MultiPolygon(exclusion_zones),
MultiPolygon(seas_zones)), f)