From e38c8886bad4bc56a353687709284587aa4b6522 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Sat, 14 Nov 2020 17:18:14 -0800
Subject: [PATCH 1/9] Initial read support of Track/MultiTrack
---
fastkml/gx.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
fastkml/kml.py | 15 ++++++++++-
2 files changed, 83 insertions(+), 1 deletion(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index 1ddd9a66..3a4ce9b3 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -77,5 +77,74 @@
located at http://developers.google.com/kml/schema/kml22gx.xsd.
"""
+try:
+ from shapely.geometry.linestring import LineString
+ from shapely.geometry.multilinestring import MultiLineString
+
+except ImportError:
+ from pygeoif.geometry import LineString, MultiLineString
+
+from xml.etree.ElementTree import Element
+
+from pygeoif.geometry import GeometryCollection
+
+from .config import etree
+from .config import GXNS as NS
+from .geometry import Geometry
+
+import fastkml.config as config
+
import logging
logger = logging.getLogger('fastkml.gx')
+
+
+class GxGeometry(Geometry):
+ """
+
+ """
+ def __init__(
+ self, ns=None, id=None,
+ ):
+ """
+ gxgeometry: a read-only subclass of geometry supporting gx: features, like gx:Track
+
+
+ """
+ if ns is None:
+ self.ns = NS
+ else:
+ self.ns = ns
+ super(Geometry, self).__init__(ns, id)
+
+ def _get_geometry(self, element):
+ # Track
+ if element.tag == ('%sTrack' % self.ns):
+ coords = self._get_coordinates(element)
+ self._get_geometry_spec(element)
+ return LineString(coords)
+
+ def _get_multigeometry(self, element):
+ # MultiTrack
+ geoms = []
+ if element.tag == ('%sMultiGeometry' % self.ns):
+ tracks = element.find("%sTrack" % self.ns)
+ if tracks:
+ for track in tracks:
+ self._get_geometry_spec(track)
+ geoms.append()
+
+ if len(geoms) > 0:
+ geom_types = []
+ for geom in geoms:
+ geom_types.append(geom.geom_type)
+ geom_types = list(set(geom_types))
+ if len(geom_types) > 1:
+ return GeometryCollection(geoms)
+ if geom_types[0] == 'LineString':
+ return MultiLineString(geoms)
+
+ def _get_coordinates(self, element):
+ coordinates = element.findall('%scoords' % self.ns)
+ if coordinates is not None:
+ coords = [coord.text.strip().split() for coord in coordinates]
+ return coords
diff --git a/fastkml/kml.py b/fastkml/kml.py
index 22321e82..801c239b 100644
--- a/fastkml/kml.py
+++ b/fastkml/kml.py
@@ -35,6 +35,7 @@
# from .geometry import MultiPoint, MultiLineString, MultiPolygon
# from .geometry import LinearRing
from .geometry import Geometry
+from .gx import GxGeometry
from datetime import datetime, date
@@ -54,7 +55,7 @@
from .styles import StyleUrl, Style, StyleMap, _StyleSelector
import fastkml.atom as atom
-# import fastkml.gx as gx
+import fastkml.gx as gx
import fastkml.config as config
try:
@@ -1095,6 +1096,18 @@ def from_element(self, element):
geom.from_element(multigeometry)
self._geometry = geom
return
+ track = element.find("%sTrack" % gx.NS)
+ if track is not None:
+ geom = GxGeometry(ns=gx.NS)
+ geom.from_element(track)
+ self._geometry = geom
+ return
+ multitrack = element.find("%sMultiTrack" % gx.NS)
+ if line is not None:
+ geom = GxGeometry(ns=gx.NS)
+ geom.from_element(multitrack)
+ self._geometry = geom
+ return
logger.warn('No geometries found')
logger.debug(u'Problem with element: {}'.format(etree.tostring(element)))
From 287da2fabf53247345700c996ba8c6a7ecd024d8 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Tue, 17 Nov 2020 22:25:33 -0800
Subject: [PATCH 2/9] Fixing a bunch of copypasted strings
---
fastkml/gx.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index 3a4ce9b3..b0a24c86 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -110,11 +110,11 @@ def __init__(
"""
+ super(GxGeometry, self).__init__(ns, id)
if ns is None:
self.ns = NS
else:
self.ns = ns
- super(Geometry, self).__init__(ns, id)
def _get_geometry(self, element):
# Track
@@ -126,12 +126,12 @@ def _get_geometry(self, element):
def _get_multigeometry(self, element):
# MultiTrack
geoms = []
- if element.tag == ('%sMultiGeometry' % self.ns):
- tracks = element.find("%sTrack" % self.ns)
+ if element.tag == ('%sMultiTrack' % self.ns):
+ tracks = element.findall("%sTrack" % self.ns)
if tracks:
for track in tracks:
self._get_geometry_spec(track)
- geoms.append()
+ geoms.append(LineString(self._get_coordinates(track)))
if len(geoms) > 0:
geom_types = []
@@ -144,7 +144,8 @@ def _get_multigeometry(self, element):
return MultiLineString(geoms)
def _get_coordinates(self, element):
- coordinates = element.findall('%scoords' % self.ns)
+ coordinates = element.findall('%scoord' % self.ns)
if coordinates is not None:
- coords = [coord.text.strip().split() for coord in coordinates]
+ coords = [[float(c) for c in coord.text.strip().split()]
+ for coord in coordinates]
return coords
From a1bb0aa6f98f63f3ba24c3d2f39343ac10eef324 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Tue, 17 Nov 2020 22:27:23 -0800
Subject: [PATCH 3/9] Add testcases for GxGeometry
---
fastkml/test_main.py | 44 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/fastkml/test_main.py b/fastkml/test_main.py
index 6d149bb2..aa9c4355 100644
--- a/fastkml/test_main.py
+++ b/fastkml/test_main.py
@@ -25,7 +25,6 @@
from fastkml import base
from fastkml import atom
from fastkml import config
-from fastkml import gx # NOQA
import datetime
from dateutil.tz import tzutc, tzoffset
@@ -37,6 +36,8 @@
from fastkml.geometry import LinearRing, GeometryCollection
from fastkml.geometry import Geometry
+from fastkml.gx import GxGeometry
+
class BaseClassesTestCase(unittest.TestCase):
""" BaseClasses must raise a NotImplementedError on etree_element
@@ -2123,6 +2124,46 @@ def test_geometrycollection(self):
self.assertEqual(len(g.geometry), 2)
self.assertEqual(g.geometry.geom_type, 'GeometryCollection')
+class GetGxGeometryTestCase(unittest.TestCase):
+
+ def test_track(self):
+ 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)
+ self.assertEqual(
+ g.geometry.__geo_interface__,
+ {
+ 'type': 'LineString',
+ 'coordinates': ((0.0, 0.0), (1.0, 1.0))})
+
+ def test_multitrack(self):
+ 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)
+ self.assertEqual(len(g.geometry), 2)
+
class Force3DTestCase(unittest.TestCase):
@@ -2588,6 +2629,7 @@ def test_suite():
suite.addTest(unittest.makeSuite(AtomTestCase))
suite.addTest(unittest.makeSuite(SetGeometryTestCase))
suite.addTest(unittest.makeSuite(GetGeometryTestCase))
+ suite.addTest(unittest.makeSuite(GetGxGeometryTestCase))
suite.addTest(unittest.makeSuite(Force3DTestCase))
suite.addTest(unittest.makeSuite(BaseOverlayTestCase))
suite.addTest(unittest.makeSuite(GroundOverlayTestCase))
From 114241e668c7a950be1b74811c4d8a87e5a15742 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Mon, 23 Nov 2020 18:41:39 -0800
Subject: [PATCH 4/9] Cleaning up unneeded imports
---
fastkml/gx.py | 5 -----
fastkml/kml.py | 5 ++---
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index b0a24c86..cd11c30b 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -84,16 +84,11 @@
except ImportError:
from pygeoif.geometry import LineString, MultiLineString
-from xml.etree.ElementTree import Element
-
from pygeoif.geometry import GeometryCollection
-from .config import etree
from .config import GXNS as NS
from .geometry import Geometry
-import fastkml.config as config
-
import logging
logger = logging.getLogger('fastkml.gx')
diff --git a/fastkml/kml.py b/fastkml/kml.py
index 801c239b..c3ca210f 100644
--- a/fastkml/kml.py
+++ b/fastkml/kml.py
@@ -35,7 +35,6 @@
# from .geometry import MultiPoint, MultiLineString, MultiPolygon
# from .geometry import LinearRing
from .geometry import Geometry
-from .gx import GxGeometry
from datetime import datetime, date
@@ -1098,13 +1097,13 @@ def from_element(self, element):
return
track = element.find("%sTrack" % gx.NS)
if track is not None:
- geom = GxGeometry(ns=gx.NS)
+ geom = gx.GxGeometry(ns=gx.NS)
geom.from_element(track)
self._geometry = geom
return
multitrack = element.find("%sMultiTrack" % gx.NS)
if line is not None:
- geom = GxGeometry(ns=gx.NS)
+ geom = gx.GxGeometry(ns=gx.NS)
geom.from_element(multitrack)
self._geometry = geom
return
From 3a0b43890a896a9c989f13b671ac95aed8364891 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Mon, 23 Nov 2020 18:42:32 -0800
Subject: [PATCH 5/9] Line length and spacing
---
fastkml/gx.py | 6 +++---
fastkml/test_main.py | 1 +
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index cd11c30b..ca38ea90 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -97,13 +97,13 @@ class GxGeometry(Geometry):
"""
"""
+
def __init__(
self, ns=None, id=None,
):
"""
- gxgeometry: a read-only subclass of geometry supporting gx: features, like gx:Track
-
-
+ gxgeometry: a read-only subclass of geometry supporting gx: features,
+ like gx:Track
"""
super(GxGeometry, self).__init__(ns, id)
if ns is None:
diff --git a/fastkml/test_main.py b/fastkml/test_main.py
index aa9c4355..958290aa 100644
--- a/fastkml/test_main.py
+++ b/fastkml/test_main.py
@@ -2124,6 +2124,7 @@ def test_geometrycollection(self):
self.assertEqual(len(g.geometry), 2)
self.assertEqual(g.geometry.geom_type, 'GeometryCollection')
+
class GetGxGeometryTestCase(unittest.TestCase):
def test_track(self):
From 230982f3a1e8ea3f360067df5e4775d75b24619b Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Mon, 23 Nov 2020 18:42:42 -0800
Subject: [PATCH 6/9] Simplifying code
---
fastkml/gx.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index ca38ea90..50bc4c70 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -128,11 +128,8 @@ def _get_multigeometry(self, element):
self._get_geometry_spec(track)
geoms.append(LineString(self._get_coordinates(track)))
- if len(geoms) > 0:
- geom_types = []
- for geom in geoms:
- geom_types.append(geom.geom_type)
- geom_types = list(set(geom_types))
+ if geoms:
+ geom_types = list({geom.geom_type for geom in geoms})
if len(geom_types) > 1:
return GeometryCollection(geoms)
if geom_types[0] == 'LineString':
From b7670da85ae3435dc404416379a4827a8e554232 Mon Sep 17 00:00:00 2001
From: Sourcery AI <>
Date: Tue, 24 Nov 2020 02:43:37 +0000
Subject: [PATCH 7/9] 'Refactored by Sourcery'
---
fastkml/gx.py | 8 +---
fastkml/kml.py | 121 ++++++++++++++++++++++---------------------------
2 files changed, 56 insertions(+), 73 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index 50bc4c70..b80483b9 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -106,10 +106,7 @@ def __init__(
like gx:Track
"""
super(GxGeometry, self).__init__(ns, id)
- if ns is None:
- self.ns = NS
- else:
- self.ns = ns
+ self.ns = NS if ns is None else ns
def _get_geometry(self, element):
# Track
@@ -138,6 +135,5 @@ def _get_multigeometry(self, element):
def _get_coordinates(self, element):
coordinates = element.findall('%scoord' % self.ns)
if coordinates is not None:
- coords = [[float(c) for c in coord.text.strip().split()]
+ return [[float(c) for c in coord.text.strip().split()]
for coord in coordinates]
- return coords
diff --git a/fastkml/kml.py b/fastkml/kml.py
index c3ca210f..1d6ca110 100644
--- a/fastkml/kml.py
+++ b/fastkml/kml.py
@@ -78,10 +78,7 @@ def __init__(self, ns=None):
"""
self._features = []
- if ns is None:
- self.ns = config.KMLNS
- else:
- self.ns = ns
+ self.ns = config.KMLNS if ns is None else ns
def from_string(self, xml_string):
""" create a KML object from a xml string"""
@@ -93,26 +90,26 @@ def from_string(self, xml_string):
else:
element = etree.XML(xml_string)
- if element.tag.endswith('kml'):
- ns = element.tag.rstrip('kml')
- documents = element.findall('%sDocument' % ns)
- for document in documents:
- feature = Document(ns)
- feature.from_element(document)
- self.append(feature)
- folders = element.findall('%sFolder' % ns)
- for folder in folders:
- feature = Folder(ns)
- feature.from_element(folder)
- self.append(feature)
- placemarks = element.findall('%sPlacemark' % ns)
- for placemark in placemarks:
- feature = Placemark(ns)
- feature.from_element(placemark)
- self.append(feature)
- else:
+ if not element.tag.endswith('kml'):
raise TypeError
+ ns = element.tag.rstrip('kml')
+ documents = element.findall('%sDocument' % ns)
+ for document in documents:
+ feature = Document(ns)
+ feature.from_element(document)
+ self.append(feature)
+ folders = element.findall('%sFolder' % ns)
+ for folder in folders:
+ feature = Folder(ns)
+ feature.from_element(folder)
+ self.append(feature)
+ placemarks = element.findall('%sPlacemark' % ns)
+ for placemark in placemarks:
+ feature = Placemark(ns)
+ feature.from_element(placemark)
+ self.append(feature)
+
def etree_element(self):
# self.ns may be empty, which leads to unprefixed kml elements.
# However, in this case the xlmns should still be mentioned on the kml
@@ -309,10 +306,7 @@ def timeStamp(self):
@timeStamp.setter
def timeStamp(self, dt):
- if dt is None:
- self._time_stamp = None
- else:
- self._time_stamp = TimeStamp(timestamp=dt)
+ self._time_stamp = None if dt is None else TimeStamp(timestamp=dt)
if self._time_span is not None:
logger.warn('Setting a TimeStamp, TimeSpan deleted')
self._time_span = None
@@ -404,24 +398,25 @@ def styles(self):
@property
def snippet(self):
- if self._snippet:
- if isinstance(self._snippet, dict):
- text = self._snippet.get('text')
- if text:
- assert (isinstance(text, basestring))
- max_lines = self._snippet.get('maxLines', None)
- if max_lines is None:
- return {'text': text}
- elif int(max_lines) > 0:
- # if maxLines <=0 ignore it
- return {'text': text, 'maxLines': max_lines}
- elif isinstance(self._snippet, basestring):
- return self._snippet
- else:
- raise ValueError(
- "Snippet must be dict of "
- "{'text':t, 'maxLines':i} or string"
- )
+ if not self._snippet:
+ return
+ if isinstance(self._snippet, dict):
+ text = self._snippet.get('text')
+ if text:
+ assert (isinstance(text, basestring))
+ max_lines = self._snippet.get('maxLines', None)
+ if max_lines is None:
+ return {'text': text}
+ elif int(max_lines) > 0:
+ # if maxLines <=0 ignore it
+ return {'text': text, 'maxLines': max_lines}
+ elif isinstance(self._snippet, basestring):
+ return self._snippet
+ else:
+ raise ValueError(
+ "Snippet must be dict of "
+ "{'text':t, 'maxLines':i} or string"
+ )
@snippet.setter
def snippet(self, snip=None):
@@ -526,16 +521,10 @@ def from_element(self, element):
self.description = description.text
visibility = element.find('%svisibility' % self.ns)
if visibility is not None:
- if visibility.text in ['1', 'true']:
- self.visibility = 1
- else:
- self.visibility = 0
+ self.visibility = 1 if visibility.text in ['1', 'true'] else 0
isopen = element.find('%sopen' % self.ns)
if isopen is not None:
- if isopen.text in ['1', 'true']:
- self.isopen = 1
- else:
- self.isopen = 0
+ self.isopen = 1 if isopen.text in ['1', 'true'] else 0
styles = element.findall('%sStyle' % self.ns)
for style in styles:
s = Style(self.ns)
@@ -971,8 +960,7 @@ class Document(_Container):
def schemata(self):
if self._schemata:
- for schema in self._schemata:
- yield schema
+ yield from self._schemata
def append_schema(self, schema):
if self._schemata is None:
@@ -1173,7 +1161,7 @@ def parse_str(self, datestr):
year = int(datestr.split('-')[0])
month = int(datestr.split('-')[1])
dt = datetime(year, month, day)
- elif len(datestr) == 8 or len(datestr) == 10:
+ elif len(datestr) in [8, 10]:
resolution = 'date'
dt = dateutil.parser.parse(datestr)
elif len(datestr) > 10:
@@ -1292,14 +1280,16 @@ def __init__(self, ns=None, id=None, name=None, fields=None):
@property
def simple_fields(self):
- sfs = []
- for simple_field in self._simple_fields:
- if simple_field.get('type') and simple_field.get('name'):
- sfs.append({
- 'type': simple_field['type'],
- 'name': simple_field['name'],
- 'displayName': simple_field.get('displayName')
- })
+ sfs = [
+ {
+ 'type': simple_field['type'],
+ 'name': simple_field['name'],
+ 'displayName': simple_field.get('displayName'),
+ }
+ for simple_field in self._simple_fields
+ if simple_field.get('type') and simple_field.get('name')
+ ]
+
return tuple(sfs)
@simple_fields.setter
@@ -1364,10 +1354,7 @@ def from_element(self, element):
sfname = simple_field.get('name')
sftype = simple_field.get('type')
display_name = simple_field.find('%sdisplayName' % self.ns)
- if display_name is not None:
- sfdisplay_name = display_name.text
- else:
- sfdisplay_name = None
+ sfdisplay_name = display_name.text if display_name is not None else None
self.append(sftype, sfname, sfdisplay_name)
def etree_element(self):
From 8e01d2d4fd77502fc2d7864c7e4a5670357bdf5a Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Sun, 29 Nov 2020 12:36:42 -0800
Subject: [PATCH 8/9] Simplify use of sets in geom_types
---
fastkml/gx.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index 50bc4c70..eb3087e5 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -94,9 +94,6 @@
class GxGeometry(Geometry):
- """
-
- """
def __init__(
self, ns=None, id=None,
@@ -129,10 +126,10 @@ def _get_multigeometry(self, element):
geoms.append(LineString(self._get_coordinates(track)))
if geoms:
- geom_types = list({geom.geom_type for geom in geoms})
+ geom_types = {geom.geom_type for geom in geoms}
if len(geom_types) > 1:
return GeometryCollection(geoms)
- if geom_types[0] == 'LineString':
+ if 'LineString' in geom_types:
return MultiLineString(geoms)
def _get_coordinates(self, element):
From b81d0722a8d7025bba7fb297f7c43ed002a40e92 Mon Sep 17 00:00:00 2001
From: Evan Derickson <3587185+dericke@users.noreply.github.com>
Date: Sun, 29 Nov 2020 13:44:46 -0800
Subject: [PATCH 9/9] Remove unneeded conditionals
---
fastkml/gx.py | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/fastkml/gx.py b/fastkml/gx.py
index bb6fca44..bb8156ca 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -117,20 +117,20 @@ def _get_multigeometry(self, element):
geoms = []
if element.tag == ('%sMultiTrack' % self.ns):
tracks = element.findall("%sTrack" % self.ns)
- if tracks:
- for track in tracks:
- self._get_geometry_spec(track)
- geoms.append(LineString(self._get_coordinates(track)))
-
- if geoms:
- geom_types = {geom.geom_type for geom in geoms}
- if len(geom_types) > 1:
- return GeometryCollection(geoms)
- if 'LineString' in geom_types:
- return MultiLineString(geoms)
+ for track in tracks:
+ self._get_geometry_spec(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(geoms)
def _get_coordinates(self, element):
coordinates = element.findall('%scoord' % self.ns)
if coordinates is not None:
- return [[float(c) for c in coord.text.strip().split()]
- for coord in coordinates]
+ return [
+ [float(c) for c in coord.text.strip().split()]
+ for coord in coordinates
+ ]