Skip to content

Commit

Permalink
Merge pull request #281 from cleder/develop
Browse files Browse the repository at this point in the history
1.0 alpha 8
  • Loading branch information
cleder authored Nov 25, 2023
2 parents 5329175 + 9966ac0 commit ccc0839
Show file tree
Hide file tree
Showing 23 changed files with 3,372 additions and 2,746 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,13 @@ jobs:
--outdir dist/
- name: Publish distribution 📦 to Test PyPI for tags
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI for push to main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
...
9 changes: 5 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ repos:
rev: "v6.2.0"
hooks:
- id: rstcheck
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.0
hooks:
- id: mypy
additional_dependencies: [pygeoif, arrow]
# - repo: https://github.com/mgedmin/check-manifest
# rev: "0.49"
# hooks:
Expand All @@ -81,8 +86,4 @@ repos:
# rev: v1.0.1
# hooks:
# - id: rst-linter
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v0.910
# hooks:
# - id: mypy
...
10 changes: 7 additions & 3 deletions fastkml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@
from fastkml.atom import Author
from fastkml.atom import Contributor
from fastkml.atom import Link
from fastkml.containers import Document
from fastkml.containers import Folder
from fastkml.data import Data
from fastkml.data import ExtendedData
from fastkml.data import Schema
from fastkml.data import SchemaData
from fastkml.features import Placemark
from fastkml.kml import KML
from fastkml.kml import Document
from fastkml.kml import Folder
from fastkml.kml import Placemark
from fastkml.overlays import GroundOverlay
from fastkml.overlays import PhotoOverlay
from fastkml.styles import BalloonStyle
from fastkml.styles import IconStyle
from fastkml.styles import LabelStyle
Expand All @@ -53,11 +55,13 @@
"KML",
"Document",
"Folder",
"GroundOverlay",
"Placemark",
"TimeSpan",
"TimeStamp",
"ExtendedData",
"Data",
"PhotoOverlay",
"Schema",
"SchemaData",
"StyleUrl",
Expand Down
2 changes: 1 addition & 1 deletion fastkml/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
The only purpose of this module is to provide a version number for the package.
"""
__version__ = "1.0.a7"
__version__ = "1.0.a8"
30 changes: 0 additions & 30 deletions fastkml/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,6 @@ def etree_element(
)
return element

def from_element(self, element: Element) -> None:
"""
Load the KML Object from an Element.
This implementation is deprecated and will be replaced by class_from_element
making it a classmethod.
"""
if f"{self.ns}{self.__name__}" != element.tag:
msg = "Call of abstract base class, subclasses implement this!"
raise TypeError(msg)

def from_string(self, xml_string: str) -> None:
"""
Load the KML Object from serialized xml.
This implementation is deprecated and will be replaced by class_from_string
making it a classmethod.
"""
self.from_element(
cast(Element, config.etree.XML(xml_string)), # type: ignore[attr-defined]
)

def to_string(
self,
*,
Expand Down Expand Up @@ -252,14 +230,6 @@ def etree_element(
element.set("targetId", self.target_id)
return element

def from_element(self, element: Element) -> None:
"""Load the KML Object from an Element."""
super().from_element(element)
if element.get("id"):
self.id = element.get("id")
if element.get("targetId"):
self.target_id = element.get("targetId")

@classmethod
def _get_id(cls, element: Element, strict: bool) -> str:
return element.get("id") or ""
Expand Down
274 changes: 274 additions & 0 deletions fastkml/containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
"""Container classes for KML elements."""
import logging
import urllib.parse as urlparse
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Union

from fastkml import atom
from fastkml import gx
from fastkml.data import ExtendedData
from fastkml.data import Schema
from fastkml.enums import Verbosity
from fastkml.features import Placemark
from fastkml.features import Snippet
from fastkml.features import _Feature
from fastkml.geometry import LinearRing
from fastkml.geometry import LineString
from fastkml.geometry import MultiGeometry
from fastkml.geometry import Point
from fastkml.geometry import Polygon
from fastkml.styles import Style
from fastkml.styles import StyleMap
from fastkml.styles import StyleUrl
from fastkml.times import TimeSpan
from fastkml.times import TimeStamp
from fastkml.types import Element
from fastkml.views import Camera
from fastkml.views import LookAt
from fastkml.views import Region

logger = logging.getLogger(__name__)

KmlGeometry = Union[
Point,
LineString,
LinearRing,
Polygon,
MultiGeometry,
gx.MultiTrack,
gx.Track,
]


class _Container(_Feature):
"""
abstract element; do not create
A Container element holds one or more Features and allows the
creation of nested hierarchies.
subclasses are:
Document,
Folder.
"""

_features: Optional[List[_Feature]]

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[Dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
name: Optional[str] = None,
visibility: Optional[bool] = None,
isopen: Optional[bool] = None,
atom_link: Optional[atom.Link] = None,
atom_author: Optional[atom.Author] = None,
address: Optional[str] = None,
phone_number: Optional[str] = None,
snippet: Optional[Snippet] = None,
description: Optional[str] = None,
view: Optional[Union[Camera, LookAt]] = None,
times: Optional[Union[TimeSpan, TimeStamp]] = None,
style_url: Optional[StyleUrl] = None,
styles: Optional[Iterable[Union[Style, StyleMap]]] = None,
region: Optional[Region] = None,
extended_data: Optional[ExtendedData] = None,
# Container specific
features: Optional[List[_Feature]] = None,
) -> None:
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
name=name,
visibility=visibility,
isopen=isopen,
atom_link=atom_link,
atom_author=atom_author,
address=address,
phone_number=phone_number,
snippet=snippet,
description=description,
view=view,
times=times,
style_url=style_url,
styles=styles,
region=region,
extended_data=extended_data,
)
self._features = features or []

def features(self) -> Iterator[_Feature]:
"""Iterate over features."""
assert self._features is not None
yield from self._features

def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
element = super().etree_element(precision=precision, verbosity=verbosity)
for feature in self.features():
element.append(feature.etree_element())
return element

def append(self, kmlobj: _Feature) -> None:
"""Append a feature."""
if kmlobj is self:
msg = "Cannot append self"
raise ValueError(msg)
assert self._features is not None
self._features.append(kmlobj)

@classmethod
def _get_kwargs(
cls,
*,
ns: str,
name_spaces: Optional[Dict[str, str]] = None,
element: Element,
strict: bool,
) -> Dict[str, Any]:
kwargs = super()._get_kwargs(
ns=ns,
name_spaces=name_spaces,
element=element,
strict=strict,
)
kwargs["features"] = []
folders = element.findall(f"{ns}Folder")
kwargs["features"] += [
Folder.class_from_element(ns=ns, element=folder, strict=strict)
for folder in folders
]
placemarks = element.findall(f"{ns}Placemark")
kwargs["features"] += [
Placemark.class_from_element(ns=ns, element=placemark, strict=strict)
for placemark in placemarks
]
documents = element.findall(f"{ns}Document")
kwargs["features"] += [
Document.class_from_element(ns=ns, element=document, strict=strict)
for document in documents
]
return kwargs


class Folder(_Container):
"""
A Folder is used to arrange other Features hierarchically
(Folders, Placemarks, #NetworkLinks, or #Overlays).
"""

__name__ = "Folder"


class Document(_Container):
"""
A Document is a container for features and styles. This element is
required if your KML file uses shared styles or schemata for typed
extended data.
"""

__name__ = "Document"
_schemata: Optional[List[Schema]]

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[Dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
name: Optional[str] = None,
visibility: Optional[bool] = None,
isopen: Optional[bool] = None,
atom_link: Optional[atom.Link] = None,
atom_author: Optional[atom.Author] = None,
address: Optional[str] = None,
phone_number: Optional[str] = None,
snippet: Optional[Snippet] = None,
description: Optional[str] = None,
view: Optional[Union[Camera, LookAt]] = None,
times: Optional[Union[TimeSpan, TimeStamp]] = None,
style_url: Optional[StyleUrl] = None,
styles: Optional[Iterable[Union[Style, StyleMap]]] = None,
region: Optional[Region] = None,
extended_data: Optional[ExtendedData] = None,
features: Optional[List[_Feature]] = None,
schemata: Optional[Iterable[Schema]] = None,
) -> None:
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
name=name,
visibility=visibility,
isopen=isopen,
atom_link=atom_link,
atom_author=atom_author,
address=address,
phone_number=phone_number,
snippet=snippet,
description=description,
view=view,
times=times,
style_url=style_url,
styles=styles,
region=region,
extended_data=extended_data,
features=features,
)
self._schemata = list(schemata) if schemata else []

def schemata(self) -> Iterator[Schema]:
if self._schemata:
yield from self._schemata

def append_schema(self, schema: Schema) -> None:
assert self._schemata is not None
self._schemata.append(schema)

def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
element = super().etree_element(precision=precision, verbosity=verbosity)
if self._schemata is not None:
for schema in self._schemata:
element.append(schema.etree_element())
return element

def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]:
id_ = urlparse.urlparse(style_url).fragment
return next((style for style in self.styles() if style.id == id_), None)

@classmethod
def _get_kwargs(
cls,
*,
ns: str,
name_spaces: Optional[Dict[str, str]] = None,
element: Element,
strict: bool,
) -> Dict[str, Any]:
kwargs = super()._get_kwargs(
ns=ns,
name_spaces=name_spaces,
element=element,
strict=strict,
)
schemata = element.findall(f"{ns}Schema")
kwargs["schemata"] = [
Schema.class_from_element(ns=ns, element=schema, strict=strict)
for schema in schemata
]
return kwargs
Loading

0 comments on commit ccc0839

Please sign in to comment.