Skip to content

Commit

Permalink
Merge pull request #180 from cleder/typing
Browse files Browse the repository at this point in the history
Add type annotations
  • Loading branch information
cleder authored Oct 8, 2022
2 parents 53c27ec + 94ce71f commit 44fcb59
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 96 deletions.
110 changes: 76 additions & 34 deletions fastkml/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ def _set_altitude_mode(self, element: Element) -> None:
"absolute",
]
if self.altitude_mode != "clampToGround":
am_element = config.etree.SubElement(
element, f"{self.ns}altitudeMode" # type: ignore[arg-type]
am_element = config.etree.SubElement( # type: ignore[attr-defined]
element, f"{self.ns}altitudeMode"
)
am_element.text = self.altitude_mode

Expand All @@ -152,8 +152,8 @@ def _set_extrude(self, element: Element) -> None:
]:
et_element = cast(
Element,
config.etree.SubElement(
element, f"{self.ns}extrude" # type: ignore[arg-type]
config.etree.SubElement( # type: ignore[attr-defined]
element, f"{self.ns}extrude"
),
)
et_element.text = "1"
Expand All @@ -162,7 +162,10 @@ def _etree_coordinates(
self,
coordinates: Sequence[PointType],
) -> Element:
element = cast(Element, config.etree.Element(f"{self.ns}coordinates"))
element = cast(
Element,
config.etree.Element(f"{self.ns}coordinates"), # type: ignore[attr-defined]
)
if len(coordinates[0]) == 2:
if config.FORCE3D: # and not clampToGround:
tuples = (f"{c[0]:f},{c[1]:f},0.000000" for c in coordinates)
Expand All @@ -187,8 +190,8 @@ def _etree_linestring(self, linestring: LineString) -> Element:
"clampToGround",
"clampToSeaFloor",
]:
ts_element = config.etree.SubElement(
element, f"{self.ns}tessellate" # type: ignore[arg-type]
ts_element = config.etree.SubElement( # type: ignore[attr-defined]
element, f"{self.ns}tessellate"
)
ts_element.text = "1"
return self._extracted_from__etree_linearring_5(linestring, element)
Expand All @@ -208,58 +211,83 @@ def _etree_polygon(self, polygon: Polygon) -> Element:
element = self._extrude_and_altitude_mode("Polygon")
outer_boundary = cast(
Element,
config.etree.SubElement(
element, # type: ignore[arg-type]
config.etree.SubElement( # type: ignore[attr-defined]
element,
f"{self.ns}outerBoundaryIs",
),
)
outer_boundary.append(self._etree_linearring(polygon.exterior))
for ib in polygon.interiors:
inner_boundary = cast(
Element,
config.etree.SubElement(
element, # type: ignore[arg-type]
config.etree.SubElement( # type: ignore[attr-defined]
element,
f"{self.ns}innerBoundaryIs",
),
)
inner_boundary.append(self._etree_linearring(ib))
return element

def _extrude_and_altitude_mode(self, kml_geometry: str) -> Element:
result = cast(Element, config.etree.Element(f"{self.ns}{kml_geometry}"))
result = cast(
Element,
config.etree.Element( # type: ignore[attr-defined]
f"{self.ns}{kml_geometry}"
),
)
self._set_extrude(result)
self._set_altitude_mode(result)
return result

def _etree_multipoint(self, points: MultiPoint) -> Element:
element = cast(Element, config.etree.Element(f"{self.ns}MultiGeometry"))
element = cast(
Element,
config.etree.Element( # type: ignore[attr-defined]
f"{self.ns}MultiGeometry"
),
)
for point in points.geoms:
element.append(self._etree_point(point))
return element

def _etree_multilinestring(self, linestrings: MultiLineString) -> Element:
element = cast(Element, config.etree.Element(f"{self.ns}MultiGeometry"))
element = cast(
Element,
config.etree.Element( # type: ignore[attr-defined]
f"{self.ns}MultiGeometry"
),
)
for linestring in linestrings.geoms:
element.append(self._etree_linestring(linestring))
return element

def _etree_multipolygon(self, polygons: MultiPolygon) -> Element:
element = cast(Element, config.etree.Element(f"{self.ns}MultiGeometry"))
element = cast(
Element,
config.etree.Element( # type: ignore[attr-defined]
f"{self.ns}MultiGeometry"
),
)
for polygon in polygons.geoms:
element.append(self._etree_polygon(polygon))
return element

def _etree_collection(self, features: GeometryCollection) -> Element:
element = cast(Element, config.etree.Element(f"{self.ns}MultiGeometry"))
element = cast(
Element,
config.etree.Element( # type: ignore[attr-defined]
f"{self.ns}MultiGeometry"
),
)
for feature in features.geoms:
if feature.geom_type == "Point":
element.append(self._etree_point(feature))
element.append(self._etree_point(cast(Point, feature)))
elif feature.geom_type == "LinearRing":
element.append(self._etree_linearring(feature))
element.append(self._etree_linearring(cast(LinearRing, feature)))
elif feature.geom_type == "LineString":
element.append(self._etree_linestring(feature))
element.append(self._etree_linestring(cast(LineString, feature)))
elif feature.geom_type == "Polygon":
element.append(self._etree_polygon(feature))
element.append(self._etree_polygon(cast(Polygon, feature)))
else:
raise ValueError("Illegal geometry type.")
return element
Expand Down Expand Up @@ -295,7 +323,7 @@ def _get_geometry_spec(self, element: Element) -> None:
et = False
self.extrude = et
else:
self.extrude = False
self.extrude = False # type: ignore[unreachable]
tessellate = element.find(f"{self.ns}tessellate")
if tessellate is not None:
try:
Expand All @@ -304,7 +332,7 @@ def _get_geometry_spec(self, element: Element) -> None:
te = False
self.tessellate = te
else:
self.tessellate = False
self.tessellate = False # type: ignore[unreachable]
altitude_mode = element.find(f"{self.ns}altitudeMode")
if altitude_mode is not None:
am = altitude_mode.text.strip()
Expand All @@ -318,7 +346,7 @@ def _get_geometry_spec(self, element: Element) -> None:
else:
self.altitude_mode = None
else:
self.altitude_mode = None
self.altitude_mode = None # type: ignore[unreachable]

def _get_coordinates(self, element: Element) -> List[PointType]:
coordinates = element.find(f"{self.ns}coordinates")
Expand All @@ -341,7 +369,7 @@ def _get_linear_ring(self, element: Element) -> Optional[LinearRing]:
if lr is not None:
coords = self._get_coordinates(lr)
return LinearRing(coords)
return None
return None # type: ignore[unreachable]

def _get_geometry(self, element: Element) -> Optional[GeometryType]:
# Point, LineString,
Expand Down Expand Up @@ -372,9 +400,9 @@ def _get_geometry(self, element: Element) -> Optional[GeometryType]:
return LinearRing(coords)
return None

def _get_multigeometry(self, element: Element) -> Optional[GeometryType]:
def _get_multigeometry(self, element: Element) -> Optional[MultiGeometryType]:
# MultiGeometry
geoms: List[AnyGeometryType] = []
geoms: List[Union[AnyGeometryType, None]] = []
if element.tag == f"{self.ns}MultiGeometry":
points = element.findall(f"{self.ns}Point")
for point in points:
Expand All @@ -389,29 +417,43 @@ def _get_multigeometry(self, element: Element) -> Optional[GeometryType]:
self._get_geometry_spec(polygon)
outer_boundary = polygon.find(f"{self.ns}outerBoundaryIs")
ob = self._get_linear_ring(outer_boundary)
if not ob:
continue
inner_boundaries = polygon.findall(f"{self.ns}innerBoundaryIs")
ibs = [
inner_bs = [
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))
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)))
if geoms:
geom_types = {geom.geom_type for geom in geoms}
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(geoms)
return GeometryCollection(
clean_geoms, # type: ignore[arg-type]
)
if "Point" in geom_types:
return MultiPoint.from_points(*geoms)
return MultiPoint.from_points(
*clean_geoms, # type: ignore[arg-type]
)
elif "LineString" in geom_types:
return MultiLineString.from_linestrings(*geoms)
return MultiLineString.from_linestrings(
*clean_geoms, # type: ignore[arg-type]
)
elif "Polygon" in geom_types:
return MultiPolygon.from_polygons(*geoms)
return MultiPolygon.from_polygons(
*clean_geoms, # type: ignore[arg-type]
)
elif "LinearRing" in geom_types:
return GeometryCollection(geoms)
return GeometryCollection(
clean_geoms, # type: ignore[arg-type]
)
return None

def from_element(self, element: Element) -> None:
Expand Down
41 changes: 30 additions & 11 deletions fastkml/gx.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,58 +77,77 @@
"""

import logging
from typing import List
from typing import Optional
from typing import Union
from typing import cast

from pygeoif.geometry import GeometryCollection
from pygeoif.geometry import LineString
from pygeoif.geometry import MultiLineString
from pygeoif.types import PointType

from .config import GXNS as NS
from .geometry import Geometry
from fastkml.config import GXNS as NS
from fastkml.geometry import Geometry
from fastkml.types import Element

logger = logging.getLogger(__name__)


class GxGeometry(Geometry):
def __init__(
self,
ns=None,
id=None,
):
ns: None = None,
id: None = None,
) -> None:
"""
gxgeometry: a read-only subclass of geometry supporting gx: features,
like gx:Track
"""
super().__init__(ns, id)
self.ns = NS if ns is None else ns

def _get_geometry(self, element):
def _get_geometry(self, element: Element) -> Optional[LineString]:
# Track
if element.tag == (f"{self.ns}Track"):
coords = self._get_coordinates(element)
self._get_geometry_spec(element)
return LineString(coords)
return LineString(
coords,
)
return None

def _get_multigeometry(self, element):
def _get_multigeometry(
self,
element: Element,
) -> Union[MultiLineString, GeometryCollection, None]:
# MultiTrack
geoms = []
if element.tag == (f"{self.ns}MultiTrack"):
tracks = element.findall(f"{self.ns}Track")
for track in tracks:
self._get_geometry_spec(track)
geoms.append(LineString(self._get_coordinates(track)))
geoms.append(
LineString(
self._get_coordinates(track),
)
)

geom_types = {geom.geom_type for geom in geoms}
if len(geom_types) > 1:
return GeometryCollection(geoms)
if "LineString" in geom_types:
return MultiLineString.from_linestrings(*geoms)
return None

def _get_coordinates(self, element):
def _get_coordinates(self, element: Element) -> List[PointType]:
coordinates = element.findall(f"{self.ns}coord")
if coordinates is not None:
return [
[float(c) for c in coord.text.strip().split()] for coord in coordinates
cast(PointType, tuple(float(c) for c in coord.text.strip().split()))
for coord in coordinates
]
return [] # type: ignore[unreachable]


__all__ = ["GxGeometry"]
Loading

0 comments on commit 44fcb59

Please sign in to comment.