Skip to content

Commit ab5f476

Browse files
committed
Merge pull request #166 from e-mission/div_marker_support
Add support for leaflet div markers to folium
2 parents a55680b + 032a7b0 commit ab5f476

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

folium/folium.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ def __init__(self, location=None, width='100%', height='100%',
192192
'stamenterrain', 'stamentoner',
193193
'stamenwatercolor',
194194
'cartodbpositron', 'cartodbdark_matter']
195+
195196
self.tile_types = {}
196197
for tile in self.default_tiles:
197198
tile_path = 'tiles/%s' % tile
@@ -357,6 +358,58 @@ def simple_marker(self, location=None, popup=None,
357358
append = (icon, marker, popup_out, add_mark)
358359
self.template_vars.setdefault(name, []).append(append)
359360

361+
@iter_obj('div_mark')
362+
def div_markers(self, locations=None, popups=None, marker_size=10, popup_width=300):
363+
"""Create a simple div marker on the map, with optional
364+
popup text or Vincent visualization. Useful for marking points along a
365+
line.
366+
367+
Parameters
368+
----------
369+
locations: list of locations, where each location is an array
370+
Latitude and Longitude of Marker (Northing, Easting)
371+
popup: list of popups, each popup should be a string or tuple, default 'Pop Text'
372+
Input text or visualization for object. Can pass either text,
373+
or a tuple of the form (Vincent object, 'vis_path.json')
374+
It is possible to adjust the width of text/HTML popups
375+
using the optional keywords `popup_width`. (Leaflet default is 300px.)
376+
marker_size
377+
default is 5
378+
379+
Returns
380+
-------
381+
Marker names and HTML in obj.template_vars
382+
383+
Example
384+
-------
385+
>>>map.div_markers(locations=[[37.421114, -122.128314], [37.391637, -122.085416], [37.388832, -122.087709]], popups=['1437494575531', '1437492135937', '1437493590434'])
386+
387+
"""
388+
call_cnt = self.mark_cnt['div_mark']
389+
if locations is None or popups is None:
390+
raise RuntimeError("Both locations and popups are mandatory")
391+
for (point_cnt, (location, popup)) in enumerate(zip(locations, popups)):
392+
marker_num = 'div_marker_{0}_{1}'.format(call_cnt, point_cnt)
393+
394+
icon_temp = self.env.get_template('static_div_icon.js')
395+
icon_name = marker_num+"_icon"
396+
icon = icon_temp.render({'icon_name': icon_name,
397+
'size': marker_size})
398+
399+
mark_temp = self.env.get_template('simple_marker.js')
400+
# Get marker and popup.
401+
marker = mark_temp.render({'marker': marker_num,
402+
'lat': location[0],
403+
'lon': location[1],
404+
'icon': "{'icon':"+icon_name+"}"
405+
})
406+
407+
popup_out = self._popup_render(popup=popup, mk_name='div_marker_{0}_'.format(call_cnt),
408+
count=point_cnt, width=popup_width)
409+
add_mark = 'map.addLayer(div_marker_{0}_{1})'.format(call_cnt, point_cnt)
410+
append = (icon, marker, popup_out, add_mark)
411+
self.template_vars.setdefault('div_markers', []).append(append)
412+
360413
@iter_obj('line')
361414
def line(self, locations,
362415
line_color=None, line_opacity=None, line_weight=None,

folium/templates/fol_template.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@
159159
{{ add_mark }}
160160
{% endfor %}
161161

162+
{% for icon, mark, popup, add_mark in div_markers %}
163+
{{ icon }}
164+
{{ mark }}
165+
{{ popup }}
166+
{{ add_mark }}
167+
{% endfor %}
168+
162169
{% for mark, popup, add_mark in markers %}
163170
{{ mark }}
164171
{{ popup }}

folium/templates/static_div_icon.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var {{icon_name}} = L.divIcon({className: 'leaflet-div-icon',
2+
'iconSize': [{{size}},{{size}}]});

tests/folium_tests.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,44 @@ def test_simple_marker(self):
170170
nopopup = ''
171171
assert self.map.template_vars['custom_markers'][2][2] == nopopup
172172

173+
def test_div_markers(self):
174+
'''Test div marker list addition'''
175+
176+
icon_templ = self.env.get_template('static_div_icon.js')
177+
mark_templ = self.env.get_template('simple_marker.js')
178+
popup_templ = self.env.get_template('simple_popup.js')
179+
180+
# Test with popups (expected use case)
181+
self.map.div_markers(locations=[[37.421114, -122.128314], [37.391637, -122.085416], [37.388832, -122.087709]], popups=['1437494575531', '1437492135937', '1437493590434'])
182+
icon_1 = icon_templ.render({'icon_name': 'div_marker_1_0_icon', 'size': 10})
183+
mark_1 = mark_templ.render({'marker': 'div_marker_1_0', 'lat': 37.421114,
184+
'lon': -122.128314,
185+
'icon': "{'icon':div_marker_1_0_icon}"})
186+
popup_1 = popup_templ.render({'pop_name': 'div_marker_1_0',
187+
'pop_txt': '"1437494575531"',
188+
'width': 300})
189+
nt.assert_equals(self.map.mark_cnt['div_mark'], 1)
190+
nt.assert_equals(self.map.template_vars['div_markers'][0][0], icon_1)
191+
nt.assert_equals(self.map.template_vars['div_markers'][0][1], mark_1)
192+
nt.assert_equals(self.map.template_vars['div_markers'][0][2], popup_1)
193+
194+
# Second set of markers with popups to test the numbering
195+
self.map.div_markers(locations=[[37.421114, -122.128314], [37.391637, -122.085416], [37.388832, -122.087709]], popups=['1437494575531', '1437492135937', '1437493590434'])
196+
icon_2 = icon_templ.render({'icon_name': 'div_marker_2_1_icon', 'size': 10})
197+
mark_2 = mark_templ.render({'marker': 'div_marker_2_1', 'lat': 37.391637,
198+
'lon': -122.085416,
199+
'icon': "{'icon':div_marker_2_1_icon}"})
200+
popup_2 = popup_templ.render({'pop_name': 'div_marker_2_1',
201+
'pop_txt': '"1437492135937"',
202+
'width': 300})
203+
nt.assert_equals(self.map.mark_cnt['div_mark'], 2)
204+
nt.assert_equals(self.map.template_vars['div_markers'][4][0], icon_2)
205+
nt.assert_equals(self.map.template_vars['div_markers'][4][1], mark_2)
206+
nt.assert_equals(self.map.template_vars['div_markers'][4][2], popup_2)
207+
208+
# Test no popup. If there are no popups, then we should get a runtimeerror.
209+
nt.assert_raises(RuntimeError, self.map.div_markers, [[45.60, -122.8]])
210+
173211
def test_circle_marker(self):
174212
"""Test circle marker additions."""
175213

0 commit comments

Comments
 (0)