From 2dba0f07adc367bef5099d52b9891156221987e8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 7 Sep 2022 14:12:32 -0700 Subject: [PATCH] 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. --- client/src/api/_liberationApi.ts | 66 +++++++++---------- .../terrainzones/TerrainZonesLayers.tsx | 2 +- .../src/components/threatzones/ThreatZone.tsx | 2 +- game/server/debuggeometries/models.py | 6 +- game/server/leaflet.py | 30 ++++++--- 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/client/src/api/_liberationApi.ts b/client/src/api/_liberationApi.ts index 4aba5980..a1c35b43 100644 --- a/client/src/api/_liberationApi.ts +++ b/client/src/api/_liberationApi.ts @@ -267,7 +267,7 @@ export type GetFlightByIdApiArg = { withWaypoints?: boolean; }; export type GetCommitBoundaryForFlightApiResponse = - /** status 200 Successful Response */ LatLng[]; + /** status 200 Successful Response */ LatLng[][]; export type GetCommitBoundaryForFlightApiArg = { flightId: string; }; @@ -297,27 +297,27 @@ export type GetNavmeshApiArg = { forPlayer: boolean; }; export type OpenNewFrontLinePackageDialogApiResponse = - /** status 204 Successful Response */ undefined; + /** status 200 Successful Response */ any; export type OpenNewFrontLinePackageDialogApiArg = { frontLineId: string; }; export type OpenNewTgoPackageDialogApiResponse = - /** status 204 Successful Response */ undefined; + /** status 200 Successful Response */ any; export type OpenNewTgoPackageDialogApiArg = { tgoId: string; }; export type OpenTgoInfoDialogApiResponse = - /** status 204 Successful Response */ undefined; + /** status 200 Successful Response */ any; export type OpenTgoInfoDialogApiArg = { tgoId: string; }; export type OpenNewControlPointPackageDialogApiResponse = - /** status 204 Successful Response */ undefined; + /** status 200 Successful Response */ any; export type OpenNewControlPointPackageDialogApiArg = { cpId: string; }; export type OpenControlPointInfoDialogApiResponse = - /** status 204 Successful Response */ undefined; + /** status 200 Successful Response */ any; export type OpenControlPointInfoDialogApiArg = { cpId: string; }; @@ -364,7 +364,7 @@ export type ControlPoint = { sidc: string; }; export type ValidationError = { - loc: string[]; + loc: (string | number)[]; msg: string; type: string; }; @@ -372,25 +372,25 @@ export type HttpValidationError = { detail?: ValidationError[]; }; export type HoldZones = { - homeBubble: LatLng[]; - targetBubble: LatLng[]; - joinBubble: LatLng[]; - excludedZones: LatLng[][]; - permissibleZones: LatLng[][]; + homeBubble: LatLng[][]; + targetBubble: LatLng[][]; + joinBubble: LatLng[][]; + excludedZones: LatLng[][][]; + permissibleZones: LatLng[][][]; preferredLines: LatLng[][]; }; export type IpZones = { - homeBubble: LatLng[]; - ipBubble: LatLng[]; - permissibleZone: LatLng[]; - safeZones: LatLng[][]; + homeBubble: LatLng[][]; + ipBubble: LatLng[][]; + permissibleZone: LatLng[][]; + safeZones: LatLng[][][]; }; export type JoinZones = { - homeBubble: LatLng[]; - targetBubble: LatLng[]; - ipBubble: LatLng[]; - excludedZones: LatLng[][]; - permissibleZones: LatLng[][]; + homeBubble: LatLng[][]; + targetBubble: LatLng[][]; + ipBubble: LatLng[][]; + excludedZones: LatLng[][][]; + permissibleZones: LatLng[][][]; preferredLines: LatLng[][]; }; export type Waypoint = { @@ -449,17 +449,17 @@ export type IadsNetwork = { connections: IadsConnection[]; }; export type ThreatZones = { - full: LatLng[][]; - aircraft: LatLng[][]; - air_defenses: LatLng[][]; - radar_sams: LatLng[][]; + full: LatLng[][][]; + aircraft: LatLng[][][]; + air_defenses: LatLng[][][]; + radar_sams: LatLng[][][]; }; export type ThreatZoneContainer = { blue: ThreatZones; red: ThreatZones; }; export type NavMeshPoly = { - poly: LatLng[]; + poly: LatLng[][]; threatened: boolean; }; export type NavMesh = { @@ -469,6 +469,10 @@ export type NavMeshes = { blue: NavMesh; red: NavMesh; }; +export type UnculledZone = { + position: LatLng; + radius: number; +}; export type Game = { control_points: ControlPoint[]; tgos: Tgo[]; @@ -482,13 +486,9 @@ export type Game = { unculled_zones: UnculledZone[]; }; export type MapZones = { - inclusion: LatLng[][]; - exclusion: LatLng[][]; - sea: LatLng[][]; -}; -export type UnculledZone = { - position: LatLng; - radius: number; + inclusion: LatLng[][][]; + exclusion: LatLng[][][]; + sea: LatLng[][][]; }; export const { useListControlPointsQuery, diff --git a/client/src/components/terrainzones/TerrainZonesLayers.tsx b/client/src/components/terrainzones/TerrainZonesLayers.tsx index 34147c90..5d5bb2c3 100644 --- a/client/src/components/terrainzones/TerrainZonesLayers.tsx +++ b/client/src/components/terrainzones/TerrainZonesLayers.tsx @@ -3,7 +3,7 @@ import { LatLngLiteral } from "leaflet"; import { LayerGroup, LayersControl, Polygon } from "react-leaflet"; interface TerrainZoneLayerProps { - zones: LatLngLiteral[][]; + zones: LatLngLiteral[][][]; color: string; fillColor: string; } diff --git a/client/src/components/threatzones/ThreatZone.tsx b/client/src/components/threatzones/ThreatZone.tsx index ea14b6d2..506d3602 100644 --- a/client/src/components/threatzones/ThreatZone.tsx +++ b/client/src/components/threatzones/ThreatZone.tsx @@ -2,7 +2,7 @@ import { LatLng } from "../../api/liberationApi"; import { Polygon } from "react-leaflet"; interface ThreatZoneProps { - poly: LatLng[]; + poly: LatLng[][]; blue: boolean; } diff --git a/game/server/debuggeometries/models.py b/game/server/debuggeometries/models.py index 3fd3348e..8dd271cf 100644 --- a/game/server/debuggeometries/models.py +++ b/game/server/debuggeometries/models.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field from game import Game from game.ato import Flight from game.flightplan import HoldZoneGeometry, IpZoneGeometry, JoinZoneGeometry -from ..leaflet import LeafletPoly, ShapelyUtil +from ..leaflet import LeafletLine, LeafletPoly, ShapelyUtil class HoldZonesJs(BaseModel): @@ -14,7 +14,7 @@ class HoldZonesJs(BaseModel): join_bubble: LeafletPoly = Field(alias="joinBubble") excluded_zones: list[LeafletPoly] = Field(alias="excludedZones") permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones") - preferred_lines: list[LeafletPoly] = Field(alias="preferredLines") + preferred_lines: list[LeafletLine] = Field(alias="preferredLines") class Config: title = "HoldZones" @@ -93,7 +93,7 @@ class JoinZonesJs(BaseModel): ip_bubble: LeafletPoly = Field(alias="ipBubble") excluded_zones: list[LeafletPoly] = Field(alias="excludedZones") permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones") - preferred_lines: list[LeafletPoly] = Field(alias="preferredLines") + preferred_lines: list[LeafletLine] = Field(alias="preferredLines") class Config: title = "JoinZones" diff --git a/game/server/leaflet.py b/game/server/leaflet.py index 52cee137..6e5ed816 100644 --- a/game/server/leaflet.py +++ b/game/server/leaflet.py @@ -20,7 +20,15 @@ class LeafletPoint(BaseModel): 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: @@ -32,10 +40,11 @@ class ShapelyUtil: def poly_to_leaflet(cls, poly: Polygon, theater: ConflictTheater) -> LeafletPoly: if poly.is_empty: return [] - return [ - cls.latlng_to_leaflet(Point(x, y, theater.terrain).latlng()) - for x, y in poly.exterior.coords - ] + try: + lines = poly.boundary.geoms + except AttributeError: + lines = [poly.boundary] + return cls.lines_to_leaflet(MultiLineString(lines), theater) @classmethod def polys_to_leaflet( @@ -47,14 +56,17 @@ class ShapelyUtil: polys = [poly] return [cls.poly_to_leaflet(poly, theater) for poly in polys] - @staticmethod - def line_to_leaflet(line: LineString, theater: ConflictTheater) -> list[LatLng]: - return [Point(x, y, theater.terrain).latlng() for x, y in line.coords] + @classmethod + def line_to_leaflet(cls, line: LineString, theater: ConflictTheater) -> LeafletLine: + return [ + cls.latlng_to_leaflet(Point(x, y, theater.terrain).latlng()) + for x, y in line.coords + ] @classmethod def lines_to_leaflet( cls, line_string: MultiLineString | LineString, theater: ConflictTheater - ) -> list[list[LatLng]]: + ) -> list[LeafletLine]: if isinstance(line_string, MultiLineString): lines = line_string.geoms else: