diff --git a/docs/user_guide/plugins.rst b/docs/user_guide/plugins.rst index 0de18d3838..68447bc3bd 100644 --- a/docs/user_guide/plugins.rst +++ b/docs/user_guide/plugins.rst @@ -26,6 +26,7 @@ Plugins plugins/pattern plugins/polygon_encoded plugins/polyline_encoded + plugins/polyline_decorator plugins/polyline_offset plugins/polyline_textpath plugins/realtime @@ -94,6 +95,8 @@ Plugins - Draw a polygon directly from an encoded string. * - :doc:`Polyline Encoded ` - Draw a polyline directly from an encoded string. + * - :doc:`Polyline Decorator ` + - Draw arrowheads, dashes or markers along polylines. * - :doc:`Polyline Offset ` - Shift relative pixel offset, without actually changing the actual latitude longitude values. * - :doc:`Polyline Textpath ` diff --git a/docs/user_guide/plugins/polyline_decorator.md b/docs/user_guide/plugins/polyline_decorator.md new file mode 100644 index 0000000000..581dfe6303 --- /dev/null +++ b/docs/user_guide/plugins/polyline_decorator.md @@ -0,0 +1,49 @@ +```{code-cell} ipython3 +--- +nbsphinx: hidden +--- +import folium +import folium.plugins +``` + +## PolyLineDecorator + +Draw arrowheads, dashes or markers along a line, for example to show the direction of travel. + +```{code-cell} ipython3 +m = folium.Map([46.5, 6.6], zoom_start=9) + +route = folium.PolyLine( + [[46.2, 6.15], [46.52, 6.63], [46.95, 7.45]], color="#3388ff", weight=4 +).add_to(m) + +folium.plugins.PolyLineDecorator( + route, + [ + { + "offset": 25, + "repeat": 50, + "symbol": { + "type": "arrowHead", + "pixel_size": 12, + "path_options": {"color": "#3388ff", "fill_opacity": 1, "weight": 0}, + }, + } + ], +).add_to(m) + +border = folium.PolyLine([[46.0, 6.0], [46.3, 7.2]], color="black", weight=1).add_to(m) + +folium.plugins.PolyLineDecorator( + border, + [ + { + "offset": 0, + "repeat": 10, + "symbol": {"type": "dash", "pixel_size": 5, "path_options": {"color": "red"}}, + } + ], +).add_to(m) + +m +``` diff --git a/folium/plugins/__init__.py b/folium/plugins/__init__.py index bfc4208a65..f68b9b17a9 100644 --- a/folium/plugins/__init__.py +++ b/folium/plugins/__init__.py @@ -22,6 +22,7 @@ from folium.plugins.mouse_position import MousePosition from folium.plugins.overlapping_marker_spiderfier import OverlappingMarkerSpiderfier from folium.plugins.pattern import CirclePattern, StripePattern +from folium.plugins.polyline_decorator import PolyLineDecorator from folium.plugins.polyline_offset import PolyLineOffset from folium.plugins.polyline_text_path import PolyLineTextPath from folium.plugins.realtime import Realtime @@ -69,6 +70,7 @@ "PolygonFromEncoded", "PolyLineFromEncoded", "PolyLineTextPath", + "PolyLineDecorator", "PolyLineOffset", "Realtime", "ScrollZoomToggler", diff --git a/folium/plugins/polyline_decorator.py b/folium/plugins/polyline_decorator.py new file mode 100644 index 0000000000..66d8251836 --- /dev/null +++ b/folium/plugins/polyline_decorator.py @@ -0,0 +1,63 @@ +from folium.elements import JSCSSMixin +from folium.features import MacroElement +from folium.template import Template, tojavascript +from folium.utilities import JsCode + + +class PolyLineDecorator(JSCSSMixin, MacroElement): + """ + Draws patterns of symbols, such as arrowheads or dashes, along a line. + + Parameters + ---------- + polyline: folium.PolyLine or folium.Polygon + The line to decorate. + patterns: list of dict + Each pattern has a ``symbol`` and optionally ``offset``, ``end_offset`` + and ``repeat``, in pixels or as a percentage string such as "10%". + The ``symbol`` is a dict with a ``type`` of "arrowHead", "dash" or + "marker", and the options of that symbol, for example + ``{"type": "arrowHead", "pixel_size": 15, "path_options": {"color": "red"}}``. + + See https://github.com/bbecquet/Leaflet.PolylineDecorator for the options. + + """ + + _template = Template(""" + {% macro script(this, kwargs) %} + var {{ this.get_name() }} = L.polylineDecorator( + {{ this.polyline.get_name() }}, + {{ this.options|tojavascript }} + ).addTo({{ this._parent.get_name() }}); + {% endmacro %} + """) + + default_js = [ + ( + "polylinedecorator", + "https://cdn.jsdelivr.net/npm/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylineDecorator.min.js", + ) + ] + + symbol_types = {"arrowHead", "dash", "marker"} + + def __init__(self, polyline, patterns, **kwargs): + super().__init__() + self._name = "PolyLineDecorator" + self.polyline = polyline + self.options = { + "patterns": [self._to_pattern(pattern) for pattern in patterns], + **kwargs, + } + + def _to_pattern(self, pattern): + pattern = dict(pattern) + symbol = dict(pattern["symbol"]) + symbol_type = symbol.pop("type") + if symbol_type not in self.symbol_types: + raise ValueError( + f"symbol type should be one of {sorted(self.symbol_types)}, " + f"got {symbol_type!r}" + ) + pattern["symbol"] = JsCode(f"L.Symbol.{symbol_type}({tojavascript(symbol)})") + return pattern diff --git a/tests/plugins/test_polyline_decorator.py b/tests/plugins/test_polyline_decorator.py new file mode 100644 index 0000000000..c5f7c6d9f6 --- /dev/null +++ b/tests/plugins/test_polyline_decorator.py @@ -0,0 +1,53 @@ +""" +Test PolyLineDecorator +---------------------- +""" + +import pytest + +import folium +from folium import plugins +from folium.utilities import normalize + + +def test_polyline_decorator(): + m = folium.Map([46.5, 6.6], zoom_start=9) + line = folium.PolyLine([[46.2, 6.15], [46.52, 6.63]]).add_to(m) + decorator = plugins.PolyLineDecorator( + line, + [ + { + "offset": "100%", + "repeat": 0, + "symbol": {"type": "arrowHead", "pixel_size": 15}, + } + ], + ).add_to(m) + + out = normalize(m._parent.render()) + + script = '' # noqa + assert script in out + expected = normalize(f""" + var {decorator.get_name()} = L.polylineDecorator( + {line.get_name()}, + {{ + "patterns": [ + {{ + "offset": "100%", + "repeat": 0, + "symbol": L.Symbol.arrowHead({{ + "pixelSize": 15, + }}), + }}, + ], + }} + ).addTo({m.get_name()}); + """) + assert expected in out + + +def test_polyline_decorator_unknown_symbol(): + line = folium.PolyLine([[0, 0], [1, 1]]) + with pytest.raises(ValueError): + plugins.PolyLineDecorator(line, [{"symbol": {"type": "arrow"}}])