Skip to content

Move Map and Plugin object to a tree structure #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
91 changes: 55 additions & 36 deletions folium/folium.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
import functools
import json
from uuid import uuid4
from collections import OrderedDict

from jinja2 import Environment, PackageLoader
from pkg_resources import resource_string

from folium import utilities
from folium.six import text_type, binary_type, iteritems
from .plugins.marker import Popup, VegaPopup, RegularPolygonMarker

import sys
import base64
Expand Down Expand Up @@ -54,6 +56,12 @@ def wrapper(self, *args, **kwargs):
return wrapper
return decorator

class Figure(object):
def __init__(self):
self.header = OrderedDict()
self.css = OrderedDict()
self.body = OrderedDict()
self.script = OrderedDict()

class Map(object):
"""Create a Map with Folium."""
Expand Down Expand Up @@ -128,6 +136,8 @@ def __init__(self, location=None, width='100%', height='100%',
self.json_data = {}
self.plugins = {}

self.figure = Figure()

# No location means we will use automatic bounds and ignore zoom
self.location = location

Expand Down Expand Up @@ -605,36 +615,14 @@ def polygon_marker(self, location=None, line_color='black', line_opacity=1,

count = self.mark_cnt['polygon']

poly_temp = self.env.get_template('poly_marker.js')

polygon = poly_temp.render({'marker': 'polygon_' + str(count),
'lat': location[0],
'lon': location[1],
'line_color': line_color,
'line_opacity': line_opacity,
'line_weight': line_weight,
'fill_color': fill_color,
'fill_opacity': fill_opacity,
'num_sides': num_sides,
'rotation': rotation,
'radius': radius})

popup_out = self._popup_render(popup=popup, mk_name='polygon_',
count=count, width=popup_width)

add_mark = 'map.addLayer(polygon_{0})'.format(count)
popup = self._popup_(popup=popup, width=popup_width)

self.template_vars.setdefault('markers', []).append((polygon,
popup_out,
add_mark))
# Update JS/CSS and other Plugin files.
js_temp = self.env.get_template('dvf_js_ref.txt').render()
self.template_vars.update({'dvf_js': js_temp})
rpm = RegularPolygonMarker(location, popup=popup, icon=None,
color=line_color, opacity=line_opacity, weight=line_weight,
fill_color=fill_color, fill_opacity=fill_opacity,
number_of_sides=num_sides, rotation=rotation,radius=radius)

polygon_js = resource_string('folium',
'plugins/leaflet-dvf.markers.min.js')

self.plugins.update({'leaflet-dvf.markers.min.js': polygon_js})
self.add_plugin(rpm)

def lat_lng_popover(self):
"""Enable popovers to display Lat and Lon on each click."""
Expand Down Expand Up @@ -811,6 +799,40 @@ def _popup_render(self, popup=None, mk_name=None, count=None,
else:
raise TypeError("Unrecognized popup type: {!r}".format(popup))

def _popup_(self, popup=None, width=300):
"""Popup renderer: either text or Vincent/Vega.
Unlike _popup_render, it returns a Popup plugin object

Parameters
----------
popup: str or Vincent tuple, default None
String for text popup, or tuple of (Vincent object, json_path)
"""
if not popup:
return None
else:
if sys.version_info >= (3, 0):
utype, stype = str, bytes
else:
utype, stype = unicode, str

if isinstance(popup, (utype, stype)):
if isinstance(popup, utype):
popup_txt = popup.encode('ascii', 'xmlcharrefreplace')
else:
popup_txt = popup
if sys.version_info >= (3, 0):
popup_txt = popup_txt.decode()
return Popup(popup_txt, max_width=width)
elif isinstance(popup, tuple):
# Update template with JS libs.
vega = popup[0]
vega_data = json.loads(vega.to_json())
return VegaPopup(vega_data, width=width)

else:
raise TypeError("Unrecognized popup type: {!r}".format(popup))

@iter_obj('geojson')
def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
data=None, columns=None, key_on=None, threshold_scale=None,
Expand Down Expand Up @@ -1115,7 +1137,11 @@ def _build_map(self, html_templ=None, templ_type='string'):
if templ_type == 'string':
html_templ = self.env.from_string(html_templ)

self.HTML = html_templ.render(self.template_vars, plugins=self.plugins)
for plugin_name, plugin_list in self.plugins.items():
for nb, plugin in enumerate(plugin_list):
plugin.render(nb=nb, name=plugin_name)

self.HTML = html_templ.render(self.template_vars, plugins=self.plugins, figure=self.figure)

def create_map(self, path='map.html', plugin_data_out=True, template=None):
"""Write Map output to HTML and data output to JSON if available.
Expand All @@ -1142,13 +1168,6 @@ def create_map(self, path='map.html', plugin_data_out=True, template=None):
with open(path, 'w') as g:
json.dump(data, g)

if self.plugins and plugin_data_out:
for name, plugin in iteritems(self.plugins):
with open(name, 'w') as f:
if isinstance(plugin, binary_type):
plugin = text_type(plugin, 'utf8')
f.write(plugin)

def _repr_html_(self):
"""Build the HTML representation for IPython."""
map_types = {'base': 'ipynb_repr.html',
Expand Down
1 change: 1 addition & 0 deletions folium/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from .layer import Layer, LayerControl
from .geo_json import GeoJson
from .timestamped_geo_json import TimestampedGeoJson
from .links import JavascriptLink, CssLink
69 changes: 69 additions & 0 deletions folium/plugins/links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""
Links
-----

Plugins to add links into the header of the file.

"""

#from six import Module_six_moves_urllib as urllib
#urlopen = urllib.request.urlopen
import sys, urllib
PY3 = sys.version_info[0] == 3
urlopen = urllib.request.urlopen if PY3 else urllib.urlopen

from .plugin import Plugin

class Link(Plugin):
def __init__(self, url, download=False):
"""Create a link object based on a url.
Parameters
----------
url : str
The url to be linked
download : bool, default False
Whether the target document shall be loaded right now.
"""
self.url = url
self.code = None
if download:
self.code = urlopen(self.url).read()
def render_header(self, nb, embedded=False):
"""Generates the Header part of the plugin."""
return self.render(embedded=embedded)

class JavascriptLink(Link):
def render(self, embedded=False, **kwargs):
"""Renders the object.

Parameters
----------
embedded : bool, default False
Whether the code shall be embedded explicitely in the render.
"""
if embedded:
if self.code is None:
self.code = urlopen(self.url).read()
return '<script>{}</script>'.format(self.code)
else:
return '<script src="{}"></script>'.format(self.url)

class CssLink(Link):
def render(self, embedded=False, **kwargs):
"""Renders the object.

Parameters
----------
embedded : bool, default False
Whether the code shall be embedded explicitely in the render.
"""
if embedded:
if self.code is None:
self.code = urlopen(self.url).read()
return '<style>{}</style>'.format(self.code)
else:
return '<link rel="stylesheet" href="{}" />'.format(self.url)



Loading