mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add ability to convert landmap to/from miz.
This PR adds utility functions that import/export landmap files to .miz polygons. In addition to the unit test, this PR has been tested by writing the Caucuses & Syria landmaps to a .miz file, loading the generated .miz file back in and checking that the loaded landmap object is identical to the original files.
This commit is contained in:
parent
29ffb526f2
commit
e50ee976ed
@ -4,10 +4,17 @@ from functools import cached_property
|
|||||||
from typing import Optional, Tuple, Union
|
from typing import Optional, Tuple, Union
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from shapely import geometry
|
from shapely import geometry
|
||||||
from shapely.geometry import MultiPolygon, Polygon
|
from shapely.geometry import MultiPolygon, Polygon
|
||||||
|
|
||||||
|
from dcs.drawing.drawing import LineStyle, Rgba
|
||||||
|
from dcs.drawing.polygon import FreeFormPolygon
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.mission import Mission
|
||||||
|
from dcs.terrain.terrain import Terrain
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Landmap:
|
class Landmap:
|
||||||
@ -39,3 +46,94 @@ def load_landmap(filename: Path) -> Optional[Landmap]:
|
|||||||
|
|
||||||
def poly_contains(x: float, y: float, poly: Union[MultiPolygon, Polygon]) -> bool:
|
def poly_contains(x: float, y: float, poly: Union[MultiPolygon, Polygon]) -> bool:
|
||||||
return poly.contains(geometry.Point(x, y))
|
return poly.contains(geometry.Point(x, y))
|
||||||
|
|
||||||
|
|
||||||
|
def to_miz(landmap: Landmap, terrain: Terrain, mission_filename: str) -> None:
|
||||||
|
"""
|
||||||
|
Writes landmap to .miz file so that zones can be visualized and edited in the
|
||||||
|
mission editor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def multi_polygon_to_miz(
|
||||||
|
mission: Mission,
|
||||||
|
terrain: Terrain,
|
||||||
|
multi_polygon: MultiPolygon,
|
||||||
|
color: Rgba,
|
||||||
|
prefix: str,
|
||||||
|
layer_index: int = 4,
|
||||||
|
layer_name: str = "Author",
|
||||||
|
) -> None:
|
||||||
|
reference_position = Point(0, 0, terrain)
|
||||||
|
for i in range(len(multi_polygon.geoms)):
|
||||||
|
polygon = multi_polygon.geoms[i]
|
||||||
|
if len(polygon.interiors) > 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Polygon hole found when trying to export {prefix} {i}. to_miz() does not support landmap zones with holes."
|
||||||
|
)
|
||||||
|
coordinates = polygon.exterior.xy
|
||||||
|
points = []
|
||||||
|
for j in range(len(coordinates[0])):
|
||||||
|
points.append(Point(coordinates[0][j], coordinates[1][j], terrain))
|
||||||
|
polygon_drawing = FreeFormPolygon(
|
||||||
|
visible=True,
|
||||||
|
position=reference_position,
|
||||||
|
name=f"{prefix}-{i}",
|
||||||
|
color=color,
|
||||||
|
layer_name=layer_name,
|
||||||
|
fill=color,
|
||||||
|
line_thickness=10,
|
||||||
|
line_style=LineStyle.Solid,
|
||||||
|
points=points,
|
||||||
|
)
|
||||||
|
mission.drawings.layers[layer_index].objects.append(polygon_drawing)
|
||||||
|
|
||||||
|
mission = Mission(terrain=terrain)
|
||||||
|
multi_polygon_to_miz(
|
||||||
|
mission, terrain, landmap.exclusion_zones, Rgba(255, 0, 0, 128), "Exclusion"
|
||||||
|
)
|
||||||
|
multi_polygon_to_miz(
|
||||||
|
mission, terrain, landmap.sea_zones, Rgba(0, 0, 255, 128), "Sea"
|
||||||
|
)
|
||||||
|
multi_polygon_to_miz(
|
||||||
|
mission, terrain, landmap.inclusion_zones, Rgba(0, 255, 0, 128), "Inclusion"
|
||||||
|
)
|
||||||
|
mission.save(mission_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def from_miz(mission_filename: str, layer_index: int = 4) -> Landmap:
|
||||||
|
"""
|
||||||
|
Generate Landmap object from Free Form Polygons drawn in a .miz file.
|
||||||
|
Landmap.inclusion_zones are expected to be named Inclusion-<suffix>
|
||||||
|
Landmap.exclusion_zones are expected to be named Exclusion-<suffix>
|
||||||
|
Landmap.sea_zones are expected to be named Sea-<suffix>
|
||||||
|
"""
|
||||||
|
mission = Mission()
|
||||||
|
mission.load_file(mission_filename)
|
||||||
|
polygons: dict[str, List[Polygon]] = {"Inclusion": [], "Exclusion": [], "Sea": []}
|
||||||
|
for draw_object in mission.drawings.layers[layer_index].objects:
|
||||||
|
if type(draw_object) != FreeFormPolygon:
|
||||||
|
logging.debug(
|
||||||
|
f"Object {draw_object.name} is not a FreeFormPolygon, ignoring"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
name_split = draw_object.name.split(
|
||||||
|
"-"
|
||||||
|
) # names are in the format <Inclusion|Exclusion|Sea>-<suffix>
|
||||||
|
zone_type = name_split[0]
|
||||||
|
if len(name_split) != 2 or zone_type not in ("Exclusion", "Sea", "Inclusion"):
|
||||||
|
logging.debug(
|
||||||
|
f"Object name {draw_object.name} does not conform to expected format <Exclusion|Sea|Inclusion>-<suffix>, ignoring"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
polygon_points = []
|
||||||
|
for point in draw_object.points:
|
||||||
|
polygon_points.append(
|
||||||
|
(point.x + draw_object.position.x, point.y + draw_object.position.y)
|
||||||
|
)
|
||||||
|
polygons[zone_type].append(Polygon(polygon_points))
|
||||||
|
landmap = Landmap(
|
||||||
|
inclusion_zones=MultiPolygon(polygons["Inclusion"]),
|
||||||
|
exclusion_zones=MultiPolygon(polygons["Exclusion"]),
|
||||||
|
sea_zones=MultiPolygon(polygons["Sea"]),
|
||||||
|
)
|
||||||
|
return landmap
|
||||||
|
|||||||
32
tests/theater/test_landmap.py
Normal file
32
tests/theater/test_landmap.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from shapely.geometry import MultiPolygon, Polygon
|
||||||
|
|
||||||
|
from dcs.terrain.caucasus.caucasus import Caucasus
|
||||||
|
from game.theater import landmap
|
||||||
|
|
||||||
|
|
||||||
|
def test_miz() -> None:
|
||||||
|
"""
|
||||||
|
Test miz generation and loading
|
||||||
|
"""
|
||||||
|
test_map = landmap.Landmap(
|
||||||
|
inclusion_zones=MultiPolygon([Polygon([(0, 0), (0, 1), (1, 0)])]),
|
||||||
|
exclusion_zones=MultiPolygon([Polygon([(1, 1), (0, 1), (1, 0)])]),
|
||||||
|
sea_zones=MultiPolygon([Polygon([(0, 0), (0, 2), (1, 0)])]),
|
||||||
|
)
|
||||||
|
test_filename = "test.miz"
|
||||||
|
landmap.to_miz(test_map, Caucasus(), test_filename)
|
||||||
|
assert os.path.isfile("test.miz")
|
||||||
|
loaded_map = landmap.from_miz("test.miz")
|
||||||
|
assert test_map.inclusion_zones.equals_exact(
|
||||||
|
loaded_map.inclusion_zones, tolerance=1e-6
|
||||||
|
)
|
||||||
|
assert test_map.sea_zones.equals_exact(loaded_map.sea_zones, tolerance=1e-6)
|
||||||
|
assert test_map.exclusion_zones.equals_exact(
|
||||||
|
loaded_map.exclusion_zones, tolerance=1e-6
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.isfile(test_filename):
|
||||||
|
os.remove(test_filename)
|
||||||
Loading…
x
Reference in New Issue
Block a user