From d5663ada7a0f7ec85d7c86d14bbfec50f3d1b246 Mon Sep 17 00:00:00 2001 From: Bastien GAUTHIER Date: Fri, 6 Oct 2023 16:29:09 +0200 Subject: [PATCH 1/5] Make the map pickable using copyreg --- folium/folium.py | 92 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/folium/folium.py b/folium/folium.py index a9e396613..dbd7424c5 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -2,9 +2,11 @@ Make beautiful, interactive maps with Python and Leaflet.js """ - +import copyreg +import re import time import webbrowser +from collections import OrderedDict from typing import Any, List, Optional, Sequence, Union from branca.element import Element, Figure, MacroElement @@ -16,7 +18,6 @@ from folium.utilities import ( TypeBounds, TypeJsonValue, - _parse_size, parse_options, temp_html_filepath, validate_location, @@ -24,6 +25,26 @@ ENV = Environment(loader=PackageLoader("folium", "templates")) +def _parse_size(value): + if isinstance(value, (int, float)): + return float(value), "px" + elif isinstance(value, str): + # match digits or a point, possibly followed by a space, + # followed by a unit: either 1 to 5 letters or a percent sign + match = re.fullmatch(r"([\d.]+)\s?(\w{1,5}|%)", value.strip()) + if match: + return float(match.group(1)), match.group(2) + else: + raise ValueError( + f"Cannot parse {value!r}, it should be a number followed by a unit.", + ) + elif isinstance(value, tuple) and isinstance(value[0], (int, float)) and isinstance(value[1], str): + # value had been already parsed + return value + else: + raise TypeError( + f"Cannot parse {value!r}, it should be a number or a string containing a number and a unit.", + ) _default_js = [ ("leaflet", "https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"), @@ -258,9 +279,26 @@ def __init__( disable_3d: bool = False, png_enabled: bool = False, zoom_control: bool = True, + _children: OrderedDict = OrderedDict(), **kwargs: TypeJsonValue, ): super().__init__() + self.tiles = tiles + self.attr = attr + self.min_zoom = min_zoom + self.max_zoom = max_zoom + self.zoom_start = zoom_start + self.min_lat = min_lat + self.max_lat = max_lat + self.min_lon = min_lon + self.max_lon = max_lon + self.max_bounds = max_bounds + self.prefer_canvas = prefer_canvas + self.no_touch = no_touch + self.disable_3d = disable_3d + self.zoom_control = zoom_control + self._children = _children + self._name = "Map" self._env = ENV @@ -301,14 +339,18 @@ def __init__( self.global_switches = GlobalSwitches(no_touch, disable_3d) self.objects_to_stay_in_front: List[Layer] = [] - - if isinstance(tiles, TileLayer): - self.add_child(tiles) - elif tiles: - tile_layer = TileLayer( - tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom - ) - self.add_child(tile_layer, name=tile_layer.tile_name) + + if len(self._children) > 0: # Add already prepared childrens + for key, child in self._children.items(): # add the missing ones (key is a safety) + self.add_child(child, name = key) + else: # Initialize the tiles + if isinstance(tiles, TileLayer): + self.add_child(tiles, index = 0) + elif tiles: + tile_layer = TileLayer( + tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom, index = 0 + ) + self.add_child(tile_layer, name=tile_layer.tile_name) def _repr_html_(self, **kwargs) -> str: """Displays the HTML Map in a Jupyter notebook.""" @@ -473,3 +515,33 @@ def keep_in_front(self, *args: Layer) -> None: """ for obj in args: self.objects_to_stay_in_front.append(obj) + +def map_reduce(map_instance): + return (Map, ( + map_instance.location, + map_instance.width, + map_instance.height, + map_instance.left, + map_instance.top, + map_instance.position, + map_instance.tiles, + map_instance.attr, + map_instance.min_zoom, + map_instance.max_zoom, + map_instance.zoom_start, + map_instance.min_lat, + map_instance.max_lat, + map_instance.min_lon, + map_instance.max_lon, + map_instance.max_bounds, + map_instance.crs, + map_instance.control_scale, + map_instance.prefer_canvas, + map_instance.no_touch, + map_instance.disable_3d, + map_instance.png_enabled, + map_instance.zoom_control, + map_instance._children, + )) + +copyreg.pickle(Map, map_reduce) \ No newline at end of file From 1254fe94c42198b8bae18d575420dcda554095cb Mon Sep 17 00:00:00 2001 From: Bastien GAUTHIER Date: Fri, 6 Oct 2023 17:47:33 +0200 Subject: [PATCH 2/5] remove index = 0 for selenium compatibility add set_parent_for_children function needed after unpickling --- folium/folium.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/folium/folium.py b/folium/folium.py index dbd7424c5..b1b077ed8 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -345,13 +345,18 @@ def __init__( self.add_child(child, name = key) else: # Initialize the tiles if isinstance(tiles, TileLayer): - self.add_child(tiles, index = 0) + self.add_child(tiles) elif tiles: tile_layer = TileLayer( - tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom, index = 0 + tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom ) self.add_child(tile_layer, name=tile_layer.tile_name) + def set_parent_for_children(self) -> None: + for _, child in self._children.items(): + child._parent = self + return None + def _repr_html_(self, **kwargs) -> str: """Displays the HTML Map in a Jupyter notebook.""" if self._parent is None: @@ -516,7 +521,7 @@ def keep_in_front(self, *args: Layer) -> None: for obj in args: self.objects_to_stay_in_front.append(obj) -def map_reduce(map_instance): +def map_pickler(map_instance): return (Map, ( map_instance.location, map_instance.width, @@ -544,4 +549,4 @@ def map_reduce(map_instance): map_instance._children, )) -copyreg.pickle(Map, map_reduce) \ No newline at end of file +copyreg.pickle(Map, map_pickler) \ No newline at end of file From 7c45bca125ff07e1b0a9996ce17142be737d474c Mon Sep 17 00:00:00 2001 From: Bastien GAUTHIER Date: Thu, 12 Oct 2023 14:29:57 +0200 Subject: [PATCH 3/5] Remove copyreg & adapt __setstate__ in each class --- folium/features.py | 129 +++++++++++++++++++++++++++------------- folium/folium.py | 120 +++++++++++++------------------------ folium/map.py | 99 +++++++++++++++++++----------- folium/raster_layers.py | 44 +++++++++----- folium/vector_layers.py | 52 ++++++++++------ 5 files changed, 257 insertions(+), 187 deletions(-) diff --git a/folium/features.py b/folium/features.py index 0d157c7bb..e6914294e 100644 --- a/folium/features.py +++ b/folium/features.py @@ -62,9 +62,7 @@ class RegularPolygonMarker(JSCSSMixin, Marker): https://humangeo.github.io/leaflet-dvf/ """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = new L.RegularPolygonMarker( {{ this.location|tojson }}, @@ -72,7 +70,7 @@ class RegularPolygonMarker(JSCSSMixin, Marker): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - ) + _template = Template(_template_str) default_js = [ ( @@ -101,6 +99,10 @@ def __init__( ) ) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class Vega(JSCSSMixin, Element): """ @@ -132,7 +134,8 @@ class Vega(JSCSSMixin, Element): """ - _template = Template("") + _template_str = "" + _template = Template(_template_str) default_js = [ ("d3", "https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"), @@ -166,6 +169,11 @@ def __init__( self.top = _parse_size(top) self.position = position + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" super().render(**kwargs) @@ -254,8 +262,8 @@ class VegaLite(Element): Ex: 'relative', 'absolute' """ - - _template = Template("") + _template_str = "" + _template = Template(_template_str) def __init__( self, @@ -285,6 +293,11 @@ def __init__( self.top = _parse_size(top) self.position = position + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" self._parent.html.add_child( @@ -522,8 +535,7 @@ class GeoJson(Layer): """ - _template = Template( - """ + _template_str =""" {% macro script(this, kwargs) %} {%- if this.style %} function {{ this.get_name() }}_styler(feature) { @@ -623,8 +635,8 @@ class GeoJson(Layer): {%- endif %} {% endmacro %} - """ - ) # noqa + """ # noqa + _template = Template(_template_str) def __init__( self, @@ -682,6 +694,11 @@ def __init__( if isinstance(popup, (GeoJsonPopup, Popup)): self.add_child(popup) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def process_data(self, data: Any) -> dict: """Convert an unknown data input into a geojson dictionary.""" if isinstance(data, dict): @@ -925,8 +942,7 @@ class TopoJson(JSCSSMixin, Layer): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }}_data = {{ this.data|tojson }}; var {{ this.get_name() }} = L.geoJson( @@ -945,7 +961,7 @@ class TopoJson(JSCSSMixin, Layer): }); {% endmacro %} """ - ) # noqa + _template = Template(_template_str) default_js = [ ( @@ -993,6 +1009,11 @@ def __init__( elif tooltip is not None: self.add_child(Tooltip(tooltip)) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def style_data(self) -> None: """Applies self.style_function to each feature of self.data.""" @@ -1225,15 +1246,15 @@ class GeoJsonTooltip(GeoJsonDetail): >>> GeoJsonTooltip(fields=("CNTY_NM",), labels=False, sticky=False) """ - _template = Template( - """ + _template_str = "".join([""" {% macro script(this, kwargs) %} - {{ this._parent.get_name() }}.bindTooltip(""" - + GeoJsonDetail.base_template - + """,{{ this.tooltip_options | tojson | safe }}); + {{ this._parent.get_name() }}.bindTooltip(""", + GeoJsonDetail.base_template, + """,{{ this.tooltip_options | tojson | safe }}); {% endmacro %} """ - ) + ]) + _template = Template(_template_str) def __init__( self, @@ -1258,6 +1279,11 @@ def __init__( kwargs.update({"sticky": sticky, "class_name": class_name}) self.tooltip_options = {camelize(key): kwargs[key] for key in kwargs.keys()} + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class GeoJsonPopup(GeoJsonDetail): """ @@ -1293,15 +1319,16 @@ class GeoJsonPopup(GeoJsonDetail): ).add_to(gjson) """ - _template = Template( + _template_str = "".join([ """ {% macro script(this, kwargs) %} - {{ this._parent.get_name() }}.bindPopup(""" - + GeoJsonDetail.base_template - + """,{{ this.popup_options | tojson | safe }}); + {{ this._parent.get_name() }}.bindPopup(""", + GeoJsonDetail.base_template, + """,{{ this.popup_options | tojson | safe }}); {% endmacro %} """ - ) + ]) + _template_ = Template(_template_str) def __init__( self, @@ -1325,6 +1352,10 @@ def __init__( kwargs.update({"class_name": self.class_name}) self.popup_options = {camelize(key): value for key, value in kwargs.items()} + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class Choropleth(FeatureGroup): """Apply a GeoJSON overlay to the map. @@ -1663,14 +1694,13 @@ class DivIcon(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.divIcon({{ this.options|tojson }}); {{this._parent.get_name()}}.setIcon({{this.get_name()}}); {% endmacro %} """ - ) # noqa + _template_ = Template(_template_str) def __init__( self, @@ -1690,6 +1720,11 @@ def __init__( class_name=class_name, ) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class LatLngPopup(MacroElement): """ @@ -1698,8 +1733,7 @@ class LatLngPopup(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{this.get_name()}} = L.popup(); function latLngPop(e) { @@ -1712,12 +1746,16 @@ class LatLngPopup(MacroElement): {{this._parent.get_name()}}.on('click', latLngPop); {% endmacro %} """ - ) # noqa + _template_ = Template(_template_str) def __init__(self): super().__init__() self._name = "LatLngPopup" + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class ClickForMarker(MacroElement): """ @@ -1739,8 +1777,7 @@ class ClickForMarker(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} function newMarker(e){ var new_mark = L.marker().setLatLng(e.latlng).addTo({{this._parent.get_name()}}); @@ -1753,7 +1790,7 @@ class ClickForMarker(MacroElement): {{this._parent.get_name()}}.on('click', newMarker); {% endmacro %} """ - ) # noqa + _template = Template(_template_str) def __init__(self, popup: Union[IFrame, Html, str, None] = None): super().__init__() @@ -1766,6 +1803,11 @@ def __init__(self, popup: Union[IFrame, Html, str, None] = None): else: self.popup = '"Latitude: " + lat + "
Longitude: " + lng ' + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class ClickForLatLng(MacroElement): """ @@ -1783,8 +1825,7 @@ class ClickForLatLng(MacroElement): Whether there should be an alert when something has been copied to clipboard. """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} function getLatLng(e){ var lat = e.latlng.lat.toFixed(6), @@ -1796,7 +1837,7 @@ class ClickForLatLng(MacroElement): {{this._parent.get_name()}}.on('click', getLatLng); {% endmacro %} """ - ) # noqa + _template = Template(_template_str) def __init__(self, format_str: Optional[str] = None, alert: bool = True): super().__init__() @@ -1804,6 +1845,10 @@ def __init__(self, format_str: Optional[str] = None, alert: bool = True): self.format_str = format_str or 'lat + "," + lng' self.alert = alert + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class CustomIcon(Icon): """ @@ -1839,14 +1884,13 @@ class CustomIcon(Icon): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.icon({{ this.options|tojson }}); {{ this._parent.get_name() }}.setIcon({{ this.get_name() }}); {% endmacro %} """ - ) # noqa + _template = Template(_template_str) def __init__( self, @@ -1870,6 +1914,11 @@ def __init__( popup_anchor=popup_anchor, ) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class ColorLine(FeatureGroup): """ diff --git a/folium/folium.py b/folium/folium.py index b1b077ed8..6d74c5e5f 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -2,11 +2,8 @@ Make beautiful, interactive maps with Python and Leaflet.js """ -import copyreg -import re import time import webbrowser -from collections import OrderedDict from typing import Any, List, Optional, Sequence, Union from branca.element import Element, Figure, MacroElement @@ -18,6 +15,7 @@ from folium.utilities import ( TypeBounds, TypeJsonValue, + _parse_size, parse_options, temp_html_filepath, validate_location, @@ -25,26 +23,6 @@ ENV = Environment(loader=PackageLoader("folium", "templates")) -def _parse_size(value): - if isinstance(value, (int, float)): - return float(value), "px" - elif isinstance(value, str): - # match digits or a point, possibly followed by a space, - # followed by a unit: either 1 to 5 letters or a percent sign - match = re.fullmatch(r"([\d.]+)\s?(\w{1,5}|%)", value.strip()) - if match: - return float(match.group(1)), match.group(2) - else: - raise ValueError( - f"Cannot parse {value!r}, it should be a number followed by a unit.", - ) - elif isinstance(value, tuple) and isinstance(value[0], (int, float)) and isinstance(value[1], str): - # value had been already parsed - return value - else: - raise TypeError( - f"Cannot parse {value!r}, it should be a number or a string containing a number and a unit.", - ) _default_js = [ ("leaflet", "https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"), @@ -86,14 +64,13 @@ def _parse_size(value): class GlobalSwitches(Element): - _template = Template( - """ + _template_str = """ """ - ) + _template = Template(_template_str) def __init__(self, no_touch=False, disable_3d=False): super().__init__() @@ -101,6 +78,10 @@ def __init__(self, no_touch=False, disable_3d=False): self.no_touch = no_touch self.disable_3d = disable_3d + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class Map(JSCSSMixin, MacroElement): """Create a Map with Folium and Leaflet.js @@ -199,8 +180,7 @@ class Map(JSCSSMixin, MacroElement): """ # noqa - _template = Template( - """ + _template_str = """ {% macro header(this, kwargs) %} @@ -248,7 +228,7 @@ class Map(JSCSSMixin, MacroElement): {% endmacro %} """ - ) + _template = Template(_template_str) # use the module variables for backwards compatibility default_js = _default_js @@ -279,25 +259,9 @@ def __init__( disable_3d: bool = False, png_enabled: bool = False, zoom_control: bool = True, - _children: OrderedDict = OrderedDict(), **kwargs: TypeJsonValue, ): super().__init__() - self.tiles = tiles - self.attr = attr - self.min_zoom = min_zoom - self.max_zoom = max_zoom - self.zoom_start = zoom_start - self.min_lat = min_lat - self.max_lat = max_lat - self.min_lon = min_lon - self.max_lon = max_lon - self.max_bounds = max_bounds - self.prefer_canvas = prefer_canvas - self.no_touch = no_touch - self.disable_3d = disable_3d - self.zoom_control = zoom_control - self._children = _children self._name = "Map" self._env = ENV @@ -352,10 +316,38 @@ def __init__( ) self.add_child(tile_layer, name=tile_layer.tile_name) - def set_parent_for_children(self) -> None: - for _, child in self._children.items(): - child._parent = self - return None + def __getstate__(self): + """Modify object state when pickling the object. + jinja2 Environment cannot be pickled, so set + the ._env attribute to None. This will be added back + when unpickling (see __setstate__) + """ + state = super().__getstate__() + state["_png_image"] = None + return state + + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self.zoom_start = state["options"]["zoom"] + self.zoom_control = state["options"]["zoomControl"] + self.prefer_canvas = state["options"]["preferCanvas"] + max_bounds_array = state["options"].get("maxBounds") + if max_bounds_array is None: + self.max_bounds = False + self.min_lat = -90 + self.max_lat = 90 + self.min_lon = -180 + self.max_lon = 180 + else: # max_bounds_array = [[min_lat, min_lon], [max_lat, max_lon]] + self.max_bounds = True + self.min_lat = max_bounds_array[0][0] + self.max_lat = max_bounds_array[1][0] + self.min_lon = max_bounds_array[0][1] + self.max_lon = max_bounds_array[1][1] + + self._template = Template(self._template_str) + def _repr_html_(self, **kwargs) -> str: """Displays the HTML Map in a Jupyter notebook.""" @@ -520,33 +512,3 @@ def keep_in_front(self, *args: Layer) -> None: """ for obj in args: self.objects_to_stay_in_front.append(obj) - -def map_pickler(map_instance): - return (Map, ( - map_instance.location, - map_instance.width, - map_instance.height, - map_instance.left, - map_instance.top, - map_instance.position, - map_instance.tiles, - map_instance.attr, - map_instance.min_zoom, - map_instance.max_zoom, - map_instance.zoom_start, - map_instance.min_lat, - map_instance.max_lat, - map_instance.min_lon, - map_instance.max_lon, - map_instance.max_bounds, - map_instance.crs, - map_instance.control_scale, - map_instance.prefer_canvas, - map_instance.no_touch, - map_instance.disable_3d, - map_instance.png_enabled, - map_instance.zoom_control, - map_instance._children, - )) - -copyreg.pickle(Map, map_pickler) \ No newline at end of file diff --git a/folium/map.py b/folium/map.py index 8a8c054b6..25d7c8231 100644 --- a/folium/map.py +++ b/folium/map.py @@ -95,16 +95,14 @@ class FeatureGroup(Layer): https://leafletjs.com/reference.html#featuregroup """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.featureGroup( {{ this.options|tojson }} ); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -119,6 +117,10 @@ def __init__( self.tile_name = name if name is not None else self.get_name() self.options = parse_options(**kwargs) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class LayerControl(MacroElement): """ @@ -153,8 +155,7 @@ class LayerControl(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this,kwargs) %} var {{ this.get_name() }}_layers = { base_layers : { @@ -180,7 +181,7 @@ class LayerControl(MacroElement): {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -199,6 +200,11 @@ def __init__( self.base_layers: OrderedDict[str, str] = OrderedDict() self.overlays: OrderedDict[str, str] = OrderedDict() + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def reset(self) -> None: self.base_layers = OrderedDict() self.overlays = OrderedDict() @@ -250,8 +256,7 @@ class Icon(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.AwesomeMarkers.icon( {{ this.options|tojson }} @@ -259,7 +264,7 @@ class Icon(MacroElement): {{ this._parent.get_name() }}.setIcon({{ this.get_name() }}); {% endmacro %} """ - ) + _template = Template(_template_str) color_options = { "red", "darkred", @@ -307,6 +312,10 @@ def __init__( **kwargs, ) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class Marker(MacroElement): """ @@ -342,8 +351,7 @@ class Marker(MacroElement): ... ) """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.marker( {{ this.location|tojson }}, @@ -351,7 +359,7 @@ class Marker(MacroElement): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -378,6 +386,11 @@ def __init__( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def _get_self_bounds(self) -> List[List[float]]: """Computes the bounds of the object itself. @@ -413,8 +426,7 @@ class Popup(Element): True only loads the Popup content when clicking on the Marker. """ - _template = Template( - """ + _template_str = """ var {{this.get_name()}} = L.popup({{ this.options|tojson }}); {% for name, element in this.html._children.items() %} @@ -434,8 +446,8 @@ class Popup(Element): {% for name, element in this.script._children.items() %} {{element.render()}} {% endfor %} - """ - ) # noqa + """ # noqa + _template = Template(_template_str) def __init__( self, @@ -474,6 +486,11 @@ def __init__( **kwargs, ) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" for name, child in self._children.items(): @@ -509,9 +526,7 @@ class Tooltip(MacroElement): available here: https://leafletjs.com/reference.html#tooltip """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} {{ this._parent.get_name() }}.bindTooltip( ` @@ -521,7 +536,7 @@ class Tooltip(MacroElement): ); {% endmacro %} """ - ) + _template = Template(_template_str) valid_options: Dict[str, Tuple[Type, ...]] = { "pane": (str,), "offset": (tuple,), @@ -556,6 +571,11 @@ def __init__( # noqa outside of type checking. self.style = style + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def parse_options( self, kwargs: Dict[str, TypeJsonValue], @@ -595,8 +615,7 @@ class FitBounds(MacroElement): Maximum zoom to be used. """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} {{ this._parent.get_name() }}.fitBounds( {{ this.bounds|tojson }}, @@ -604,7 +623,7 @@ class FitBounds(MacroElement): ); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -624,6 +643,11 @@ def __init__( padding=padding, ) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class FitOverlays(MacroElement): """Fit the bounds of the maps to the enabled overlays. @@ -639,9 +663,7 @@ class FitOverlays(MacroElement): fit_on_map_load: bool, default True Apply the fit when initially loading the map. """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} function customFlyToBounds() { let bounds = L.latLngBounds([]); @@ -660,8 +682,8 @@ class FitOverlays(MacroElement): {%- endif %} {% endmacro %} """ - ) - + _template = Template(_template_str) + def __init__( self, padding: int = 0, @@ -675,6 +697,10 @@ def __init__( self.fit_on_map_load = fit_on_map_load self.options = parse_options(padding=(padding, padding), max_zoom=max_zoom) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class CustomPane(MacroElement): """ @@ -699,9 +725,7 @@ class CustomPane(MacroElement): cursor. Setting to False will prevent interfering with pointer events associated with lower layers. """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = {{ this._parent.get_name() }}.createPane( {{ this.name|tojson }}); @@ -710,9 +734,9 @@ class CustomPane(MacroElement): {{ this.get_name() }}.style.pointerEvents = 'none'; {% endif %} {% endmacro %} - """ - ) - + """ + _template = Template(_template_str) + def __init__( self, name: str, @@ -724,3 +748,8 @@ def __init__( self.name = name self.z_index = z_index self.pointer_events = pointer_events + + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) \ No newline at end of file diff --git a/folium/raster_layers.py b/folium/raster_layers.py index 411b671f4..3c68fc0c0 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -78,8 +78,7 @@ class TileLayer(Layer): object. """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer( {{ this.tiles|tojson }}, @@ -87,7 +86,7 @@ class TileLayer(Layer): ); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -159,6 +158,10 @@ def __init__( **kwargs ) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class WmsTileLayer(Layer): """ @@ -197,8 +200,7 @@ class WmsTileLayer(Layer): See https://leafletjs.com/reference.html#tilelayer-wms """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer.wms( {{ this.url|tojson }}, @@ -206,8 +208,8 @@ class WmsTileLayer(Layer): ); {% endmacro %} """ - ) # noqa - + _template = Template(_template_str) + def __init__( self, url: str, @@ -239,6 +241,10 @@ def __init__( # special parameter that shouldn't be camelized self.options["cql_filter"] = cql_filter + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class ImageOverlay(Layer): """ @@ -287,8 +293,7 @@ class ImageOverlay(Layer): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.imageOverlay( {{ this.url|tojson }}, @@ -297,8 +302,8 @@ class ImageOverlay(Layer): ); {% endmacro %} """ - ) - + _template = Template(_template_str) + def __init__( self, image: Any, @@ -325,6 +330,11 @@ def __init__( self.url = image_to_url(image, origin=origin, colormap=colormap) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def render(self, **kwargs) -> None: super().render() @@ -386,8 +396,7 @@ class VideoOverlay(Layer): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.videoOverlay( {{ this.video_url|tojson }}, @@ -396,8 +405,8 @@ class VideoOverlay(Layer): ); {% endmacro %} """ - ) - + _template = Template(_template_str) + def __init__( self, video_url: str, @@ -417,6 +426,11 @@ def __init__( self.bounds = bounds self.options = parse_options(autoplay=autoplay, loop=loop, **kwargs) + def __setstate__(self, state: dict): + """Re-add ._template attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + def _get_self_bounds(self) -> TypeBounds: """ Computes the bounds of the object itself (not including it's children) diff --git a/folium/vector_layers.py b/folium/vector_layers.py index 6e0350610..b7cd987f8 100644 --- a/folium/vector_layers.py +++ b/folium/vector_layers.py @@ -173,9 +173,7 @@ class PolyLine(BaseMultiLocation): https://leafletjs.com/reference.html#polyline """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.polyline( {{ this.locations|tojson }}, @@ -183,13 +181,17 @@ class PolyLine(BaseMultiLocation): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__(self, locations, popup=None, tooltip=None, **kwargs): super().__init__(locations, popup=popup, tooltip=tooltip) self._name = "PolyLine" self.options = path_options(line=True, **kwargs) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class Polygon(BaseMultiLocation): """Draw polygon overlays on a map. @@ -212,9 +214,8 @@ class Polygon(BaseMultiLocation): https://leafletjs.com/reference.html#polygon """ - - _template = Template( - """ + + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.polygon( {{ this.locations|tojson }}, @@ -222,7 +223,7 @@ class Polygon(BaseMultiLocation): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -235,7 +236,11 @@ def __init__( self._name = "Polygon" self.options = path_options(line=True, radius=None, **kwargs) - + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) + class Rectangle(MacroElement): """Draw rectangle overlays on a map. @@ -255,8 +260,7 @@ class Rectangle(MacroElement): """ - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{this.get_name()}} = L.rectangle( {{ this.locations|tojson }}, @@ -264,7 +268,7 @@ class Rectangle(MacroElement): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -284,6 +288,11 @@ def __init__( self.add_child( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) + + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) def _get_self_bounds(self) -> List[List[Optional[float]]]: """Compute the bounds of the object itself.""" @@ -314,8 +323,7 @@ class Circle(Marker): https://leafletjs.com/reference.html#circle """ - - _template = Template( + _template_str = ( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.circle( @@ -325,6 +333,7 @@ class Circle(Marker): {% endmacro %} """ ) + _template = Template(_template_str) def __init__( self, @@ -338,6 +347,10 @@ def __init__( self._name = "circle" self.options = path_options(line=False, radius=radius, **kwargs) + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) class CircleMarker(Marker): """ @@ -360,9 +373,7 @@ class CircleMarker(Marker): https://leafletjs.com/reference.html#circlemarker """ - - _template = Template( - """ + _template_str = """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.circleMarker( {{ this.location|tojson }}, @@ -370,7 +381,7 @@ class CircleMarker(Marker): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - ) + _template = Template(_template_str) def __init__( self, @@ -383,3 +394,8 @@ def __init__( super().__init__(location, popup=popup, tooltip=tooltip) self._name = "CircleMarker" self.options = path_options(line=False, radius=radius, **kwargs) + + def __setstate__(self, state: dict): + """Re-add ._env attribute when unpickling""" + super().__setstate__(state) + self._template = Template(self._template_str) \ No newline at end of file From ac5dbe19e7bd51147bdd67b1d9fdc29ecaec8fb7 Mon Sep 17 00:00:00 2001 From: Bastien GAUTHIER Date: Thu, 12 Oct 2023 16:21:23 +0200 Subject: [PATCH 4/5] __setstate__ simplification --- folium/features.py | 55 ----------------------------------------- folium/folium.py | 17 ------------- folium/map.py | 42 ------------------------------- folium/raster_layers.py | 18 -------------- folium/vector_layers.py | 22 ----------------- 5 files changed, 154 deletions(-) diff --git a/folium/features.py b/folium/features.py index e6914294e..fdf939061 100644 --- a/folium/features.py +++ b/folium/features.py @@ -99,10 +99,6 @@ def __init__( ) ) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Vega(JSCSSMixin, Element): """ @@ -169,10 +165,6 @@ def __init__( self.top = _parse_size(top) self.position = position - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" @@ -293,11 +285,6 @@ def __init__( self.top = _parse_size(top) self.position = position - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" self._parent.html.add_child( @@ -694,11 +681,6 @@ def __init__( if isinstance(popup, (GeoJsonPopup, Popup)): self.add_child(popup) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def process_data(self, data: Any) -> dict: """Convert an unknown data input into a geojson dictionary.""" if isinstance(data, dict): @@ -1009,11 +991,6 @@ def __init__( elif tooltip is not None: self.add_child(Tooltip(tooltip)) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def style_data(self) -> None: """Applies self.style_function to each feature of self.data.""" @@ -1279,11 +1256,6 @@ def __init__( kwargs.update({"sticky": sticky, "class_name": class_name}) self.tooltip_options = {camelize(key): kwargs[key] for key in kwargs.keys()} - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - class GeoJsonPopup(GeoJsonDetail): """ @@ -1352,10 +1324,6 @@ def __init__( kwargs.update({"class_name": self.class_name}) self.popup_options = {camelize(key): value for key, value in kwargs.items()} - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Choropleth(FeatureGroup): """Apply a GeoJSON overlay to the map. @@ -1720,11 +1688,6 @@ def __init__( class_name=class_name, ) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - class LatLngPopup(MacroElement): """ @@ -1752,10 +1715,6 @@ def __init__(self): super().__init__() self._name = "LatLngPopup" - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class ClickForMarker(MacroElement): """ @@ -1803,11 +1762,6 @@ def __init__(self, popup: Union[IFrame, Html, str, None] = None): else: self.popup = '"Latitude: " + lat + "
Longitude: " + lng ' - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - class ClickForLatLng(MacroElement): """ @@ -1845,10 +1799,6 @@ def __init__(self, format_str: Optional[str] = None, alert: bool = True): self.format_str = format_str or 'lat + "," + lng' self.alert = alert - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class CustomIcon(Icon): """ @@ -1914,11 +1864,6 @@ def __init__( popup_anchor=popup_anchor, ) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - class ColorLine(FeatureGroup): """ diff --git a/folium/folium.py b/folium/folium.py index 6d74c5e5f..cba1ff82d 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -78,10 +78,6 @@ def __init__(self, no_touch=False, disable_3d=False): self.no_touch = no_touch self.disable_3d = disable_3d - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Map(JSCSSMixin, MacroElement): """Create a Map with Folium and Leaflet.js @@ -316,16 +312,6 @@ def __init__( ) self.add_child(tile_layer, name=tile_layer.tile_name) - def __getstate__(self): - """Modify object state when pickling the object. - jinja2 Environment cannot be pickled, so set - the ._env attribute to None. This will be added back - when unpickling (see __setstate__) - """ - state = super().__getstate__() - state["_png_image"] = None - return state - def __setstate__(self, state: dict): """Re-add ._env attribute when unpickling""" super().__setstate__(state) @@ -346,9 +332,6 @@ def __setstate__(self, state: dict): self.min_lon = max_bounds_array[0][1] self.max_lon = max_bounds_array[1][1] - self._template = Template(self._template_str) - - def _repr_html_(self, **kwargs) -> str: """Displays the HTML Map in a Jupyter notebook.""" if self._parent is None: diff --git a/folium/map.py b/folium/map.py index 25d7c8231..a17adf46a 100644 --- a/folium/map.py +++ b/folium/map.py @@ -117,10 +117,6 @@ def __init__( self.tile_name = name if name is not None else self.get_name() self.options = parse_options(**kwargs) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class LayerControl(MacroElement): """ @@ -200,11 +196,6 @@ def __init__( self.base_layers: OrderedDict[str, str] = OrderedDict() self.overlays: OrderedDict[str, str] = OrderedDict() - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def reset(self) -> None: self.base_layers = OrderedDict() self.overlays = OrderedDict() @@ -312,10 +303,6 @@ def __init__( **kwargs, ) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Marker(MacroElement): """ @@ -386,11 +373,6 @@ def __init__( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def _get_self_bounds(self) -> List[List[float]]: """Computes the bounds of the object itself. @@ -486,11 +468,6 @@ def __init__( **kwargs, ) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" for name, child in self._children.items(): @@ -571,11 +548,6 @@ def __init__( # noqa outside of type checking. self.style = style - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def parse_options( self, kwargs: Dict[str, TypeJsonValue], @@ -642,11 +614,6 @@ def __init__( padding_bottom_right=padding_bottom_right, padding=padding, ) - - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class FitOverlays(MacroElement): @@ -697,10 +664,6 @@ def __init__( self.fit_on_map_load = fit_on_map_load self.options = parse_options(padding=(padding, padding), max_zoom=max_zoom) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class CustomPane(MacroElement): """ @@ -748,8 +711,3 @@ def __init__( self.name = name self.z_index = z_index self.pointer_events = pointer_events - - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) \ No newline at end of file diff --git a/folium/raster_layers.py b/folium/raster_layers.py index 3c68fc0c0..790794788 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -158,10 +158,6 @@ def __init__( **kwargs ) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class WmsTileLayer(Layer): """ @@ -241,10 +237,6 @@ def __init__( # special parameter that shouldn't be camelized self.options["cql_filter"] = cql_filter - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class ImageOverlay(Layer): """ @@ -330,11 +322,6 @@ def __init__( self.url = image_to_url(image, origin=origin, colormap=colormap) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def render(self, **kwargs) -> None: super().render() @@ -426,11 +413,6 @@ def __init__( self.bounds = bounds self.options = parse_options(autoplay=autoplay, loop=loop, **kwargs) - def __setstate__(self, state: dict): - """Re-add ._template attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) - def _get_self_bounds(self) -> TypeBounds: """ Computes the bounds of the object itself (not including it's children) diff --git a/folium/vector_layers.py b/folium/vector_layers.py index b7cd987f8..3dfaf4337 100644 --- a/folium/vector_layers.py +++ b/folium/vector_layers.py @@ -188,10 +188,6 @@ def __init__(self, locations, popup=None, tooltip=None, **kwargs): self._name = "PolyLine" self.options = path_options(line=True, **kwargs) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Polygon(BaseMultiLocation): """Draw polygon overlays on a map. @@ -236,10 +232,6 @@ def __init__( self._name = "Polygon" self.options = path_options(line=True, radius=None, **kwargs) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class Rectangle(MacroElement): """Draw rectangle overlays on a map. @@ -288,11 +280,6 @@ def __init__( self.add_child( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) - - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) def _get_self_bounds(self) -> List[List[Optional[float]]]: """Compute the bounds of the object itself.""" @@ -347,10 +334,6 @@ def __init__( self._name = "circle" self.options = path_options(line=False, radius=radius, **kwargs) - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) class CircleMarker(Marker): """ @@ -394,8 +377,3 @@ def __init__( super().__init__(location, popup=popup, tooltip=tooltip) self._name = "CircleMarker" self.options = path_options(line=False, radius=radius, **kwargs) - - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self._template = Template(self._template_str) \ No newline at end of file From 7cbc61bfa26c2a1f000577b8e1ce85a8e2ecfd55 Mon Sep 17 00:00:00 2001 From: Bastien GAUTHIER Date: Fri, 13 Oct 2023 11:11:06 +0200 Subject: [PATCH 5/5] reboot --- folium/features.py | 76 ++++++++++++++++++++++------------------- folium/folium.py | 54 +++++++++-------------------- folium/map.py | 61 ++++++++++++++++++++------------- folium/raster_layers.py | 28 ++++++++------- folium/vector_layers.py | 32 ++++++++++------- 5 files changed, 129 insertions(+), 122 deletions(-) diff --git a/folium/features.py b/folium/features.py index fdf939061..d75288a4e 100644 --- a/folium/features.py +++ b/folium/features.py @@ -62,7 +62,9 @@ class RegularPolygonMarker(JSCSSMixin, Marker): https://humangeo.github.io/leaflet-dvf/ """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = new L.RegularPolygonMarker( {{ this.location|tojson }}, @@ -70,7 +72,7 @@ class RegularPolygonMarker(JSCSSMixin, Marker): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - _template = Template(_template_str) + ) default_js = [ ( @@ -130,8 +132,7 @@ class Vega(JSCSSMixin, Element): """ - _template_str = "" - _template = Template(_template_str) + _template = Template("") default_js = [ ("d3", "https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"), @@ -165,7 +166,6 @@ def __init__( self.top = _parse_size(top) self.position = position - def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" super().render(**kwargs) @@ -254,8 +254,8 @@ class VegaLite(Element): Ex: 'relative', 'absolute' """ - _template_str = "" - _template = Template(_template_str) + + _template = Template("") def __init__( self, @@ -522,7 +522,8 @@ class GeoJson(Layer): """ - _template_str =""" + _template = Template( + """ {% macro script(this, kwargs) %} {%- if this.style %} function {{ this.get_name() }}_styler(feature) { @@ -622,8 +623,8 @@ class GeoJson(Layer): {%- endif %} {% endmacro %} - """ # noqa - _template = Template(_template_str) + """ + ) # noqa def __init__( self, @@ -924,7 +925,8 @@ class TopoJson(JSCSSMixin, Layer): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }}_data = {{ this.data|tojson }}; var {{ this.get_name() }} = L.geoJson( @@ -943,7 +945,7 @@ class TopoJson(JSCSSMixin, Layer): }); {% endmacro %} """ - _template = Template(_template_str) + ) # noqa default_js = [ ( @@ -1223,15 +1225,15 @@ class GeoJsonTooltip(GeoJsonDetail): >>> GeoJsonTooltip(fields=("CNTY_NM",), labels=False, sticky=False) """ - _template_str = "".join([""" + _template = Template( + """ {% macro script(this, kwargs) %} - {{ this._parent.get_name() }}.bindTooltip(""", - GeoJsonDetail.base_template, - """,{{ this.tooltip_options | tojson | safe }}); + {{ this._parent.get_name() }}.bindTooltip(""" + + GeoJsonDetail.base_template + + """,{{ this.tooltip_options | tojson | safe }}); {% endmacro %} """ - ]) - _template = Template(_template_str) + ) def __init__( self, @@ -1291,16 +1293,15 @@ class GeoJsonPopup(GeoJsonDetail): ).add_to(gjson) """ - _template_str = "".join([ + _template = Template( """ {% macro script(this, kwargs) %} - {{ this._parent.get_name() }}.bindPopup(""", - GeoJsonDetail.base_template, - """,{{ this.popup_options | tojson | safe }}); + {{ this._parent.get_name() }}.bindPopup(""" + + GeoJsonDetail.base_template + + """,{{ this.popup_options | tojson | safe }}); {% endmacro %} """ - ]) - _template_ = Template(_template_str) + ) def __init__( self, @@ -1662,13 +1663,14 @@ class DivIcon(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.divIcon({{ this.options|tojson }}); {{this._parent.get_name()}}.setIcon({{this.get_name()}}); {% endmacro %} """ - _template_ = Template(_template_str) + ) # noqa def __init__( self, @@ -1696,7 +1698,8 @@ class LatLngPopup(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{this.get_name()}} = L.popup(); function latLngPop(e) { @@ -1709,7 +1712,7 @@ class LatLngPopup(MacroElement): {{this._parent.get_name()}}.on('click', latLngPop); {% endmacro %} """ - _template_ = Template(_template_str) + ) # noqa def __init__(self): super().__init__() @@ -1736,7 +1739,8 @@ class ClickForMarker(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} function newMarker(e){ var new_mark = L.marker().setLatLng(e.latlng).addTo({{this._parent.get_name()}}); @@ -1749,7 +1753,7 @@ class ClickForMarker(MacroElement): {{this._parent.get_name()}}.on('click', newMarker); {% endmacro %} """ - _template = Template(_template_str) + ) # noqa def __init__(self, popup: Union[IFrame, Html, str, None] = None): super().__init__() @@ -1779,7 +1783,8 @@ class ClickForLatLng(MacroElement): Whether there should be an alert when something has been copied to clipboard. """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} function getLatLng(e){ var lat = e.latlng.lat.toFixed(6), @@ -1791,7 +1796,7 @@ class ClickForLatLng(MacroElement): {{this._parent.get_name()}}.on('click', getLatLng); {% endmacro %} """ - _template = Template(_template_str) + ) # noqa def __init__(self, format_str: Optional[str] = None, alert: bool = True): super().__init__() @@ -1834,13 +1839,14 @@ class CustomIcon(Icon): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.icon({{ this.options|tojson }}); {{ this._parent.get_name() }}.setIcon({{ this.get_name() }}); {% endmacro %} """ - _template = Template(_template_str) + ) # noqa def __init__( self, @@ -1928,4 +1934,4 @@ def __init__( for (lat1, lng1), (lat2, lng2), color in zip(coords[:-1], coords[1:], colors): out.setdefault(cm(color), []).append([[lat1, lng1], [lat2, lng2]]) for key, val in out.items(): - self.add_child(PolyLine(val, color=key, weight=weight, opacity=opacity)) + self.add_child(PolyLine(val, color=key, weight=weight, opacity=opacity)) \ No newline at end of file diff --git a/folium/folium.py b/folium/folium.py index cba1ff82d..6d1dbb2b1 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -2,6 +2,7 @@ Make beautiful, interactive maps with Python and Leaflet.js """ + import time import webbrowser from typing import Any, List, Optional, Sequence, Union @@ -64,13 +65,14 @@ class GlobalSwitches(Element): - _template_str = """ + _template = Template( + """ """ - _template = Template(_template_str) + ) def __init__(self, no_touch=False, disable_3d=False): super().__init__() @@ -176,7 +178,8 @@ class Map(JSCSSMixin, MacroElement): """ # noqa - _template_str = """ + _template = Template( + """ {% macro header(this, kwargs) %} @@ -224,7 +227,7 @@ class Map(JSCSSMixin, MacroElement): {% endmacro %} """ - _template = Template(_template_str) + ) # use the module variables for backwards compatibility default_js = _default_js @@ -258,7 +261,6 @@ def __init__( **kwargs: TypeJsonValue, ): super().__init__() - self._name = "Map" self._env = ENV @@ -299,38 +301,14 @@ def __init__( self.global_switches = GlobalSwitches(no_touch, disable_3d) self.objects_to_stay_in_front: List[Layer] = [] - - if len(self._children) > 0: # Add already prepared childrens - for key, child in self._children.items(): # add the missing ones (key is a safety) - self.add_child(child, name = key) - else: # Initialize the tiles - if isinstance(tiles, TileLayer): - self.add_child(tiles) - elif tiles: - tile_layer = TileLayer( - tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom - ) - self.add_child(tile_layer, name=tile_layer.tile_name) - - def __setstate__(self, state: dict): - """Re-add ._env attribute when unpickling""" - super().__setstate__(state) - self.zoom_start = state["options"]["zoom"] - self.zoom_control = state["options"]["zoomControl"] - self.prefer_canvas = state["options"]["preferCanvas"] - max_bounds_array = state["options"].get("maxBounds") - if max_bounds_array is None: - self.max_bounds = False - self.min_lat = -90 - self.max_lat = 90 - self.min_lon = -180 - self.max_lon = 180 - else: # max_bounds_array = [[min_lat, min_lon], [max_lat, max_lon]] - self.max_bounds = True - self.min_lat = max_bounds_array[0][0] - self.max_lat = max_bounds_array[1][0] - self.min_lon = max_bounds_array[0][1] - self.max_lon = max_bounds_array[1][1] + + if isinstance(tiles, TileLayer): + self.add_child(tiles) + elif tiles: + tile_layer = TileLayer( + tiles=tiles, attr=attr, min_zoom=min_zoom, max_zoom=max_zoom + ) + self.add_child(tile_layer, name=tile_layer.tile_name) def _repr_html_(self, **kwargs) -> str: """Displays the HTML Map in a Jupyter notebook.""" @@ -494,4 +472,4 @@ def keep_in_front(self, *args: Layer) -> None: Does not work with markers, for those use z_index_offset. """ for obj in args: - self.objects_to_stay_in_front.append(obj) + self.objects_to_stay_in_front.append(obj) \ No newline at end of file diff --git a/folium/map.py b/folium/map.py index a17adf46a..7db9da24a 100644 --- a/folium/map.py +++ b/folium/map.py @@ -95,14 +95,16 @@ class FeatureGroup(Layer): https://leafletjs.com/reference.html#featuregroup """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.featureGroup( {{ this.options|tojson }} ); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -151,7 +153,8 @@ class LayerControl(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this,kwargs) %} var {{ this.get_name() }}_layers = { base_layers : { @@ -177,7 +180,7 @@ class LayerControl(MacroElement): {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -247,7 +250,8 @@ class Icon(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.AwesomeMarkers.icon( {{ this.options|tojson }} @@ -255,7 +259,7 @@ class Icon(MacroElement): {{ this._parent.get_name() }}.setIcon({{ this.get_name() }}); {% endmacro %} """ - _template = Template(_template_str) + ) color_options = { "red", "darkred", @@ -338,7 +342,8 @@ class Marker(MacroElement): ... ) """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.marker( {{ this.location|tojson }}, @@ -346,7 +351,7 @@ class Marker(MacroElement): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -408,7 +413,8 @@ class Popup(Element): True only loads the Popup content when clicking on the Marker. """ - _template_str = """ + _template = Template( + """ var {{this.get_name()}} = L.popup({{ this.options|tojson }}); {% for name, element in this.html._children.items() %} @@ -428,8 +434,8 @@ class Popup(Element): {% for name, element in this.script._children.items() %} {{element.render()}} {% endfor %} - """ # noqa - _template = Template(_template_str) + """ + ) # noqa def __init__( self, @@ -503,7 +509,9 @@ class Tooltip(MacroElement): available here: https://leafletjs.com/reference.html#tooltip """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} {{ this._parent.get_name() }}.bindTooltip( ` @@ -513,7 +521,7 @@ class Tooltip(MacroElement): ); {% endmacro %} """ - _template = Template(_template_str) + ) valid_options: Dict[str, Tuple[Type, ...]] = { "pane": (str,), "offset": (tuple,), @@ -587,7 +595,8 @@ class FitBounds(MacroElement): Maximum zoom to be used. """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} {{ this._parent.get_name() }}.fitBounds( {{ this.bounds|tojson }}, @@ -595,7 +604,7 @@ class FitBounds(MacroElement): ); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -614,7 +623,7 @@ def __init__( padding_bottom_right=padding_bottom_right, padding=padding, ) - + class FitOverlays(MacroElement): """Fit the bounds of the maps to the enabled overlays. @@ -630,7 +639,9 @@ class FitOverlays(MacroElement): fit_on_map_load: bool, default True Apply the fit when initially loading the map. """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} function customFlyToBounds() { let bounds = L.latLngBounds([]); @@ -649,8 +660,8 @@ class FitOverlays(MacroElement): {%- endif %} {% endmacro %} """ - _template = Template(_template_str) - + ) + def __init__( self, padding: int = 0, @@ -688,7 +699,9 @@ class CustomPane(MacroElement): cursor. Setting to False will prevent interfering with pointer events associated with lower layers. """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = {{ this._parent.get_name() }}.createPane( {{ this.name|tojson }}); @@ -697,9 +710,9 @@ class CustomPane(MacroElement): {{ this.get_name() }}.style.pointerEvents = 'none'; {% endif %} {% endmacro %} - """ - _template = Template(_template_str) - + """ + ) + def __init__( self, name: str, @@ -710,4 +723,4 @@ def __init__( self._name = "Pane" self.name = name self.z_index = z_index - self.pointer_events = pointer_events + self.pointer_events = pointer_events \ No newline at end of file diff --git a/folium/raster_layers.py b/folium/raster_layers.py index 790794788..ff449871a 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -78,7 +78,8 @@ class TileLayer(Layer): object. """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer( {{ this.tiles|tojson }}, @@ -86,7 +87,7 @@ class TileLayer(Layer): ); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -196,7 +197,8 @@ class WmsTileLayer(Layer): See https://leafletjs.com/reference.html#tilelayer-wms """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer.wms( {{ this.url|tojson }}, @@ -204,8 +206,8 @@ class WmsTileLayer(Layer): ); {% endmacro %} """ - _template = Template(_template_str) - + ) # noqa + def __init__( self, url: str, @@ -285,7 +287,8 @@ class ImageOverlay(Layer): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.imageOverlay( {{ this.url|tojson }}, @@ -294,8 +297,8 @@ class ImageOverlay(Layer): ); {% endmacro %} """ - _template = Template(_template_str) - + ) + def __init__( self, image: Any, @@ -383,7 +386,8 @@ class VideoOverlay(Layer): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.videoOverlay( {{ this.video_url|tojson }}, @@ -392,8 +396,8 @@ class VideoOverlay(Layer): ); {% endmacro %} """ - _template = Template(_template_str) - + ) + def __init__( self, video_url: str, @@ -419,4 +423,4 @@ def _get_self_bounds(self) -> TypeBounds: in the form [[lat_min, lon_min], [lat_max, lon_max]] """ - return self.bounds + return self.bounds \ No newline at end of file diff --git a/folium/vector_layers.py b/folium/vector_layers.py index 3dfaf4337..83ec1c8cc 100644 --- a/folium/vector_layers.py +++ b/folium/vector_layers.py @@ -173,7 +173,9 @@ class PolyLine(BaseMultiLocation): https://leafletjs.com/reference.html#polyline """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.polyline( {{ this.locations|tojson }}, @@ -181,7 +183,7 @@ class PolyLine(BaseMultiLocation): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__(self, locations, popup=None, tooltip=None, **kwargs): super().__init__(locations, popup=popup, tooltip=tooltip) @@ -210,8 +212,9 @@ class Polygon(BaseMultiLocation): https://leafletjs.com/reference.html#polygon """ - - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.polygon( {{ this.locations|tojson }}, @@ -219,7 +222,7 @@ class Polygon(BaseMultiLocation): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -232,7 +235,7 @@ def __init__( self._name = "Polygon" self.options = path_options(line=True, radius=None, **kwargs) - + class Rectangle(MacroElement): """Draw rectangle overlays on a map. @@ -252,7 +255,8 @@ class Rectangle(MacroElement): """ - _template_str = """ + _template = Template( + """ {% macro script(this, kwargs) %} var {{this.get_name()}} = L.rectangle( {{ this.locations|tojson }}, @@ -260,7 +264,7 @@ class Rectangle(MacroElement): ).addTo({{this._parent.get_name()}}); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -310,7 +314,8 @@ class Circle(Marker): https://leafletjs.com/reference.html#circle """ - _template_str = ( + + _template = Template( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.circle( @@ -320,7 +325,6 @@ class Circle(Marker): {% endmacro %} """ ) - _template = Template(_template_str) def __init__( self, @@ -356,7 +360,9 @@ class CircleMarker(Marker): https://leafletjs.com/reference.html#circlemarker """ - _template_str = """ + + _template = Template( + """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.circleMarker( {{ this.location|tojson }}, @@ -364,7 +370,7 @@ class CircleMarker(Marker): ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ - _template = Template(_template_str) + ) def __init__( self, @@ -376,4 +382,4 @@ def __init__( ): super().__init__(location, popup=popup, tooltip=tooltip) self._name = "CircleMarker" - self.options = path_options(line=False, radius=radius, **kwargs) + self.options = path_options(line=False, radius=radius, **kwargs) \ No newline at end of file