Skip to content

Commit

Permalink
Scaffold Track #242
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Oct 14, 2023
1 parent 4e77361 commit f35b942
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 63 deletions.
4 changes: 3 additions & 1 deletion fastkml/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down
61 changes: 50 additions & 11 deletions fastkml/gx.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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__)
Expand All @@ -107,37 +108,37 @@ 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

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"):
tracks = element.findall(f"{self.ns}Track")
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]:
Expand All @@ -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"]
8 changes: 0 additions & 8 deletions tests/geometries/geometry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """<kml:Point xmlns:kml="http://www.opengis.net/kml/2.2">
Expand Down Expand Up @@ -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."""

Expand Down
47 changes: 47 additions & 0 deletions tests/gx_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -23,5 +24,51 @@ class TestStdLibrary(StdLibrary):
"""Test with the standard library."""


class TestGetGxGeometry(StdLibrary):
def test_track(self) -> None:
doc = """<gx:Track xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2">
<when>2020-01-01T00:00:00Z</when>
<when>2020-01-01T00:10:00Z</when>
<gx:coord>0.000000 0.000000</gx:coord>
<gx:coord>1.000000 1.000000</gx:coord>
</gx:Track>"""

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 = """
<gx:MultiTrack xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2">
<gx:Track>
<when>2020-01-01T00:00:00Z</when>
<when>2020-01-01T00:10:00Z</when>
<gx:coord>0.000000 0.000000</gx:coord>
<gx:coord>1.000000 0.000000</gx:coord>
</gx:Track>
<gx:Track>
<when>2020-01-01T00:10:00Z</when>
<when>2020-01-01T00:20:00Z</when>
<gx:coord>0.000000 1.000000</gx:coord>
<gx:coord>1.000000 1.000000</gx:coord>
</gx:Track>
</gx:MultiTrack>
"""

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."""
43 changes: 0 additions & 43 deletions tests/oldunit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1694,48 +1693,6 @@ def test_nested_multigeometry(self):
assert len(list(second_multigeometry.geoms)) == 2


class TestGetGxGeometry:
def test_track(self) -> None:
doc = """<gx:Track xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2">
<when>2020-01-01T00:00:00Z</when>
<when>2020-01-01T00:10:00Z</when>
<gx:coord>0.000000 0.000000</gx:coord>
<gx:coord>1.000000 1.000000</gx:coord>
</gx:Track>"""

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 = """
<gx:MultiTrack xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2">
<gx:Track>
<when>2020-01-01T00:00:00Z</when>
<when>2020-01-01T00:10:00Z</when>
<gx:coord>0.000000 0.000000</gx:coord>
<gx:coord>1.000000 0.000000</gx:coord>
</gx:Track>
<gx:Track>
<when>2020-01-01T00:10:00Z</when>
<when>2020-01-01T00:20:00Z</when>
<gx:coord>0.000000 1.000000</gx:coord>
<gx:coord>1.000000 1.000000</gx:coord>
</gx:Track>
</gx:MultiTrack>
"""

g = GxGeometry()
g.from_string(doc)
assert len(g.geometry) == 2


class TestBaseFeature:
def test_address_string(self) -> None:
f = kml._Feature()
Expand Down

0 comments on commit f35b942

Please sign in to comment.