From 57f957c8d474051944c871c67bf0e7bb89c7196e Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 8 Oct 2022 19:08:58 +0100 Subject: [PATCH 1/2] #133 type annotate geometries --- fastkml/geometry.py | 110 ++++++++++++++++++++++++++++++-------------- pyproject.toml | 2 +- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 7292cda3..3d6f4eec 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -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 @@ -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" @@ -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) @@ -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) @@ -208,8 +211,8 @@ 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", ), ) @@ -217,8 +220,8 @@ def _etree_polygon(self, polygon: Polygon) -> Element: 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", ), ) @@ -226,40 +229,65 @@ def _etree_polygon(self, polygon: Polygon) -> Element: 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 @@ -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: @@ -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() @@ -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") @@ -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, @@ -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: @@ -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: diff --git a/pyproject.toml b/pyproject.toml index 2650c6b0..685d87c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,6 @@ show_error_codes = true [[tool.mypy.overrides]] module = [ "fastkml.kml", "fastkml.gx", - "fastkml.geometry", "fastkml.styles", + "fastkml.styles", "fastkml.tests.oldunit_test", "fastkml.tests.config_test"] ignore_errors = true From 94ce71f2820c57fea33e69d739d02362f8f04614 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 8 Oct 2022 19:54:51 +0100 Subject: [PATCH 2/2] #133 type annotate gx --- fastkml/gx.py | 41 ++++++++++++----- fastkml/styles.py | 109 +++++++++++++++++++++++++--------------------- pyproject.toml | 3 +- 3 files changed, 91 insertions(+), 62 deletions(-) diff --git a/fastkml/gx.py b/fastkml/gx.py index b1a710fd..c37f37dc 100644 --- a/fastkml/gx.py +++ b/fastkml/gx.py @@ -77,13 +77,19 @@ """ 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__) @@ -91,9 +97,9 @@ 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 @@ -101,34 +107,47 @@ def __init__( 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"] diff --git a/fastkml/styles.py b/fastkml/styles.py index 77327fc3..5df7a08c 100644 --- a/fastkml/styles.py +++ b/fastkml/styles.py @@ -54,7 +54,7 @@ def __init__( target_id: Optional[str] = None, url: Optional[str] = None, ) -> None: - super().__init__(ns, id, target_id) + super().__init__(ns=ns, id=id, target_id=target_id) self.url = url def etree_element(self) -> Element: @@ -109,21 +109,21 @@ def __init__( color: Optional[str] = None, color_mode: Optional[str] = None, ) -> None: - super().__init__(ns, id, target_id) + super().__init__(ns=ns, id=id, target_id=target_id) self.color = color self.color_mode = color_mode def etree_element(self) -> Element: element = super().etree_element() if self.color: - color = config.etree.SubElement( - element, # type: ignore[arg-type] + color = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}color", ) color.text = self.color if self.color_mode: - color_mode = config.etree.SubElement( - element, # type: ignore[arg-type] + color_mode = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}colorMode", ) color_mode.text = self.color_mode @@ -170,9 +170,12 @@ def __init__( scale: float = 1.0, heading: Optional[float] = None, icon_href: Optional[str] = None, - hot_spot: HotSpot = None, + hot_spot: Optional[HotSpot] = None, ) -> None: - super().__init__(ns, id, target_id, color, color_mode) + super().__init__( + ns=ns, id=id, target_id=target_id, color=color, color_mode=color_mode + ) + self.scale = scale self.heading = heading self.icon_href = icon_href @@ -181,29 +184,31 @@ def __init__( def etree_element(self) -> Element: element = super().etree_element() if self.scale is not None: - scale = config.etree.SubElement( - element, # type: ignore[arg-type] + scale = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}scale", ) scale.text = str(self.scale) if self.heading is not None: - heading = config.etree.SubElement( - element, # type: ignore[arg-type] + heading = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}heading", ) heading.text = str(self.heading) if self.icon_href: - icon = config.etree.SubElement( - element, # type: ignore[arg-type] + icon = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}Icon", ) - href = config.etree.SubElement( + href = config.etree.SubElement( # type: ignore[attr-defined] icon, f"{self.ns}href", ) href.text = self.icon_href if self.hot_spot: - hot_spot = config.etree.SubElement(element, f"{self.ns}hotSpot") + hot_spot = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}hotSpot" + ) hot_spot.attrib["x"] = str(self.hot_spot["x"]) hot_spot.attrib["y"] = str(self.hot_spot["y"]) hot_spot.attrib["xunits"] = str(self.hot_spot["xunits"]) @@ -225,11 +230,11 @@ def from_element(self, element: Element) -> None: self.icon_href = href.text hot_spot = element.find(f"{self.ns}hotSpot") if hot_spot is not None: - self.hot_spot: HotSpot = { - "x": hot_spot.attrib["x"], - "y": hot_spot.attrib["y"], - "xunits": hot_spot.attrib["xunits"], - "yunits": hot_spot.attrib["yunits"], + self.hot_spot: HotSpot = { # type: ignore[no-redef] + "x": hot_spot.attrib["x"], # type: ignore[attr-defined] + "y": hot_spot.attrib["y"], # type: ignore[attr-defined] + "xunits": hot_spot.attrib["xunits"], # type: ignore[attr-defined] + "yunits": hot_spot.attrib["yunits"], # type: ignore[attr-defined] } @@ -254,14 +259,16 @@ def __init__( color_mode: Optional[str] = None, width: Union[int, float] = 1, ) -> None: - super().__init__(ns, id, target_id, color, color_mode) + super().__init__( + ns=ns, id=id, target_id=target_id, color=color, color_mode=color_mode + ) self.width = width def etree_element(self) -> Element: element = super().etree_element() if self.width is not None: - width = config.etree.SubElement( - element, # type: ignore[arg-type] + width = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}width", ) width.text = str(self.width) @@ -298,21 +305,23 @@ def __init__( fill: int = 1, outline: int = 1, ) -> None: - super().__init__(ns, id, target_id, color, color_mode) + super().__init__( + ns=ns, id=id, target_id=target_id, color=color, color_mode=color_mode + ) self.fill = fill self.outline = outline def etree_element(self) -> Element: element = super().etree_element() if self.fill is not None: - fill = config.etree.SubElement( - element, # type: ignore[arg-type] + fill = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}fill", ) fill.text = str(self.fill) if self.outline is not None: - outline = config.etree.SubElement( - element, # type: ignore[arg-type] + outline = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}outline", ) outline.text = str(self.outline) @@ -354,14 +363,16 @@ def __init__( color_mode: Optional[str] = None, scale: float = 1.0, ) -> None: - super().__init__(ns, id, target_id, color, color_mode) + super().__init__( + ns=ns, id=id, target_id=target_id, color=color, color_mode=color_mode + ) self.scale = scale def etree_element(self) -> Element: element = super().etree_element() if self.scale is not None: - scale = config.etree.SubElement( - element, # type: ignore[arg-type] + scale = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}scale", ) scale.text = str(self.scale) @@ -435,7 +446,7 @@ def __init__( text: Optional[str] = None, display_mode: Optional[str] = None, ) -> None: - super().__init__(ns, id, target_id) + super().__init__(ns=ns, id=id, target_id=target_id) self.bg_color = bg_color self.text_color = text_color self.text = text @@ -463,26 +474,26 @@ def from_element(self, element: Element) -> None: def etree_element(self) -> Element: element = super().etree_element() if self.bg_color is not None: - elem = config.etree.SubElement( - element, # type: ignore[arg-type] + elem = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}bgColor", ) elem.text = self.bg_color if self.text_color is not None: - elem = config.etree.SubElement( - element, # type: ignore[arg-type] + elem = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}textColor", ) elem.text = self.text_color if self.text is not None: - elem = config.etree.SubElement( - element, # type: ignore[arg-type] + elem = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}text", ) elem.text = self.text if self.display_mode is not None: - elem = config.etree.SubElement( - element, # type: ignore[arg-type] + elem = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}displayMode", ) elem.text = self.display_mode @@ -611,27 +622,27 @@ def from_element(self, element: Element) -> None: def etree_element(self) -> Element: element = super().etree_element() if self.normal and isinstance(self.normal, (Style, StyleUrl)): - pair = config.etree.SubElement( - element, # type: ignore[arg-type] + pair = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}Pair", ) - key = config.etree.SubElement( + key = config.etree.SubElement( # type: ignore[attr-defined] pair, f"{self.ns}key", ) key.text = "normal" - pair.append(self.normal.etree_element()) # type: ignore[arg-type] + pair.append(self.normal.etree_element()) if self.highlight and isinstance(self.highlight, (Style, StyleUrl)): - pair = config.etree.SubElement( - element, # type: ignore[arg-type] + pair = config.etree.SubElement( # type: ignore[attr-defined] + element, f"{self.ns}Pair", ) - key = config.etree.SubElement( + key = config.etree.SubElement( # type: ignore[attr-defined] pair, f"{self.ns}key", ) key.text = "highlight" - pair.append(self.highlight.etree_element()) # type: ignore[arg-type] + pair.append(self.highlight.etree_element()) return element diff --git a/pyproject.toml b/pyproject.toml index 685d87c7..7ccb8487 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ show_error_codes = true [[tool.mypy.overrides]] module = [ - "fastkml.kml", "fastkml.gx", - "fastkml.styles", + "fastkml.kml", "fastkml.tests.oldunit_test", "fastkml.tests.config_test"] ignore_errors = true