# 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
"""
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 collections.abc import Iterable
from typing import Any
from typing import Optional
from pygeoif.geometry import Point
from fastkml import config
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
__all__ = ["Alias", "Location", "Model", "Orientation", "ResourceMap", "Scale"]
[docs]
class Location(_BaseObject):
"""Represents a location in KML."""
_default_nsid = config.KML
latitude: Optional[float]
longitude: Optional[float]
altitude: Optional[float]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
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,
id=id,
target_id=target_id,
**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:
"""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"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"altitude={self.altitude!r}, "
f"latitude={self.latitude!r}, "
f"longitude={self.longitude!r}, "
f"**{self._get_splat()!r},"
")"
)
@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,
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,
default=0.0,
),
)
[docs]
class Orientation(_BaseObject):
"""Represents an orientation in KML."""
_default_nsid = config.KML
heading: Optional[float]
tilt: Optional[float]
roll: Optional[float]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
heading: Optional[float] = None,
tilt: Optional[float] = None,
roll: Optional[float] = None,
**kwargs: Any,
) -> None:
"""Create a new Orientation."""
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
**kwargs,
)
self.heading = heading
self.tilt = tilt
self.roll = roll
def __bool__(self) -> bool:
"""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),
)
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"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"heading={self.heading!r}, "
f"tilt={self.tilt!r}, "
f"roll={self.roll!r}, "
f"**{self._get_splat()!r},"
")"
)
registry.register(
Orientation,
RegistryItem(
ns_ids=("kml", ""),
attr_name="heading",
node_name="heading",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=0.0,
),
)
registry.register(
Orientation,
RegistryItem(
ns_ids=("kml", ""),
attr_name="tilt",
node_name="tilt",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=0.0,
),
)
registry.register(
Orientation,
RegistryItem(
ns_ids=("kml", ""),
attr_name="roll",
node_name="roll",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=0.0,
),
)
[docs]
class Scale(_BaseObject):
"""Represents a scale in KML."""
_default_nsid = config.KML
x: Optional[float]
y: Optional[float]
z: Optional[float]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
x: Optional[float] = None,
y: Optional[float] = None,
z: Optional[float] = None,
**kwargs: Any,
) -> None:
"""Create a new Scale."""
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
**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:
"""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"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"x={self.x!r}, "
f"y={self.y!r}, "
f"z={self.z!r}, "
f"**{self._get_splat()!r},"
")"
)
registry.register(
Scale,
RegistryItem(
ns_ids=("kml", ""),
attr_name="x",
node_name="x",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=1.0,
),
)
registry.register(
Scale,
RegistryItem(
ns_ids=("kml", ""),
attr_name="y",
node_name="y",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=1.0,
),
)
registry.register(
Scale,
RegistryItem(
ns_ids=("kml", ""),
attr_name="z",
node_name="z",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=1.0,
),
)
[docs]
class Alias(_BaseObject):
"""Represents an alias in KML."""
_default_nsid = config.KML
target_href: Optional[str]
source_href: Optional[str]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
target_href: Optional[str] = None,
source_href: Optional[str] = None,
**kwargs: Any,
) -> None:
"""Create a new Alias."""
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
**kwargs,
)
self.target_href = clean_string(target_href)
self.source_href = clean_string(source_href)
def __bool__(self) -> bool:
"""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:
"""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"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"target_href={self.target_href!r}, "
f"source_href={self.source_href!r}, "
f"**{self._get_splat()!r},"
")"
)
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,
),
)
[docs]
class ResourceMap(_BaseObject):
"""Represents a resource map in KML."""
_default_nsid = config.KML
aliases: list[Alias]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
aliases: Optional[Iterable[Alias]] = None,
**kwargs: Any,
) -> None:
"""Create a new ResourceMap."""
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
**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:
"""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"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"aliases={self.aliases!r}, "
f"**{self._get_splat()!r},"
")"
)
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,
),
)
[docs]
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,
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,
**kwargs: Any,
) -> None:
"""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
self.scale = scale
self.link = link
self.resource_map = resource_map
def __bool__(self) -> bool:
"""Return True if link and location are set."""
return all((self.link, self.location))
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}, "
f"scale={self.scale!r}, "
f"link={self.link!r}, "
f"resource_map={self.resource_map!r}, "
f"**{self._get_splat()!r},"
")"
)
@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,
RegistryItem(
ns_ids=("kml", "gx", ""),
attr_name="altitude_mode",
node_name="altitudeMode",
classes=(AltitudeMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
default=AltitudeMode.clamp_to_ground,
),
)
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,
),
)