From cf87477af91dbb7df3daf15be0209d8369e24960 Mon Sep 17 00:00:00 2001 From: Eric Massip Date: Sat, 8 Apr 2023 11:16:14 +0100 Subject: [PATCH 1/4] Added support for nested multigeometries with test coverage --- fastkml/geometry.py | 5 +++++ tests/oldunit_test.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 62b0c5ab..3e337f95 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -406,6 +406,11 @@ def _get_multigeometry(self, element: Element) -> Optional[MultiGeometryType]: # MultiGeometry geoms: List[Union[AnyGeometryType, None]] = [] if element.tag == f"{self.ns}MultiGeometry": + multigeometries = element.findall(f"{self.ns}MultiGeometry") + for multigeometry in multigeometries: + geom = Geometry(ns=self.ns) + geom.from_element(multigeometry) + geoms.append(geom.geometry) points = element.findall(f"{self.ns}Point") for point in points: self._get_geometry_spec(point) diff --git a/tests/oldunit_test.py b/tests/oldunit_test.py index 5bc4fbd5..f0aa9582 100644 --- a/tests/oldunit_test.py +++ b/tests/oldunit_test.py @@ -1867,6 +1867,53 @@ def test_geometrycollection(self): assert len(g.geometry) == 2 assert g.geometry.geom_type == "GeometryCollection" + def test_nested_multigeometry(self): + doc = """ + + + + + + -122.366278,37.818844,0 -122.365248,37.819267,0 -122.365640,37.819875,0 -122.366278,37.818844,0 + + + + + + -122.365,37.819,0 + + + + + -122.365278,37.819000,0 -122.365248,37.819267,0 + + + + + + + -122.365248,37.819267,0 -122.365640,37.819875,0 -122.366278,37.818844,0 -122.365248,37.819267,0 + + + + + + + + """ + + k = kml.KML() + k.from_string(doc) + placemark = list(list(k.features())[0].features())[0] + + first_multigeometry = placemark.geometry + assert len(list(first_multigeometry.geoms)) == 3 + + second_multigeometry = [ + g for g in first_multigeometry.geoms if g.geom_type == "GeometryCollection" + ][0] + assert len(list(second_multigeometry.geoms)) == 2 + class TestGetGxGeometry: def test_track(self): From e78d346a93d5518b03f26581a4f8d039fd7d0659 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:57:27 +0000 Subject: [PATCH 2/4] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hakancelikdev/unimport: 0.15.0 → 0.16.0](https://github.com/hakancelikdev/unimport/compare/0.15.0...0.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0699468e..a7329e0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: hooks: - id: absolufy-imports - repo: https://github.com/hakancelikdev/unimport - rev: 0.15.0 + rev: 0.16.0 hooks: - id: unimport args: [--remove, --include-star-import, --ignore-init, --gitignore] From 42b1c1c4523db4fe2f1bdd0995db1c402a61669f Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 14 Jan 2023 13:53:29 +0000 Subject: [PATCH 3/4] back to dev, bump version to alpha 5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5bfb0fe7..c33d5194 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def run_tests(self) -> None: setup( name="fastkml", - version="1.0.alpha.4", + version="1.0.alpha.5", description="Fast KML processing in python", long_description=( open("README.rst").read() From c84d504be61a2c25e05d7630b8ebd2f9b594c6be Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 14 Jan 2023 14:39:56 +0000 Subject: [PATCH 4/4] qualify pygeoif classes with geo. prefix --- fastkml/geometry.py | 105 ++++++++++++++++++++---------------------- tests/oldunit_test.py | 16 +++---- 2 files changed, 58 insertions(+), 63 deletions(-) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 3e337f95..6fc6245e 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -23,15 +23,8 @@ from typing import Union from typing import cast +import pygeoif.geometry as geo from pygeoif.factories import shape -from pygeoif.geometry import GeometryCollection -from pygeoif.geometry import LinearRing -from pygeoif.geometry import LineString -from pygeoif.geometry import MultiLineString -from pygeoif.geometry import MultiPoint -from pygeoif.geometry import MultiPolygon -from pygeoif.geometry import Point -from pygeoif.geometry import Polygon from pygeoif.types import PointType from fastkml import config @@ -40,8 +33,10 @@ logger = logging.getLogger(__name__) -GeometryType = Union[Polygon, LineString, LinearRing, Point] -MultiGeometryType = Union[MultiPoint, MultiLineString, MultiPolygon, GeometryCollection] +GeometryType = Union[geo.Polygon, geo.LineString, geo.LinearRing, geo.Point] +MultiGeometryType = Union[ + geo.MultiPoint, geo.MultiLineString, geo.MultiPolygon, geo.GeometryCollection +] AnyGeometryType = Union[GeometryType, MultiGeometryType] @@ -116,14 +111,14 @@ def __init__( if isinstance( geometry, ( - Point, - LineString, - Polygon, - MultiPoint, - MultiLineString, - MultiPolygon, - LinearRing, - GeometryCollection, + geo.Point, + geo.LineString, + geo.Polygon, + geo.MultiPoint, + geo.MultiLineString, + geo.MultiPolygon, + geo.LinearRing, + geo.GeometryCollection, ), ): self.geometry = geometry @@ -182,11 +177,11 @@ def _etree_coordinates( element.text = " ".join(tuples) return element - def _etree_point(self, point: Point) -> Element: + def _etree_point(self, point: geo.Point) -> Element: element = self._extrude_and_altitude_mode("Point") return self._extracted_from__etree_linearring_5(point, element) - def _etree_linestring(self, linestring: LineString) -> Element: + def _etree_linestring(self, linestring: geo.LineString) -> Element: element = self._extrude_and_altitude_mode("LineString") if self.tessellate and self.altitude_mode in [ "clampToGround", @@ -198,18 +193,18 @@ def _etree_linestring(self, linestring: LineString) -> Element: ts_element.text = "1" return self._extracted_from__etree_linearring_5(linestring, element) - def _etree_linearring(self, linearring: LinearRing) -> Element: + def _etree_linearring(self, linearring: geo.LinearRing) -> Element: element = self._extrude_and_altitude_mode("LinearRing") return self._extracted_from__etree_linearring_5(linearring, element) def _extracted_from__etree_linearring_5( - self, arg0: Union[LineString, LinearRing, Point], element: Element + self, arg0: Union[geo.LineString, geo.LinearRing, geo.Point], element: Element ) -> Element: coords = list(arg0.coords) element.append(self._etree_coordinates(coords)) return element - def _etree_polygon(self, polygon: Polygon) -> Element: + def _etree_polygon(self, polygon: geo.Polygon) -> Element: element = self._extrude_and_altitude_mode("Polygon") outer_boundary = cast( Element, @@ -241,7 +236,7 @@ def _extrude_and_altitude_mode(self, kml_geometry: str) -> Element: self._set_altitude_mode(result) return result - def _etree_multipoint(self, points: MultiPoint) -> Element: + def _etree_multipoint(self, points: geo.MultiPoint) -> Element: element = cast( Element, config.etree.Element( # type: ignore[attr-defined] @@ -252,7 +247,7 @@ def _etree_multipoint(self, points: MultiPoint) -> Element: element.append(self._etree_point(point)) return element - def _etree_multilinestring(self, linestrings: MultiLineString) -> Element: + def _etree_multilinestring(self, linestrings: geo.MultiLineString) -> Element: element = cast( Element, config.etree.Element( # type: ignore[attr-defined] @@ -263,7 +258,7 @@ def _etree_multilinestring(self, linestrings: MultiLineString) -> Element: element.append(self._etree_linestring(linestring)) return element - def _etree_multipolygon(self, polygons: MultiPolygon) -> Element: + def _etree_multipolygon(self, polygons: geo.MultiPolygon) -> Element: element = cast( Element, config.etree.Element( # type: ignore[attr-defined] @@ -274,7 +269,7 @@ def _etree_multipolygon(self, polygons: MultiPolygon) -> Element: element.append(self._etree_polygon(polygon)) return element - def _etree_collection(self, features: GeometryCollection) -> Element: + def _etree_collection(self, features: geo.GeometryCollection) -> Element: element = cast( Element, config.etree.Element( # type: ignore[attr-defined] @@ -283,33 +278,33 @@ def _etree_collection(self, features: GeometryCollection) -> Element: ) for feature in features.geoms: if feature.geom_type == "Point": - element.append(self._etree_point(cast(Point, feature))) + element.append(self._etree_point(cast(geo.Point, feature))) elif feature.geom_type == "LinearRing": - element.append(self._etree_linearring(cast(LinearRing, feature))) + element.append(self._etree_linearring(cast(geo.LinearRing, feature))) elif feature.geom_type == "LineString": - element.append(self._etree_linestring(cast(LineString, feature))) + element.append(self._etree_linestring(cast(geo.LineString, feature))) elif feature.geom_type == "Polygon": - element.append(self._etree_polygon(cast(Polygon, feature))) + element.append(self._etree_polygon(cast(geo.Polygon, feature))) else: raise ValueError("Illegal geometry type.") return element def etree_element(self) -> Element: - if isinstance(self.geometry, Point): + if isinstance(self.geometry, geo.Point): return self._etree_point(self.geometry) - elif isinstance(self.geometry, LinearRing): + elif isinstance(self.geometry, geo.LinearRing): return self._etree_linearring(self.geometry) - elif isinstance(self.geometry, LineString): + elif isinstance(self.geometry, geo.LineString): return self._etree_linestring(self.geometry) - elif isinstance(self.geometry, Polygon): + elif isinstance(self.geometry, geo.Polygon): return self._etree_polygon(self.geometry) - elif isinstance(self.geometry, MultiPoint): + elif isinstance(self.geometry, geo.MultiPoint): return self._etree_multipoint(self.geometry) - elif isinstance(self.geometry, MultiLineString): + elif isinstance(self.geometry, geo.MultiLineString): return self._etree_multilinestring(self.geometry) - elif isinstance(self.geometry, MultiPolygon): + elif isinstance(self.geometry, geo.MultiPolygon): return self._etree_multipolygon(self.geometry) - elif isinstance(self.geometry, GeometryCollection): + elif isinstance(self.geometry, geo.GeometryCollection): return self._etree_collection(self.geometry) else: raise ValueError("Illegal geometry type.") @@ -365,12 +360,12 @@ def _get_coordinates(self, element: Element) -> List[PointType]: for latlon in latlons ] - def _get_linear_ring(self, element: Element) -> Optional[LinearRing]: + def _get_linear_ring(self, element: Element) -> Optional[geo.LinearRing]: # LinearRing in polygon lr = element.find(f"{self.ns}LinearRing") if lr is not None: coords = self._get_coordinates(lr) - return LinearRing(coords) + return geo.LinearRing(coords) return None # type: ignore[unreachable] def _get_geometry(self, element: Element) -> Optional[GeometryType]: @@ -379,11 +374,11 @@ def _get_geometry(self, element: Element) -> Optional[GeometryType]: if element.tag == f"{self.ns}Point": coords = self._get_coordinates(element) self._get_geometry_spec(element) - return Point.from_coordinates(coords) + return geo.Point.from_coordinates(coords) if element.tag == f"{self.ns}LineString": coords = self._get_coordinates(element) self._get_geometry_spec(element) - return LineString(coords) + return geo.LineString(coords) if element.tag == f"{self.ns}Polygon": self._get_geometry_spec(element) outer_boundary = element.find(f"{self.ns}outerBoundaryIs") @@ -395,11 +390,11 @@ def _get_geometry(self, element: Element) -> Optional[GeometryType]: self._get_linear_ring(inner_boundary) for inner_boundary in inner_boundaries ] - return Polygon.from_linear_rings(ob, *[b for b in ibs if b]) + return geo.Polygon.from_linear_rings(ob, *[b for b in ibs if b]) if element.tag == f"{self.ns}LinearRing": coords = self._get_coordinates(element) self._get_geometry_spec(element) - return LinearRing(coords) + return geo.LinearRing(coords) return None def _get_multigeometry(self, element: Element) -> Optional[MultiGeometryType]: @@ -414,11 +409,11 @@ def _get_multigeometry(self, element: Element) -> Optional[MultiGeometryType]: points = element.findall(f"{self.ns}Point") for point in points: self._get_geometry_spec(point) - geoms.append(Point.from_coordinates(self._get_coordinates(point))) + geoms.append(geo.Point.from_coordinates(self._get_coordinates(point))) linestrings = element.findall(f"{self.ns}LineString") for ls in linestrings: self._get_geometry_spec(ls) - geoms.append(LineString(self._get_coordinates(ls))) + geoms.append(geo.LineString(self._get_coordinates(ls))) polygons = element.findall(f"{self.ns}Polygon") for polygon in polygons: self._get_geometry_spec(polygon) @@ -431,34 +426,34 @@ def _get_multigeometry(self, element: Element) -> Optional[MultiGeometryType]: self._get_linear_ring(inner_boundary) for inner_boundary in inner_boundaries ] - ibs: List[LinearRing] = [ib for ib in inner_bs if ib] - geoms.append(Polygon.from_linear_rings(ob, *ibs)) + ibs: List[geo.LinearRing] = [ib for ib in inner_bs if ib] + geoms.append(geo.Polygon.from_linear_rings(ob, *ibs)) linearings = element.findall(f"{self.ns}LinearRing") if linearings: for lr in linearings: self._get_geometry_spec(lr) - geoms.append(LinearRing(self._get_coordinates(lr))) + geoms.append(geo.LinearRing(self._get_coordinates(lr))) clean_geoms: List[AnyGeometryType] = [g for g in geoms if g] if clean_geoms: geom_types = {geom.geom_type for geom in clean_geoms} if len(geom_types) > 1: - return GeometryCollection( + return geo.GeometryCollection( clean_geoms, # type: ignore[arg-type] ) if "Point" in geom_types: - return MultiPoint.from_points( + return geo.MultiPoint.from_points( *clean_geoms, # type: ignore[arg-type] ) elif "LineString" in geom_types: - return MultiLineString.from_linestrings( + return geo.MultiLineString.from_linestrings( *clean_geoms, # type: ignore[arg-type] ) elif "Polygon" in geom_types: - return MultiPolygon.from_polygons( + return geo.MultiPolygon.from_polygons( *clean_geoms, # type: ignore[arg-type] ) elif "LinearRing" in geom_types: - return GeometryCollection( + return geo.GeometryCollection( clean_geoms, # type: ignore[arg-type] ) return None diff --git a/tests/oldunit_test.py b/tests/oldunit_test.py index f0aa9582..9d832d88 100644 --- a/tests/oldunit_test.py +++ b/tests/oldunit_test.py @@ -16,6 +16,14 @@ import xml.etree.ElementTree import pytest +from pygeoif.geometry import GeometryCollection +from pygeoif.geometry import LinearRing +from pygeoif.geometry import LineString +from pygeoif.geometry import MultiLineString +from pygeoif.geometry import MultiPoint +from pygeoif.geometry import MultiPolygon +from pygeoif.geometry import Point +from pygeoif.geometry import Polygon from fastkml import atom from fastkml import base @@ -24,14 +32,6 @@ from fastkml import kml from fastkml import styles from fastkml.geometry import Geometry -from fastkml.geometry import GeometryCollection -from fastkml.geometry import LinearRing -from fastkml.geometry import LineString -from fastkml.geometry import MultiLineString -from fastkml.geometry import MultiPoint -from fastkml.geometry import MultiPolygon -from fastkml.geometry import Point -from fastkml.geometry import Polygon from fastkml.gx import GxGeometry try: