diff --git a/folium/__init__.py b/folium/__init__.py index 01a0b66077..c6fa376e4c 100644 --- a/folium/__init__.py +++ b/folium/__init__.py @@ -17,6 +17,7 @@ ClickForLatLng, ClickForMarker, ColorLine, + Control, CustomIcon, DivIcon, GeoJson, @@ -64,6 +65,7 @@ "ClickForLatLng", "ColorLine", "ColorMap", + "Control", "CssLink", "CustomIcon", "Div", diff --git a/folium/features.py b/folium/features.py index 1c18f426db..2b90967155 100644 --- a/folium/features.py +++ b/folium/features.py @@ -7,7 +7,18 @@ import json import operator import warnings -from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Sequence, + Tuple, + Union, + get_args, +) import numpy as np import requests @@ -33,6 +44,7 @@ TypeJsonValue, TypeLine, TypePathOptions, + TypePosition, _parse_size, escape_backticks, get_bounds, @@ -1813,7 +1825,7 @@ def __init__(self, popup: Union[IFrame, Html, str, None] = None): if isinstance(popup, Element): popup = popup.render() if popup: - self.popup = "`" + escape_backticks(popup) + "`" + self.popup = "`" + escape_backticks(popup) + "`" # type: ignore else: self.popup = '"Latitude: " + lat + "
Longitude: " + lng ' @@ -1992,3 +2004,61 @@ def __init__( 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)) + + +class Control(JSCSSMixin, MacroElement): + """ + Add a Leaflet Control object to the map + + Parameters + ---------- + control: str + The javascript class name of the control to be rendered. + position: str + One of "bottomright", "bottomleft", "topright", "topleft" + + Examples + -------- + + >>> import folium + >>> from folium.features import Control, Marker + >>> from folium.plugins import Geocoder + + >>> m = folium.Map( + ... location=[46.603354, 1.8883335], attr=None, zoom_control=False, zoom_start=5 + ... ) + >>> Control("Zoom", position="topleft").add_to(m) + """ + + _template = Template( + """ + {% macro script(this, kwargs) %} + var {{ this.get_name() }} = new L.Control.{{this._name}}( + {% for arg in this.args %} + {{ arg | tojavascript }}, + {% endfor %} + {{ this.options|tojavascript }} + ).addTo({{ this._parent.get_name() }}); + {% endmacro %} + """ + ) + + def __init__( + self, + control: Optional[str] = None, + *args, + position: Optional[TypePosition] = None, + **kwargs, + ): + super().__init__() + if control: + self._name = control + + if position is not None: + position = position.lower() # type: ignore + if position not in (args := get_args(TypePosition)): + raise TypeError(f"position must be one of {args}") + kwargs["position"] = position + + self.args = args + self.options = remove_empty(**kwargs) diff --git a/folium/utilities.py b/folium/utilities.py index 131492e453..eaf1bc4df8 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -16,6 +16,7 @@ Iterable, Iterator, List, + Literal, Optional, Sequence, Tuple, @@ -57,6 +58,7 @@ TypeBoundsReturn = List[List[Optional[float]]] TypeContainer = Union[Figure, Div, "Popup"] +TypePosition = Literal["bottomright", "bottomleft", "topright", "topleft"] _VALID_URLS = set(uses_relative + uses_netloc + uses_params) diff --git a/tests/test_folium.py b/tests/test_folium.py index 58ad28c978..2a945b056c 100644 --- a/tests/test_folium.py +++ b/tests/test_folium.py @@ -501,3 +501,29 @@ def test_json_request(self): np.testing.assert_allclose( bounds, [[18.948267, -178.123152], [71.351633, 173.304726]] ) + + def test_control_typecheck(self): + m = folium.Map( + location=[39.949610, -75.150282], zoom_start=5, zoom_control=False + ) + tiles = TileLayer( + tiles="OpenStreetMap", + show=False, + control=False, + ) + tiles.add_to(m) + + with pytest.raises(TypeError) as excinfo: + minimap = folium.Control("MiniMap", tiles, position="downunder") + minimap.add_js_link( + "minimap_js", + "https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.min.js", + ) + minimap.add_css_link( + "minimap_css", + "https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.css", + ) + minimap.add_to(m) + assert "position must be one of ('bottomright', 'bottomleft'" in str( + excinfo.value + )