diff --git a/fastkml/geometry.py b/fastkml/geometry.py index f341f6b1..50dd48e5 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -817,9 +817,9 @@ def geometry(self) -> Optional[geo.LinearRing]: return None -class OuterBoundaryIs(_XMLObject): +class BoundaryIs(_XMLObject): """ - Represents the outer boundary of a polygon in KML. + Represents the inner or outer boundary of a polygon in KML. Attributes ---------- @@ -905,19 +905,6 @@ def __repr__(self) -> str: ")" ) - @classmethod - def get_tag_name(cls) -> str: - """ - Get the tag name for the OuterBoundaryIs object. - - Returns - ------- - str - The tag name. - - """ - return "outerBoundaryIs" - @property def geometry(self) -> Optional[geo.LinearRing]: """ @@ -932,106 +919,34 @@ def geometry(self) -> Optional[geo.LinearRing]: return self.kml_geometry.geometry if self.kml_geometry else None -registry.register( - OuterBoundaryIs, - item=RegistryItem( - ns_ids=("kml", ""), - classes=(LinearRing,), - attr_name="kml_geometry", - node_name="LinearRing", - get_kwarg=xml_subelement_kwarg, - set_element=xml_subelement, - ), -) +class OuterBoundaryIs(BoundaryIs): + """Represents the outer boundary of a polygon in KML.""" - -class InnerBoundaryIs(_XMLObject): - """Represents the inner boundary of a polygon in KML.""" - - _default_nsid = config.KML - kml_geometry: Optional[LinearRing] - - def __init__( - self, - *, - ns: Optional[str] = None, - name_spaces: Optional[Dict[str, str]] = None, - geometry: Optional[geo.LinearRing] = None, - kml_geometry: Optional[LinearRing] = None, - **kwargs: Any, - ) -> None: + @classmethod + def get_tag_name(cls) -> str: """ - Initialize a Geometry object. - - Parameters - ---------- - ns : Optional[str], optional - The namespace for the KML element, by default None. - name_spaces : Optional[Dict[str, str]], optional - The namespace dictionary for the KML element, by default None. - geometry : Optional[geo.LinearRing], optional - The geometry to be converted to a KML geometry, by default None. - kml_geometry : Optional[LinearRing], optional - The KML geometry, by default None. - **kwargs : Any - Additional keyword arguments. - - Raises - ------ - GeometryError - If both `geometry` and `kml_geometry` are provided. + Get the tag name for the OuterBoundaryIs object. - Notes - ----- - - If `geometry` is provided, it will be converted to KML geometries and - stored in `kml_geometry`. - - If `geometry` and `kml_geometry` are both provided, a GeometryError will be - raised. + Returns + ------- + str + The tag name. """ - if geometry is not None and kml_geometry is not None: - raise GeometryError(MsgMutualExclusive) - if kml_geometry is None: - kml_geometry = LinearRing(ns=ns, name_spaces=name_spaces, geometry=geometry) - self.kml_geometry = kml_geometry - super().__init__( - ns=ns, - name_spaces=name_spaces, - **kwargs, - ) + return "outerBoundaryIs" - def __bool__(self) -> bool: - """Return True if any of the inner boundary geometries exist.""" - return bool(self.kml_geometry) - def __repr__(self) -> str: - """Create a string (c)representation for InnerBoundaryIs.""" - return ( - f"{self.__class__.__module__}.{self.__class__.__name__}(" - f"ns={self.ns!r}, " - f"name_spaces={self.name_spaces!r}, " - f"kml_geometry={self.kml_geometry!r}, " - f"**{self._get_splat()}," - ")" - ) +class InnerBoundaryIs(BoundaryIs): + """Represents the inner boundary of a polygon in KML.""" @classmethod def get_tag_name(cls) -> str: """Return the tag name of the element.""" return "innerBoundaryIs" - @property - def geometry(self) -> Optional[geo.LinearRing]: - """ - Return the list of LinearRing objects representing the inner boundary. - - If no inner boundary geometries exist, returns None. - """ - return self.kml_geometry.geometry if self.kml_geometry else None - registry.register( - InnerBoundaryIs, + BoundaryIs, item=RegistryItem( ns_ids=("kml",), classes=(LinearRing,), diff --git a/fastkml/utils.py b/fastkml/utils.py index 604d422b..2851cb5c 100644 --- a/fastkml/utils.py +++ b/fastkml/utils.py @@ -88,7 +88,4 @@ def find( The first instance of the given type in the given object or None if not found. """ - try: - return next(find_all(obj, of_type=of_type, **kwargs)) - except StopIteration: - return None + return next(find_all(obj, of_type=of_type, **kwargs), None) diff --git a/tests/geometries/boundaries_test.py b/tests/geometries/boundaries_test.py index 3ee79abc..8e422a68 100644 --- a/tests/geometries/boundaries_test.py +++ b/tests/geometries/boundaries_test.py @@ -22,6 +22,7 @@ import pygeoif.geometry as geo import pytest +import fastkml from fastkml.exceptions import GeometryError from fastkml.geometry import Coordinates from fastkml.geometry import InnerBoundaryIs @@ -122,6 +123,22 @@ def test_read_inner_boundary_multiple_linestrings(self) -> None: ((1, 4), (2, 0), (0, 0), (1, 4)), ) + def test_inner_boundary_repr_roundtrip(self) -> None: + """Test that repr(obj) can be eval'd back to obj.""" + coords = ((1, 2), (2, 0), (0, 0), (1, 2)) + inner_boundary = InnerBoundaryIs( + kml_geometry=LinearRing(kml_coordinates=Coordinates(coords=coords)), + ) + + assert inner_boundary == eval( # noqa: S307 + repr(inner_boundary), + {}, + { + "fastkml": fastkml, + "LinearRing": geo.LinearRing, + }, + ) + class TestBoundariesLxml(Lxml, TestBoundaries): pass diff --git a/tests/geometries/point_test.py b/tests/geometries/point_test.py index 888be317..32fb08df 100644 --- a/tests/geometries/point_test.py +++ b/tests/geometries/point_test.py @@ -196,6 +196,30 @@ def test_from_string_uppercase_altitude_mode_strict(self) -> None: "", ) + def test_from_string_invalid_altitude_mode_strict(self) -> None: + with pytest.raises( + KMLParseError, + match=r"^Error parsing '<", + ): + assert Point.from_string( + '' + "INVALID" + "1.000000,2.000000" + "", + ) + + def test_from_string_invalid_altitude_mode_relaxed(self) -> None: + point = Point.from_string( + '' + "invalid" + "1.000000,2.000000" + "", + strict=False, + ) + + assert point.geometry == geo.Point(1, 2) + assert not point.altitude_mode + def test_from_string_3d(self) -> None: """Test the from_string method for a 3 dimensional point.""" point = Point.from_string( diff --git a/tests/gx_test.py b/tests/gx_test.py index 33c6f8bc..8ad064d6 100644 --- a/tests/gx_test.py +++ b/tests/gx_test.py @@ -18,6 +18,7 @@ import datetime import pygeoif.geometry as geo +import pytest from dateutil.tz import tzoffset from dateutil.tz import tzutc @@ -175,6 +176,48 @@ def test_track_from_track_items(self) -> None: assert "angles>" in track.to_string() assert ">0.0 0.0 0.0 None: + whens = [ + KmlDateTime( + datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc), + ), + ] + coords = [(1, 2)] + + track = Track( + whens=whens, + coords=coords, + ) + + assert "when>" in track.to_string() + assert ">2023-01-01T00:00:00+00:00" in track.to_string() + assert ">1 2 None: + whens = [ + KmlDateTime( + datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc), + ), + ] + coords = [(1, 2)] + time1 = KmlDateTime( + datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc), + ) + angle = Angle() + track_items = [TrackItem(when=time1, coord=geo.Point(1, 2), angle=angle)] + + with pytest.raises( + ValueError, + match="^Cannot specify both geometry and track_items$", + ): + Track( + whens=whens, + coords=coords, + track_items=track_items, + ) + def test_track_precision(self) -> None: track = Track( id="x", @@ -335,6 +378,20 @@ def test_track_from_str_invalid_when(self) -> None: assert track.track_items == [] + def test_track_from_str_invalid_coord(self) -> None: + doc = """ + + 2010-02-14T02:02:09Z + 45.54676 66.2342 77.0 + XYZ 37.371915 156.000000 + + """ + + track = Track.from_string(doc, strict=False) + + assert track.track_items == [] + class TestMultiTrack(StdLibrary): """Test gx.MultiTrack."""