From 9a653d2be12a94bc28a200a426286a5a06a8719e Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 30 Nov 2024 17:31:03 +0000 Subject: [PATCH 01/19] Add KML Model element scaffold implementation with associated classes --- fastkml/model.py | 153 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 fastkml/model.py diff --git a/fastkml/model.py b/fastkml/model.py new file mode 100644 index 00000000..62bb8f64 --- /dev/null +++ b/fastkml/model.py @@ -0,0 +1,153 @@ +# 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 +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +""" +Model element. + +The Model element defines a 3D model that is attached to a Placemark. + +https://developers.google.com/kml/documentation/models +https://developers.google.com/kml/documentation/kmlreference#model + +""" + +from typing import Dict +from typing import Iterable +from typing import Optional + +from fastkml.base import _XMLObject +from fastkml.enums import AltitudeMode +from fastkml.helpers import clean_string +from fastkml.kml_base import _BaseObject +from fastkml.links import Link + + +class Location(_XMLObject): + """Represents a location in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + altitude: Optional[float] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces) + self.altitude = altitude + self.latitude = latitude + self.longitude = longitude + + +class Orientation(_XMLObject): + """Represents an orientation in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + heading: Optional[float] = None, + tilt: Optional[float] = None, + roll: Optional[float] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces) + self.heading = heading + self.tilt = tilt + self.roll = roll + + +class Scale(_XMLObject): + """Represents a scale in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces) + self.x = x + self.y = y + self.z = z + + +class Alias(_XMLObject): + """Represents an alias in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + target_href: Optional[str] = None, + source_href: Optional[str] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces) + self.target_href = clean_string(target_href) + self.source_href = clean_string(source_href) + + +class ResourceMap(_XMLObject): + """Represents a resource map in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + aliases: Optional[Iterable[Alias]] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces) + self.aliases = list(aliases) if aliases is not None else [] + + +class Model(_BaseObject): + """Represents a model in KML.""" + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + id: Optional[str] = None, + target_id: Optional[str] = None, + altitude_mode: Optional[AltitudeMode] = None, + location: Optional[Location] = None, + orientation: Optional[Orientation] = None, + scale: Optional[Scale] = None, + link: Optional[Link] = None, + resource_map: Optional[ResourceMap] = None, + ) -> None: + super().__init__(ns=ns, name_spaces=name_spaces, id=id, target_id=target_id) + self.altitude_mode = altitude_mode + self.location = location + self.orientation = orientation + self.scale = scale + self.link = link + self.resource_map = resource_map + + def __repr__(self) -> str: + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"altitude_mode={self.altitude_mode}, " + f"location={self.location!r}, " + f"orientation={self.orientation!r}, " + f"scale={self.scale!r}, " + f"link={self.link!r}, " + f"resource_map={self.resource_map!r}, " + f"**{self._get_splat()!r}," + ")" + ) From b4ba74fa0401f7c4ca11f74f4a1e9236ed3bb167 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 30 Nov 2024 17:43:19 +0000 Subject: [PATCH 02/19] add __bool__ and __repr__ for model classes --- fastkml/model.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/fastkml/model.py b/fastkml/model.py index 62bb8f64..22843dbf 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -45,11 +45,27 @@ def __init__( latitude: Optional[float] = None, longitude: Optional[float] = None, ) -> None: + """Create a new Location.""" super().__init__(ns=ns, name_spaces=name_spaces) self.altitude = altitude self.latitude = latitude self.longitude = longitude + def __bool__(self) -> bool: + return all((self.latitude is not None, self.longitude is not None)) + + def __repr__(self) -> str: + """Create a string (c)representation for Location.""" + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"altitude={self.altitude!r}, " + f"latitude={self.latitude!r}, " + f"longitude={self.longitude!r}, " + ")" + ) + class Orientation(_XMLObject): """Represents an orientation in KML.""" @@ -67,6 +83,23 @@ def __init__( self.tilt = tilt self.roll = roll + def __bool__(self) -> bool: + return any( + (self.heading is not None, self.tilt is not None, self.roll is not None) + ) + + def __repr__(self) -> str: + """Create a string (c)representation for Orientation.""" + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"heading={self.heading!r}, " + f"tilt={self.tilt!r}, " + f"roll={self.roll!r}, " + ")" + ) + class Scale(_XMLObject): """Represents a scale in KML.""" @@ -84,6 +117,21 @@ def __init__( self.y = y self.z = z + def __bool__(self) -> bool: + return any((self.x is not None, self.y is not None, self.z is not None)) + + def __repr__(self) -> str: + """Create a string (c)representation for Scale.""" + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"x={self.x!r}, " + f"y={self.y!r}, " + f"z={self.z!r}, " + ")" + ) + class Alias(_XMLObject): """Represents an alias in KML.""" @@ -99,6 +147,20 @@ def __init__( self.target_href = clean_string(target_href) self.source_href = clean_string(source_href) + def __bool__(self) -> bool: + return all((self.target_href is not None, self.source_href is not None)) + + def __repr__(self) -> str: + """Create a string (c)representation for Alias.""" + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"target_href={self.target_href!r}, " + f"source_href={self.source_href!r}, " + ")" + ) + class ResourceMap(_XMLObject): """Represents a resource map in KML.""" @@ -112,6 +174,19 @@ def __init__( super().__init__(ns=ns, name_spaces=name_spaces) self.aliases = list(aliases) if aliases is not None else [] + def __bool__(self) -> bool: + return bool(self.aliases) + + def __repr__(self) -> str: + """Create a string (c)representation for ResourceMap.""" + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"aliases={self.aliases!r}, " + ")" + ) + class Model(_BaseObject): """Represents a model in KML.""" @@ -137,6 +212,9 @@ def __init__( self.link = link self.resource_map = resource_map + def __bool__(self) -> bool: + return bool(self.link) + def __repr__(self) -> str: return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" From 1d80099dbaaf2aaeba5bcfe85c55f251d0ce8978 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 30 Nov 2024 17:46:06 +0000 Subject: [PATCH 03/19] Add documentation for fastkml.model module and its members --- docs/fastkml.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/fastkml.rst b/docs/fastkml.rst index 86e54762..d4a13808 100644 --- a/docs/fastkml.rst +++ b/docs/fastkml.rst @@ -157,6 +157,15 @@ fastkml.mixins :undoc-members: :show-inheritance: +fastkml.model +-------------------- + +.. automodule:: fastkml.model + :members: + :undoc-members: + :show-inheritance: + + fastkml.overlays ----------------------- From 6e3febb5e117657b12d34cf6c0c5f30da2253aed Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 30 Nov 2024 18:17:55 +0000 Subject: [PATCH 04/19] add attributes and registry registrations for KML Model classes --- fastkml/model.py | 249 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 248 insertions(+), 1 deletion(-) diff --git a/fastkml/model.py b/fastkml/model.py index 22843dbf..06c395f5 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -25,18 +25,35 @@ from typing import Dict from typing import Iterable +from typing import List from typing import Optional from fastkml.base import _XMLObject from fastkml.enums import AltitudeMode from fastkml.helpers import clean_string +from fastkml.helpers import enum_subelement +from fastkml.helpers import float_subelement +from fastkml.helpers import subelement_enum_kwarg +from fastkml.helpers import subelement_float_kwarg +from fastkml.helpers import subelement_text_kwarg +from fastkml.helpers import text_subelement +from fastkml.helpers import xml_subelement +from fastkml.helpers import xml_subelement_kwarg +from fastkml.helpers import xml_subelement_list +from fastkml.helpers import xml_subelement_list_kwarg from fastkml.kml_base import _BaseObject from fastkml.links import Link +from fastkml.registry import RegistryItem +from fastkml.registry import registry class Location(_XMLObject): """Represents a location in KML.""" + latitude: Optional[float] + longitude: Optional[float] + altitude: Optional[float] + def __init__( self, ns: Optional[str] = None, @@ -67,9 +84,48 @@ def __repr__(self) -> str: ) +registry.register( + Location, + RegistryItem( + ns_ids=("kml", ""), + attr_name="longitude", + node_name="longitude", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Location, + RegistryItem( + ns_ids=("kml", ""), + attr_name="latitude", + node_name="latitude", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Location, + RegistryItem( + ns_ids=("kml", ""), + attr_name="altitude", + node_name="altitude", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) + + class Orientation(_XMLObject): """Represents an orientation in KML.""" + heading: Optional[float] + tilt: Optional[float] + roll: Optional[float] + def __init__( self, ns: Optional[str] = None, @@ -85,7 +141,7 @@ def __init__( def __bool__(self) -> bool: return any( - (self.heading is not None, self.tilt is not None, self.roll is not None) + (self.heading is not None, self.tilt is not None, self.roll is not None), ) def __repr__(self) -> str: @@ -101,9 +157,48 @@ def __repr__(self) -> str: ) +registry.register( + Orientation, + RegistryItem( + ns_ids=("kml", ""), + attr_name="heading", + node_name="heading", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Orientation, + RegistryItem( + ns_ids=("kml", ""), + attr_name="tilt", + node_name="tilt", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Orientation, + RegistryItem( + ns_ids=("kml", ""), + attr_name="roll", + node_name="roll", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) + + class Scale(_XMLObject): """Represents a scale in KML.""" + x: Optional[float] + y: Optional[float] + z: Optional[float] + def __init__( self, ns: Optional[str] = None, @@ -133,9 +228,47 @@ def __repr__(self) -> str: ) +registry.register( + Scale, + RegistryItem( + ns_ids=("kml", ""), + attr_name="x", + node_name="x", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Scale, + RegistryItem( + ns_ids=("kml", ""), + attr_name="y", + node_name="y", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) +registry.register( + Scale, + RegistryItem( + ns_ids=("kml", ""), + attr_name="z", + node_name="z", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + ), +) + + class Alias(_XMLObject): """Represents an alias in KML.""" + target_href: Optional[str] + source_href: Optional[str] + def __init__( self, ns: Optional[str] = None, @@ -162,9 +295,35 @@ def __repr__(self) -> str: ) +registry.register( + Alias, + RegistryItem( + ns_ids=("kml", ""), + attr_name="target_href", + node_name="targetHref", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + Alias, + RegistryItem( + ns_ids=("kml", ""), + attr_name="source_href", + node_name="sourceHref", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) + + class ResourceMap(_XMLObject): """Represents a resource map in KML.""" + aliases: List[Alias] + def __init__( self, ns: Optional[str] = None, @@ -188,9 +347,29 @@ def __repr__(self) -> str: ) +registry.register( + ResourceMap, + RegistryItem( + ns_ids=("kml", ""), + attr_name="aliases", + node_name="Alias", + classes=(Alias,), + get_kwarg=xml_subelement_list_kwarg, + set_element=xml_subelement_list, + ), +) + + class Model(_BaseObject): """Represents a model in KML.""" + altitude_mode: Optional[AltitudeMode] + location: Optional[Location] + orientation: Optional[Orientation] + scale: Optional[Scale] + link: Optional[Link] + resource_map: Optional[ResourceMap] + def __init__( self, ns: Optional[str] = None, @@ -229,3 +408,71 @@ def __repr__(self) -> str: f"**{self._get_splat()!r}," ")" ) + + +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="altitude_mode", + node_name="altitudeMode", + classes=(AltitudeMode,), + get_kwarg=subelement_enum_kwarg, + set_element=enum_subelement, + ), +) +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="location", + node_name="Location", + classes=(Location,), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="orientation", + node_name="Orientation", + classes=(Orientation,), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="scale", + node_name="Scale", + classes=(Scale,), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="link", + node_name="Link", + classes=(Link,), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) +registry.register( + Model, + RegistryItem( + ns_ids=("kml", ""), + attr_name="resource_map", + node_name="ResourceMap", + classes=(ResourceMap,), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) From cd1b7233e99eef8df3d0aea4c57d34c71bd68c2f Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 16:21:16 +0000 Subject: [PATCH 05/19] refactor to allow for empty kml namespace --- _typos.toml | 1 + docs/working_with_kml.rst | 2 +- fastkml/containers.py | 4 +-- fastkml/data.py | 6 ++-- fastkml/features.py | 32 ++++++++++----------- fastkml/geometry.py | 24 ++++++++-------- fastkml/kml.py | 2 +- fastkml/links.py | 16 +++++------ fastkml/model.py | 4 ++- fastkml/overlays.py | 58 +++++++++++++++++++-------------------- fastkml/styles.py | 38 ++++++++++++------------- fastkml/views.py | 38 ++++++++++++------------- 12 files changed, 114 insertions(+), 111 deletions(-) diff --git a/_typos.toml b/_typos.toml index a1cb930e..598e4d1f 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,6 +1,7 @@ [default.extend-words] lod = "lod" Lod = "Lod" +id ="04AFE6060F147CE66FBD" [files] extend-exclude = ["tests/ogc_conformance/data/kml/*.kml"] diff --git a/docs/working_with_kml.rst b/docs/working_with_kml.rst index 3da5942e..f2f0639d 100644 --- a/docs/working_with_kml.rst +++ b/docs/working_with_kml.rst @@ -123,7 +123,7 @@ We need to register the attributes of the KML object to be able to parse it: >>> registry.register( ... CascadingStyle, ... RegistryItem( - ... ns_ids=("kml",), + ... ns_ids=("kml", ""), ... attr_name="style", ... node_name="Style", ... classes=(Style,), diff --git a/fastkml/containers.py b/fastkml/containers.py index 8d0f6cfe..1a3e7737 100644 --- a/fastkml/containers.py +++ b/fastkml/containers.py @@ -329,7 +329,7 @@ def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]: registry.register( _Container, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="features", node_name=( "Folder,Placemark,Document,GroundOverlay,PhotoOverlay,ScreenOverlay," @@ -351,7 +351,7 @@ def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]: registry.register( Document, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="schemata", node_name="Schema", classes=(Schema,), diff --git a/fastkml/data.py b/fastkml/data.py index 20e80575..573ca074 100644 --- a/fastkml/data.py +++ b/fastkml/data.py @@ -311,7 +311,7 @@ def append(self, field: SimpleField) -> None: registry.register( Schema, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="fields", node_name="SimpleField", classes=(SimpleField,), @@ -644,7 +644,7 @@ def append_data(self, data: SimpleData) -> None: registry.register( SchemaData, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="data", node_name="SimpleData", classes=(SimpleData,), @@ -725,7 +725,7 @@ def __bool__(self) -> bool: registry.register( ExtendedData, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="elements", node_name="Data,SchemaData", classes=( diff --git a/fastkml/features.py b/fastkml/features.py index e8635bd2..9b19997a 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -309,7 +309,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="name", node_name="name", classes=(str,), @@ -320,7 +320,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="visibility", node_name="visibility", classes=(bool,), @@ -332,7 +332,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="isopen", node_name="open", classes=(bool,), @@ -366,7 +366,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="address", node_name="address", classes=(str,), @@ -377,7 +377,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="phone_number", node_name="phoneNumber", classes=(str,), @@ -388,7 +388,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="snippet", node_name="Snippet", classes=(Snippet,), @@ -399,7 +399,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="description", node_name="description", classes=(str,), @@ -410,7 +410,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view", node_name="Camera,LookAt", classes=( @@ -424,7 +424,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="times", node_name="TimeSpan,TimeStamp", classes=( @@ -438,7 +438,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="style_url", node_name="styleUrl", classes=(StyleUrl,), @@ -449,7 +449,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="styles", node_name="Style,StyleMap", classes=( @@ -463,7 +463,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="region", node_name="region", classes=(Region,), @@ -474,7 +474,7 @@ def __init__( registry.register( _Feature, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="extended_data", node_name="ExtendedData", classes=(ExtendedData,), @@ -891,7 +891,7 @@ def __bool__(self) -> bool: registry.register( NetworkLink, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="refresh_visibility", node_name="refreshVisibility", classes=(bool,), @@ -903,7 +903,7 @@ def __bool__(self) -> bool: registry.register( NetworkLink, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="fly_to_view", node_name="flyToView", classes=(bool,), @@ -915,7 +915,7 @@ def __bool__(self) -> bool: registry.register( NetworkLink, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="link", node_name="Link", classes=(Link,), diff --git a/fastkml/geometry.py b/fastkml/geometry.py index ed20c6d9..1b62b3ab 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -300,7 +300,7 @@ def get_tag_name(cls) -> str: registry.register( Coordinates, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(LineType,), # type: ignore[arg-type] attr_name="coords", node_name="coordinates", @@ -501,7 +501,7 @@ def geometry(self) -> Optional[geo.Point]: registry.register( Point, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(bool,), attr_name="extrude", node_name="extrude", @@ -525,7 +525,7 @@ def geometry(self) -> Optional[geo.Point]: registry.register( Point, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(Coordinates,), attr_name="kml_coordinates", node_name="coordinates", @@ -668,7 +668,7 @@ def geometry(self) -> Optional[geo.LineString]: registry.register( LineString, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(bool,), attr_name="extrude", node_name="extrude", @@ -680,7 +680,7 @@ def geometry(self) -> Optional[geo.LineString]: registry.register( LineString, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(bool,), attr_name="tessellate", node_name="tessellate", @@ -704,7 +704,7 @@ def geometry(self) -> Optional[geo.LineString]: registry.register( LineString, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(Coordinates,), attr_name="kml_coordinates", node_name="coordinates", @@ -936,7 +936,7 @@ def get_tag_name(cls) -> str: registry.register( BoundaryIs, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(LinearRing,), attr_name="kml_geometry", node_name="LinearRing", @@ -1134,7 +1134,7 @@ def __eq__(self, other: object) -> bool: registry.register( Polygon, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(bool,), attr_name="extrude", node_name="extrude", @@ -1146,7 +1146,7 @@ def __eq__(self, other: object) -> bool: registry.register( Polygon, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(bool,), attr_name="tessellate", node_name="tessellate", @@ -1170,7 +1170,7 @@ def __eq__(self, other: object) -> bool: registry.register( Polygon, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(OuterBoundaryIs,), attr_name="outer_boundary", node_name="outerBoundaryIs", @@ -1181,7 +1181,7 @@ def __eq__(self, other: object) -> bool: registry.register( Polygon, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(InnerBoundaryIs,), attr_name="inner_boundaries", node_name="innerBoundaryIs", @@ -1344,7 +1344,7 @@ def geometry(self) -> Optional[MultiGeometryType]: registry.register( MultiGeometry, item=RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(Point, LineString, Polygon, LinearRing, MultiGeometry), attr_name="kml_geometries", node_name="(Point|LineString|Polygon|LinearRing|MultiGeometry)", diff --git a/fastkml/kml.py b/fastkml/kml.py index 0886c6ba..aa9c4db1 100644 --- a/fastkml/kml.py +++ b/fastkml/kml.py @@ -285,7 +285,7 @@ def write( registry.register( KML, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), classes=(Document, Folder, Placemark, GroundOverlay, PhotoOverlay, NetworkLink), node_name="Document,Folder,Placemark,GroundOverlay,PhotoOverlay,NetworkLink", attr_name="features", diff --git a/fastkml/links.py b/fastkml/links.py index 3553cb07..8347f069 100644 --- a/fastkml/links.py +++ b/fastkml/links.py @@ -126,7 +126,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="href", node_name="href", classes=(str,), @@ -137,7 +137,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="refresh_mode", node_name="refreshMode", classes=(RefreshMode,), @@ -149,7 +149,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="refresh_interval", node_name="refreshInterval", classes=(float,), @@ -161,7 +161,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view_refresh_mode", node_name="viewRefreshMode", classes=(ViewRefreshMode,), @@ -173,7 +173,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view_refresh_time", node_name="viewRefreshTime", classes=(float,), @@ -185,7 +185,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view_bound_scale", node_name="viewBoundScale", classes=(float,), @@ -197,7 +197,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view_format", node_name="viewFormat", classes=(str,), @@ -209,7 +209,7 @@ def __bool__(self) -> bool: registry.register( Link, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="http_query", node_name="httpQuery", classes=(str,), diff --git a/fastkml/model.py b/fastkml/model.py index 06c395f5..fe16902a 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2023 Christian Ledermann +# Copyright (C) 2024 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 @@ -46,6 +46,8 @@ from fastkml.registry import RegistryItem from fastkml.registry import registry +__all__ = ["Alias", "Location", "Model", "Orientation", "ResourceMap", "Scale"] + class Location(_XMLObject): """Represents a location in KML.""" diff --git a/fastkml/overlays.py b/fastkml/overlays.py index 9b6bde8a..48bfcdd9 100644 --- a/fastkml/overlays.py +++ b/fastkml/overlays.py @@ -229,7 +229,7 @@ def __init__( registry.register( _Overlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="color", node_name="color", classes=(str,), @@ -241,7 +241,7 @@ def __init__( registry.register( _Overlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="draw_order", node_name="drawOrder", classes=(int,), @@ -253,7 +253,7 @@ def __init__( registry.register( _Overlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="icon", node_name="Icon", classes=(Icon,), @@ -380,7 +380,7 @@ def __bool__(self) -> bool: registry.register( ViewVolume, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="left_fov", node_name="leftFov", classes=(float,), @@ -392,7 +392,7 @@ def __bool__(self) -> bool: registry.register( ViewVolume, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="right_fov", node_name="rightFov", classes=(float,), @@ -404,7 +404,7 @@ def __bool__(self) -> bool: registry.register( ViewVolume, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="bottom_fov", node_name="bottomFov", classes=(float,), @@ -416,7 +416,7 @@ def __bool__(self) -> bool: registry.register( ViewVolume, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="top_fov", node_name="topFov", classes=(float,), @@ -428,7 +428,7 @@ def __bool__(self) -> bool: registry.register( ViewVolume, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="near", node_name="near", classes=(float,), @@ -550,7 +550,7 @@ def __bool__(self) -> bool: registry.register( ImagePyramid, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="tile_size", node_name="tileSize", classes=(int,), @@ -562,7 +562,7 @@ def __bool__(self) -> bool: registry.register( ImagePyramid, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="max_width", node_name="maxWidth", classes=(int,), @@ -573,7 +573,7 @@ def __bool__(self) -> bool: registry.register( ImagePyramid, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="max_height", node_name="maxHeight", classes=(int,), @@ -584,7 +584,7 @@ def __bool__(self) -> bool: registry.register( ImagePyramid, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="grid_origin", node_name="gridOrigin", classes=(GridOrigin,), @@ -821,7 +821,7 @@ def __repr__(self) -> str: registry.register( PhotoOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="rotation", node_name="rotation", classes=(float,), @@ -833,7 +833,7 @@ def __repr__(self) -> str: registry.register( PhotoOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="view_volume", node_name="ViewVolume", classes=(ViewVolume,), @@ -844,7 +844,7 @@ def __repr__(self) -> str: registry.register( PhotoOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="image_pyramid", node_name="ImagePyramid", classes=(ImagePyramid,), @@ -855,7 +855,7 @@ def __repr__(self) -> str: registry.register( PhotoOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="point", node_name="Point", classes=(Point,), @@ -866,7 +866,7 @@ def __repr__(self) -> str: registry.register( PhotoOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="shape", node_name="shape", classes=(Shape,), @@ -992,7 +992,7 @@ def __bool__(self) -> bool: registry.register( LatLonBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="north", node_name="north", classes=(float,), @@ -1003,7 +1003,7 @@ def __bool__(self) -> bool: registry.register( LatLonBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="south", node_name="south", classes=(float,), @@ -1014,7 +1014,7 @@ def __bool__(self) -> bool: registry.register( LatLonBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="east", node_name="east", classes=(float,), @@ -1025,7 +1025,7 @@ def __bool__(self) -> bool: registry.register( LatLonBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="west", node_name="west", classes=(float,), @@ -1036,7 +1036,7 @@ def __bool__(self) -> bool: registry.register( LatLonBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="rotation", node_name="rotation", classes=(float,), @@ -1241,7 +1241,7 @@ def __repr__(self) -> str: registry.register( GroundOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="altitude", node_name="altitude", classes=(float,), @@ -1265,7 +1265,7 @@ def __repr__(self) -> str: registry.register( GroundOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="lat_lon_box", node_name="LatLonBox", classes=(LatLonBox,), @@ -1622,7 +1622,7 @@ def __repr__(self) -> str: registry.register( ScreenOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="overlay_xy", node_name="overlayXY", classes=(OverlayXY,), @@ -1633,7 +1633,7 @@ def __repr__(self) -> str: registry.register( ScreenOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="screen_xy", node_name="screenXY", classes=(ScreenXY,), @@ -1644,7 +1644,7 @@ def __repr__(self) -> str: registry.register( ScreenOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="rotation_xy", node_name="rotationXY", classes=(RotationXY,), @@ -1655,7 +1655,7 @@ def __repr__(self) -> str: registry.register( ScreenOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="size", node_name="size", classes=(Size,), @@ -1666,7 +1666,7 @@ def __repr__(self) -> str: registry.register( ScreenOverlay, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="rotation", node_name="rotation", classes=(float,), diff --git a/fastkml/styles.py b/fastkml/styles.py index 023517a8..b5b9292a 100644 --- a/fastkml/styles.py +++ b/fastkml/styles.py @@ -156,7 +156,7 @@ def get_tag_name(cls) -> str: registry.register( StyleUrl, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="url", node_name="styleUrl", classes=(str,), @@ -242,7 +242,7 @@ def __init__( registry.register( _ColorStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="color", node_name="color", classes=(str,), @@ -254,7 +254,7 @@ def __init__( registry.register( _ColorStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="color_mode", node_name="colorMode", classes=(ColorMode,), @@ -526,7 +526,7 @@ def icon_href(self) -> Optional[str]: registry.register( IconStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="scale", node_name="scale", classes=(float,), @@ -538,7 +538,7 @@ def icon_href(self) -> Optional[str]: registry.register( IconStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="heading", node_name="heading", classes=(float,), @@ -550,7 +550,7 @@ def icon_href(self) -> Optional[str]: registry.register( IconStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="icon", node_name="Icon", classes=(Icon,), @@ -561,7 +561,7 @@ def icon_href(self) -> Optional[str]: registry.register( IconStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="hot_spot", node_name="hotSpot", classes=(HotSpot,), @@ -659,7 +659,7 @@ def __bool__(self) -> bool: registry.register( LineStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="width", node_name="width", classes=(float,), @@ -768,7 +768,7 @@ def __bool__(self) -> bool: registry.register( PolyStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="fill", node_name="fill", classes=(bool,), @@ -780,7 +780,7 @@ def __bool__(self) -> bool: registry.register( PolyStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="outline", node_name="outline", classes=(bool,), @@ -882,7 +882,7 @@ def __bool__(self) -> bool: registry.register( LabelStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="scale", node_name="scale", classes=(float,), @@ -1034,7 +1034,7 @@ def __bool__(self) -> bool: registry.register( BalloonStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="bg_color", node_name="bgColor", classes=(str,), @@ -1046,7 +1046,7 @@ def __bool__(self) -> bool: registry.register( BalloonStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="text_color", node_name="textColor", classes=(str,), @@ -1058,7 +1058,7 @@ def __bool__(self) -> bool: registry.register( BalloonStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="text", node_name="text", classes=(str,), @@ -1069,7 +1069,7 @@ def __bool__(self) -> bool: registry.register( BalloonStyle, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="display_mode", node_name="displayMode", classes=(DisplayMode,), @@ -1161,7 +1161,7 @@ def __bool__(self) -> bool: registry.register( Style, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="styles", node_name="Style", classes=( @@ -1271,7 +1271,7 @@ def __bool__(self) -> bool: registry.register( Pair, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="key", node_name="key", classes=(PairKey,), @@ -1282,7 +1282,7 @@ def __bool__(self) -> bool: registry.register( Pair, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="style", node_name="Style", classes=( @@ -1404,7 +1404,7 @@ def highlight(self) -> Optional[Union[StyleUrl, Style]]: registry.register( StyleMap, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="pairs", node_name="Pair", classes=(Pair,), diff --git a/fastkml/views.py b/fastkml/views.py index 16ac27fe..57b30481 100644 --- a/fastkml/views.py +++ b/fastkml/views.py @@ -153,7 +153,7 @@ def __init__( registry.register( _AbstractView, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="longitude", node_name="longitude", classes=(float,), @@ -165,7 +165,7 @@ def __init__( registry.register( _AbstractView, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="latitude", node_name="latitude", classes=(float,), @@ -177,7 +177,7 @@ def __init__( registry.register( _AbstractView, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="altitude", node_name="altitude", classes=(float,), @@ -189,7 +189,7 @@ def __init__( registry.register( _AbstractView, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="heading", node_name="heading", classes=(float,), @@ -201,7 +201,7 @@ def __init__( registry.register( _AbstractView, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="tilt", node_name="tilt", classes=(float,), @@ -317,7 +317,7 @@ def __repr__(self) -> str: registry.register( Camera, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="roll", node_name="roll", classes=(float,), @@ -432,7 +432,7 @@ def __repr__(self) -> str: registry.register( LookAt, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="range", node_name="range", classes=(float,), @@ -554,7 +554,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="north", node_name="north", classes=(float,), @@ -565,7 +565,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="south", node_name="south", classes=(float,), @@ -576,7 +576,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="east", node_name="east", classes=(float,), @@ -587,7 +587,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="west", node_name="west", classes=(float,), @@ -598,7 +598,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="min_altitude", node_name="minAltitude", classes=(float,), @@ -610,7 +610,7 @@ def __bool__(self) -> bool: registry.register( LatLonAltBox, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="max_altitude", node_name="maxAltitude", classes=(float,), @@ -716,7 +716,7 @@ def __bool__(self) -> bool: registry.register( Lod, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="min_lod_pixels", node_name="minLodPixels", classes=(float,), @@ -728,7 +728,7 @@ def __bool__(self) -> bool: registry.register( Lod, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="max_lod_pixels", node_name="maxLodPixels", classes=(float,), @@ -740,7 +740,7 @@ def __bool__(self) -> bool: registry.register( Lod, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="min_fade_extent", node_name="minFadeExtent", classes=(float,), @@ -752,7 +752,7 @@ def __bool__(self) -> bool: registry.register( Lod, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="max_fade_extent", node_name="maxFadeExtent", classes=(float,), @@ -852,7 +852,7 @@ def __bool__(self) -> bool: registry.register( Region, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="lat_lon_alt_box", node_name="LatLonAltBox", classes=(LatLonAltBox,), @@ -863,7 +863,7 @@ def __bool__(self) -> bool: registry.register( Region, RegistryItem( - ns_ids=("kml",), + ns_ids=("kml", ""), attr_name="lod", node_name="Lod", classes=(Lod,), From df56d564ccdd4421134f0bb35c4598c5e1f6c5a2 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 16:39:01 +0000 Subject: [PATCH 06/19] add ignore identifiers for typos in KML namespace --- _typos.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/_typos.toml b/_typos.toml index 598e4d1f..269e7f28 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,7 +1,15 @@ +[default] +extend-ignore-identifiers-re = [ + "04AFE6060F147CE66FBD", + "Lod", + "lod", +] + + + [default.extend-words] lod = "lod" Lod = "Lod" -id ="04AFE6060F147CE66FBD" [files] extend-exclude = ["tests/ogc_conformance/data/kml/*.kml"] From 79e500f52fe6330183e13efa9016cafdf307b884 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 16:41:51 +0000 Subject: [PATCH 07/19] fix typo --- docs/working_with_kml.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/working_with_kml.rst b/docs/working_with_kml.rst index eee1dd55..ebb7ef6b 100644 --- a/docs/working_with_kml.rst +++ b/docs/working_with_kml.rst @@ -236,7 +236,7 @@ Now we can remove the CascadingStyle from the document and have a look at the re hide - + Ort1 10.06256752902339 From e8ccb0495a0bd704e9a9cb0cf88271ec39afe1e1 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 18:04:07 +0000 Subject: [PATCH 08/19] add default KML namespace identifier to model classes and implement hypothesis tests for KML model --- fastkml/model.py | 59 ++++++- tests/hypothesis/model_test.py | 288 +++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 tests/hypothesis/model_test.py diff --git a/fastkml/model.py b/fastkml/model.py index fe16902a..0cbd0a21 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -23,11 +23,13 @@ """ +from typing import Any from typing import Dict from typing import Iterable from typing import List from typing import Optional +from fastkml import config from fastkml.base import _XMLObject from fastkml.enums import AltitudeMode from fastkml.helpers import clean_string @@ -52,6 +54,8 @@ class Location(_XMLObject): """Represents a location in KML.""" + _default_nsid = config.KML + latitude: Optional[float] longitude: Optional[float] altitude: Optional[float] @@ -63,14 +67,16 @@ def __init__( altitude: Optional[float] = None, latitude: Optional[float] = None, longitude: Optional[float] = None, + **kwargs: Any, ) -> None: """Create a new Location.""" - super().__init__(ns=ns, name_spaces=name_spaces) + super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.altitude = altitude self.latitude = latitude self.longitude = longitude def __bool__(self) -> bool: + """Return True if latitude and longitude are set.""" return all((self.latitude is not None, self.longitude is not None)) def __repr__(self) -> str: @@ -82,6 +88,7 @@ def __repr__(self) -> str: f"altitude={self.altitude!r}, " f"latitude={self.latitude!r}, " f"longitude={self.longitude!r}, " + f"**{self._get_splat()!r}," ")" ) @@ -124,6 +131,8 @@ def __repr__(self) -> str: class Orientation(_XMLObject): """Represents an orientation in KML.""" + _default_nsid = config.KML + heading: Optional[float] tilt: Optional[float] roll: Optional[float] @@ -135,13 +144,16 @@ def __init__( heading: Optional[float] = None, tilt: Optional[float] = None, roll: Optional[float] = None, + **kwargs: Any, ) -> None: - super().__init__(ns=ns, name_spaces=name_spaces) + """Create a new Orientation.""" + super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.heading = heading self.tilt = tilt self.roll = roll def __bool__(self) -> bool: + """Return True if heading, tilt, and roll are set.""" return any( (self.heading is not None, self.tilt is not None, self.roll is not None), ) @@ -155,6 +167,7 @@ def __repr__(self) -> str: f"heading={self.heading!r}, " f"tilt={self.tilt!r}, " f"roll={self.roll!r}, " + f"**{self._get_splat()!r}," ")" ) @@ -197,6 +210,8 @@ def __repr__(self) -> str: class Scale(_XMLObject): """Represents a scale in KML.""" + _default_nsid = config.KML + x: Optional[float] y: Optional[float] z: Optional[float] @@ -208,13 +223,16 @@ def __init__( x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None, + **kwargs: Any, ) -> None: - super().__init__(ns=ns, name_spaces=name_spaces) + """Create a new Scale.""" + super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.x = x self.y = y self.z = z def __bool__(self) -> bool: + """Return True if x, y, or z are set.""" return any((self.x is not None, self.y is not None, self.z is not None)) def __repr__(self) -> str: @@ -226,6 +244,7 @@ def __repr__(self) -> str: f"x={self.x!r}, " f"y={self.y!r}, " f"z={self.z!r}, " + f"**{self._get_splat()!r}," ")" ) @@ -268,6 +287,8 @@ def __repr__(self) -> str: class Alias(_XMLObject): """Represents an alias in KML.""" + _default_nsid = config.KML + target_href: Optional[str] source_href: Optional[str] @@ -277,13 +298,16 @@ def __init__( name_spaces: Optional[Dict[str, str]] = None, target_href: Optional[str] = None, source_href: Optional[str] = None, + **kwargs: Any, ) -> None: - super().__init__(ns=ns, name_spaces=name_spaces) + """Create a new Alias.""" + super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.target_href = clean_string(target_href) self.source_href = clean_string(source_href) def __bool__(self) -> bool: - return all((self.target_href is not None, self.source_href is not None)) + """Return True if target_href and source_href are set.""" + return any((self.target_href is not None, self.source_href is not None)) def __repr__(self) -> str: """Create a string (c)representation for Alias.""" @@ -293,6 +317,7 @@ def __repr__(self) -> str: f"name_spaces={self.name_spaces!r}, " f"target_href={self.target_href!r}, " f"source_href={self.source_href!r}, " + f"**{self._get_splat()!r}," ")" ) @@ -324,6 +349,8 @@ def __repr__(self) -> str: class ResourceMap(_XMLObject): """Represents a resource map in KML.""" + _default_nsid = config.KML + aliases: List[Alias] def __init__( @@ -331,11 +358,14 @@ def __init__( ns: Optional[str] = None, name_spaces: Optional[Dict[str, str]] = None, aliases: Optional[Iterable[Alias]] = None, + **kwargs: Any, ) -> None: - super().__init__(ns=ns, name_spaces=name_spaces) + """Create a new ResourceMap.""" + super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.aliases = list(aliases) if aliases is not None else [] def __bool__(self) -> bool: + """Return True if aliases are set.""" return bool(self.aliases) def __repr__(self) -> str: @@ -345,6 +375,7 @@ def __repr__(self) -> str: f"ns={self.ns!r}, " f"name_spaces={self.name_spaces!r}, " f"aliases={self.aliases!r}, " + f"**{self._get_splat()!r}," ")" ) @@ -384,8 +415,16 @@ def __init__( scale: Optional[Scale] = None, link: Optional[Link] = None, resource_map: Optional[ResourceMap] = None, + **kwargs: Any, ) -> None: - super().__init__(ns=ns, name_spaces=name_spaces, id=id, target_id=target_id) + """Create a new Model.""" + super().__init__( + ns=ns, + name_spaces=name_spaces, + id=id, + target_id=target_id, + **kwargs, + ) self.altitude_mode = altitude_mode self.location = location self.orientation = orientation @@ -394,13 +433,17 @@ def __init__( self.resource_map = resource_map def __bool__(self) -> bool: + """Return True if link is set.""" return bool(self.link) def __repr__(self) -> str: + """Create a string representation for Model.""" return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " f"name_spaces={self.name_spaces!r}, " + f"id={self.id!r}, " + f"target_id={self.target_id!r}, " f"altitude_mode={self.altitude_mode}, " f"location={self.location!r}, " f"orientation={self.orientation!r}, " @@ -415,7 +458,7 @@ def __repr__(self) -> str: registry.register( Model, RegistryItem( - ns_ids=("kml", ""), + ns_ids=("kml", "gx", ""), attr_name="altitude_mode", node_name="altitudeMode", classes=(AltitudeMode,), diff --git a/tests/hypothesis/model_test.py b/tests/hypothesis/model_test.py new file mode 100644 index 00000000..ee318107 --- /dev/null +++ b/tests/hypothesis/model_test.py @@ -0,0 +1,288 @@ +# Copyright (C) 2024 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 +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +"""Hypothesis tests for the fastkml.model module.""" + +import typing + +from hypothesis import given +from hypothesis import strategies as st +from hypothesis.provisional import urls + +import fastkml +import fastkml.enums +import fastkml.links +import fastkml.model +from tests.base import Lxml +from tests.hypothesis.common import assert_repr_roundtrip +from tests.hypothesis.common import assert_str_roundtrip +from tests.hypothesis.common import assert_str_roundtrip_terse +from tests.hypothesis.common import assert_str_roundtrip_verbose +from tests.hypothesis.strategies import nc_name + + +class TestLxml(Lxml): + @given( + altitude=st.one_of( + st.none(), + st.just(0.0), + st.floats(allow_nan=False, allow_infinity=False).filter(lambda x: x != 0), + ), + latitude=st.one_of( + st.none(), + st.just(0.0), + st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-90, + max_value=90, + ), + ), + longitude=st.one_of( + st.none(), + st.just(0.0), + st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ), + ), + ) + def test_fuzz_location( + self, + altitude: typing.Optional[float], + latitude: typing.Optional[float], + longitude: typing.Optional[float], + ) -> None: + location = fastkml.model.Location( + altitude=altitude, + latitude=latitude, + longitude=longitude, + ) + + assert_repr_roundtrip(location) + assert_str_roundtrip(location) + assert_str_roundtrip_terse(location) + assert_str_roundtrip_verbose(location) + + @given( + heading=st.one_of( + st.none(), + st.just(0.0), + st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-360, + max_value=360, + ).filter(lambda x: x != 0), + ), + tilt=st.one_of( + st.none(), + st.just(0.0), + st.floats( + allow_nan=False, + allow_infinity=False, + min_value=0, + max_value=180, + ).filter(lambda x: x != 0), + ), + roll=st.one_of( + st.none(), + st.just(0.0), + st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ).filter(lambda x: x != 0), + ), + ) + def test_fuzz_orientation( + self, + heading: typing.Optional[float], + tilt: typing.Optional[float], + roll: typing.Optional[float], + ) -> None: + orientation = fastkml.model.Orientation(heading=heading, tilt=tilt, roll=roll) + + assert_repr_roundtrip(orientation) + assert_str_roundtrip(orientation) + assert_str_roundtrip_terse(orientation) + assert_str_roundtrip_verbose(orientation) + + @given( + x=st.one_of(st.none(), st.floats(allow_nan=False, allow_infinity=False)), + y=st.one_of(st.none(), st.floats(allow_nan=False, allow_infinity=False)), + z=st.one_of(st.none(), st.floats(allow_nan=False, allow_infinity=False)), + ) + def test_fuzz_scale( + self, + x: typing.Optional[float], + y: typing.Optional[float], + z: typing.Optional[float], + ) -> None: + scale = fastkml.model.Scale(x=x, y=y, z=z) + + assert_repr_roundtrip(scale) + assert_str_roundtrip(scale) + assert_str_roundtrip_terse(scale) + assert_str_roundtrip_verbose(scale) + + @given( + target_href=st.one_of(st.none(), urls()), + source_href=st.one_of(st.none(), urls()), + ) + def test_fuzz_alias( + self, + target_href: typing.Optional[str], + source_href: typing.Optional[str], + ) -> None: + alias = fastkml.model.Alias(target_href=target_href, source_href=source_href) + + assert_repr_roundtrip(alias) + assert_str_roundtrip(alias) + assert_str_roundtrip_terse(alias) + assert_str_roundtrip_verbose(alias) + + @given( + aliases=st.one_of( + st.none(), + st.lists( + st.builds( + fastkml.model.Alias, + source_href=urls(), + target_href=urls(), + ), + ), + ), + ) + def test_fuzz_resource_map( + self, + aliases: typing.Optional[typing.Iterable[fastkml.model.Alias]], + ) -> None: + resource_map = fastkml.model.ResourceMap(aliases=aliases) + + assert_repr_roundtrip(resource_map) + assert_str_roundtrip(resource_map) + assert_str_roundtrip_terse(resource_map) + assert_str_roundtrip_verbose(resource_map) + + @given( + id=st.one_of(st.none(), nc_name()), + target_id=st.one_of(st.none(), nc_name()), + altitude_mode=st.one_of(st.none(), st.sampled_from(fastkml.enums.AltitudeMode)), + location=st.one_of( + st.none(), + st.builds( + fastkml.model.Location, + altitude=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + latitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-90, + max_value=90, + ), + longitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ), + ), + ), + orientation=st.one_of( + st.none(), + st.builds( + fastkml.model.Orientation, + heading=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-360, + max_value=360, + ).filter(lambda x: x != 0), + tilt=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=0, + max_value=180, + ).filter(lambda x: x != 0), + roll=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ).filter(lambda x: x != 0), + ), + ), + scale=st.one_of( + st.none(), + st.builds( + fastkml.model.Scale, + x=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + y=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + z=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + ), + ), + link=st.one_of(st.none(), st.builds(fastkml.Link, href=urls())), + resource_map=st.one_of( + st.none(), + st.builds( + fastkml.model.ResourceMap, + aliases=st.lists( + st.builds( + fastkml.model.Alias, + source_href=urls(), + target_href=urls(), + ), + min_size=1, + ), + ), + ), + ) + def test_fuzz_model( + self, + id: typing.Optional[str], + target_id: typing.Optional[str], + altitude_mode: typing.Optional[fastkml.enums.AltitudeMode], + location: typing.Optional[fastkml.model.Location], + orientation: typing.Optional[fastkml.model.Orientation], + scale: typing.Optional[fastkml.model.Scale], + link: typing.Optional[fastkml.Link], + resource_map: typing.Optional[fastkml.model.ResourceMap], + ) -> None: + model = fastkml.model.Model( + id=id, + target_id=target_id, + altitude_mode=altitude_mode, + location=location, + orientation=orientation, + scale=scale, + link=link, + resource_map=resource_map, + ) + + assert_repr_roundtrip(model) + assert_str_roundtrip(model) + assert_str_roundtrip_terse(model) + assert_str_roundtrip_verbose(model) From aabc0622dacff5f7b5c3d9211d3acc38d0b2e2f3 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 18:34:28 +0000 Subject: [PATCH 09/19] add geometry property to Model and Location classes; update registry to include Model in KML geometry types --- fastkml/features.py | 5 +++- fastkml/model.py | 18 ++++++++++++++- tests/hypothesis/feature_test.py | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/fastkml/features.py b/fastkml/features.py index 9b19997a..2c557ed2 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -58,6 +58,7 @@ from fastkml.kml_base import _BaseObject from fastkml.links import Link from fastkml.mixins import TimeMixin +from fastkml.model import Model from fastkml.registry import RegistryItem from fastkml.registry import registry from fastkml.styles import Style @@ -78,6 +79,7 @@ LineString, LinearRing, Polygon, + Model, MultiGeometry, gx.MultiTrack, gx.Track, @@ -665,7 +667,7 @@ def geometry(self) -> Optional[AnyGeometryType]: ns_ids=("kml", "gx"), attr_name="kml_geometry", node_name=( - "Point,LineString,LinearRing,Polygon,MultiGeometry," + "Point,LineString,LinearRing,Polygon,MultiGeometry,Model," "gx:MultiTrack,gx:Track" ), classes=( @@ -674,6 +676,7 @@ def geometry(self) -> Optional[AnyGeometryType]: LinearRing, Polygon, MultiGeometry, + Model, gx.MultiTrack, gx.Track, ), diff --git a/fastkml/model.py b/fastkml/model.py index 0cbd0a21..2fe2cee7 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -29,6 +29,8 @@ from typing import List from typing import Optional +from pygeoif.geometry import Point + from fastkml import config from fastkml.base import _XMLObject from fastkml.enums import AltitudeMode @@ -92,6 +94,15 @@ def __repr__(self) -> str: ")" ) + @property + def geometry(self) -> Optional[Point]: + """Return a Point representation of the geometry.""" + if not self: + return None + assert self.longitude is not None # noqa: S101 + assert self.latitude is not None # noqa: S101 + return Point(self.longitude, self.latitude, self.altitude) + registry.register( Location, @@ -434,7 +445,7 @@ def __init__( def __bool__(self) -> bool: """Return True if link is set.""" - return bool(self.link) + return all((self.link, self.location)) def __repr__(self) -> str: """Create a string representation for Model.""" @@ -454,6 +465,11 @@ def __repr__(self) -> str: ")" ) + @property + def geometry(self) -> Optional[Point]: + """Return a Point representation of the geometry.""" + return self.location.geometry if self.location else None + registry.register( Model, diff --git a/tests/hypothesis/feature_test.py b/tests/hypothesis/feature_test.py index 6f289c45..2751cc99 100644 --- a/tests/hypothesis/feature_test.py +++ b/tests/hypothesis/feature_test.py @@ -29,6 +29,7 @@ import fastkml.features import fastkml.gx import fastkml.links +import fastkml.model import fastkml.styles import fastkml.views from tests.base import Lxml @@ -419,6 +420,44 @@ def test_fuzz_placemark_styles( assert_str_roundtrip_terse(placemark) assert_str_roundtrip_verbose(placemark) + @given( + model=st.builds( + fastkml.model.Model, + altitude_mode=st.sampled_from(fastkml.enums.AltitudeMode), + location=st.builds( + fastkml.model.Location, + latitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-90, + max_value=90, + ).filter(lambda x: x != 0), + longitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ).filter(lambda x: x != 0), + altitude=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + ), + link=st.builds(fastkml.Link, href=urls()), + ), + ) + def fuzz_placemark_model( + self, + model: fastkml.model.Model, + ) -> None: + placemark = fastkml.Placemark( + kml_geometry=model, + ) + + assert_repr_roundtrip(placemark) + assert_str_roundtrip(placemark) + assert_str_roundtrip_terse(placemark) + assert_str_roundtrip_verbose(placemark) + @given( refresh_visibility=st.one_of(st.none(), st.booleans()), fly_to_view=st.one_of(st.none(), st.booleans()), From 15f113fbeb85f60aee3b960cc590afa7ef5c6c0c Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 18:40:56 +0000 Subject: [PATCH 10/19] add empty string to KML namespace identifiers to enable parsing kml files without namespace declaration --- fastkml/features.py | 2 +- fastkml/geometry.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fastkml/features.py b/fastkml/features.py index 2c557ed2..af535ec2 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -664,7 +664,7 @@ def geometry(self) -> Optional[AnyGeometryType]: registry.register( Placemark, RegistryItem( - ns_ids=("kml", "gx"), + ns_ids=("kml", "gx", ""), attr_name="kml_geometry", node_name=( "Point,LineString,LinearRing,Polygon,MultiGeometry,Model," diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 1b62b3ab..bb14f78c 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -513,7 +513,7 @@ def geometry(self) -> Optional[geo.Point]: registry.register( Point, item=RegistryItem( - ns_ids=("kml", "gx"), + ns_ids=("kml", "gx", ""), classes=(AltitudeMode,), attr_name="altitude_mode", node_name="altitudeMode", @@ -692,7 +692,7 @@ def geometry(self) -> Optional[geo.LineString]: registry.register( LineString, item=RegistryItem( - ns_ids=("kml", "gx"), + ns_ids=("kml", "gx", ""), classes=(AltitudeMode,), attr_name="altitude_mode", node_name="altitudeMode", @@ -1158,7 +1158,7 @@ def __eq__(self, other: object) -> bool: registry.register( Polygon, item=RegistryItem( - ns_ids=("kml", "gx"), + ns_ids=("kml", "gx", ""), classes=(AltitudeMode,), attr_name="altitude_mode", node_name="altitudeMode", From e7e781e46d498fd353886b267d30f03ada5e5e68 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 18:42:07 +0000 Subject: [PATCH 11/19] update changelog for version 1.1.0; add support for ScreenOverlay and Model, and allow parsing KML files without namespace declarations --- docs/HISTORY.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/HISTORY.rst b/docs/HISTORY.rst index f15ed935..360ef1f5 100644 --- a/docs/HISTORY.rst +++ b/docs/HISTORY.rst @@ -1,10 +1,11 @@ Changelog ========= -1.0.0dev0 (unreleased) +1.1.0 (unreleased) ---------------------- -- Add support for ScreenOverlay +- Add support for ScreenOverlay and Model. +- allow parsing kml files without namespace declarations. 1.0 (2024/11/19) From 31aa4dc5864279f71452d4901566f4660fce8c88 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 19:18:34 +0000 Subject: [PATCH 12/19] add default values for float properties in model registry and rename fuzz test method --- fastkml/model.py | 8 ++++++++ tests/hypothesis/feature_test.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/fastkml/model.py b/fastkml/model.py index 2fe2cee7..9b29c11b 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -135,6 +135,7 @@ def geometry(self) -> Optional[Point]: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=0.0, ), ) @@ -192,6 +193,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=0.0, ), ) registry.register( @@ -203,6 +205,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=0.0, ), ) registry.register( @@ -214,6 +217,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=0.0, ), ) @@ -269,6 +273,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=1.0, ), ) registry.register( @@ -280,6 +285,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=1.0, ), ) registry.register( @@ -291,6 +297,7 @@ def __repr__(self) -> str: classes=(float,), get_kwarg=subelement_float_kwarg, set_element=float_subelement, + default=1.0, ), ) @@ -480,6 +487,7 @@ def geometry(self) -> Optional[Point]: classes=(AltitudeMode,), get_kwarg=subelement_enum_kwarg, set_element=enum_subelement, + default=AltitudeMode.clamp_to_ground, ), ) registry.register( diff --git a/tests/hypothesis/feature_test.py b/tests/hypothesis/feature_test.py index 2751cc99..f04bc8e8 100644 --- a/tests/hypothesis/feature_test.py +++ b/tests/hypothesis/feature_test.py @@ -445,7 +445,7 @@ def test_fuzz_placemark_styles( link=st.builds(fastkml.Link, href=urls()), ), ) - def fuzz_placemark_model( + def test_fuzz_placemark_model( self, model: fastkml.model.Model, ) -> None: From 8b5da4a9ba85e4acbad2226532a98644e6f2192e Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 19:25:45 +0000 Subject: [PATCH 13/19] refactor boolean methods in model classes to clarify conditions for truthiness --- fastkml/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastkml/model.py b/fastkml/model.py index 9b29c11b..9387b98f 100644 --- a/fastkml/model.py +++ b/fastkml/model.py @@ -165,7 +165,7 @@ def __init__( self.roll = roll def __bool__(self) -> bool: - """Return True if heading, tilt, and roll are set.""" + """Return True if heading, tilt, or roll are set.""" return any( (self.heading is not None, self.tilt is not None, self.roll is not None), ) @@ -324,7 +324,7 @@ def __init__( self.source_href = clean_string(source_href) def __bool__(self) -> bool: - """Return True if target_href and source_href are set.""" + """Return True if target_href or source_href are set.""" return any((self.target_href is not None, self.source_href is not None)) def __repr__(self) -> str: @@ -451,7 +451,7 @@ def __init__( self.resource_map = resource_map def __bool__(self) -> bool: - """Return True if link is set.""" + """Return True if link and location are set.""" return all((self.link, self.location)) def __repr__(self) -> str: From a85ea4c2758e2e3c353536bda97379548e122612 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 19:55:06 +0000 Subject: [PATCH 14/19] add unit tests for Model class and update overlay test description --- tests/model_test.py | 84 ++++++++++++++++++++++++++++++++++++++++++ tests/overlays_test.py | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/model_test.py diff --git a/tests/model_test.py b/tests/model_test.py new file mode 100644 index 00000000..5ba56339 --- /dev/null +++ b/tests/model_test.py @@ -0,0 +1,84 @@ +# Copyright (C) 2021 - 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 +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Test the kml overlay classes.""" + +from pygeoif.geometry import Point + +import fastkml.links +import fastkml.model +from fastkml.enums import AltitudeMode +from tests.base import Lxml +from tests.base import StdLibrary + + +class TestModel(StdLibrary): + def test_from_string(self) -> None: + doc = ( + '' + "absolute" + "" + "-123.115776547816" + "49.279804095564" + "21.614010375743" + "" + "111" + "http://barcelona.galdos.local/files/PublicLibrary.dae" + '' + "" + "http://barcelona.galdos.local/images/Concrete2.jpg" + "../images/Concrete.jpg" + "" + "" + "" + ) + + model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute + assert model.geometry == Point( + -123.115776547816, + 49.279804095564, + 21.614010375743, + ) + assert model == fastkml.model.Model( + altitude_mode=AltitudeMode.absolute, + location=fastkml.model.Location( + altitude=21.614010375743, + latitude=49.279804095564, + longitude=-123.115776547816, + ), + orientation=None, + scale=fastkml.model.Scale( + x=1.0, + y=1.0, + z=1.0, + ), + link=fastkml.links.Link( + href="http://barcelona.galdos.local/files/PublicLibrary.dae", + ), + resource_map=fastkml.model.ResourceMap( + aliases=[ + fastkml.model.Alias( + target_href="http://barcelona.galdos.local/images/Concrete2.jpg", + source_href="../images/Concrete.jpg", + ), + ], + ), + ) + + +class TestModelLxml(TestModel, Lxml): + pass diff --git a/tests/overlays_test.py b/tests/overlays_test.py index 9539acb0..0209e6ed 100644 --- a/tests/overlays_test.py +++ b/tests/overlays_test.py @@ -14,7 +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 -"""Test the kml classes.""" +"""Test the kml overlay classes.""" import contextlib From 7286d0948d2344f802692f8e1ec7449c139e70b3 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 20:00:59 +0000 Subject: [PATCH 15/19] add note why validate=False when parsing undocumented elements --- docs/working_with_kml.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/working_with_kml.rst b/docs/working_with_kml.rst index ebb7ef6b..b810f79f 100644 --- a/docs/working_with_kml.rst +++ b/docs/working_with_kml.rst @@ -156,8 +156,10 @@ 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. -When parsing the document we have to skip the validation as the ``gx:CascadingStyle`` is -not in the XSD Schema. + +.. note:: + When parsing the document we have to skip the validation by passing ``validate=False`` + to ``KML.parse`` as the ``gx:CascadingStyle`` is not in the XSD Schema. Create a new KML object and confirm that the new element is parsed correctly: From cfd9994752b1d21607ab0ed412d481a25963efc1 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 20:11:13 +0000 Subject: [PATCH 16/19] add test model with missing location --- tests/model_test.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/model_test.py b/tests/model_test.py index 5ba56339..6e1b325f 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -79,6 +79,26 @@ def test_from_string(self) -> None: ), ) + def test_from_string_no_location(self) -> None: + doc = ( + '' + "absolute" + "111" + "http://barcelona.galdos.local/files/PublicLibrary.dae" + '' + "" + "http://barcelona.galdos.local/images/Concrete2.jpg" + "../images/Concrete.jpg" + "" + "" + "" + ) + + model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute + assert model.geometry is None + assert not model + class TestModelLxml(TestModel, Lxml): pass From d38b0968739f94a236a380701d746edc8364454c Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 20:22:21 +0000 Subject: [PATCH 17/19] improve readme --- README.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index f4a6f4e4..55ecf1bc 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,8 @@ Introduction .. inclusion-marker-do-not-remove -KML is an XML geospatial data format and an OGC_ standard that deserves a canonical python implementation. +KML is an XML geospatial data format and an OGC_ standard that deserves a canonical +python implementation. Fastkml is a library to read, write and manipulate KML files. It aims to keep it simple and fast (using lxml_ if available). Fast refers to the time you @@ -19,9 +20,12 @@ For more details about the KML Specification, check out the `KML Reference `_ on the Google developers site. -Geometries are handled as pygeoif_ objects. +Geometries are handled as pygeoif_ objects, which are compatible with any geometry that +implements the ``__geo_interface__`` protocol, such as shapely_. -Fastkml is continually tested +Fastkml is tested on `CPython `_ and +`PyPy `_, but it should work on alternative +Python implementations (that implement the language specification *>=3.8*) as well. |test| |hypothesis| |cov| |black| |mypy| |commit| @@ -49,7 +53,7 @@ Fastkml is continually tested :target: https://github.com/pre-commit/pre-commit :alt: pre-commit -Is Maintained and documented: +Is maintained and documented: |pypi| |conda-forge| |status| |license| |doc| |stats| |pyversion| |pyimpl| |dependencies| |downloads| @@ -134,3 +138,4 @@ Please submit a PR with the features you'd like to see implemented. .. _lxml: https://pypi.python.org/pypi/lxml .. _arrow: https://pypi.python.org/pypi/arrow .. _OGC: https://www.ogc.org/standard/kml/ +.. _shapely: https://shapely.readthedocs.io/ From 917719149a89101b0c9822c65f71959867ea5f8f Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 20:33:27 +0000 Subject: [PATCH 18/19] add test when location is invalid --- tests/model_test.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/model_test.py b/tests/model_test.py index 6e1b325f..93a4ff53 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -99,6 +99,31 @@ def test_from_string_no_location(self) -> None: assert model.geometry is None assert not model + def test_from_string_invalid_location(self) -> None: + doc = ( + '' + "absolute" + "" + "" + "49.279804095564" + "21.614010375743" + "" + "111" + "http://barcelona.galdos.local/files/PublicLibrary.dae" + '' + "" + "http://barcelona.galdos.local/images/Concrete2.jpg" + "../images/Concrete.jpg" + "" + "" + "" + ) + + model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute + assert model.geometry is None + assert not model + class TestModelLxml(TestModel, Lxml): pass From abcc0d9d96d4367515febd1b99cb4f8dd5003bb6 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 1 Dec 2024 20:38:29 +0000 Subject: [PATCH 19/19] add test for handling invalid location in model --- tests/model_test.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/model_test.py b/tests/model_test.py index 93a4ff53..fb10c150 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -47,6 +47,7 @@ def test_from_string(self) -> None: ) model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute assert model.geometry == Point( -123.115776547816, @@ -95,6 +96,7 @@ def test_from_string_no_location(self) -> None: ) model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute assert model.geometry is None assert not model @@ -120,10 +122,27 @@ def test_from_string_invalid_location(self) -> None: ) model = fastkml.model.Model.from_string(doc) + assert model.altitude_mode == AltitudeMode.absolute assert model.geometry is None assert not model + def test_location_from_string_invalid_location(self) -> None: + doc = ( + '' + "" + "49.279804095564" + "21.614010375743" + "" + ) + + location = fastkml.model.Location.from_string(doc) + + assert location.altitude == 21.614010375743 + assert location.latitude == 49.279804095564 + assert location.geometry is None + assert not location + class TestModelLxml(TestModel, Lxml): pass