Skip to content

Commit c1210ce

Browse files
authored
Allow users to add new Control classes without implementing a plugin (#2122)
* Add leaflet control * Updated after review comments * Fix some type errors * Added test for Control typechecking
1 parent 764c691 commit c1210ce

File tree

4 files changed

+102
-2
lines changed

4 files changed

+102
-2
lines changed

folium/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
ClickForLatLng,
1818
ClickForMarker,
1919
ColorLine,
20+
Control,
2021
CustomIcon,
2122
DivIcon,
2223
GeoJson,
@@ -64,6 +65,7 @@
6465
"ClickForLatLng",
6566
"ColorLine",
6667
"ColorMap",
68+
"Control",
6769
"CssLink",
6870
"CustomIcon",
6971
"Div",

folium/features.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@
77
import json
88
import operator
99
import warnings
10-
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
10+
from typing import (
11+
Any,
12+
Callable,
13+
Dict,
14+
Iterable,
15+
List,
16+
Optional,
17+
Sequence,
18+
Tuple,
19+
Union,
20+
get_args,
21+
)
1122

1223
import numpy as np
1324
import requests
@@ -34,6 +45,7 @@
3445
TypeJsonValue,
3546
TypeLine,
3647
TypePathOptions,
48+
TypePosition,
3749
_parse_size,
3850
escape_backticks,
3951
get_bounds,
@@ -1831,7 +1843,7 @@ def __init__(self, popup: Union[IFrame, Html, str, None] = None):
18311843
if isinstance(popup, Element):
18321844
popup = popup.render()
18331845
if popup:
1834-
self.popup = "`" + escape_backticks(popup) + "`"
1846+
self.popup = "`" + escape_backticks(popup) + "`" # type: ignore
18351847
else:
18361848
self.popup = '"Latitude: " + lat + "<br>Longitude: " + lng '
18371849

@@ -2009,3 +2021,61 @@ def __init__(
20092021
out.setdefault(cm(color), []).append([[lat1, lng1], [lat2, lng2]])
20102022
for key, val in out.items():
20112023
self.add_child(PolyLine(val, color=key, weight=weight, opacity=opacity))
2024+
2025+
2026+
class Control(JSCSSMixin, MacroElement):
2027+
"""
2028+
Add a Leaflet Control object to the map
2029+
2030+
Parameters
2031+
----------
2032+
control: str
2033+
The javascript class name of the control to be rendered.
2034+
position: str
2035+
One of "bottomright", "bottomleft", "topright", "topleft"
2036+
2037+
Examples
2038+
--------
2039+
2040+
>>> import folium
2041+
>>> from folium.features import Control, Marker
2042+
>>> from folium.plugins import Geocoder
2043+
2044+
>>> m = folium.Map(
2045+
... location=[46.603354, 1.8883335], attr=None, zoom_control=False, zoom_start=5
2046+
... )
2047+
>>> Control("Zoom", position="topleft").add_to(m)
2048+
"""
2049+
2050+
_template = Template(
2051+
"""
2052+
{% macro script(this, kwargs) %}
2053+
var {{ this.get_name() }} = new L.Control.{{this._name}}(
2054+
{% for arg in this.args %}
2055+
{{ arg | tojavascript }},
2056+
{% endfor %}
2057+
{{ this.options|tojavascript }}
2058+
).addTo({{ this._parent.get_name() }});
2059+
{% endmacro %}
2060+
"""
2061+
)
2062+
2063+
def __init__(
2064+
self,
2065+
control: Optional[str] = None,
2066+
*args,
2067+
position: Optional[TypePosition] = None,
2068+
**kwargs,
2069+
):
2070+
super().__init__()
2071+
if control:
2072+
self._name = control
2073+
2074+
if position is not None:
2075+
position = position.lower() # type: ignore
2076+
if position not in (args := get_args(TypePosition)):
2077+
raise TypeError(f"position must be one of {args}")
2078+
kwargs["position"] = position
2079+
2080+
self.args = args
2081+
self.options = remove_empty(**kwargs)

folium/utilities.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Iterable,
1717
Iterator,
1818
List,
19+
Literal,
1920
Optional,
2021
Sequence,
2122
Tuple,
@@ -57,6 +58,7 @@
5758
TypeBoundsReturn = List[List[Optional[float]]]
5859

5960
TypeContainer = Union[Figure, Div, "Popup"]
61+
TypePosition = Literal["bottomright", "bottomleft", "topright", "topleft"]
6062

6163

6264
_VALID_URLS = set(uses_relative + uses_netloc + uses_params)

tests/test_folium.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,29 @@ def test_json_request(self):
501501
np.testing.assert_allclose(
502502
bounds, [[18.948267, -178.123152], [71.351633, 173.304726]]
503503
)
504+
505+
def test_control_typecheck(self):
506+
m = folium.Map(
507+
location=[39.949610, -75.150282], zoom_start=5, zoom_control=False
508+
)
509+
tiles = TileLayer(
510+
tiles="OpenStreetMap",
511+
show=False,
512+
control=False,
513+
)
514+
tiles.add_to(m)
515+
516+
with pytest.raises(TypeError) as excinfo:
517+
minimap = folium.Control("MiniMap", tiles, position="downunder")
518+
minimap.add_js_link(
519+
"minimap_js",
520+
"https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.min.js",
521+
)
522+
minimap.add_css_link(
523+
"minimap_css",
524+
"https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.css",
525+
)
526+
minimap.add_to(m)
527+
assert "position must be one of ('bottomright', 'bottomleft'" in str(
528+
excinfo.value
529+
)

0 commit comments

Comments
 (0)