Skip to content

Commit a55680b

Browse files
committed
Merge pull request #160 from BibMartin/merge_with_plugins
Merge branch 'plugins'
2 parents accaa21 + f3bb1f6 commit a55680b

17 files changed

+6848
-5
lines changed

examples/plugins_examples.ipynb

Lines changed: 6037 additions & 0 deletions
Large diffs are not rendered by default.

folium/folium.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,17 @@ def fit_bounds(self, bounds, padding_top_left=None,
657657

658658
self.template_vars.update({'fit_bounds': fit_bounds_str})
659659

660+
def add_plugin(self, plugin):
661+
"""Adds a plugin to the map.
662+
663+
Parameters
664+
----------
665+
plugin: folium.plugins object
666+
A plugin to be added to the map. It has to implement the methods
667+
`render_html`, `render_css` and `render_js`.
668+
"""
669+
plugin.add_to_map(self)
670+
660671
def _auto_bounds(self):
661672
if 'fit_bounds' in self.template_vars:
662673
return
@@ -1051,7 +1062,7 @@ def _build_map(self, html_templ=None, templ_type='string'):
10511062
if templ_type == 'string':
10521063
html_templ = self.env.from_string(html_templ)
10531064

1054-
self.HTML = html_templ.render(self.template_vars)
1065+
self.HTML = html_templ.render(self.template_vars, plugins=self.plugins)
10551066

10561067
def create_map(self, path='map.html', plugin_data_out=True, template=None):
10571068
"""Write Map output to HTML and data output to JSON if available.

folium/plugins/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
11
# -*- coding: utf-8 -*-
2+
"""
3+
Folium plugins
4+
--------------
5+
6+
Add different objetcs/effects on a folium map.
7+
"""
8+
from .marker_cluster import MarkerCluster
9+
from .scroll_zoom_toggler import ScrollZoomToggler
10+
from .terminator import Terminator
11+
from .boat_marker import BoatMarker
12+
from .layer import Layer, LayerControl
13+
from .geo_json import GeoJson
14+
from .timestamped_geo_json import TimestampedGeoJson

folium/plugins/boat_marker.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Boat marker
4+
-----------
5+
6+
Creates a marker shaped like a boat. Optionally you can append a wind direction.
7+
"""
8+
import json
9+
10+
from .plugin import Plugin
11+
12+
class BoatMarker(Plugin):
13+
"""Adds a BoatMarker layer on the map."""
14+
def __init__(self, position=None, heading=0, wind_heading=None, wind_speed=0, **kwargs):
15+
"""Creates a BoatMarker plugin to append into a map with
16+
Map.add_plugin.
17+
18+
Parameters
19+
----------
20+
position: tuple of length 2, default None
21+
The latitude and longitude of the marker.
22+
If None, then the middle of the map is used.
23+
24+
heading: int, default 0
25+
Heading of the boat to an angle value between 0 and 360 degrees
26+
27+
wind_heading: int, default None
28+
Heading of the wind to an angle value between 0 and 360 degrees
29+
If None, then no wind is represented.
30+
31+
wind_speed: int, default 0
32+
Speed of the wind in knots.
33+
"""
34+
super(BoatMarker, self).__init__()
35+
self.plugin_name = 'BoatMarker'
36+
self.position = None if position is None else tuple(position)
37+
self.heading = heading
38+
self.wind_heading = wind_heading
39+
self.wind_speed = wind_speed
40+
self.kwargs = kwargs.copy()
41+
42+
def render_header(self, nb):
43+
"""Generates the HTML part of the plugin."""
44+
return """
45+
<script src="https://thomasbrueggemann.github.io/leaflet.boatmarker/js/leaflet.boatmarker.min.js"></script>
46+
""" if nb==0 else ""
47+
48+
def render_js(self, nb):
49+
"""Generates the Javascript part of the plugin."""
50+
kwargs_str = "{%s}" % ",".join(["%s : %s" % (key,json.dumps(val)) for (key,val) in self.kwargs.items()])
51+
position_str = "map.getCenter()" if self.position is None else "[%.12f,%.12f]"%self.position
52+
out = 'var boatMarker_%s = L.boatMarker(%s, %s).addTo(map);' % (nb,position_str,kwargs_str)
53+
54+
if self.wind_heading is None:
55+
out += "boatMarker_%s.setHeading(%s);" % (nb,int(self.heading))
56+
else:
57+
out += "boatMarker_%s.setHeadingWind(%s, %s, %s);"%(nb,int(self.heading),
58+
int(self.wind_speed),
59+
int(self.wind_heading),
60+
)
61+
return out

folium/plugins/geo_json.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
GeoJson plugin
4+
--------------
5+
6+
Add a geojson feature collection on a folium map.
7+
"""
8+
import json
9+
10+
from .plugin import Plugin
11+
12+
class GeoJson(Plugin):
13+
"""Adds a GeoJson layer on the map."""
14+
def __init__(self, data):
15+
"""Creates a GeoJson plugin to append into a map with
16+
Map.add_plugin.
17+
18+
Parameters
19+
----------
20+
data: file, dict or str.
21+
The geo-json data you want to plot.
22+
If file, then data will be read in the file and fully embeded in Leaflet's javascript.
23+
If dict, then data will be converted to json and embeded in the javascript.
24+
If str, then data will be passed to the javascript as-is.
25+
26+
examples :
27+
# providing file
28+
GeoJson(open('foo.json'))
29+
30+
# providing dict
31+
GeoJson(json.load(open('foo.json')))
32+
33+
# providing string
34+
GeoJson(open('foo.json').read())
35+
"""
36+
super(GeoJson, self).__init__()
37+
self.plugin_name = 'GeoJson'
38+
if 'read' in dir(data):
39+
self.data = data.read()
40+
elif type(data) is dict:
41+
self.data = json.dumps(data)
42+
else:
43+
self.data = data
44+
45+
def render_js(self, nb):
46+
"""Generates the Javascript part of the plugin."""
47+
out = """
48+
var geojson_{nb} = L.geoJson({data}).addTo(map);
49+
""".format(nb=nb, data = self.data)
50+
return out

folium/plugins/layer.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Layer plugin
4+
------------
5+
6+
Add layers and layer control to the map.
7+
"""
8+
from .plugin import Plugin
9+
10+
class Layer(Plugin):
11+
"""Adds a layer to the map."""
12+
def __init__(self, url=None, layer_name = None, min_zoom=1, max_zoom=18, attribution=''):
13+
"""Crates a layer object to be added on a folium map.
14+
15+
Parameters
16+
----------
17+
url : str
18+
The url of the layer service, in the classical leaflet form.
19+
example: url='//otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png'
20+
layer_name : str
21+
Tha name of the layer that will be displayed in the layer control.
22+
If None, a random hexadecimal string will be created.
23+
min_zoom : int, default 1
24+
The minimal zoom allowed for this layer
25+
max_zoom : int, default 18
26+
The maximal zoom allowed for this layer
27+
attribution : str, default ''
28+
Tha atribution string for the layer.
29+
"""
30+
super(Layer, self).__init__()
31+
self.plugin_name = 'Layer'
32+
self.tile_url = url
33+
self.attribution = attribution
34+
self.min_zoom = min_zoom
35+
self.max_zoom = max_zoom
36+
self.object_id = self.object_name
37+
if layer_name is not None:
38+
self.object_name = layer_name
39+
40+
def render_js(self, nb):
41+
"""Generates the JS part of the plugin."""
42+
return """
43+
var layer_"""+self.object_id+""" = L.tileLayer('"""+self.tile_url+"""', {
44+
maxZoom: """+str(self.max_zoom)+""",
45+
minZoom: """+str(self.min_zoom)+""",
46+
attribution: '"""+str(self.attribution)+"""'
47+
});
48+
layer_"""+self.object_id+""".addTo(map);
49+
"""
50+
51+
class LayerControl(Plugin):
52+
"""Adds a layer control to the map."""
53+
def __init__(self, base_layer_name="Base Layer"):
54+
"""Creates a LayerControl object to be added on a folium map.
55+
56+
Parameters
57+
----------
58+
base_layer_name : str, default "Base Layer"
59+
The name of the base layer that you want to see on the control.
60+
"""
61+
super(LayerControl, self).__init__()
62+
self.plugin_name = 'LayerControl'
63+
self.base_layer_name = base_layer_name
64+
65+
def render_js(self, nb):
66+
"""Generates the JS part of the plugin."""
67+
return """
68+
var baseLayer = {
69+
"%s": base_tile,"""% self.base_layer_name+\
70+
",".join(['"%s" : layer_%s ' % (x.object_name,x.object_id) for x in self.map.plugins['Layer']])+\
71+
"""};
72+
73+
L.control.layers(baseLayer, layer_list).addTo(map);
74+
"""
75+
76+
77+

folium/plugins/marker_cluster.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Marker Cluster plugin
4+
---------------------
5+
6+
Creates a MarkerCluster plugin to add on a folium map.
7+
"""
8+
import json
9+
10+
from .plugin import Plugin
11+
12+
class MarkerCluster(Plugin):
13+
"""Adds a MarkerCluster layer on the map."""
14+
def __init__(self, data):
15+
"""Creates a MarkerCluster plugin to append into a map with
16+
Map.add_plugin.
17+
18+
Parameters
19+
----------
20+
data: list of list or array of shape (n,3).
21+
Data points of the form [[lat, lng, popup]].
22+
"""
23+
super(MarkerCluster, self).__init__()
24+
self.plugin_name = 'MarkerCluster'
25+
self.data = [tuple(x) for x in data]
26+
27+
def render_header(self, nb):
28+
"""Generates the HTML part of the plugin."""
29+
return """
30+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.4.0/MarkerCluster.css" />
31+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.4.0/MarkerCluster.Default.css" />
32+
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.4.0/leaflet.markercluster.js"></script>
33+
""" if nb==0 else ""
34+
35+
def render_js(self, nb):
36+
"""Generates the Javascript part of the plugin."""
37+
out = """
38+
var addressPoints = """+json.dumps(self.data)+""";
39+
40+
var markers = L.markerClusterGroup();
41+
42+
for (var i = 0; i < addressPoints.length; i++) {
43+
var a = addressPoints[i];
44+
var title = a[2];
45+
var marker = L.marker(new L.LatLng(a[0], a[1]), { title: title });
46+
marker.bindPopup(title);
47+
markers.addLayer(marker);
48+
}
49+
50+
map.addLayer(markers);
51+
"""
52+
return out

folium/plugins/plugin.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Plugin
4+
------
5+
6+
A generic class for creating plugins.
7+
Basic plugin object that does nothing.
8+
Other plugins may inherit from this one.
9+
"""
10+
from uuid import uuid4
11+
12+
from jinja2 import Environment, PackageLoader
13+
ENV = Environment(loader=PackageLoader('folium', 'plugins'))
14+
15+
class Plugin(object):
16+
"""Basic plugin object that does nothing.
17+
Other plugins may inherit from this one."""
18+
def __init__(self):
19+
"""Creates a plugin to append into a map with Map.add_plugin. """
20+
self.plugin_name = 'Plugin'
21+
self.object_name = uuid4().hex
22+
self.env = ENV
23+
24+
def add_to_map(self, map):
25+
"""Adds the plugin on a folium.map object."""
26+
map.plugins.setdefault(self.plugin_name,[]).append(self)
27+
self.map = map
28+
29+
def render_html(self, nb):
30+
"""Generates the HTML part of the plugin."""
31+
return ""
32+
33+
def render_css(self, nb):
34+
"""Generates the CSS part of the plugin."""
35+
return ""
36+
37+
def render_js(self, nb):
38+
"""Generates the Javascript part of the plugin."""
39+
return ""
40+
def render_header(self, nb):
41+
"""Generates the Header part of the plugin."""
42+
return ""

folium/plugins/scroll_zoom_toggler.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
ScrollZoomToggler plugin
4+
------------------------
5+
6+
Adds a button to enable/disable zoom scrolling.
7+
"""
8+
from .template_plugin import TemplatePlugin
9+
10+
class ScrollZoomToggler(TemplatePlugin):
11+
"""Adds a button to enable/disable zoom scrolling."""
12+
template = """
13+
{% set plugin_name = "ScrollZoomToggler" %}
14+
{% macro css(nb) %}
15+
#ScrollZoomToggler_{{nb}} {
16+
position:absolute;
17+
width:35px;
18+
bottom:10px;
19+
height:35px;
20+
left:10px;
21+
background-color:#fff;
22+
text-align:center;
23+
line-height:35px;
24+
vertical-align: middle;
25+
}
26+
{% endmacro %}
27+
28+
{% macro html(nb) %}
29+
<img id="ScrollZoomToggler_{{nb}}" alt="scroll"
30+
src="https://cdnjs.cloudflare.com/ajax/libs/ionicons/1.5.2/png/512/arrow-move.png"
31+
onclick="toggleScroll()"></img>
32+
{% endmacro %}
33+
34+
{% macro js(nb) %}
35+
{% if nb==0 %}
36+
map.scrollEnabled = true;
37+
38+
var toggleScroll = function() {
39+
if (map.scrollEnabled) {
40+
map.scrollEnabled = false;
41+
map.scrollWheelZoom.disable();
42+
}
43+
else {
44+
map.scrollEnabled = true;
45+
map.scrollWheelZoom.enable();
46+
}
47+
};
48+
49+
toggleScroll();
50+
{% endif %}
51+
{% endmacro %}
52+
"""

0 commit comments

Comments
 (0)