Skip to content

Commit

Permalink
fix multi geometry
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Oct 14, 2023
1 parent e0c071a commit b98a081
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 45 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/[email protected]
Expand All @@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/[email protected]
Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
pypy-version: ['pypy-3.7', 'pypy-3.8', 'pypy-3.9']
pypy-version: ['pypy-3.7', 'pypy-3.8', 'pypy-3.9', 'pypy-3.10']
steps:
- uses: actions/[email protected]
- name: Set up Python ${{ matrix.pypy-version }}
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ setup.cfg
# Installer logs
pip-log.txt

# Unit test / coverage reports
# Unit/static test / coverage reports
.coverage
.tox
.pytest_cache/
nosetests.xml
.ruff_cache/
monkeytype.sqlite3

# Translations
*.mo
Expand All @@ -51,6 +54,8 @@ venv
.mypy_cache/
.pyre/
.watchmanconfig
.pytype/


# misc
.dccache
1 change: 1 addition & 0 deletions fastkml/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from enum import unique


@unique
class Verbosity(Enum):
"""Enum to represent the different verbosity levels."""

Expand Down
99 changes: 60 additions & 39 deletions fastkml/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,24 +931,6 @@ def __init__(
geometry=geometry,
)

@classmethod
def _get_geometry(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> geo.LinearRing:
coords = cls._get_coordinates(ns=ns, element=element, strict=strict)
try:
return cast(geo.LinearRing, geo.LinearRing.from_coordinates(coords))
except (IndexError, TypeError) as e:
error = config.etree.tostring( # type: ignore[attr-defined]
element,
encoding="UTF-8",
).decode("UTF-8")
raise KMLParseError(f"Invalid coordinates in {error}") from e

def etree_element(
self,
precision: Optional[int] = None,
Expand Down Expand Up @@ -986,13 +968,7 @@ def etree_element(
return element

@classmethod
def _get_polygon_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
def _get_geometry(cls, *, ns: str, element: Element, strict: bool) -> geo.Polygon:
outer_boundary = element.find(f"{ns}outerBoundaryIs")
if outer_boundary is None:
error = config.etree.tostring( # type: ignore[attr-defined]
Expand All @@ -1007,7 +983,7 @@ def _get_polygon_kwargs(
encoding="UTF-8",
).decode("UTF-8")
raise KMLParseError(f"Missing LinearRing in {error}")
exterior = cls._get_geometry(ns=ns, element=outer_ring, strict=strict)
exterior = LinearRing._get_geometry(ns=ns, element=outer_ring, strict=strict)
interiors = []
for inner_boundary in element.findall(f"{ns}innerBoundaryIs"):
inner_ring = inner_boundary.find(f"{ns}LinearRing")
Expand All @@ -1018,9 +994,19 @@ def _get_polygon_kwargs(
).decode("UTF-8")
raise KMLParseError(f"Missing LinearRing in {error}")
interiors.append(
cls._get_geometry(ns=ns, element=inner_ring, strict=strict)
LinearRing._get_geometry(ns=ns, element=inner_ring, strict=strict)
)
return {"geometry": geo.Polygon.from_linear_rings(exterior, *interiors)}
return geo.Polygon.from_linear_rings(exterior, *interiors)

@classmethod
def _get_polygon_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
return {"geometry": cls._get_geometry(ns=ns, element=element, strict=strict)}

@classmethod
def _get_kwargs(
Expand All @@ -1035,12 +1021,43 @@ def _get_kwargs(
return kwargs


def create_multigeometry(
geometries: Sequence[AnyGeometryType],
) -> Optional[MultiGeometryType]:
"""Create a MultiGeometry from a sequence of geometries.
Args:
geometries: Sequence of geometries.
Returns:
MultiGeometry
"""
geom_types = {geom.geom_type for geom in geometries}
if not geom_types:
return None
if len(geom_types) == 1:
geom_type = geom_types.pop()
map_to_geometries = {
geo.Point.__name__: geo.MultiPoint.from_points,
geo.LineString.__name__: geo.MultiLineString.from_linestrings,
geo.Polygon.__name__: geo.MultiPolygon.from_polygons,
}
for geometry_name, constructor in map_to_geometries.items():
if geom_type == geometry_name:
return constructor( # type: ignore[operator, no-any-return]
*geometries,
)

return geo.GeometryCollection(geometries)


class MultiGeometry(_Geometry):
map_to_kml = {
geo.Point: Point,
geo.LineString: LineString,
geo.LinearRing: LinearRing,
geo.Polygon: Polygon,
geo.LinearRing: LinearRing,
}
multi_geometries = (
geo.MultiPoint,
Expand Down Expand Up @@ -1094,6 +1111,19 @@ def etree_element(
)
return element

@classmethod
def _get_geometry(
cls, *, ns: str, element: Element, strict: bool
) -> Optional[MultiGeometryType]:
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(ns=ns, element=e, strict=strict)
if geometry is not None:
geometries.append(geometry)
return create_multigeometry(geometries)

@classmethod
def _get_multigeometry_kwargs(
cls,
Expand All @@ -1102,16 +1132,7 @@ def _get_multigeometry_kwargs(
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)}
return {"geometry": cls._get_geometry(ns=ns, element=element, strict=strict)}

@classmethod
def _get_kwargs(
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ ignore_errors = true
[tool.ruff]
[tool.ruff.extend-per-file-ignores]
"tests/oldunit_test.py" = ["E501"]
"setup.py" = ["E501"]
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Base package requirements
pygeoif>=1.0.0
pygeoif>=1.1.0
python-dateutil
typing_extensions
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def run_tests(self) -> None:
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
# "Development Status :: 5 - Production/Stable",
Expand All @@ -58,7 +59,7 @@ def run_tests(self) -> None:
python_requires=">=3.7",
install_requires=[
# -*- Extra requirements: -*-
"pygeoif>=1.0.0",
"pygeoif>=1.1.0",
"python-dateutil",
"setuptools",
"typing_extensions",
Expand Down
Loading

0 comments on commit b98a081

Please sign in to comment.