Skip to content

Commit 4e86777

Browse files
committed
refactor raster layers
1 parent 2d457b0 commit 4e86777

File tree

11 files changed

+593
-678
lines changed

11 files changed

+593
-678
lines changed

folium/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
from folium.features import (
1212
ClickForMarker, ColorLine, CustomIcon, DivIcon, GeoJson,
1313
LatLngPopup, RegularPolygonMarker, TopoJson, Vega, VegaLite,
14-
WmsTileLayer,
1514
)
1615

16+
# from folium.raster_layers imoort TileLayer, WmsTileLayer
17+
1718
from folium.folium import Map
1819

1920
from folium.map import (
20-
FeatureGroup, FitBounds, Icon, LayerControl, Marker, Popup, TileLayer
21+
FeatureGroup, FitBounds, Icon, LayerControl, Marker, Popup
2122
)
2223

2324
from folium.vector_layers import Circle, CircleMarker, PolyLine, Polygon, Rectangle # noqa

folium/features.py

Lines changed: 2 additions & 58 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
20-
from folium.utilities import _parse_wms, get_bounds
20+
from folium.utilities import get_bounds
2121
from folium.vector_layers import PolyLine
2222

2323
from jinja2 import Template
@@ -27,63 +27,6 @@
2727
from six import binary_type, text_type
2828

2929

30-
class WmsTileLayer(Layer):
31-
"""
32-
Creates a Web Map Service (WMS) layer.
33-
34-
Parameters
35-
----------
36-
url : str
37-
The url of the WMS server.
38-
name : string, default None
39-
The name of the Layer, as it will appear in LayerControls
40-
layers : str, default ''
41-
The names of the layers to be displayed.
42-
styles : str, default ''
43-
Comma-separated list of WMS styles.
44-
fmt : str, default 'image/jpeg'
45-
The format of the service output.
46-
Ex: 'image/png'
47-
transparent: bool, default False
48-
Whether the layer shall allow transparency.
49-
version : str, default '1.1.1'
50-
Version of the WMS service to use.
51-
attr : str, default None
52-
The attribution of the service.
53-
Will be displayed in the bottom right corner.
54-
overlay : bool, default True
55-
Adds the layer as an optional overlay (True) or the base layer (False).
56-
control : bool, default True
57-
Whether the Layer will be included in LayerControls
58-
**kwargs : additional keyword arguments
59-
Passed through to the underlying tileLayer.wms object and can be used
60-
for setting extra tileLayer.wms parameters or as extra parameters in
61-
the WMS request.
62-
63-
For more information see:
64-
http://leafletjs.com/reference.html#tilelayer-wms
65-
66-
"""
67-
def __init__(self, url, name=None, attr='', overlay=True, control=True, **kwargs): # noqa
68-
super(WmsTileLayer, self).__init__(overlay=overlay, control=control, name=name) # noqa
69-
self.url = url
70-
# Options.
71-
options = _parse_wms(**kwargs)
72-
options.update({'attribution': attr})
73-
74-
self.options = json.dumps(options, sort_keys=True, indent=2)
75-
76-
self._template = Template(u"""
77-
{% macro script(this, kwargs) %}
78-
var {{this.get_name()}} = L.tileLayer.wms(
79-
'{{ this.url }}',
80-
{{ this.options }}
81-
).addTo({{this._parent.get_name()}});
82-
83-
{% endmacro %}
84-
""") # noqa
85-
86-
8730
class RegularPolygonMarker(Marker):
8831
"""
8932
Custom markers using the Leaflet Data Vis Framework.
@@ -518,6 +461,7 @@ def _get_self_bounds(self):
518461
"""
519462
return get_bounds(self.data, lonlat=True)
520463

464+
521465
class TopoJson(Layer):
522466
"""
523467
Creates a TopoJson object for plotting into a Map.

folium/folium.py

Lines changed: 258 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,70 @@
1010

1111
from __future__ import (absolute_import, division, print_function)
1212

13+
import os
14+
import tempfile
15+
import time
16+
1317
from branca.colormap import StepColormap
14-
from branca.utilities import color_brewer
18+
from branca.element import CssLink, Element, Figure, JavascriptLink, MacroElement
19+
from branca.utilities import _parse_size, color_brewer
1520

1621
from folium.features import GeoJson, TopoJson
17-
from folium.map import FitBounds, LegacyMap
18-
19-
20-
class Map(LegacyMap):
22+
from folium.map import FitBounds
23+
from folium.raster_layers import TileLayer
24+
from folium.utilities import _validate_location
25+
26+
from jinja2 import Environment, PackageLoader, Template
27+
28+
ENV = Environment(loader=PackageLoader('folium', 'templates'))
29+
30+
31+
_default_js = [
32+
('leaflet',
33+
'https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js'),
34+
('jquery',
35+
'https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'),
36+
('bootstrap',
37+
'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js'),
38+
('awesome_markers',
39+
'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js'), # noqa
40+
]
41+
42+
_default_css = [
43+
('leaflet_css',
44+
'https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css'),
45+
('bootstrap_css',
46+
'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'),
47+
('bootstrap_theme_css',
48+
'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css'), # noqa
49+
('awesome_markers_font_css',
50+
'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css'), # noqa
51+
('awesome_markers_css',
52+
'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css'), # noqa
53+
('awesome_rotate_css',
54+
'https://rawgit.com/python-visualization/folium/master/folium/templates/leaflet.awesome.rotate.css'), # noqa
55+
]
56+
57+
58+
class GlobalSwitches(Element):
59+
def __init__(self, prefer_canvas=False, no_touch=False, disable_3d=False):
60+
super(GlobalSwitches, self).__init__()
61+
self._name = 'GlobalSwitches'
62+
63+
self.prefer_canvas = prefer_canvas
64+
self.no_touch = no_touch
65+
self.disable_3d = disable_3d
66+
67+
self._template = Template(
68+
'<script>'
69+
'L_PREFER_CANVAS = {% if this.prefer_canvas %}true{% else %}false{% endif %}; '
70+
'L_NO_TOUCH = {% if this.no_touch %}true{% else %}false{% endif %}; '
71+
'L_DISABLE_3D = {% if this.disable_3d %}true{% else %}false{% endif %};'
72+
'</script>'
73+
)
74+
75+
76+
class Map(MacroElement):
2177
"""Create a Map with Folium and Leaflet.js
2278
2379
Generate a base map of given width and height with either default
@@ -89,17 +145,17 @@ class Map(LegacyMap):
89145
90146
Returns
91147
-------
92-
Folium LegacyMap Object
148+
Folium Map Object
93149
94150
Examples
95151
--------
96-
>>> map = folium.LegacyMap(location=[45.523, -122.675],
152+
>>> map = folium.Map(location=[45.523, -122.675],
97153
... width=750, height=500)
98-
>>> map = folium.LegacyMap(location=[45.523, -122.675],
154+
>>> map = folium.Map(location=[45.523, -122.675],
99155
tiles='Mapbox Control Room')
100-
>>> map = folium.LegacyMap(location=(45.523, -122.675), max_zoom=20,
156+
>>> map = folium.Map(location=(45.523, -122.675), max_zoom=20,
101157
tiles='Cloudmade', API_key='YourKey')
102-
>>> map = folium.LegacyMap(
158+
>>> map = folium.Map(
103159
... location=[45.523, -122.675],
104160
... zoom_start=2,
105161
... tiles='http://{s}.tiles.mapbox.com/v3/mapbox.control-room/{z}/{x}/{y}.png',
@@ -108,6 +164,198 @@ class Map(LegacyMap):
108164
109165
"""
110166

167+
def __init__(self, location=None, width='100%', height='100%',
168+
left='0%', top='0%', position='relative',
169+
tiles='OpenStreetMap', API_key=None, max_zoom=18, min_zoom=1,
170+
zoom_start=10, world_copy_jump=False,
171+
no_wrap=False, attr=None, min_lat=-90, max_lat=90,
172+
min_lon=-180, max_lon=180, max_bounds=False,
173+
detect_retina=False, crs='EPSG3857', control_scale=False,
174+
prefer_canvas=False, no_touch=False, disable_3d=False,
175+
subdomains='abc', png_enabled=False):
176+
super(Map, self).__init__()
177+
self._name = 'Map'
178+
self._env = ENV
179+
# Undocumented for now b/c this will be subject to a re-factor soon.
180+
self._png_image = None
181+
self.png_enabled = png_enabled
182+
183+
if not location:
184+
# If location is not passed we center and ignore zoom.
185+
self.location = [0, 0]
186+
self.zoom_start = min_zoom
187+
else:
188+
self.location = _validate_location(location)
189+
self.zoom_start = zoom_start
190+
191+
Figure().add_child(self)
192+
193+
# Map Size Parameters.
194+
self.width = _parse_size(width)
195+
self.height = _parse_size(height)
196+
self.left = _parse_size(left)
197+
self.top = _parse_size(top)
198+
self.position = position
199+
200+
self.min_lat = min_lat
201+
self.max_lat = max_lat
202+
self.min_lon = min_lon
203+
self.max_lon = max_lon
204+
self.max_bounds = max_bounds
205+
self.no_wrap = no_wrap
206+
self.world_copy_jump = world_copy_jump
207+
208+
self.crs = crs
209+
self.control_scale = control_scale
210+
211+
self.global_switches = GlobalSwitches(
212+
prefer_canvas,
213+
no_touch,
214+
disable_3d
215+
)
216+
217+
if tiles:
218+
self.add_tile_layer(
219+
tiles=tiles, min_zoom=min_zoom, max_zoom=max_zoom,
220+
no_wrap=no_wrap, attr=attr,
221+
API_key=API_key, detect_retina=detect_retina,
222+
subdomains=subdomains
223+
)
224+
225+
self._template = Template(u"""
226+
{% macro header(this, kwargs) %}
227+
<style> #{{this.get_name()}} {
228+
position : {{this.position}};
229+
width : {{this.width[0]}}{{this.width[1]}};
230+
height: {{this.height[0]}}{{this.height[1]}};
231+
left: {{this.left[0]}}{{this.left[1]}};
232+
top: {{this.top[0]}}{{this.top[1]}};
233+
}
234+
</style>
235+
{% endmacro %}
236+
{% macro html(this, kwargs) %}
237+
<div class="folium-map" id="{{this.get_name()}}" ></div>
238+
{% endmacro %}
239+
240+
{% macro script(this, kwargs) %}
241+
242+
{% if this.max_bounds %}
243+
var southWest = L.latLng({{ this.min_lat }}, {{ this.min_lon }});
244+
var northEast = L.latLng({{ this.max_lat }}, {{ this.max_lon }});
245+
var bounds = L.latLngBounds(southWest, northEast);
246+
{% else %}
247+
var bounds = null;
248+
{% endif %}
249+
250+
var {{this.get_name()}} = L.map(
251+
'{{this.get_name()}}',
252+
{center: [{{this.location[0]}},{{this.location[1]}}],
253+
zoom: {{this.zoom_start}},
254+
maxBounds: bounds,
255+
layers: [],
256+
worldCopyJump: {{this.world_copy_jump.__str__().lower()}},
257+
crs: L.CRS.{{this.crs}}
258+
});
259+
{% if this.control_scale %}L.control.scale().addTo({{this.get_name()}});{% endif %}
260+
{% endmacro %}
261+
""") # noqa
262+
263+
def _repr_html_(self, **kwargs):
264+
"""Displays the HTML Map in a Jupyter notebook."""
265+
if self._parent is None:
266+
self.add_to(Figure())
267+
out = self._parent._repr_html_(**kwargs)
268+
self._parent = None
269+
else:
270+
out = self._parent._repr_html_(**kwargs)
271+
return out
272+
273+
def _to_png(self):
274+
"""Export the HTML to byte representation of a PNG image."""
275+
if self._png_image is None:
276+
import selenium.webdriver
277+
278+
with tempfile.NamedTemporaryFile(suffix='.html') as f:
279+
fname = f.name
280+
self.save(fname, close_file=False)
281+
driver = selenium.webdriver.PhantomJS(
282+
service_log_path=os.path.devnull
283+
)
284+
driver.get('file://{}'.format(fname))
285+
driver.maximize_window()
286+
# Ignore user map size.
287+
driver.execute_script("document.body.style.width = '100%';") # noqa
288+
# We should probably monitor if some element is present,
289+
# but this is OK for now.
290+
time.sleep(3)
291+
png = driver.get_screenshot_as_png()
292+
driver.quit()
293+
self._png_image = png
294+
return self._png_image
295+
296+
def _repr_png_(self):
297+
"""Displays the PNG Map in a Jupyter notebook."""
298+
# The notebook calls all _repr_*_ by default.
299+
# We don't want that here b/c this one is quite slow.
300+
if not self.png_enabled:
301+
return None
302+
return self._to_png()
303+
304+
def add_tile_layer(self, tiles='OpenStreetMap', name=None,
305+
API_key=None, max_zoom=18, min_zoom=1,
306+
attr=None, active=False,
307+
detect_retina=False, no_wrap=False, subdomains='abc',
308+
**kwargs):
309+
"""
310+
Add a tile layer to the map. See TileLayer for options.
311+
312+
"""
313+
tile_layer = TileLayer(tiles=tiles, name=name,
314+
min_zoom=min_zoom, max_zoom=max_zoom,
315+
attr=attr, API_key=API_key,
316+
detect_retina=detect_retina,
317+
subdomains=subdomains,
318+
no_wrap=no_wrap)
319+
self.add_child(tile_layer, name=tile_layer.tile_name)
320+
321+
def render(self, **kwargs):
322+
"""Renders the HTML representation of the element."""
323+
figure = self.get_root()
324+
assert isinstance(figure, Figure), ('You cannot render this Element '
325+
'if it is not in a Figure.')
326+
327+
# Set global switches
328+
figure.header.add_child(self.global_switches, name='global_switches')
329+
330+
# Import Javascripts
331+
for name, url in _default_js:
332+
figure.header.add_child(JavascriptLink(url), name=name)
333+
334+
# Import Css
335+
for name, url in _default_css:
336+
figure.header.add_child(CssLink(url), name=name)
337+
338+
figure.header.add_child(Element(
339+
'<style>html, body {'
340+
'width: 100%;'
341+
'height: 100%;'
342+
'margin: 0;'
343+
'padding: 0;'
344+
'}'
345+
'</style>'), name='css_style')
346+
347+
figure.header.add_child(Element(
348+
'<style>#map {'
349+
'position:absolute;'
350+
'top:0;'
351+
'bottom:0;'
352+
'right:0;'
353+
'left:0;'
354+
'}'
355+
'</style>'), name='map_style')
356+
357+
super(Map, self).render(**kwargs)
358+
111359
def fit_bounds(self, bounds, padding_top_left=None,
112360
padding_bottom_right=None, padding=None, max_zoom=None):
113361
"""Fit the map to contain a bounding box with the

0 commit comments

Comments
 (0)