From f35b9423a6bb8e35f0ceb79ced4e973bfce653da Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 14 Oct 2023 21:52:34 +0100 Subject: [PATCH] Scaffold Track #242 --- fastkml/geometry.py | 4 +- fastkml/gx.py | 61 +++++++++++++++++++++++++------ tests/geometries/geometry_test.py | 8 ---- tests/gx_test.py | 47 ++++++++++++++++++++++++ tests/oldunit_test.py | 43 ---------------------- 5 files changed, 100 insertions(+), 63 deletions(-) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index b66e8dd6..66ba4923 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -518,7 +518,9 @@ def __init__( extrude: Optional[bool] = False, tessellate: Optional[bool] = False, altitude_mode: Optional[AltitudeMode] = None, - geometry: Optional[AnyGeometryType] = None, + geometry: Union[ + Optional[AnyGeometryType], Sequence[Optional[geo.Point]] + ] = None, ) -> None: """ diff --git a/fastkml/gx.py b/fastkml/gx.py index 80c58102..7ba1c09d 100644 --- a/fastkml/gx.py +++ b/fastkml/gx.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012 - 2022 Christian Ledermann +# Copyright (C) 2012 - 2023 Christian Ledermann # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free @@ -75,20 +75,21 @@ The complete XML schema for elements in this extension namespace is located at http://developers.google.com/kml/schema/kml22gx.xsd. """ - +import datetime import logging from typing import List from typing import Optional +from typing import Sequence from typing import Union from typing import cast -from pygeoif.geometry import GeometryCollection -from pygeoif.geometry import LineString -from pygeoif.geometry import MultiLineString +import pygeoif.geometry as geo from pygeoif.types import PointType from fastkml.config import GXNS as NS +from fastkml.enums import AltitudeMode from fastkml.geometry import Geometry +from fastkml.geometry import _Geometry from fastkml.types import Element logger = logging.getLogger(__name__) @@ -107,12 +108,12 @@ def __init__( super().__init__(ns, id) self.ns = NS if ns is None else ns - def _get_geometry(self, element: Element) -> Optional[LineString]: + def _get_geometry(self, element: Element) -> Optional[geo.LineString]: # Track if element.tag == (f"{self.ns}Track"): coords = self._get_coordinates(element) self._get_geometry_spec(element) - return LineString( + return geo.LineString( coords, ) return None @@ -120,7 +121,7 @@ def _get_geometry(self, element: Element) -> Optional[LineString]: def _get_multigeometry( self, element: Element, - ) -> Union[MultiLineString, GeometryCollection, None]: + ) -> Union[geo.MultiLineString, geo.GeometryCollection, None]: # MultiTrack geoms = [] if element.tag == (f"{self.ns}MultiTrack"): @@ -128,16 +129,16 @@ def _get_multigeometry( for track in tracks: self._get_geometry_spec(track) geoms.append( - LineString( + geo.LineString( self._get_coordinates(track), ) ) geom_types = {geom.geom_type for geom in geoms} if len(geom_types) > 1: - return GeometryCollection(geoms) + return geo.GeometryCollection(geoms) if "LineString" in geom_types: - return MultiLineString.from_linestrings(*geoms) + return geo.MultiLineString.from_linestrings(*geoms) return None def _get_coordinates(self, element: Element) -> List[PointType]: @@ -150,4 +151,42 @@ def _get_coordinates(self, element: Element) -> List[PointType]: return [] # type: ignore[unreachable] +class Track(_Geometry): + """ + A track describes how an object moves through the world over a given time period. + + This feature allows you to create one visible object in Google Earth + (either a Point icon or a Model) that encodes multiple positions for the same object + for multiple times. In Google Earth, the time slider allows the user to move the + view through time, which animates the position of the object. + + Tracks are a more efficient mechanism for associating time data with visible + Features, since you create only one Feature, which can be associated with multiple + time elements as the object moves through space. + """ + + def __init__( + self, + *, + ns: Optional[str] = None, + id: Optional[str] = None, + target_id: Optional[str] = None, + extrude: Optional[bool] = False, + tessellate: Optional[bool] = False, + altitude_mode: Optional[AltitudeMode] = None, + geometry: Sequence[Optional[geo.Point]], + times: Optional[Sequence[Optional[datetime.datetime]]] = None, + ) -> None: + super().__init__( + ns=ns, + id=id, + target_id=target_id, + extrude=extrude, + tessellate=tessellate, + altitude_mode=altitude_mode, + geometry=geometry, + ) + self.times = times + + __all__ = ["GxGeometry"] diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index 24c190c9..19d4528f 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -27,10 +27,6 @@ from tests.base import StdLibrary -class TestStdLibrary(StdLibrary): - """Test with the standard library.""" - - class TestGetGeometry(StdLibrary): def test_altitude_mode(self) -> None: doc = """ @@ -392,10 +388,6 @@ def test_from_string_omitting_ns(self) -> None: assert g.tessellate is True -class TestLxml(Lxml, TestStdLibrary): - """Test with lxml.""" - - class TestGetGeometryLxml(Lxml, TestGetGeometry): """Test with lxml.""" diff --git a/tests/gx_test.py b/tests/gx_test.py index 66d43167..de29048d 100644 --- a/tests/gx_test.py +++ b/tests/gx_test.py @@ -15,6 +15,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """Test the gx classes.""" +from fastkml.gx import GxGeometry from tests.base import Lxml from tests.base import StdLibrary @@ -23,5 +24,51 @@ class TestStdLibrary(StdLibrary): """Test with the standard library.""" +class TestGetGxGeometry(StdLibrary): + def test_track(self) -> None: + doc = """ + 2020-01-01T00:00:00Z + 2020-01-01T00:10:00Z + 0.000000 0.000000 + 1.000000 1.000000 + """ + + g = GxGeometry() + g.from_string(doc) + assert g.geometry.__geo_interface__ == { + "type": "LineString", + "bbox": (0.0, 0.0, 1.0, 1.0), + "coordinates": ((0.0, 0.0), (1.0, 1.0)), + } + + def test_multitrack(self) -> None: + doc = """ + + + 2020-01-01T00:00:00Z + 2020-01-01T00:10:00Z + 0.000000 0.000000 + 1.000000 0.000000 + + + 2020-01-01T00:10:00Z + 2020-01-01T00:20:00Z + 0.000000 1.000000 + 1.000000 1.000000 + + + """ + + g = GxGeometry() + g.from_string(doc) + assert len(g.geometry) == 2 + + class TestLxml(Lxml, TestStdLibrary): """Test with lxml.""" + + +class TestLxmlGetGxGeometry(Lxml, TestGetGxGeometry): + """Test with lxml.""" diff --git a/tests/oldunit_test.py b/tests/oldunit_test.py index 348c372e..dde9bc86 100644 --- a/tests/oldunit_test.py +++ b/tests/oldunit_test.py @@ -31,7 +31,6 @@ from fastkml import kml from fastkml import styles from fastkml.geometry import Geometry -from fastkml.gx import GxGeometry try: import lxml @@ -1694,48 +1693,6 @@ def test_nested_multigeometry(self): assert len(list(second_multigeometry.geoms)) == 2 -class TestGetGxGeometry: - def test_track(self) -> None: - doc = """ - 2020-01-01T00:00:00Z - 2020-01-01T00:10:00Z - 0.000000 0.000000 - 1.000000 1.000000 - """ - - g = GxGeometry() - g.from_string(doc) - assert g.geometry.__geo_interface__ == { - "type": "LineString", - "bbox": (0.0, 0.0, 1.0, 1.0), - "coordinates": ((0.0, 0.0), (1.0, 1.0)), - } - - def test_multitrack(self) -> None: - doc = """ - - - 2020-01-01T00:00:00Z - 2020-01-01T00:10:00Z - 0.000000 0.000000 - 1.000000 0.000000 - - - 2020-01-01T00:10:00Z - 2020-01-01T00:20:00Z - 0.000000 1.000000 - 1.000000 1.000000 - - - """ - - g = GxGeometry() - g.from_string(doc) - assert len(g.geometry) == 2 - - class TestBaseFeature: def test_address_string(self) -> None: f = kml._Feature()