diff --git a/folium/features.py b/folium/features.py index 0a3ac1af7..cba87e8ef 100644 --- a/folium/features.py +++ b/folium/features.py @@ -31,6 +31,7 @@ javascript_identifier_path_to_array_notation, none_max, none_min, + remove_empty, validate_locations, ) from folium.vector_layers import Circle, CircleMarker, PolyLine, path_options @@ -688,7 +689,7 @@ def __init__( self.popup_keep_highlighted = popup_keep_highlighted self.marker = marker - self.options = kwargs + self.options = remove_empty(**kwargs) self.data = self.process_data(data) @@ -1715,7 +1716,7 @@ def __init__( ): super().__init__() self._name = "DivIcon" - self.options = dict( + self.options = remove_empty( html=html, icon_size=icon_size, icon_anchor=icon_anchor, @@ -1894,7 +1895,7 @@ def __init__( ): super(Icon, self).__init__() self._name = "CustomIcon" - self.options = dict( + self.options = remove_empty( icon_url=image_to_url(icon_image), icon_size=icon_size, icon_anchor=icon_anchor, diff --git a/folium/folium.py b/folium/folium.py index d44c26364..e48049371 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -18,6 +18,7 @@ TypeJsonValue, _parse_size, parse_font_size, + remove_empty, temp_html_filepath, validate_location, ) @@ -303,7 +304,7 @@ def __init__( else: self.zoom_control_position = False - self.options = dict( + self.options = remove_empty( max_bounds=max_bounds_array, zoom=zoom_start, zoom_control=False if self.zoom_control_position else zoom_control, diff --git a/folium/map.py b/folium/map.py index d6d0a7483..3c5302b8a 100644 --- a/folium/map.py +++ b/folium/map.py @@ -17,6 +17,7 @@ TypeJsonValue, escape_backticks, parse_options, + remove_empty, validate_location, ) @@ -131,7 +132,7 @@ def __init__( super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "FeatureGroup" self.tile_name = name if name is not None else self.get_name() - self.options = kwargs + self.options = remove_empty(**kwargs) class LayerControl(MacroElement): @@ -206,7 +207,7 @@ def __init__( ): super().__init__() self._name = "LayerControl" - self.options = dict( + self.options = remove_empty( position=position, collapsed=collapsed, autoZIndex=autoZIndex, **kwargs ) self.draggable = draggable @@ -312,7 +313,7 @@ def __init__( f"color argument of Icon should be one of: {self.color_options}.", stacklevel=2, ) - self.options = dict( + self.options = remove_empty( marker_color=color, icon_color=icon_color, icon=icon, @@ -379,7 +380,7 @@ def __init__( super().__init__() self._name = "Marker" self.location = validate_location(location) if location is not None else None - self.options = dict( + self.options = remove_empty( draggable=draggable or None, autoPan=draggable or None, **kwargs ) if icon is not None: @@ -481,7 +482,7 @@ def __init__( self.show = show self.lazy = lazy - self.options = dict( + self.options = remove_empty( max_width=max_width, autoClose=False if show or sticky else None, closeOnClick=False if sticky else None, @@ -550,7 +551,7 @@ def __init__( self.text = str(text) kwargs.update({"sticky": sticky}) - self.options = kwargs + self.options = remove_empty(**kwargs) if style: assert isinstance( @@ -659,7 +660,7 @@ def __init__( self._name = "FitOverlays" self.method = "flyToBounds" if fly else "fitBounds" self.fit_on_map_load = fit_on_map_load - self.options = dict(padding=(padding, padding), max_zoom=max_zoom) + self.options = remove_empty(padding=(padding, padding), max_zoom=max_zoom) class CustomPane(MacroElement): diff --git a/folium/plugins/beautify_icon.py b/folium/plugins/beautify_icon.py index b43f301e6..d1c60b3f7 100644 --- a/folium/plugins/beautify_icon.py +++ b/folium/plugins/beautify_icon.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class BeautifyIcon(JSCSSMixin, MacroElement): @@ -99,7 +100,7 @@ def __init__( super().__init__() self._name = "BeautifyIcon" - self.options = dict( + self.options = remove_empty( icon=icon, icon_shape=icon_shape, border_width=border_width, diff --git a/folium/plugins/boat_marker.py b/folium/plugins/boat_marker.py index c88e46834..a6b71984b 100644 --- a/folium/plugins/boat_marker.py +++ b/folium/plugins/boat_marker.py @@ -1,6 +1,7 @@ from folium.elements import JSCSSMixin from folium.map import Marker from folium.template import Template +from folium.utilities import remove_empty class BoatMarker(JSCSSMixin, Marker): @@ -65,4 +66,4 @@ def __init__( self.heading = heading self.wind_heading = wind_heading self.wind_speed = wind_speed - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) diff --git a/folium/plugins/fullscreen.py b/folium/plugins/fullscreen.py index 7ec969d54..6374c491d 100644 --- a/folium/plugins/fullscreen.py +++ b/folium/plugins/fullscreen.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class Fullscreen(JSCSSMixin, MacroElement): @@ -59,7 +60,7 @@ def __init__( ): super().__init__() self._name = "Fullscreen" - self.options = dict( + self.options = remove_empty( position=position, title=title, title_cancel=title_cancel, diff --git a/folium/plugins/geocoder.py b/folium/plugins/geocoder.py index 7866dd58e..3210d55fc 100644 --- a/folium/plugins/geocoder.py +++ b/folium/plugins/geocoder.py @@ -4,6 +4,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class Geocoder(JSCSSMixin, MacroElement): @@ -81,7 +82,7 @@ def __init__( ): super().__init__() self._name = "Geocoder" - self.options = dict( + self.options = remove_empty( collapsed=collapsed, position=position, default_mark_geocode=add_marker, diff --git a/folium/plugins/groupedlayercontrol.py b/folium/plugins/groupedlayercontrol.py index 5d7391b90..b2a9fe0a3 100644 --- a/folium/plugins/groupedlayercontrol.py +++ b/folium/plugins/groupedlayercontrol.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class GroupedLayerControl(JSCSSMixin, MacroElement): @@ -68,7 +69,7 @@ class GroupedLayerControl(JSCSSMixin, MacroElement): def __init__(self, groups, exclusive_groups=True, **kwargs): super().__init__() self._name = "GroupedLayerControl" - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) if exclusive_groups: self.options["exclusiveGroups"] = list(groups.keys()) self.layers_untoggle = set() diff --git a/folium/plugins/heat_map.py b/folium/plugins/heat_map.py index b49e98850..afa74d77a 100644 --- a/folium/plugins/heat_map.py +++ b/folium/plugins/heat_map.py @@ -9,6 +9,7 @@ if_pandas_df_convert_to_numpy, none_max, none_min, + remove_empty, validate_location, ) @@ -89,7 +90,7 @@ def __init__( "The largest intensity is calculated automatically.", stacklevel=2, ) - self.options = dict( + self.options = remove_empty( min_opacity=min_opacity, max_zoom=max_zoom, radius=radius, diff --git a/folium/plugins/locate_control.py b/folium/plugins/locate_control.py index bf495d4fd..f55098bd4 100644 --- a/folium/plugins/locate_control.py +++ b/folium/plugins/locate_control.py @@ -7,6 +7,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class LocateControl(JSCSSMixin, MacroElement): @@ -73,4 +74,4 @@ def __init__(self, auto_start=False, **kwargs): super().__init__() self._name = "LocateControl" self.auto_start = auto_start - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) diff --git a/folium/plugins/marker_cluster.py b/folium/plugins/marker_cluster.py index 0af5f154e..03eec1f4d 100644 --- a/folium/plugins/marker_cluster.py +++ b/folium/plugins/marker_cluster.py @@ -1,7 +1,7 @@ from folium.elements import JSCSSMixin from folium.map import Layer, Marker from folium.template import Template -from folium.utilities import validate_locations +from folium.utilities import remove_empty, validate_locations class MarkerCluster(JSCSSMixin, Layer): @@ -102,7 +102,7 @@ def __init__( ) ) - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) if icon_create_function is not None: assert isinstance(icon_create_function, str) self.icon_create_function = icon_create_function diff --git a/folium/plugins/measure_control.py b/folium/plugins/measure_control.py index 6371a3226..440ddad8a 100644 --- a/folium/plugins/measure_control.py +++ b/folium/plugins/measure_control.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class MeasureControl(JSCSSMixin, MacroElement): @@ -72,7 +73,7 @@ def __init__( super().__init__() self._name = "MeasureControl" - self.options = dict( + self.options = remove_empty( position=position, primary_length_unit=primary_length_unit, secondary_length_unit=secondary_length_unit, diff --git a/folium/plugins/minimap.py b/folium/plugins/minimap.py index 9e482ea5a..08c864d41 100644 --- a/folium/plugins/minimap.py +++ b/folium/plugins/minimap.py @@ -3,6 +3,7 @@ from folium.elements import JSCSSMixin from folium.raster_layers import TileLayer from folium.template import Template +from folium.utilities import remove_empty class MiniMap(JSCSSMixin, MacroElement): @@ -114,7 +115,7 @@ def __init__( else: self.tile_layer = TileLayer(tile_layer) - self.options = dict( + self.options = remove_empty( position=position, width=width, height=height, diff --git a/folium/plugins/mouse_position.py b/folium/plugins/mouse_position.py index 2d6e2d89e..761fd9ada 100644 --- a/folium/plugins/mouse_position.py +++ b/folium/plugins/mouse_position.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class MousePosition(JSCSSMixin, MacroElement): @@ -87,7 +88,7 @@ def __init__( super().__init__() self._name = "MousePosition" - self.options = dict( + self.options = remove_empty( position=position, separator=separator, empty_string=empty_string, diff --git a/folium/plugins/pattern.py b/folium/plugins/pattern.py index e9b0b853a..058135435 100644 --- a/folium/plugins/pattern.py +++ b/folium/plugins/pattern.py @@ -3,7 +3,7 @@ from folium.elements import JSCSSMixin from folium.folium import Map from folium.template import Template -from folium.utilities import get_obj_in_upper_tree +from folium.utilities import get_obj_in_upper_tree, remove_empty class StripePattern(JSCSSMixin, MacroElement): @@ -59,7 +59,7 @@ def __init__( ): super().__init__() self._name = "StripePattern" - self.options = dict( + self.options = remove_empty( angle=angle, weight=weight, space_weight=space_weight, diff --git a/folium/plugins/polyline_text_path.py b/folium/plugins/polyline_text_path.py index dbd97d093..c4048f4e5 100644 --- a/folium/plugins/polyline_text_path.py +++ b/folium/plugins/polyline_text_path.py @@ -1,6 +1,7 @@ from folium.elements import JSCSSMixin from folium.features import MacroElement from folium.template import Template +from folium.utilities import remove_empty class PolyLineTextPath(JSCSSMixin, MacroElement): @@ -67,7 +68,7 @@ def __init__( self._name = "PolyLineTextPath" self.polyline = polyline self.text = text - self.options = dict( + self.options = remove_empty( repeat=repeat, center=center, below=below, diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index 56f8c64c7..489f00ff8 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -4,7 +4,7 @@ from folium.features import GeoJson from folium.map import FeatureGroup from folium.template import Template -from folium.utilities import JsCode +from folium.utilities import JsCode, remove_empty class Realtime(JSCSSMixin, FeatureGroup): @@ -115,4 +115,4 @@ def __init__( kwargs["remove_missing"] = remove_missing kwargs["container"] = container - self.options = kwargs + self.options = remove_empty(**kwargs) diff --git a/folium/plugins/search.py b/folium/plugins/search.py index 8ddbf6f3c..aa74e0f69 100644 --- a/folium/plugins/search.py +++ b/folium/plugins/search.py @@ -5,6 +5,7 @@ from folium.features import FeatureGroup, GeoJson, TopoJson from folium.plugins import MarkerCluster from folium.template import Template +from folium.utilities import remove_empty class Search(JSCSSMixin, MacroElement): @@ -121,7 +122,7 @@ def __init__( self.position = position self.placeholder = placeholder self.collapsed = collapsed - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) def test_params(self, keys): if keys is not None and self.search_label is not None: diff --git a/folium/plugins/semicircle.py b/folium/plugins/semicircle.py index 1c2ea83c8..0370a0a7d 100644 --- a/folium/plugins/semicircle.py +++ b/folium/plugins/semicircle.py @@ -1,6 +1,7 @@ from folium.elements import JSCSSMixin from folium.map import Marker from folium.template import Template +from folium.utilities import remove_empty from folium.vector_layers import path_options @@ -80,6 +81,7 @@ def __init__( stop_angle=stop_angle, ) ) + self.options = remove_empty(**self.options) if not ( (direction is None and arc is None) diff --git a/folium/plugins/tag_filter_button.py b/folium/plugins/tag_filter_button.py index 5f8a0e1ce..bdb96788f 100644 --- a/folium/plugins/tag_filter_button.py +++ b/folium/plugins/tag_filter_button.py @@ -2,6 +2,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class TagFilterButton(JSCSSMixin, MacroElement): @@ -85,7 +86,7 @@ def __init__( ): super().__init__() self._name = "TagFilterButton" - self.options = dict( + self.options = remove_empty( data=data, icon=icon, clear_text=clear_text, diff --git a/folium/plugins/timeline.py b/folium/plugins/timeline.py index cefe5d0d7..a0e46dcca 100644 --- a/folium/plugins/timeline.py +++ b/folium/plugins/timeline.py @@ -6,7 +6,7 @@ from folium.features import GeoJson from folium.folium import Map from folium.template import Template -from folium.utilities import JsCode, get_bounds +from folium.utilities import JsCode, get_bounds, remove_empty class Timeline(GeoJson): @@ -117,7 +117,7 @@ def __init__( if get_interval is not None: kwargs["get_interval"] = get_interval - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) def _get_self_bounds(self): """ @@ -243,7 +243,7 @@ def __init__( ) self.timelines: List[Timeline] = [] - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) def add_timelines(self, *args): """Add timelines to the control""" diff --git a/folium/plugins/timestamped_geo_json.py b/folium/plugins/timestamped_geo_json.py index 1062f7367..2c3000a99 100644 --- a/folium/plugins/timestamped_geo_json.py +++ b/folium/plugins/timestamped_geo_json.py @@ -5,7 +5,7 @@ from folium.elements import JSCSSMixin from folium.folium import Map from folium.template import Template -from folium.utilities import get_bounds +from folium.utilities import get_bounds, remove_empty class TimestampedGeoJson(JSCSSMixin, MacroElement): @@ -211,7 +211,7 @@ def __init__( self.date_options = date_options self.duration = "undefined" if duration is None else '"' + duration + '"' - self.options = dict( + self.options = remove_empty( position="bottomleft", min_speed=min_speed, max_speed=max_speed, diff --git a/folium/plugins/timestamped_wmstilelayer.py b/folium/plugins/timestamped_wmstilelayer.py index 53288f755..735c3b8dc 100644 --- a/folium/plugins/timestamped_wmstilelayer.py +++ b/folium/plugins/timestamped_wmstilelayer.py @@ -3,6 +3,7 @@ from folium.elements import JSCSSMixin from folium.raster_layers import WmsTileLayer from folium.template import Template +from folium.utilities import remove_empty class TimestampedWmsTileLayers(JSCSSMixin, MacroElement): @@ -128,7 +129,7 @@ def __init__( ): super().__init__() self._name = "TimestampedWmsTileLayers" - self.options = dict( + self.options = remove_empty( period=period, time_interval=time_interval, ) diff --git a/folium/plugins/treelayercontrol.py b/folium/plugins/treelayercontrol.py index 5b3028e12..4096cfad0 100644 --- a/folium/plugins/treelayercontrol.py +++ b/folium/plugins/treelayercontrol.py @@ -4,6 +4,7 @@ from folium.elements import JSCSSMixin from folium.template import Template +from folium.utilities import remove_empty class TreeLayerControl(JSCSSMixin, MacroElement): @@ -157,6 +158,6 @@ def __init__( kwargs["collapse_all"] = collapse_all kwargs["expand_all"] = expand_all kwargs["label_is_selector"] = label_is_selector - self.options = dict(**kwargs) + self.options = remove_empty(**kwargs) self.base_tree = base_tree self.overlay_tree = overlay_tree diff --git a/folium/raster_layers.py b/folium/raster_layers.py index 6bfed69e4..bbb2f0b9f 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -16,6 +16,7 @@ image_to_url, mercator_transform, parse_options, + remove_empty, ) @@ -139,7 +140,7 @@ def __init__( if not attr: raise ValueError("Custom tiles must have an attribution.") - self.options = dict( + self.options = remove_empty( min_zoom=min_zoom or 0, max_zoom=max_zoom or 18, max_native_zoom=max_native_zoom or max_zoom or 18, @@ -309,7 +310,7 @@ def __init__( super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "ImageOverlay" self.bounds = bounds - self.options = kwargs + self.options = remove_empty(**kwargs) self.pixelated = pixelated if mercator_project: image = mercator_transform( @@ -408,7 +409,7 @@ def __init__( self.video_url = video_url self.bounds = bounds - self.options = dict(autoplay=autoplay, loop=loop, **kwargs) + self.options = remove_empty(autoplay=autoplay, loop=loop, **kwargs) def _get_self_bounds(self) -> TypeBounds: """ diff --git a/folium/utilities.py b/folium/utilities.py index 9e5ac51b7..a730417c3 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -397,6 +397,11 @@ def parse_options(**kwargs: TypeJsonValue) -> Dict[str, TypeJsonValueNoNone]: return {camelize(key): value for key, value in kwargs.items() if value is not None} +def remove_empty(**kwargs: TypeJsonValue) -> Dict[str, TypeJsonValueNoNone]: + """Return a dict without None values.""" + return {key: value for key, value in kwargs.items() if value is not None} + + def escape_backticks(text: str) -> str: """Escape backticks so text can be used in a JS template.""" return re.sub(r"(?Some text.`)[0]; {popup_name}.setContent({html_name}); @@ -155,8 +155,6 @@ def test_popup_backticks(): expected = """ var {popup_name} = L.popup({{ "maxWidth": "100%", - "autoClose": null, - "closeOnClick": null, }}); var {html_name} = $(`
back\\`tick\\`tick
`)[0]; {popup_name}.setContent({html_name}); @@ -176,8 +174,6 @@ def test_popup_backticks_already_escaped(): expected = """ var {popup_name} = L.popup({{ "maxWidth": "100%", - "autoClose": null, - "closeOnClick": null, }}); var {html_name} = $(`
back\\`tick
`)[0]; {popup_name}.setContent({html_name});