diff --git a/fastkml/atom.py b/fastkml/atom.py index d841c23c..592d667f 100644 --- a/fastkml/atom.py +++ b/fastkml/atom.py @@ -15,6 +15,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """ +Basic Atom support for KML. + KML 2.2 supports new elements for including data about the author and related website in your KML file. This information is displayed in geo search results, both in Earth browsers such as Google Earth, and in other applications such as @@ -72,7 +74,9 @@ def get_tag_name(cls) -> str: class Link(_AtomObject): """ - Identifies a related Web page. The rel attribute defines the type of relation. + Identifies a related Web page. + + The rel attribute defines the type of relation. A feed is limited to one alternate per type and hreflang. is patterned after html's link element. It has one required attribute, href, and five optional attributes: rel, type, hreflang, @@ -80,31 +84,11 @@ class Link(_AtomObject): """ href: Optional[str] - # href is the URI of the referenced resource - rel: Optional[str] - # rel contains a single link relationship type. - # It can be a full URI, or one of the following predefined values - # (default=alternate): - # alternate: an alternate representation - # enclosure: a related resource which is potentially large in size - # and might require special handling, for example an audio or video - # recording. - # related: an document related to the entry or feed. - # self: the feed itself. - # via: the source of the information provided in the entry. - type: Optional[str] - # indicates the media type of the resource - hreflang: Optional[str] - # indicates the language of the referenced resource - title: Optional[str] - # human readable information about the link - length: Optional[int] - # the length of the resource, in bytes def __init__( self, @@ -118,6 +102,37 @@ def __init__( length: Optional[int] = None, **kwargs: Any, ) -> None: + """ + Initialize a Link object. + + Parameters + ---------- + ns : str, optional + The namespace of the Link object. + name_spaces : dict, optional + The dictionary of namespace prefixes and URIs. + href : str, optional + The URI of the referenced resource. + rel : str, optional + The link relationship type. It can be a full URI or one of the + following predefined values: 'alternate', 'enclosure', 'related', + 'self', or 'via'. + type : str, optional + The media type of the resource. + hreflang : str, optional + The language of the referenced resource. + title : str, optional + Human-readable information about the link. + length : int, optional + The length of the resource in bytes. + kwargs : dict, optional + Additional keyword arguments. + + Returns + ------- + None + + """ super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.href = href self.rel = rel @@ -127,7 +142,15 @@ def __init__( self.length = length def __repr__(self) -> str: - """Create a string (c)representation for Link.""" + """ + Return a string representation of the Link object. + + Returns + ------- + str + The string representation of the Link object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -143,11 +166,34 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the Link object is truthy. + + Returns + ------- + bool + True if the Link object has a href attribute, False otherwise. + + """ return bool(self.href) def __eq__(self, other: object) -> bool: + """ + Check if the Link object is equal to another object. + + Parameters + ---------- + other : object + The object to compare with. + + Returns + ------- + bool + True if the Link object is equal to the other object, False otherwise. + + """ try: - assert isinstance(other, type(self)) + assert isinstance(other, type(self)) # noqa: S101 except AssertionError: return False return ( @@ -232,19 +278,19 @@ def __eq__(self, other: object) -> bool: class _Person(_AtomObject): """ - and describe a person, corporation, or similar - entity. It has one required element, name, and two optional elements: - uri, email. + Represents a person, corporation, or similar entity. + + Attributes + ---------- + name (Optional[str]): A human-readable name for the person. + uri (Optional[str]): A home page for the person. + email (Optional[str]): An email address for the person. + """ name: Optional[str] - # conveys a human-readable name for the person. - uri: Optional[str] - # contains a home page for the person. - email: Optional[str] - # contains an email address for the person. def __init__( self, @@ -255,13 +301,33 @@ def __init__( email: Optional[str] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the _Person class. + + Args: + ---- + ns (Optional[str]): The namespace for the XML element. + name_spaces (Optional[Dict[str, str]]): The namespace dictionary. + name (Optional[str]): A human-readable name for the person. + uri (Optional[str]): A home page for the person. + email (Optional[str]): An email address for the person. + **kwargs: Additional keyword arguments. + + """ super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.name = name self.uri = uri self.email = email def __repr__(self) -> str: - """Create a string (c)representation for _Person.""" + """ + Return a string representation of the _Person object. + + Returns + ------- + str: The string representation of the _Person object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -274,11 +340,32 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the _Person object has a name. + + Returns + ------- + bool: True if the _Person object has a name, False otherwise. + + """ return bool(self.name) def __eq__(self, other: object) -> bool: + """ + Check if the _Person object is equal to another object. + + Args: + ---- + other (object): The object to compare with. + + Returns: + ------- + bool: True if the _Person object is equal to the other object, + False otherwise. + + """ try: - assert isinstance(other, type(self)) + assert isinstance(other, type(self)) # noqa: S101 except AssertionError: return False return ( diff --git a/fastkml/containers.py b/fastkml/containers.py index e513701b..9b477b8e 100644 --- a/fastkml/containers.py +++ b/fastkml/containers.py @@ -63,9 +63,9 @@ class _Container(_Feature): """ - abstract element; do not create - A Container element holds one or more Features and allows the - creation of nested hierarchies. + A Container element that holds one or more Features. + + Supports the creation of nested hierarchies. subclasses are: Document, Folder. @@ -155,21 +155,23 @@ def append(self, kmlobj: _Feature) -> None: if kmlobj is self: msg = "Cannot append self" raise ValueError(msg) - assert self.features is not None + assert self.features is not None # noqa: S101 self.features.append(kmlobj) class Folder(_Container): """ - A Folder is used to arrange other Features hierarchically - (Folders, Placemarks, #NetworkLinks, or #Overlays). + A Folder is used to arrange other Features hierarchically. + + It may contain Folders, Placemarks, NetworkLinks, or Overlays. """ 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 + 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. """ @@ -200,6 +202,40 @@ def __init__( schemata: Optional[Iterable[Schema]] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the class. + + Args: + ---- + ns (Optional[str]): The namespace. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the container. + target_id (Optional[str]): The target ID. + name (Optional[str]): The name of the container. + visibility (Optional[bool]): The visibility flag. + isopen (Optional[bool]): The isopen flag. + atom_link (Optional[atom.Link]): The Atom link. + atom_author (Optional[atom.Author]): The Atom author. + address (Optional[str]): The address. + phone_number (Optional[str]): The phone number. + snippet (Optional[Snippet]): The snippet. + description (Optional[str]): The description. + view (Optional[Union[Camera, LookAt]]): The view. + times (Optional[Union[TimeSpan, TimeStamp]]): The times. + style_url (Optional[StyleUrl]): The style URL. + styles (Optional[Iterable[Union[Style, StyleMap]]]): The styles. + region (Optional[Region]): The region. + extended_data (Optional[ExtendedData]): The extended data. + features (Optional[List[_Feature]]): The list of features. + schemata (Optional[Iterable[Schema]]): The schemata. + **kwargs (Any): Additional keyword arguments. + + Returns: + ------- + None + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -255,6 +291,20 @@ def __repr__(self) -> str: ) def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]: + """ + Get a style by URL. + + Parameters + ---------- + style_url : str + The URL of the style. + + Returns + ------- + Optional[Union[Style, StyleMap]] + The style object if found, otherwise None. + + """ id_ = urlparse.urlparse(style_url).fragment return next((style for style in self.styles if style.id == id_), None) diff --git a/fastkml/data.py b/fastkml/data.py index 6dc1bb24..1b2c5822 100644 --- a/fastkml/data.py +++ b/fastkml/data.py @@ -16,7 +16,7 @@ """ Add Custom Data. -https://developers.google.com/kml/documentation/extendeddata#example +https://developers.google.com/kml/documentation/extendeddata """ import logging from typing import Any @@ -91,6 +91,26 @@ def __init__( display_name: Optional[str] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the Data class. + + Args: + ---- + ns (Optional[str]): The namespace of the data. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the data. + target_id (Optional[str]): The target ID of the data. + name (Optional[str]): The name of the data. + type (Optional[DataType]): The type of the data. + display_name (Optional[str]): The display name of the data. + **kwargs (Any): Additional keyword arguments. + + Returns: + ------- + None + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -103,7 +123,14 @@ def __init__( self.display_name = display_name def __repr__(self) -> str: - """Create a string (c)representation for SimpleField.""" + """ + Return a string representation of the SimpleField object. + + Returns + ------- + str: A string representation of the SimpleField object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -118,6 +145,14 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the object is considered True or False. + + Returns + ------- + bool: True if both the name and type are non-empty, False otherwise. + + """ return bool(self.name) and bool(self.type) @@ -180,6 +215,32 @@ def __init__( fields: Optional[Iterable[SimpleField]] = None, **kwargs: Any, ) -> None: + """ + Initialize a Schema object. + + Parameters + ---------- + ns : str, optional + The namespace of the schema. + name_spaces : dict[str, str], optional + The dictionary of namespace prefixes and URIs. + id : str, optional + The unique identifier for the schema. + target_id : str, optional + The target identifier for the schema. + name : str, optional + The name of the schema. + fields : Iterable[SimpleField], optional + The list of fields in the schema. + **kwargs : Any + Additional keyword arguments. + + Raises + ------ + KMLSchemaError + If the id is not provided. + + """ if id is None: msg = "Id is required for schema" raise KMLSchemaError(msg) @@ -194,7 +255,15 @@ def __init__( self.fields = list(fields) if fields else [] def __repr__(self) -> str: - """Create a string (c)representation for Schema.""" + """ + Return a string representation of the Schema object. + + Returns + ------- + str + The string representation of the Schema object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -208,7 +277,15 @@ def __repr__(self) -> str: ) def append(self, field: SimpleField) -> None: - """Append a field.""" + """ + Append a field to the schema. + + Parameters + ---------- + field : SimpleField + The field to be appended. + + """ self.fields.append(field) @@ -254,6 +331,26 @@ def __init__( display_name: Optional[str] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the Data class. + + Args: + ---- + ns (Optional[str]): The namespace of the data. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the data. + target_id (Optional[str]): The target ID of the data. + name (Optional[str]): The name of the data. + value (Optional[str]): The value of the data. + display_name (Optional[str]): The display name of the data. + **kwargs (Any): Additional keyword arguments. + + Returns: + ------- + None + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -266,7 +363,15 @@ def __init__( self.display_name = display_name def __repr__(self) -> str: - """Create a string (c)representation for Data.""" + """ + Create a string representation for Data. + + Returns + ------- + str + The string representation of the Data object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -281,6 +386,15 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the Data object is truthy. + + Returns + ------- + bool + True if the Data object has a name and a non-None value, False otherwise. + + """ return bool(self.name) and self.value is not None @@ -334,6 +448,21 @@ def __init__( value: Optional[str] = None, **kwargs: Any, ) -> None: + """ + Initialize a SimpleData object. + + Args: + ---- + ns (Optional[str]): The namespace of the object. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the object. + target_id (Optional[str]): The target ID of the object. + name (Optional[str]): The name of the object. + value (Optional[str]): The value of the object. + **kwargs: Additional keyword arguments. + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -345,7 +474,14 @@ def __init__( self.value = value def __repr__(self) -> str: - """Create a string (c)representation for SimpleData.""" + """ + Return a string representation of the SimpleData object. + + Returns + ------- + str: The string representation of the SimpleData object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -359,6 +495,14 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the SimpleData object is truthy. + + Returns + ------- + bool: True if the name and the value attribute are set, False otherwise. + + """ return bool(self.name) and self.value is not None @@ -388,15 +532,13 @@ def __bool__(self) -> bool: class SchemaData(_BaseObject): """ - - This element is used in conjunction with to add typed + Represents the SchemaData element in KML. + + The SchemaData element is used in conjunction with Schema to add typed custom data to a KML Feature. The Schema element (identified by the schemaUrl attribute) declares the custom data type. The actual data objects ("instances" of the custom data) are defined using the SchemaData element. - The can be a full URL, a reference to a Schema ID defined - in an external KML file, or a reference to a Schema ID defined - in the same KML file. """ schema_url: Optional[str] @@ -412,6 +554,25 @@ def __init__( data: Optional[Iterable[SimpleData]] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the Data class. + + Args: + ---- + ns (Optional[str]): The namespace for the data. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the data. + target_id (Optional[str]): The target ID of the data. + schema_url (Optional[str]): The URL of the schema for the data. + data (Optional[Iterable[SimpleData]]): The iterable of SimpleData objects. + **kwargs (Any): Additional keyword arguments. + + Returns: + ------- + None + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -423,7 +584,7 @@ def __init__( self.data = list(data) if data else [] def __repr__(self) -> str: - """Create a string (c)representation for SchemaData.""" + """Create a string representation for SchemaData.""" return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -437,9 +598,26 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the SchemaData object contains data. + + Returns + ------- + bool: True if the SchemaData object contains data and a + schema URL, False otherwise. + + """ return bool(self.data) and bool(self.schema_url) def append_data(self, data: SimpleData) -> None: + """ + Append a SimpleData object to the SchemaData. + + Args: + ---- + data (SimpleData): The SimpleData object to be appended. + + """ self.data.append(data) @@ -468,13 +646,7 @@ def append_data(self, data: SimpleData) -> None: class ExtendedData(_BaseObject): - """ - Represents a list of untyped name/value pairs. See docs: - - -> 'Adding Untyped Name/Value Pairs' - https://developers.google.com/kml/documentation/extendeddata - - """ + """Represents a list of untyped name/value pairs.""" elements: List[Union[Data, SchemaData]] @@ -487,6 +659,25 @@ def __init__( elements: Optional[Iterable[Union[Data, SchemaData]]] = None, **kwargs: Any, ) -> None: + """ + Initialize a new instance of the Data class. + + Args: + ---- + ns (Optional[str]): The namespace for the data. + name_spaces (Optional[Dict[str, str]]): + The dictionary of namespace prefixes and URIs. + id (Optional[str]): The ID of the data. + target_id (Optional[str]): The target ID of the data. + elements (Optional[Iterable[Union[Data, SchemaData]]]): + The iterable of data elements. + **kwargs (Any): Additional keyword arguments. + + Returns: + ------- + - None + + """ super().__init__( ns=ns, name_spaces=name_spaces, @@ -497,7 +688,14 @@ def __init__( self.elements = list(elements) if elements else [] def __repr__(self) -> str: - """Create a string (c)representation for ExtendedData.""" + """ + Return a string representation of the ExtendedData object. + + Returns + ------- + str: A string representation of the ExtendedData object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -510,6 +708,15 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the object has any elements. + + Returns + ------- + bool + True if the object has elements, False otherwise. + + """ return bool(self.elements) diff --git a/fastkml/enums.py b/fastkml/enums.py index 8200b1e7..407efed5 100644 --- a/fastkml/enums.py +++ b/fastkml/enums.py @@ -54,14 +54,16 @@ class MyEnum(RelaxedEnum): @classmethod def _missing_(cls, value: object) -> "RelaxedEnum": - assert isinstance(value, str) + assert isinstance(value, str) # noqa: S101 value = value.lower() for member in cls: - assert isinstance(member.value, str) + assert isinstance(member.value, str) # noqa: S101 if member.value.lower() == value.lower(): logger.warning( - f"{cls.__name__}: " - f"Found case-insensitive match for {value} in {member.value}", + "%s: Found case-insensitive match for %s in %r", + cls.__name__, + value, + member.value, ) return member msg = ( @@ -140,6 +142,8 @@ class AltitudeMode(RelaxedEnum): @unique class DataType(RelaxedEnum): + """Data type for SimpleField in extended data.""" + string = "string" int_ = "int" uint = "uint" @@ -251,9 +255,7 @@ class Units(RelaxedEnum): @unique class PairKey(RelaxedEnum): - """ - Key for Pair. - """ + """Key for Pair.""" normal = "normal" highlight = "highlight" diff --git a/fastkml/features.py b/fastkml/features.py index 52f5bad0..3190d2cd 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -83,6 +83,7 @@ class Snippet(_XMLObject): """ A short description of the feature. + In Google Earth, this description is displayed in the Places panel under the name of the feature. If a Snippet is not supplied, the first two lines of the @@ -107,12 +108,40 @@ def __init__( max_lines: Optional[int] = None, **kwargs: Any, ) -> None: + """ + Initialize a Feature object. + + Args: + ---- + ns : str, optional + The namespace for the feature. + name_spaces : dict[str, str], optional + A dictionary of namespace prefixes and URIs. + text : str, optional + The text content of the feature. + max_lines : int, optional + The maximum number of lines for the feature. + **kwargs : Any + Additional keyword arguments. + + Returns: + ------- + None + + """ super().__init__(ns=ns, name_spaces=name_spaces, **kwargs) self.text = text self.max_lines = max_lines def __repr__(self) -> str: - """Create a string (c)representation for Snippet.""" + """ + Create a string representation for Snippet. + + Returns + ------- + str: The string representation of the Snippet object. + + """ return ( f"{self.__class__.__module__}.{self.__class__.__name__}(" f"ns={self.ns!r}, " @@ -124,6 +153,15 @@ def __repr__(self) -> str: ) def __bool__(self) -> bool: + """ + Check if the feature has text. + + Returns + ------- + bool + True if the feature has text, False otherwise. + + """ return bool(self.text) @@ -153,102 +191,30 @@ def __bool__(self) -> bool: class _Feature(TimeMixin, _BaseObject): """ - abstract element; do not create - subclasses are: - * Container (Document, Folder) - * Placemark - * Overlay - * NetworkLink. + Abstract base class representing a feature in KML. + + Direct known subclasses: + - Container (Document, Folder) + - Placemark + - Overlay + - NetworkLink + - NetworkLink. """ name: Optional[str] - # User-defined text displayed in the 3D viewer as the label for the - # object (for example, for a Placemark, Folder, or NetworkLink). - visibility: Optional[bool] - # Boolean value. Specifies whether the feature is drawn in the 3D - # viewer when it is initially loaded. In order for a feature to be - # visible, the tag of all its ancestors must also be - # set to 1. - isopen: Optional[bool] - # Boolean value. Specifies whether a Document or Folder appears - # closed or open when first loaded into the Places panel. - # 0=collapsed (the default), 1=expanded. - atom_author: Optional[atom.Author] - # KML 2.2 supports new elements for including data about the author - # and related website in your KML file. This information is displayed - # in geo search results, both in Earth browsers such as Google Earth, - # and in other applications such as Google Maps. - atom_link: Optional[atom.Link] - # Specifies the URL of the website containing this KML or KMZ file. - address: Optional[str] - # A string value representing an unstructured address written as a - # standard street, city, state address, and/or as a postal code. - # You can use the
tag to specify the location of a point - # instead of using latitude and longitude coordinates. - phone_number: Optional[str] - # A string value representing a telephone number. - # This element is used by Google Maps Mobile only. - snippet: Optional[Snippet] - # _snippet is either a tuple of a string Snippet.text and an integer - # Snippet.maxLines or a string - # - # A short description of the feature. In Google Earth, this - # description is displayed in the Places panel under the name of the - # feature. If a Snippet is not supplied, the first two lines of - # the are used. In Google Earth, if a Placemark - # contains both a description and a Snippet, the appears - # beneath the Placemark in the Places panel, and the - # appears in the Placemark's description balloon. This tag does not - # support HTML markup. has a maxLines attribute, an integer - # that specifies the maximum number of lines to display. - description: Optional[str] - # User-supplied content that appears in the description balloon. - style_url: Optional[StyleUrl] - # URL of a