Skip to content

Commit

Permalink
Merge pull request #383 from cleder/374-add-hypothesis-tests-links
Browse files Browse the repository at this point in the history
374 add hypothesis tests for kml, containers and features
  • Loading branch information
cleder authored Nov 12, 2024
2 parents f5a7127 + dc38194 commit 3fa23eb
Show file tree
Hide file tree
Showing 60 changed files with 1,461 additions and 221 deletions.
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ repos:
rev: 7.1.1
hooks:
- id: flake8
additional_dependencies:
- flake8-cognitive-complexity
- flake8-comments
- flake8-dunder-all
- flake8-encodings
- flake8-expression-complexity
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
Expand Down
95 changes: 48 additions & 47 deletions docs/create_kml_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,53 +166,54 @@ Each placemark will have a time-span that covers the whole year:

.. code-block:: pycon
>>> styles = []
>>> folders = []
>>> for feature in shp.__geo_interface__["features"]:
... iso3_code = feature["properties"]["ADM0_A3"]
... geometry = shape(feature["geometry"])
... color = random.randint(0, 0xFFFFFF)
... styles.append(
... fastkml.styles.Style(
... id=iso3_code,
... styles=[
... fastkml.styles.LineStyle(color=f"33{color:06X}", width=2),
... fastkml.styles.PolyStyle(
... color=f"88{color:06X}",
... fill=True,
... outline=True,
... ),
... ],
... ),
... )
... style_url = fastkml.styles.StyleUrl(url=f"#{iso3_code}")
... folder = fastkml.containers.Folder(name=feature["properties"]["NAME"])
... co2_growth = 0
... for year in range(1995, 2023):
... co2_year = co2_pa[str(year)].get(iso3_code, 0)
... co2_growth += co2_year
... kml_geometry = create_kml_geometry(
... force_3d(geometry, co2_growth * 5_000),
... extrude=True,
... altitude_mode=AltitudeMode.relative_to_ground,
... )
... timespan = fastkml.times.TimeSpan(
... begin=fastkml.times.KmlDateTime(
... datetime.date(year, 1, 1), resolution=DateTimeResolution.year_month
... ),
... end=fastkml.times.KmlDateTime(
... datetime.date(year, 12, 31), resolution=DateTimeResolution.year_month
... ),
... )
... placemark = fastkml.features.Placemark(
... name=f"{feature['properties']['NAME']} - {year}",
... description=feature["properties"]["FORMAL_EN"],
... kml_geometry=kml_geometry,
... style_url=style_url,
... times=timespan,
... )
... folder.features.append(placemark)
... folders.append(folder)
>>> styles = []
>>> folders = []
>>> for feature in shp.__geo_interface__["features"]:
... iso3_code = feature["properties"]["ADM0_A3"]
... geometry = shape(feature["geometry"])
... color = random.randint(0, 0xFFFFFF)
... styles.append(
... fastkml.styles.Style(
... id=iso3_code,
... styles=[
... fastkml.styles.LineStyle(color=f"33{color:06X}", width=2),
... fastkml.styles.PolyStyle(
... color=f"88{color:06X}",
... fill=True,
... outline=True,
... ),
... ],
... ),
... )
... style_url = fastkml.styles.StyleUrl(url=f"#{iso3_code}")
... folder = fastkml.containers.Folder(name=feature["properties"]["NAME"])
... co2_growth = 0
... for year in range(1995, 2023):
... co2_year = co2_pa[str(year)].get(iso3_code, 0)
... co2_growth += co2_year
... kml_geometry = create_kml_geometry(
... force_3d(geometry, co2_growth * 5_000),
... extrude=True,
... altitude_mode=AltitudeMode.relative_to_ground,
... )
... timespan = fastkml.times.TimeSpan(
... begin=fastkml.times.KmlDateTime(
... datetime.date(year, 1, 1), resolution=DateTimeResolution.year_month
... ),
... end=fastkml.times.KmlDateTime(
... datetime.date(year, 12, 31), resolution=DateTimeResolution.year_month
... ),
... )
... placemark = fastkml.features.Placemark(
... name=f"{feature['properties']['NAME']} - {year}",
... description=feature["properties"]["FORMAL_EN"],
... kml_geometry=kml_geometry,
... style_url=style_url,
... times=timespan,
... )
... folder.features.append(placemark)
... folders.append(folder)
...
Finally, we create the KML object and write it to a file:

Expand Down
11 changes: 7 additions & 4 deletions docs/working_with_kml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ We could also search for all Points, which will also return the Points inside th
POINT Z (-123.3215766 49.2760338 0.0)
POINT Z (-123.2643704 49.3301853 0.0)
POINT Z (-123.2477084 49.2890857 0.0)
POINT Z (8.23 46.707 0.0)
``find_all`` can also search for arbitrary elements by their attributes, by passing the
Expand Down Expand Up @@ -152,11 +152,14 @@ And register the new element with the KML Document object:
The CascadingStyle object is now part of the KML document and can be accessed like any
other element.
Now we can create a new KML object and confirm that the new element is parsed correctly:
When parsing the document we have to skip the validation as the ``gx:CascadingStyle`` is
not in the XSD Schema.

Create a new KML object and confirm that the new element is parsed correctly:

.. code-block:: pycon
>>> cs_kml = KML.parse("examples/gx_cascading_style.kml")
>>> cs_kml = KML.parse("examples/gx_cascading_style.kml", validate=False)
>>> cs = find(cs_kml, of_type=CascadingStyle)
>>> cs.style # doctest: +ELLIPSIS
fastkml.styles.Style(...
Expand All @@ -181,7 +184,7 @@ Now we can remove the CascadingStyle from the document and have a look at the re
.. code-block:: pycon
>>> document.gx_cascading_style = []
>>> print(document.to_string(prettyprint=True))
>>> print(document)
<kml:Document xmlns:kml="http://www.opengis.net/kml/2.2">
<kml:name>Test2</kml:name>
<kml:StyleMap id="__managed_style_0D301BCC0014827EFCCB">
Expand Down
2 changes: 1 addition & 1 deletion examples/transform_cascading_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(
),
)

cs_kml = KML.parse(examples_dir / "gx_cascading_style.kml")
cs_kml = KML.parse(examples_dir / "gx_cascading_style.kml", validate=False)
document = find(cs_kml, of_type=Document)
for cascading_style in document.gx_cascading_style:
kml_style = cascading_style.style
Expand Down
1 change: 1 addition & 0 deletions fastkml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
multiple clients such as openlayers and google maps rather than to give you all
functionality that KML on google earth provides.
"""

from fastkml.about import __version__ # noqa: F401
from fastkml.atom import Author as AtomAuthor
from fastkml.atom import Contributor as AtomContributor
Expand Down
27 changes: 14 additions & 13 deletions fastkml/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from fastkml.base import _XMLObject
from fastkml.helpers import attribute_int_kwarg
from fastkml.helpers import attribute_text_kwarg
from fastkml.helpers import clean_string
from fastkml.helpers import int_attribute
from fastkml.helpers import subelement_text_kwarg
from fastkml.helpers import text_attribute
Expand Down Expand Up @@ -82,11 +83,11 @@ class Link(_AtomObject):
title, and length.
"""

href: str
rel: str
type: str
hreflang: str
title: str
href: Optional[str]
rel: Optional[str]
type: Optional[str]
hreflang: Optional[str]
title: Optional[str]
length: Optional[int]

def __init__(
Expand Down Expand Up @@ -133,11 +134,11 @@ def __init__(
"""
super().__init__(ns=ns, name_spaces=name_spaces, **kwargs)
self.href = href or ""
self.rel = rel or ""
self.type = type or ""
self.hreflang = hreflang or ""
self.title = title or ""
self.href = clean_string(href)
self.rel = clean_string(rel)
self.type = clean_string(type)
self.hreflang = clean_string(hreflang)
self.title = clean_string(title)
self.length = length

def __repr__(self) -> str:
Expand Down Expand Up @@ -285,9 +286,9 @@ def __init__(
"""
super().__init__(ns=ns, name_spaces=name_spaces, **kwargs)
self.name = name.strip() or None if name else None
self.uri = uri.strip() or None if uri else None
self.email = email.strip() or None if email else None
self.name = clean_string(name)
self.uri = clean_string(uri)
self.email = clean_string(email)

def __repr__(self) -> str:
"""
Expand Down
19 changes: 19 additions & 0 deletions fastkml/base.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

"""Abstract base classes."""

import logging
from typing import Any
from typing import Dict
Expand All @@ -28,6 +29,7 @@
from fastkml.enums import Verbosity
from fastkml.registry import registry
from fastkml.types import Element
from fastkml.validator import validate

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -204,6 +206,23 @@ def to_string(
),
)

def validate(self) -> Optional[bool]:
"""
Validate the KML object against the XML schema.
Returns
-------
Optional[bool]
True if the object is valid, None if the XMLSchema is not available.
Raises
------
AssertionError
If the object is not valid.
"""
return validate(element=self.etree_element())

def _get_splat(self) -> Dict[str, Any]:
"""
Get the keyword arguments as a dictionary.
Expand Down
3 changes: 2 additions & 1 deletion fastkml/config.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

"""Frequently used constants and configuration options."""

import logging
import warnings
from types import ModuleType
Expand All @@ -36,7 +37,7 @@

except ImportError: # pragma: no cover
warnings.warn("Package `lxml` missing. Pretty print will be disabled") # noqa: B028
import xml.etree.ElementTree as etree # noqa: N813
import xml.etree.ElementTree as etree # noqa: N813, ICN001


logger = logging.getLogger(__name__)
Expand Down
12 changes: 9 additions & 3 deletions fastkml/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Container classes for KML elements."""

import logging
import urllib.parse as urlparse
from typing import Any
Expand All @@ -27,6 +28,7 @@
from fastkml import gx
from fastkml.data import ExtendedData
from fastkml.data import Schema
from fastkml.features import NetworkLink
from fastkml.features import Placemark
from fastkml.features import Snippet
from fastkml.features import _Feature
Expand All @@ -37,6 +39,8 @@
from fastkml.geometry import Polygon
from fastkml.helpers import xml_subelement_list
from fastkml.helpers import xml_subelement_list_kwarg
from fastkml.overlays import GroundOverlay
from fastkml.overlays import PhotoOverlay
from fastkml.registry import RegistryItem
from fastkml.registry import registry
from fastkml.styles import Style
Expand Down Expand Up @@ -165,6 +169,8 @@ class Folder(_Container):
A Folder is used to arrange other Features hierarchically.
It may contain Folders, Placemarks, NetworkLinks, or Overlays.
https://developers.google.com/kml/documentation/kmlreference#folder
"""


Expand Down Expand Up @@ -199,7 +205,7 @@ def __init__(
styles: Optional[Iterable[Union[Style, StyleMap]]] = None,
region: Optional[Region] = None,
extended_data: Optional[ExtendedData] = None,
features: Optional[List[_Feature]] = None,
features: Optional[Iterable[_Feature]] = None,
schemata: Optional[Iterable[Schema]] = None,
**kwargs: Any,
) -> None:
Expand Down Expand Up @@ -322,8 +328,8 @@ def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]:
RegistryItem(
ns_ids=("kml",),
attr_name="features",
node_name="Folder,Placemark,Document",
classes=(Folder, Placemark, Document),
node_name="Folder,Placemark,Document,GroundOverlay,PhotoOverlay,NetworkLink",
classes=(Document, Folder, Placemark, GroundOverlay, PhotoOverlay, NetworkLink),
get_kwarg=xml_subelement_list_kwarg,
set_element=xml_subelement_list,
),
Expand Down
21 changes: 11 additions & 10 deletions fastkml/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from fastkml.exceptions import KMLSchemaError
from fastkml.helpers import attribute_enum_kwarg
from fastkml.helpers import attribute_text_kwarg
from fastkml.helpers import clean_string
from fastkml.helpers import enum_attribute
from fastkml.helpers import node_text
from fastkml.helpers import node_text_kwarg
Expand Down Expand Up @@ -116,9 +117,9 @@ def __init__(
name_spaces=name_spaces,
**kwargs,
)
self.name = name.strip() or None if name else None
self.name = clean_string(name)
self.type_ = type_ or None
self.display_name = display_name.strip() or None if display_name else None
self.display_name = clean_string(display_name)

def __repr__(self) -> str:
"""
Expand Down Expand Up @@ -246,9 +247,9 @@ def __init__(
name_spaces=name_spaces,
**kwargs,
)
self.name = name.strip() or None if name else None
self.name = clean_string(name)
self.fields = list(fields) if fields else []
self.id = id.strip() or None if id else None
self.id = clean_string(id)

def __repr__(self) -> str:
"""
Expand Down Expand Up @@ -364,9 +365,9 @@ def __init__(
target_id=target_id,
**kwargs,
)
self.name = name.strip() or None if name else None
self.value = value.strip() or None if value else None
self.display_name = display_name.strip() or None if display_name else None
self.name = clean_string(name)
self.value = clean_string(value)
self.display_name = clean_string(display_name)

def __repr__(self) -> str:
"""
Expand Down Expand Up @@ -471,8 +472,8 @@ def __init__(
name_spaces=name_spaces,
**kwargs,
)
self.name = name.strip() or None if name else None
self.value = value.strip() or None if value else None
self.name = clean_string(name)
self.value = clean_string(value)

def __repr__(self) -> str:
"""
Expand Down Expand Up @@ -579,7 +580,7 @@ def __init__(
target_id=target_id,
**kwargs,
)
self.schema_url = schema_url.strip() or None if schema_url else None
self.schema_url = clean_string(schema_url)
self.data = list(data) if data else []

def __repr__(self) -> str:
Expand Down
Loading

0 comments on commit 3fa23eb

Please sign in to comment.