Skip to content

Commit

Permalink
WIP: MultiGeometry
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Apr 3, 2023
1 parent b099d96 commit 16408cf
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 3 deletions.
93 changes: 91 additions & 2 deletions fastkml/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ def __init__(
extrude: Optional[bool] = False,
tessellate: Optional[bool] = False,
altitude_mode: Optional[AltitudeMode] = None,
geometry: Optional[GeometryType] = None,
geometry: Optional[AnyGeometryType] = None,
) -> None:
"""
Expand Down Expand Up @@ -1031,4 +1031,93 @@ def _get_kwargs(


class MultiGeometry(_Geometry):
...
map_to_kml = {
geo.Point: Point,
geo.LineString: LineString,
geo.LinearRing: LinearRing,
geo.Polygon: Polygon,
}
multi_geometries = (
geo.MultiPoint,
geo.MultiLineString,
geo.MultiPolygon,
geo.GeometryCollection,
)

def __init__(
self,
*,
ns: Optional[str] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
extrude: Optional[bool] = False,
tessellate: Optional[bool] = False,
altitude_mode: Optional[AltitudeMode] = None,
geometry: MultiGeometryType,
) -> None:
super().__init__(
ns=ns,
id=id,
target_id=target_id,
extrude=extrude,
tessellate=tessellate,
altitude_mode=altitude_mode,
geometry=geometry,
)

def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
self.__name__ = self.__class__.__name__
element = super().etree_element(precision=precision, verbosity=verbosity)
assert isinstance(self.geometry, geo._MultiGeometry)
_map_to_kml = {mg: self.__class__ for mg in self.multi_geometries}
_map_to_kml.update(self.map_to_kml)

for geometry in self.geometry.geoms:
geometry_class = _map_to_kml[type(geometry)]
element.append(
geometry_class(
ns=self.ns,
extrude=None,
tessellate=None,
altitude_mode=None,
geometry=geometry, # type: ignore[arg-type]
).etree_element(precision=precision, verbosity=verbosity)
)
return element

@classmethod
def _get_multigeometry_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
geometries = []
allowed_geometries = (cls,) + tuple(cls.map_to_kml.values())
for g in allowed_geometries:
for e in element.findall(f"{ns}{g.__name__}"):
geometry = g._get_geometry( # type: ignore[attr-defined]
ns=ns, element=e, strict=strict
)
if geometry is not None:
geometries.append(geometry)
return {"geometry": geo.GeometryCollection(geometries)}

@classmethod
def _get_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
kwargs = super()._get_kwargs(ns=ns, element=element, strict=strict)
kwargs.update(
cls._get_multigeometry_kwargs(ns=ns, element=element, strict=strict)
)
return kwargs
2 changes: 1 addition & 1 deletion tests/geometries/geometry_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2021 Christian Ledermann
# Copyright (C) 2021 - 2023 Christian Ledermann
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
Expand Down
219 changes: 219 additions & 0 deletions tests/geometries/multigeometry_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Copyright (C) 2021 - 2023 Christian Ledermann
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""Test the geometry classes."""
import pygeoif.geometry as geo

from fastkml.geometry import MultiGeometry
from tests.base import Lxml
from tests.base import StdLibrary


class TestMultiPointStdLibrary(StdLibrary):
"""Test with the standard library."""

def test_1_point(self):
"""Test with one point."""
p = geo.MultiPoint([(1, 2)])

mg = MultiGeometry(ns="", geometry=p)

assert "coordinates>1.000000,2.000000</" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()

def test_2_points(self):
"""Test with two points."""
p = geo.MultiPoint([(1, 2), (3, 4)])

mg = MultiGeometry(ns="", geometry=p)

assert "coordinates>1.000000,2.000000</" in mg.to_string()
assert "coordinates>3.000000,4.000000</" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()


class TestMultiLineStringStdLibrary(StdLibrary):
def test_1_linestring(self):
"""Test with one linestring."""
p = geo.MultiLineString([[(1, 2), (3, 4)]])

mg = MultiGeometry(ns="", geometry=p)

assert "coordinates>1.000000,2.000000 3.000000,4.000000</" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
assert "LineString>" in mg.to_string()

def test_2_linestrings(self):
"""Test with two linestrings."""
p = geo.MultiLineString([[(1, 2), (3, 4)], [(5, 6), (7, 8)]])

mg = MultiGeometry(ns="", geometry=p)

assert "coordinates>1.000000,2.000000 3.000000,4.000000</" in mg.to_string()
assert "coordinates>5.000000,6.000000 7.000000,8.000000</" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
assert "LineString>" in mg.to_string()


class TestMultiPolygonStdLibrary(StdLibrary):
def test_1_polygon(self):
"""Test with one polygon."""
p = geo.MultiPolygon([[[[1, 2], [3, 4], [5, 6], [1, 2]]]])

mg = MultiGeometry(ns="", geometry=p)

assert (
"coordinates>1.000000,2.000000 3.000000,4.000000 5.000000,6.000000 "
"1.000000,2.000000</" in mg.to_string()
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
assert "outerBoundaryIs>" in mg.to_string()
assert "innerBoundaryIs>" not in mg.to_string()

def test_1_polygons_with_holes(self):
"""Test with one polygon with holes."""
p = geo.MultiPolygon(
[
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))],
),
],
)
mg = MultiGeometry(ns="", geometry=p)

assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000</coordinates" in mg.to_string()
)
assert (
"coordinates>0.250000,0.250000 0.250000,0.500000 0.500000,0.500000 "
"0.500000,0.250000 0.250000,0.250000</coordinates" in mg.to_string()
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
assert "outerBoundaryIs>" in mg.to_string()
assert "innerBoundaryIs>" in mg.to_string()

def test_2_polygons(self):
"""Test with two polygons."""
p = geo.MultiPolygon(
[
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1))],
),
(((0.0, 0.0), (0.0, 2.0), (1.0, 1.0), (1.0, 0.0)),),
],
)

mg = MultiGeometry(ns="", geometry=p)

assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000</coordinates" in mg.to_string()
)
assert (
"coordinates>0.100000,0.100000 0.100000,0.200000 0.200000,0.200000 "
"0.200000,0.100000 0.100000,0.100000</coordinates" in mg.to_string()
)
assert (
"coordinates>0.000000,0.000000 0.000000,2.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000</coordinates" in mg.to_string()
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
assert "outerBoundaryIs>" in mg.to_string()
assert "innerBoundaryIs>" in mg.to_string()


class TestGeometryCollectionStdLibrary(StdLibrary):
"""Test heterogeneous geometry collections."""

def test_1_point(self):
"""Test with one point."""
p = geo.GeometryCollection([geo.Point(1, 2)])

mg = MultiGeometry(ns="", geometry=p)

assert "coordinates>1.000000,2.000000</" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()

def test_geometries(self):
p = geo.Point(1, 2)
ls = geo.LineString(((1, 2), (2, 0)))
lr = geo.LinearRing(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
poly = geo.Polygon(
[(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)],
[[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]],
)
gc = geo.GeometryCollection([p, ls, lr, poly])

mg = MultiGeometry(ns="", geometry=gc)

assert "Point>" in mg.to_string()
assert "LineString>" in mg.to_string()
assert "LinearRing>" in mg.to_string()
assert "Polygon>" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()

def test_multi_geometries(self):
p = geo.Point(1, 2)
ls = geo.LineString(((1, 2), (2, 0)))
lr = geo.LinearRing(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
poly = geo.Polygon(
[(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)],
[[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]],
)
gc = geo.GeometryCollection([p, ls, lr, poly])
mp = geo.MultiPolygon(
[
(
((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
[((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1))],
),
(((0.0, 0.0), (0.0, 2.0), (1.0, 1.0), (1.0, 0.0)),),
],
)
ml = geo.MultiLineString([[(1, 2), (3, 4)], [(5, 6), (7, 8)]])
mgc = geo.GeometryCollection([gc, mp, ml])
mg = MultiGeometry(ns="", geometry=mgc)

assert "Point>" in mg.to_string()
assert "LineString>" in mg.to_string()
assert "LinearRing>" in mg.to_string()
assert "Polygon>" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()


class TestMultiPointLxml(Lxml, TestMultiPointStdLibrary):
"""Test with lxml."""


class TestMultiLineStringLxml(Lxml, TestMultiLineStringStdLibrary):
"""Test with lxml."""


class TestMultiPolygonLxml(Lxml, TestMultiPolygonStdLibrary):
"""Test with lxml."""


class TestGeometryCollectionLxml(Lxml, TestGeometryCollectionStdLibrary):
"""Test with lxml."""

0 comments on commit 16408cf

Please sign in to comment.