Support polygons with holes in the API.

We don't have any of these yet because our landmaps suck, but we'll need
holes in the sea zones to mask islands correctly.
This commit is contained in:
Dan Albert 2022-09-07 14:12:32 -07:00
parent 10d0dd861c
commit 080782e011
5 changed files with 59 additions and 47 deletions

View File

@ -267,7 +267,7 @@ export type GetFlightByIdApiArg = {
withWaypoints?: boolean; withWaypoints?: boolean;
}; };
export type GetCommitBoundaryForFlightApiResponse = export type GetCommitBoundaryForFlightApiResponse =
/** status 200 Successful Response */ LatLng[]; /** status 200 Successful Response */ LatLng[][];
export type GetCommitBoundaryForFlightApiArg = { export type GetCommitBoundaryForFlightApiArg = {
flightId: string; flightId: string;
}; };
@ -297,27 +297,27 @@ export type GetNavmeshApiArg = {
forPlayer: boolean; forPlayer: boolean;
}; };
export type OpenNewFrontLinePackageDialogApiResponse = export type OpenNewFrontLinePackageDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 200 Successful Response */ any;
export type OpenNewFrontLinePackageDialogApiArg = { export type OpenNewFrontLinePackageDialogApiArg = {
frontLineId: string; frontLineId: string;
}; };
export type OpenNewTgoPackageDialogApiResponse = export type OpenNewTgoPackageDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 200 Successful Response */ any;
export type OpenNewTgoPackageDialogApiArg = { export type OpenNewTgoPackageDialogApiArg = {
tgoId: string; tgoId: string;
}; };
export type OpenTgoInfoDialogApiResponse = export type OpenTgoInfoDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 200 Successful Response */ any;
export type OpenTgoInfoDialogApiArg = { export type OpenTgoInfoDialogApiArg = {
tgoId: string; tgoId: string;
}; };
export type OpenNewControlPointPackageDialogApiResponse = export type OpenNewControlPointPackageDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 200 Successful Response */ any;
export type OpenNewControlPointPackageDialogApiArg = { export type OpenNewControlPointPackageDialogApiArg = {
cpId: string; cpId: string;
}; };
export type OpenControlPointInfoDialogApiResponse = export type OpenControlPointInfoDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 200 Successful Response */ any;
export type OpenControlPointInfoDialogApiArg = { export type OpenControlPointInfoDialogApiArg = {
cpId: string; cpId: string;
}; };
@ -364,7 +364,7 @@ export type ControlPoint = {
sidc: string; sidc: string;
}; };
export type ValidationError = { export type ValidationError = {
loc: string[]; loc: (string | number)[];
msg: string; msg: string;
type: string; type: string;
}; };
@ -372,25 +372,25 @@ export type HttpValidationError = {
detail?: ValidationError[]; detail?: ValidationError[];
}; };
export type HoldZones = { export type HoldZones = {
homeBubble: LatLng[]; homeBubble: LatLng[][];
targetBubble: LatLng[]; targetBubble: LatLng[][];
joinBubble: LatLng[]; joinBubble: LatLng[][];
excludedZones: LatLng[][]; excludedZones: LatLng[][][];
permissibleZones: LatLng[][]; permissibleZones: LatLng[][][];
preferredLines: LatLng[][]; preferredLines: LatLng[][];
}; };
export type IpZones = { export type IpZones = {
homeBubble: LatLng[]; homeBubble: LatLng[][];
ipBubble: LatLng[]; ipBubble: LatLng[][];
permissibleZone: LatLng[]; permissibleZone: LatLng[][];
safeZones: LatLng[][]; safeZones: LatLng[][][];
}; };
export type JoinZones = { export type JoinZones = {
homeBubble: LatLng[]; homeBubble: LatLng[][];
targetBubble: LatLng[]; targetBubble: LatLng[][];
ipBubble: LatLng[]; ipBubble: LatLng[][];
excludedZones: LatLng[][]; excludedZones: LatLng[][][];
permissibleZones: LatLng[][]; permissibleZones: LatLng[][][];
preferredLines: LatLng[][]; preferredLines: LatLng[][];
}; };
export type Waypoint = { export type Waypoint = {
@ -449,17 +449,17 @@ export type IadsNetwork = {
connections: IadsConnection[]; connections: IadsConnection[];
}; };
export type ThreatZones = { export type ThreatZones = {
full: LatLng[][]; full: LatLng[][][];
aircraft: LatLng[][]; aircraft: LatLng[][][];
air_defenses: LatLng[][]; air_defenses: LatLng[][][];
radar_sams: LatLng[][]; radar_sams: LatLng[][][];
}; };
export type ThreatZoneContainer = { export type ThreatZoneContainer = {
blue: ThreatZones; blue: ThreatZones;
red: ThreatZones; red: ThreatZones;
}; };
export type NavMeshPoly = { export type NavMeshPoly = {
poly: LatLng[]; poly: LatLng[][];
threatened: boolean; threatened: boolean;
}; };
export type NavMesh = { export type NavMesh = {
@ -469,6 +469,10 @@ export type NavMeshes = {
blue: NavMesh; blue: NavMesh;
red: NavMesh; red: NavMesh;
}; };
export type UnculledZone = {
position: LatLng;
radius: number;
};
export type Game = { export type Game = {
control_points: ControlPoint[]; control_points: ControlPoint[];
tgos: Tgo[]; tgos: Tgo[];
@ -482,13 +486,9 @@ export type Game = {
unculled_zones: UnculledZone[]; unculled_zones: UnculledZone[];
}; };
export type MapZones = { export type MapZones = {
inclusion: LatLng[][]; inclusion: LatLng[][][];
exclusion: LatLng[][]; exclusion: LatLng[][][];
sea: LatLng[][]; sea: LatLng[][][];
};
export type UnculledZone = {
position: LatLng;
radius: number;
}; };
export const { export const {
useListControlPointsQuery, useListControlPointsQuery,

View File

@ -3,7 +3,7 @@ import { LatLngLiteral } from "leaflet";
import { LayerGroup, LayersControl, Polygon } from "react-leaflet"; import { LayerGroup, LayersControl, Polygon } from "react-leaflet";
interface TerrainZoneLayerProps { interface TerrainZoneLayerProps {
zones: LatLngLiteral[][]; zones: LatLngLiteral[][][];
color: string; color: string;
fillColor: string; fillColor: string;
} }

View File

@ -2,7 +2,7 @@ import { LatLng } from "../../api/liberationApi";
import { Polygon } from "react-leaflet"; import { Polygon } from "react-leaflet";
interface ThreatZoneProps { interface ThreatZoneProps {
poly: LatLng[]; poly: LatLng[][];
blue: boolean; blue: boolean;
} }

View File

@ -5,7 +5,7 @@ from pydantic import BaseModel, Field
from game import Game from game import Game
from game.ato import Flight from game.ato import Flight
from game.flightplan import HoldZoneGeometry, IpZoneGeometry, JoinZoneGeometry from game.flightplan import HoldZoneGeometry, IpZoneGeometry, JoinZoneGeometry
from ..leaflet import LeafletPoly, ShapelyUtil from ..leaflet import LeafletLine, LeafletPoly, ShapelyUtil
class HoldZonesJs(BaseModel): class HoldZonesJs(BaseModel):
@ -14,7 +14,7 @@ class HoldZonesJs(BaseModel):
join_bubble: LeafletPoly = Field(alias="joinBubble") join_bubble: LeafletPoly = Field(alias="joinBubble")
excluded_zones: list[LeafletPoly] = Field(alias="excludedZones") excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones") permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines") preferred_lines: list[LeafletLine] = Field(alias="preferredLines")
class Config: class Config:
title = "HoldZones" title = "HoldZones"
@ -93,7 +93,7 @@ class JoinZonesJs(BaseModel):
ip_bubble: LeafletPoly = Field(alias="ipBubble") ip_bubble: LeafletPoly = Field(alias="ipBubble")
excluded_zones: list[LeafletPoly] = Field(alias="excludedZones") excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones") permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines") preferred_lines: list[LeafletLine] = Field(alias="preferredLines")
class Config: class Config:
title = "JoinZones" title = "JoinZones"

View File

@ -20,7 +20,15 @@ class LeafletPoint(BaseModel):
title = "LatLng" title = "LatLng"
LeafletPoly = list[LeafletPoint] LeafletLine = list[LeafletPoint]
# Leaflet allows either a single array of latlng to define the boundary, or an array of
# arrays of latlngs, with the first array being the boundary and the rest defining
# holes. To avoid the UI needing to test for which type of polygon we send, we always
# use the array of arrays type, even for geometries without holes.
# https://leafletjs.com/reference.html#polygon
LeafletPoly = list[LeafletLine]
class ShapelyUtil: class ShapelyUtil:
@ -32,10 +40,11 @@ class ShapelyUtil:
def poly_to_leaflet(cls, poly: Polygon, theater: ConflictTheater) -> LeafletPoly: def poly_to_leaflet(cls, poly: Polygon, theater: ConflictTheater) -> LeafletPoly:
if poly.is_empty: if poly.is_empty:
return [] return []
return [ try:
cls.latlng_to_leaflet(Point(x, y, theater.terrain).latlng()) lines = poly.boundary.geoms
for x, y in poly.exterior.coords except AttributeError:
] lines = [poly.boundary]
return cls.lines_to_leaflet(MultiLineString(lines), theater)
@classmethod @classmethod
def polys_to_leaflet( def polys_to_leaflet(
@ -47,14 +56,17 @@ class ShapelyUtil:
polys = [poly] polys = [poly]
return [cls.poly_to_leaflet(poly, theater) for poly in polys] return [cls.poly_to_leaflet(poly, theater) for poly in polys]
@staticmethod @classmethod
def line_to_leaflet(line: LineString, theater: ConflictTheater) -> list[LatLng]: def line_to_leaflet(cls, line: LineString, theater: ConflictTheater) -> LeafletLine:
return [Point(x, y, theater.terrain).latlng() for x, y in line.coords] return [
cls.latlng_to_leaflet(Point(x, y, theater.terrain).latlng())
for x, y in line.coords
]
@classmethod @classmethod
def lines_to_leaflet( def lines_to_leaflet(
cls, line_string: MultiLineString | LineString, theater: ConflictTheater cls, line_string: MultiLineString | LineString, theater: ConflictTheater
) -> list[list[LatLng]]: ) -> list[LeafletLine]:
if isinstance(line_string, MultiLineString): if isinstance(line_string, MultiLineString):
lines = line_string.geoms lines = line_string.geoms
else: else: