diff --git a/mapbuilder/data/kml.py b/mapbuilder/data/kml.py index f65309e..4b4e2fb 100644 --- a/mapbuilder/data/kml.py +++ b/mapbuilder/data/kml.py @@ -55,6 +55,11 @@ def parse_recursively(self, root, result): ) elif "Point" in placemark: geom = Point(self.parse_pos_list(placemark["Point"]["coordinates"])) + elif "MultiGeometry" in placemark: + if len(placemark["MultiGeometry"]) != 1: + raise ValueError(f"Placemark '{placemark["name"]}': in MultiGeometry only one type of Geometry is allowed") + + geom = self.parse_multigeometry(placemark["MultiGeometry"]) else: msg = f"Placemark {placemark} unknown" raise ValueError(msg) @@ -64,6 +69,28 @@ def parse_recursively(self, root, result): result[name] = [geom] else: result[name].append(geom) + + def parse_multigeometry(self, root) -> list: + geom = [] + + if "LineString" in root: + for linestring in ensure_list(root["LineString"]): + geom.append(LineString(KMLParser.parse_pos_list(linestring["coordinates"]))) + elif "Polygon" in root: + for polygon in ensure_list(root["Polygon"]): + geom.append(LinearRing( + KMLParser.parse_pos_list( + polygon["outerBoundaryIs"]["LinearRing"]["coordinates"], + ) + )) + elif "Point" in root: + for point in ensure_list(root["Point"]): + geom.append(Point(KMLParser.parse_pos_list(point["coordinates"]))) + else: + msg = f"Geometry {root} unknown" + raise ValueError(msg) + + return geom def ensure_list(thing): diff --git a/mapbuilder/handlers/jinja.py b/mapbuilder/handlers/jinja.py index 5a67b90..d5513e7 100644 --- a/mapbuilder/handlers/jinja.py +++ b/mapbuilder/handlers/jinja.py @@ -6,7 +6,7 @@ import shapely.ops from jinja2 import Environment, FileSystemLoader from more_itertools import unique_everseen -from shapely import Geometry, Polygon +from shapely import MultiLineString, Geometry, Polygon from mapbuilder.data.aixm2 import AIXMFeature from mapbuilder.utils.ad import render_cl, render_runways @@ -51,6 +51,11 @@ def handle(self, item: Path) -> str: to_text=to_text, to_text_buffer=to_text_buffer, to_symbol=to_symbol, + to_multipoly=to_multipoly, + to_multiline=to_multiline, + to_multicoordline=to_multicoordline, + to_multisymbol=to_multisymbol, + to_multitext=to_multitext, ) return jinja_env.get_template(item.name).render() @@ -115,6 +120,20 @@ def to_text(geometry, label: str): labeltext, _, _ = label.partition("#") return f"TEXT:{coord2es(point.coords[0])}:{labeltext}" +def to_multitext(geometries, label: str): + lines = [] + labeltext, _, _ = label.partition("#") + + for geometry in geometries: + for point in geometry: + if point is None: + lines.append("") + continue + + lines.append(f"TEXT:{coord2es(point.coords[0])}:{labeltext}") + + return "\n".join(lines) + def to_symbol(geometry, symbol): point = geometry[0] if isinstance(geometry, list) else geometry @@ -124,6 +143,19 @@ def to_symbol(geometry, symbol): return f"SYMBOL:{symbol}:{coord2es(point.coords[0])}" +def to_multisymbol(geometries, symbol): + lines = [] + + for geometry in geometries: + for point in geometry: + if point is None: + lines.append("") + continue + + lines.append(f"SYMBOL:{symbol}:{coord2es(point.coords[0])}") + + return "\n".join(lines) + def _get_geoms(thing): """Extracts the geometries from either an AIXMFeature or geometry object""" @@ -157,6 +189,20 @@ def to_line(geometries, designator: str): return "\n".join(lines) +def to_multiline(geometries, designator: str): + lines = [f"// {designator}"] if designator else [] + + for geometry in geometries: + if isinstance(geometry, MultiLineString): + _render_linestring(lines, _get_geoms(geometry)) + continue + + for linestring in geometry: + _render_linestring(lines, _get_geoms(linestring)) + + return "\n".join(lines) + + def to_coordline(geometries, designator: str): lines = [f"// {designator}"] if designator else [] @@ -166,6 +212,23 @@ def to_coordline(geometries, designator: str): return "\n".join(lines) +def to_multicoordline(geometries, designator: str): + lines = [f"// {designator}"] if designator else [] + + for geometry in geometries: + if isinstance(geometry, MultiLineString): + _render_linestring(lines, _get_geoms(geometry)) + lines.extend(("COORDLINE", "")) + continue + + for linestring in geometry: + _render_coords(lines, _get_geoms(linestring)) + + lines.extend(("COORDLINE", "")) + + return "\n".join(lines) + + def filter_smaller_than(geometries, threshold): geo = _get_geoms(geometries) if isinstance(geo, list): @@ -186,6 +249,18 @@ def to_poly(geometries, designator: str, color: str | None = None, coordpoly=Fal return "\n".join(lines) +def to_multipoly(geometries, designator: str, color: str | None = None, coordpoly=False): + lines = [f"// {designator}"] if designator else [] + + for geometry in geometries: + for polygon in geometry: + _render_polygon(lines, _get_geoms(polygon), color) + + if coordpoly: + lines.append(f"COORDPOLY:{coordpoly}") + + return "\n".join(lines) + def _render_polygon(lines, polygons, color=None): for polygon in polygons: @@ -229,8 +304,11 @@ def simplify(geometries, tolerance): return shapely.simplify(geo, tolerance) -def join_segments(lines): - return shapely.ops.linemerge(_get_geoms(lines)) +def join_segments(geometries): + if isinstance(geometries, list): + return [shapely.ops.linemerge(_get_geoms(geometry)) for geometry in geometries] + else: + return shapely.ops.linemerge(_get_geoms(geometries)) def coord2es(coord):