Skip to content

Commit c489324

Browse files
authored
Add SemiCircle Plugin - reworked. (#1238)
1 parent 46a8e93 commit c489324

4 files changed

Lines changed: 230 additions & 29 deletions

File tree

examples/Plugins.ipynb

Lines changed: 73 additions & 29 deletions
Large diffs are not rendered by default.

folium/plugins/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from folium.plugins.polyline_text_path import PolyLineTextPath
3030
from folium.plugins.scroll_zoom_toggler import ScrollZoomToggler
3131
from folium.plugins.search import Search
32+
from folium.plugins.semicircle import SemiCircle
3233
from folium.plugins.terminator import Terminator
3334
from folium.plugins.time_slider_choropleth import TimeSliderChoropleth
3435
from folium.plugins.timestamped_geo_json import TimestampedGeoJson
@@ -56,6 +57,7 @@
5657
'PolyLineOffset',
5758
'ScrollZoomToggler',
5859
'Search',
60+
'SemiCircle',
5961
'StripePattern',
6062
'Terminator',
6163
'TimeSliderChoropleth',

folium/plugins/semicircle.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from branca.element import Figure, JavascriptLink
4+
5+
from folium.map import Marker
6+
from folium.utilities import parse_options
7+
8+
from jinja2 import Template
9+
10+
from folium.vector_layers import path_options
11+
12+
_default_js = [
13+
('semicirclejs',
14+
'https://cdn.jsdelivr.net/npm/leaflet-semicircle@2.0.4/Semicircle.min.js')
15+
]
16+
17+
18+
class SemiCircle(Marker):
19+
"""Add a marker in the shape of a semicircle, similar to the Circle class.
20+
21+
Use (direction and arc) or (start_angle and stop_angle), not both.
22+
23+
Parameters
24+
----------
25+
location: tuple[float, float]
26+
Latitude and Longitude pair (Northing, Easting)
27+
radius: float
28+
Radius of the circle, in meters.
29+
direction: int, default None
30+
Direction angle in degrees
31+
arc: int, default None
32+
Arc angle in degrees.
33+
start_angle: int, default None
34+
Start angle in degrees
35+
stop_angle: int, default None
36+
Stop angle in degrees.
37+
popup: str or folium.Popup, optional
38+
Input text or visualization for object displayed when clicking.
39+
tooltip: str or folium.Tooltip, optional
40+
Display a text when hovering over the object.
41+
**kwargs
42+
For additional arguments see :func:`folium.vector_layers.path_options`
43+
44+
Uses Leaflet plugin https://github.com/jieter/Leaflet-semicircle
45+
46+
"""
47+
_template = Template(u"""
48+
{% macro script(this, kwargs) %}
49+
var {{ this.get_name() }} = L.semiCircle(
50+
{{ this.location|tojson }},
51+
{{ this.options|tojson }}
52+
)
53+
{%- if this.direction %}
54+
.setDirection({{ this.direction[0] }}, {{ this.direction[1] }})
55+
{%- endif %}
56+
.addTo({{ this._parent.get_name() }});
57+
{% endmacro %}
58+
""")
59+
60+
def __init__(self, location, radius,
61+
direction=None, arc=None,
62+
start_angle=None, stop_angle=None,
63+
popup=None, tooltip=None, **kwargs):
64+
super(SemiCircle, self).__init__(location, popup=popup, tooltip=tooltip)
65+
self._name = 'SemiCircle'
66+
self.direction = (direction, arc) if direction is not None and arc is not None else None
67+
self.options = path_options(line=False, radius=radius, **kwargs)
68+
self.options.update(parse_options(
69+
start_angle=start_angle,
70+
stop_angle=stop_angle,
71+
))
72+
73+
if not ((direction is None and arc is None) and (start_angle is not None and stop_angle is not None)
74+
or (direction is not None and arc is not None) and (start_angle is None and stop_angle is None)):
75+
raise ValueError("Invalid arguments. Either provide direction and arc OR start_angle and stop_angle")
76+
77+
def render(self, **kwargs):
78+
super(SemiCircle, self).render(**kwargs)
79+
80+
figure = self.get_root()
81+
assert isinstance(figure, Figure), ('You cannot render this Element '
82+
'if it is not in a Figure.')
83+
84+
for name, url in _default_js:
85+
figure.header.add_child(JavascriptLink(url), name=name)

tests/plugins/test_semicircle.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
Test SemiCircle
5+
---------------
6+
7+
"""
8+
9+
import folium
10+
from folium import plugins
11+
from folium.utilities import normalize
12+
13+
from jinja2 import Template
14+
15+
16+
def test_semicircle():
17+
m = folium.Map([30., 0.], zoom_start=3)
18+
sc1 = plugins.SemiCircle(
19+
(34, -43),
20+
radius=400000,
21+
arc=300,
22+
direction=20,
23+
color='red',
24+
fill_color='red',
25+
opacity=0,
26+
popup='Direction - 20 degrees, arc 300 degrees'
27+
)
28+
sc2 = plugins.SemiCircle(
29+
(46, -30),
30+
radius=400000,
31+
start_angle=10,
32+
stop_angle=50,
33+
color='red',
34+
fill_color='red',
35+
opacity=0,
36+
popup='Start angle - 10 degrees, Stop angle - 50 degrees'
37+
)
38+
39+
m.add_child(sc1)
40+
m.add_child(sc2)
41+
m._repr_html_()
42+
43+
out = normalize(m._parent.render())
44+
45+
# We verify that the script import is present.
46+
script = '<script src="https://cdn.jsdelivr.net/npm/leaflet-semicircle@2.0.4/Semicircle.min.js"></script>' # noqa
47+
assert script in out
48+
49+
# We verify that the script part is correct.
50+
tmpl_sc1 = Template(u"""
51+
var {{ this.get_name() }} = L.semiCircle(
52+
{{ this.location|tojson }},
53+
{{ this.options | tojson }}
54+
)
55+
.setDirection{{ this.direction }}
56+
.addTo({{ this._parent.get_name() }});
57+
""")
58+
59+
tmpl_sc2 = Template(u"""
60+
var {{ this.get_name() }} = L.semiCircle(
61+
{{ this.location|tojson }},
62+
{{ this.options | tojson }}
63+
)
64+
.addTo({{ this._parent.get_name() }});
65+
""")
66+
assert normalize(tmpl_sc1.render(this=sc1)) in out
67+
assert normalize(tmpl_sc2.render(this=sc2)) in out
68+
69+
bounds = m.get_bounds()
70+
assert bounds == [[34, -43], [46, -30]], bounds

0 commit comments

Comments
 (0)