Skip to content

Commit 208c99d

Browse files
authored
Re-factor Circle and CircleMarker (#683)
* refactor circle * add tests * fix typo * update changelog
1 parent bef6984 commit 208c99d

File tree

8 files changed

+291
-205
lines changed

8 files changed

+291
-205
lines changed

CHANGES.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212

1313
API changes
1414

15+
- `Circle` and `CircleMarker` are set to leaflet's defaults and accepted all
16+
`Path` optional arguments (# 683)
1517
- Changed default `max_bounds` to `False` to reflect leaflet's default value (rdd9999 #641)
16-
- Modified `Fullscreen` plugin `kwargs` to be more "pythonic."
17-
- All `.format` properties are now `.fmt` for consistency.
18+
- Modified `Fullscreen` plugin `kwargs` to be more "pythonic"
19+
- All `.format` properties are now `.fmt` for consistency
1820

1921
Bug Fixes
2022

examples/Features.ipynb

Lines changed: 83 additions & 110 deletions
Large diffs are not rendered by default.

folium/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from folium._version import get_versions
1010

1111
from folium.features import (
12-
CircleMarker, ClickForMarker, CustomIcon, DivIcon, GeoJson, LatLngPopup,
12+
Circle, CircleMarker, ClickForMarker, CustomIcon, DivIcon, GeoJson, LatLngPopup,
1313
MarkerCluster, PolyLine, RegularPolygonMarker, TopoJson, Vega, VegaLite,
1414
WmsTileLayer,
1515
)
@@ -37,6 +37,7 @@
3737
'LinearColormap',
3838
'StepColormap',
3939
'Map',
40+
'Circle',
4041
'CircleMarker',
4142
'RectangleMarker',
4243
'Polygon',

folium/features.py

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from branca.utilities import (_locations_tolist, _parse_size, image_to_url, iter_points, none_max, none_min) # noqa
1818

1919
from folium.map import FeatureGroup, Icon, Layer, Marker, Popup
20-
from folium.utilities import _validate_coordinates, _validate_location
20+
from folium.utilities import _parse_path, _validate_coordinates, _validate_location
2121

2222
from jinja2 import Template
2323

@@ -809,43 +809,37 @@ class Circle(Marker):
809809
location: tuple or list, default None
810810
Latitude and Longitude of Marker (Northing, Easting)
811811
radius: int
812-
The radius of the circle in meters. For setting the radius in pixel,
813-
use CircleMarker.
814-
color: str, default 'black'
812+
The radius of the circle in meters.
813+
For setting the radius in pixel, use CircleMarker.
814+
color: str, default '#3388ff'
815815
The color of the marker's edge in a HTML-compatible format.
816-
fill_color: str, default 'black'
816+
fill: bool, default False
817+
If true the circle will be filled.
818+
fill_color: str, default to the same as color
817819
The fill color of the marker in a HTML-compatible format.
818-
fill_opacity: float, default 0.6
820+
fill_opacity: float, default 0.2
819821
The fill opacity of the marker, between 0. and 1.
820822
popup: string or folium.Popup, default None
821823
Input text or visualization for object.
822824
825+
See http://leafletjs.com/reference-1.2.0.html#path for more otions.
826+
823827
"""
824-
def __init__(self, location, radius=500, color='black',
825-
fill_color='black', fill_opacity=0.6, popup=None):
826-
super(Circle, self).__init__(
827-
_validate_location(location),
828-
popup=popup
829-
)
830-
self._name = 'Circle'
831-
self.radius = radius
832-
self.color = color
833-
self.fill_color = fill_color
834-
self.fill_opacity = fill_opacity
828+
def __init__(self, location, radius=10, popup=None, **kw):
829+
super(Circle, self).__init__(_validate_location(location), popup=popup)
830+
self._name = 'circle'
831+
options = _parse_path(**kw)
832+
833+
options.update({'radius': radius})
834+
self.options = json.dumps(options, sort_keys=True, indent=2)
835835

836836
self._template = Template(u"""
837837
{% macro script(this, kwargs) %}
838838
839839
var {{this.get_name()}} = L.circle(
840840
[{{this.location[0]}},{{this.location[1]}}],
841-
{{ this.radius }},
842-
{
843-
color: '{{ this.color }}',
844-
fillColor: '{{ this.fill_color }}',
845-
fillOpacity: {{ this.fill_opacity }}
846-
}
847-
)
848-
.addTo({{this._parent.get_name()}});
841+
{{ this.options }}
842+
).addTo({{this._parent.get_name()}});
849843
{% endmacro %}
850844
""")
851845

@@ -859,45 +853,37 @@ class CircleMarker(Marker):
859853
location: tuple or list, default None
860854
Latitude and Longitude of Marker (Northing, Easting)
861855
radius: int
862-
The radius of the circle in pixels. For setting the radius in meter,
863-
use Circle.
864-
color: str, default 'black'
856+
The radius of the circle in pixels.
857+
For setting the radius in meter, use Circle.
858+
color: str, default '#3388ff'
865859
The color of the marker's edge in a HTML-compatible format.
866-
weight: int, default 2
867-
Stroke weight in pixels
868-
fill_color: str, default 'black'
860+
fill: bool, default False
861+
If true the circle will be filled.
862+
fill_color: str, default to the same as color
869863
The fill color of the marker in a HTML-compatible format.
870-
fill_opacity: float, default 0.6
864+
fill_opacity: float, default 0.2
871865
The fill opacity of the marker, between 0. and 1.
872866
popup: string or folium.Popup, default None
873867
Input text or visualization for object.
874868
869+
See http://leafletjs.com/reference-1.2.0.html#path for more otions.
870+
875871
"""
876-
def __init__(self, location, radius=500, color='black',
877-
weight=2, fill_color='black', fill_opacity=0.6,
878-
popup=None):
879-
super(CircleMarker, self).__init__(location, popup=popup)
872+
def __init__(self, location, radius=10, popup=None, **kw):
873+
super(CircleMarker, self).__init__(_validate_location(location), popup=popup)
880874
self._name = 'CircleMarker'
881-
self.radius = radius
882-
self.color = color
883-
self.weight = weight
884-
self.fill_color = fill_color
885-
self.fill_opacity = fill_opacity
875+
options = _parse_path(**kw)
876+
877+
options.update({'radius': radius})
878+
self.options = json.dumps(options, sort_keys=True, indent=2)
886879

887880
self._template = Template(u"""
888881
{% macro script(this, kwargs) %}
889882
890883
var {{this.get_name()}} = L.circleMarker(
891884
[{{this.location[0]}},{{this.location[1]}}],
892-
{
893-
color: '{{ this.color }}',
894-
weight: {{ this.weight }},
895-
fillColor: '{{ this.fill_color }}',
896-
fillOpacity: {{ this.fill_opacity }}
897-
}
898-
)
899-
.setRadius({{ this.radius }})
900-
.addTo({{this._parent.get_name()}});
885+
{{ this.options }}
886+
).addTo({{this._parent.get_name()}});
901887
{% endmacro %}
902888
""")
903889

folium/templates/circle_marker.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

folium/utilities.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,23 @@ def _flatten(container):
4848
def _isnan(values):
4949
"""Check if there are NaNs values in the iterable."""
5050
return any(math.isnan(value) for value in _flatten(values))
51+
52+
53+
def _parse_path(**kw):
54+
"""Parse Path http://leafletjs.com/reference-1.2.0.html#path options."""
55+
color = kw.pop('color', '#3388ff')
56+
return {
57+
'stroke': kw.pop('stroke', True),
58+
'color': color,
59+
'weight': kw.pop('weight', 3),
60+
'opacity': kw.pop('opacity', 1.0),
61+
'lineCap': kw.pop('line_cap', 'round'),
62+
'lineJoin': kw.pop('line_join', 'round'),
63+
'dashArray': kw.pop('dash_array', None),
64+
'dashOffset': kw.pop('dash_offset', None),
65+
'fill': kw.pop('fill', False),
66+
'fillColor': kw.pop('fill_color', color),
67+
'fillOpacity': kw.pop('fill_opacity', 0.2),
68+
'fillRule': kw.pop('fill_rule', 'evenodd'),
69+
'bubblingMouseEvents': kw.pop('bubbling_mouse_events', True),
70+
}

tests/test_circle.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# -*- coding: utf-8 -*-
2+
3+
""""
4+
Circle and Circle Marker Features
5+
---------------------------------
6+
7+
"""
8+
9+
from __future__ import (absolute_import, division, print_function)
10+
11+
import json
12+
13+
from folium import Map
14+
from folium.features import Circle, CircleMarker
15+
16+
17+
def test_circle():
18+
m = Map()
19+
radius = 10000
20+
popup = 'I am {} meters'.format(radius)
21+
22+
circle = Circle(
23+
location=[-27.551667, -48.478889],
24+
radius=radius,
25+
color='black',
26+
weight=2,
27+
fill_opacity=0.6,
28+
opacity=1,
29+
fill=True,
30+
popup=popup,
31+
)
32+
circle.add_to(m)
33+
34+
options = {
35+
'bubblingMouseEvents': True,
36+
'color': 'black',
37+
'dashArray': None,
38+
'dashOffset': None,
39+
'fill': True,
40+
'fillColor': 'black',
41+
'fillOpacity': 0.6,
42+
'fillRule': 'evenodd',
43+
'lineCap': 'round',
44+
'lineJoin': 'round',
45+
'opacity': 1,
46+
'radius': 10000,
47+
'stroke': True,
48+
'weight': 2,
49+
}
50+
51+
m._repr_html_()
52+
expected_bounds = [[-27.551667, -48.478889], [-27.551667, -48.478889]]
53+
expected_rendered = """
54+
var {0} = L.circle(
55+
[-27.551667,-48.478889],
56+
{{
57+
"bubblingMouseEvents": true,
58+
"color": "black",
59+
"dashArray": null,
60+
"dashOffset": null,
61+
"fill": true,
62+
"fillColor": "black",
63+
"fillOpacity": 0.6,
64+
"fillRule": "evenodd",
65+
"lineCap": "round",
66+
"lineJoin": "round",
67+
"opacity": 1,
68+
"radius": 10000,
69+
"stroke": true,
70+
"weight": 2
71+
}}
72+
).addTo({1});
73+
""".format(circle.get_name(), m.get_name())
74+
75+
rendered = circle._template.module.script(circle)
76+
assert rendered.strip().split() == expected_rendered.strip().split()
77+
assert circle.get_bounds() == expected_bounds
78+
assert json.dumps(circle.to_dict()) == circle.to_json()
79+
assert circle.location == [-27.551667, -48.478889]
80+
assert circle.options == json.dumps(options, sort_keys=True, indent=2)
81+
82+
83+
def test_circle_marker():
84+
m = Map()
85+
radius = 50
86+
popup = 'I am {} pixels'.format(radius)
87+
88+
circle_marker = CircleMarker(
89+
location=[-27.55, -48.8],
90+
radius=radius,
91+
color='black',
92+
weight=2,
93+
fill_opacity=0.6,
94+
opacity=1,
95+
fill=True,
96+
popup=popup,
97+
)
98+
circle_marker.add_to(m)
99+
100+
options = {
101+
'bubblingMouseEvents': True,
102+
'color': 'black',
103+
'dashArray': None,
104+
'dashOffset': None,
105+
'fill': True,
106+
'fillColor': 'black',
107+
'fillOpacity': 0.6,
108+
'fillRule': 'evenodd',
109+
'lineCap': 'round',
110+
'lineJoin': 'round',
111+
'opacity': 1,
112+
'radius': 50,
113+
'stroke': True,
114+
'weight': 2,
115+
}
116+
117+
m._repr_html_()
118+
expected_bounds = [[-27.55, -48.8], [-27.55, -48.8]]
119+
expected_rendered = """
120+
var {0} = L.circleMarker(
121+
[-27.55,-48.8],
122+
{{
123+
"bubblingMouseEvents": true,
124+
"color": "black",
125+
"dashArray": null,
126+
"dashOffset": null,
127+
"fill": true,
128+
"fillColor": "black",
129+
"fillOpacity": 0.6,
130+
"fillRule": "evenodd",
131+
"lineCap": "round",
132+
"lineJoin": "round",
133+
"opacity": 1,
134+
"radius": 50,
135+
"stroke": true,
136+
"weight": 2
137+
}}
138+
).addTo({1});
139+
""".format(circle_marker.get_name(), m.get_name())
140+
141+
rendered = circle_marker._template.module.script(circle_marker)
142+
assert rendered.strip().split() == expected_rendered.strip().split()
143+
assert circle_marker.get_bounds() == expected_bounds
144+
assert json.dumps(circle_marker.to_dict()) == circle_marker.to_json()
145+
assert circle_marker.location == [-27.55, -48.8]
146+
assert circle_marker.options == json.dumps(options, sort_keys=True, indent=2)

tests/test_folium.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -199,42 +199,6 @@ def test_feature_group(self):
199199
bounds = m.get_bounds()
200200
assert bounds == [[45, -30], [45, 30]], bounds
201201

202-
def test_circle_marker(self):
203-
"""Test circle marker additions."""
204-
205-
self.m = folium.Map(location=[45.60, -122.8])
206-
circ_templ = self.env.get_template('circle_marker.js')
207-
208-
# Single Circle marker.
209-
marker = folium.features.CircleMarker([45.60, -122.8], popup='Hi')
210-
self.m.add_child(marker)
211-
circle_1 = circ_templ.render({'circle': marker.get_name(),
212-
'lat': 45.60,
213-
'lon': -122.8, 'radius': 500,
214-
'weight': 2,
215-
'line_color': 'black',
216-
'fill_color': 'black',
217-
'fill_opacity': 0.6})
218-
assert (''.join(circle_1.split())[:-1] in
219-
''.join(self.m.get_root().render().split()))
220-
221-
# Second circle marker.
222-
marker = folium.features.CircleMarker([45.70, -122.9], popup='Hi',
223-
weight=1)
224-
self.m.add_child(marker)
225-
circle_2 = circ_templ.render({'circle': marker.get_name(),
226-
'lat': 45.70,
227-
'lon': -122.9, 'radius': 500,
228-
'weight': 1,
229-
'line_color': 'black',
230-
'fill_color': 'black',
231-
'fill_opacity': 0.6})
232-
assert (''.join(circle_2.split())[:-1] in
233-
''.join(self.m.get_root().render().split()))
234-
235-
bounds = self.m.get_bounds()
236-
assert bounds == [[45.6, -122.9], [45.7, -122.8]], bounds
237-
238202
def test_rectangle_marker(self):
239203
"""Test rectangle marker additions."""
240204

0 commit comments

Comments
 (0)