Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

390 network link control #398

Merged
merged 17 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Changelog

- Add support for ScreenOverlay and Model.
- allow parsing kml files without namespace declarations.
- Add support for NetworkLinkControl. [Apurva Banka]


1.0 (2024/11/19)
Expand Down
16 changes: 16 additions & 0 deletions docs/network.kml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
hint="A processing hint for a KML consumer">
<NetworkLinkControl>
<minRefreshPeriod>43200</minRefreshPeriod>
<maxSessionLength>-1</maxSessionLength>
<linkSnippet>
<![CDATA[
<p xmlns="http://www.w3.org/1999/xhtml">A snippet of <a href="http://www.w3.org/TR/xhtml-basic/">XHTML</a></p>
]]>
</linkSnippet>
<expires>2008-05-30</expires>
</NetworkLinkControl>
</kml>
2 changes: 2 additions & 0 deletions fastkml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from fastkml.model import Orientation
from fastkml.model import ResourceMap
from fastkml.model import Scale
from fastkml.network_link_control import NetworkLinkControl
from fastkml.overlays import GroundOverlay
from fastkml.overlays import ImagePyramid
from fastkml.overlays import LatLonBox
Expand Down Expand Up @@ -119,6 +120,7 @@
"Model",
"MultiGeometry",
"NetworkLink",
"NetworkLinkControl",
"Orientation",
"OuterBoundaryIs",
"OverlayXY",
Expand Down
27 changes: 23 additions & 4 deletions fastkml/kml.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from fastkml.features import Placemark
from fastkml.helpers import xml_subelement_list
from fastkml.helpers import xml_subelement_list_kwarg
from fastkml.network_link_control import NetworkLinkControl
from fastkml.overlays import GroundOverlay
from fastkml.overlays import PhotoOverlay
from fastkml.registry import RegistryItem
Expand All @@ -59,7 +60,14 @@

logger = logging.getLogger(__name__)

kml_children = Union[Folder, Document, Placemark, GroundOverlay, PhotoOverlay]
kml_children = Union[
Folder,
Document,
Placemark,
GroundOverlay,
PhotoOverlay,
NetworkLinkControl,
]


def lxml_parse_and_validate(
Expand Down Expand Up @@ -285,9 +293,20 @@ def write(
registry.register(
KML,
RegistryItem(
ns_ids=("kml", ""),
classes=(Document, Folder, Placemark, GroundOverlay, PhotoOverlay, NetworkLink),
node_name="Document,Folder,Placemark,GroundOverlay,PhotoOverlay,NetworkLink",
ns_ids=("kml",),
classes=(
Document,
Folder,
Placemark,
GroundOverlay,
PhotoOverlay,
NetworkLink,
NetworkLinkControl,
),
node_name=(
"Document,Folder,Placemark,GroundOverlay,PhotoOverlay,NetworkLink,"
"NetworkLinkControl"
),
attr_name="features",
get_kwarg=xml_subelement_list_kwarg,
set_element=xml_subelement_list,
Expand Down
261 changes: 261 additions & 0 deletions fastkml/network_link_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
# 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
"""
NetworkLinkControl class.

Controls the behavior of files fetched by a <NetworkLink>.

https://developers.google.com/kml/documentation/kmlreference#networklinkcontrol
"""

import logging
from typing import Any
from typing import Dict
from typing import Optional
from typing import Union

from fastkml import config
from fastkml.base import _XMLObject
from fastkml.helpers import clean_string
from fastkml.helpers import datetime_subelement
from fastkml.helpers import datetime_subelement_kwarg
from fastkml.helpers import float_subelement
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.registry import RegistryItem
from fastkml.registry import registry
from fastkml.times import KmlDateTime
from fastkml.views import Camera
from fastkml.views import LookAt

__all__ = [
"NetworkLinkControl",
]

logger = logging.getLogger(__name__)


class NetworkLinkControl(_XMLObject):
"""Controls the behavior of files fetched by a <NetworkLink>."""

_default_nsid = config.KML

min_refresh_period: Optional[float]
max_session_length: Optional[float]
Comment on lines +59 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure default values are consistent between __init__ and registry

The default values for min_refresh_period and max_session_length in the __init__ method are None, but in the registry registration, they default to 0 and -1, respectively. Aligning these defaults can prevent unexpected behavior.

Consider setting the same default values in the __init__ method:

 def __init__(
     self,
     ns: Optional[str] = None,
     name_spaces: Optional[Dict[str, str]] = None,
-    min_refresh_period: Optional[float] = None,
-    max_session_length: Optional[float] = None,
+    min_refresh_period: Optional[float] = 0,
+    max_session_length: Optional[float] = -1,
     cookie: Optional[str] = None,
     # ... rest of the parameters
 ) -> None:

Committable suggestion skipped: line range outside the PR's diff.

cookie: Optional[str]
message: Optional[str]
link_name: Optional[str]
link_description: Optional[str]
link_snippet: Optional[str]
expires: Optional[KmlDateTime]
view: Union[Camera, LookAt, None]

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[Dict[str, str]] = None,
min_refresh_period: Optional[float] = None,
max_session_length: Optional[float] = None,
cookie: Optional[str] = None,
message: Optional[str] = None,
link_name: Optional[str] = None,
link_description: Optional[str] = None,
link_snippet: Optional[str] = None,
expires: Optional[KmlDateTime] = None,
view: Optional[Union[Camera, LookAt]] = None,
**kwargs: Any,
) -> None:
"""
Create a NetworkLinkControl object.

Parameters
----------
ns : str, optional
The namespace to use for the NetworkLinkControl object.
name_spaces : dict, optional
A dictionary of namespaces to use for the NetworkLinkControl object.
min_refresh_period : float, optional
The minimum number of seconds between fetches. A value of -1 indicates that
the NetworkLinkControl object should be fetched only once.
max_session_length : float, optional
The maximum number of seconds that the link should be followed.
cookie : str, optional
A string value that can be used to identify the client request.
message : str, optional
A message to be displayed to the user in case of a failure.
link_name : str, optional
The name of the link.
link_description : str, optional
A description of the link.
link_snippet : str, optional
A snippet of text to be displayed in the link.
expires : KmlDateTime, optional
The time at which the link should expire.
view : Camera or LookAt, optional
The view to be used when the link is followed.
**kwargs : Any, optional
Additional keyword arguments.

"""
super().__init__(
ns=ns,
name_spaces=name_spaces,
**kwargs,
)
self.min_refresh_period = min_refresh_period
self.max_session_length = max_session_length
self.cookie = clean_string(cookie)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider handling None values before calling clean_string()

The clean_string() function is called on several parameters that could be None. Consider adding None checks or ensuring clean_string() handles None gracefully.

Suggested change
self.cookie = clean_string(cookie)
self.cookie = clean_string(cookie) if cookie is not None else None

self.message = clean_string(message)
self.link_name = clean_string(link_name)
self.link_description = clean_string(link_description)
self.link_snippet = clean_string(link_snippet)
self.expires = expires
self.view = view

def __repr__(self) -> str:
"""
Return a string representation of the NetworkLinkControl object.

Returns
-------
str: A string representation of the NetworkLinkControl object.

"""
return (
f"{self.__class__.__module__}.{self.__class__.__name__}("
f"ns={self.ns!r}, "
f"name_spaces={self.name_spaces!r}, "
f"min_refresh_period={self.min_refresh_period!r}, "
f"max_session_length={self.max_session_length!r}, "
f"cookie={self.cookie!r}, "
f"message={self.message!r}, "
f"link_name={self.link_name!r}, "
f"link_description={self.link_description!r}, "
f"link_snippet={self.link_snippet!r}, "
f"expires={self.expires!r}, "
f"view={self.view!r}, "
f"**{self._get_splat()!r},"
")"
)


registry.register(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider consolidating the repeated registry.register() calls into a single helper function with a data-driven approach.

The repeated registry.register() calls can be simplified while maintaining type safety. Here's a suggested approach:

def register_network_link_controls(cls):
    registrations = [
        # (attr_name, node_name, classes, get_kwarg, set_element, default)
        ("min_refresh_period", "minRefreshPeriod", (float,), 
         subelement_float_kwarg, float_subelement, 0),
        ("max_session_length", "maxSessionLength", (float,),
         subelement_float_kwarg, float_subelement, -1),
        ("cookie", "cookie", (str,),
         subelement_text_kwarg, text_subelement, None),
        # ... other registrations
    ]

    for reg in registrations:
        attr_name, node_name, classes, get_kwarg, set_element, default = reg
        registry.register(
            cls,
            RegistryItem(
                ns_ids=("kml",),
                attr_name=attr_name,
                node_name=node_name,
                classes=classes,
                get_kwarg=get_kwarg,
                set_element=set_element,
                default=default if default is not None else None
            )
        )

# Usage:
register_network_link_controls(NetworkLinkControl)

This approach:

  • Reduces repetition while maintaining type safety
  • Makes it easier to add/modify registrations
  • Keeps all registration config in one place
  • Reduces potential for copy-paste errors

The registration data could alternatively be stored as a module-level constant if you prefer to separate the configuration from the registration logic.

NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="min_refresh_period",
node_name="minRefreshPeriod",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=0,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="max_session_length",
node_name="maxSessionLength",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
default=-1,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="cookie",
node_name="cookie",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="message",
node_name="message",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="link_name",
node_name="linkName",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="link_description",
node_name="linkDescription",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="link_snippet",
node_name="linkSnippet",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
),
)
registry.register(
NetworkLinkControl,
item=RegistryItem(
ns_ids=("kml",),
classes=(KmlDateTime,),
attr_name="expires",
node_name="expires",
get_kwarg=datetime_subelement_kwarg,
set_element=datetime_subelement,
),
)
registry.register(
NetworkLinkControl,
RegistryItem(
ns_ids=("kml",),
attr_name="view",
node_name="Camera,LookAt",
classes=(
Camera,
LookAt,
),
get_kwarg=xml_subelement_kwarg,
set_element=xml_subelement,
),
)
Loading
Loading