Skip to content

Commit b987946

Browse files
committed
Merge pull request #305 from BibMartin/bounds
Bounds
2 parents 258c1b9 + 4575967 commit b987946

17 files changed

+364
-36
lines changed

folium/element.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import base64
1414

1515
from .six import urlopen, text_type, binary_type
16-
from .utilities import _camelify, _parse_size
16+
from .utilities import _camelify, _parse_size, none_min, none_max
1717

1818

1919
ENV = Environment(loader=PackageLoader('folium', 'templates'))
@@ -40,6 +40,32 @@ def __init__(self, template=None, template_name=None):
4040
def get_name(self):
4141
return _camelify(self._name) + '_' + self._id
4242

43+
def _get_self_bounds(self):
44+
"""Computes the bounds of the object itself (not including it's children)
45+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
46+
"""
47+
return [[None,None],[None,None]]
48+
49+
def get_bounds(self):
50+
"""Computes the bounds of the object and all it's children
51+
in the form [[lat_min, lon_min], [lat_max, lon_max]].
52+
"""
53+
bounds = self._get_self_bounds()
54+
55+
for child in self._children.values():
56+
child_bounds = child.get_bounds()
57+
bounds = [
58+
[
59+
none_min(bounds[0][0], child_bounds[0][0]),
60+
none_min(bounds[0][1], child_bounds[0][1]),
61+
],
62+
[
63+
none_max(bounds[1][0], child_bounds[1][0]),
64+
none_max(bounds[1][1], child_bounds[1][1]),
65+
],
66+
]
67+
return bounds
68+
4369
def add_children(self, child, name=None, index=None):
4470
"""Add a children."""
4571
if name is None:

folium/features.py

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
from .utilities import (color_brewer, _parse_size, legend_scaler,
1212
_locations_mirror, _locations_tolist, image_to_url,
13-
text_type, binary_type)
13+
text_type, binary_type,
14+
none_min, none_max, iter_points,
15+
)
1416

1517
from .element import Element, Figure, JavascriptLink, CssLink, MacroElement
1618
from .map import Layer, Icon, Marker, Popup
@@ -126,7 +128,6 @@ def render(self, **kwargs):
126128
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.2/leaflet-dvf.markers.min.js"), # noqa
127129
name='dvf_js')
128130

129-
130131
class Vega(Element):
131132
def __init__(self, data, width=None, height=None,
132133
left="0%", top="0%", position='relative'):
@@ -277,6 +278,35 @@ def style_data(self):
277278
self.style_function(feature))
278279
return json.dumps(self.data)
279280

281+
def _get_self_bounds(self):
282+
"""Computes the bounds of the object itself (not including it's children)
283+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
284+
"""
285+
if not self.embed:
286+
raise ValueError('Cannot compute bounds of non-embedded GeoJSON.')
287+
288+
if 'features' not in self.data.keys():
289+
# Catch case when GeoJSON is just a single Feature or a geometry.
290+
if not (isinstance(self.data, dict) and 'geometry' in self.data.keys()):
291+
# Catch case when GeoJSON is just a geometry.
292+
self.data = {'type' : 'Feature', 'geometry' : self.data}
293+
self.data = {'type' : 'FeatureCollection', 'features' : [self.data]}
294+
295+
bounds = [[None,None],[None,None]]
296+
for feature in self.data['features']:
297+
for point in iter_points(feature.get('geometry',{}).get('coordinates',{})):
298+
bounds = [
299+
[
300+
none_min(bounds[0][0], point[1]),
301+
none_min(bounds[0][1], point[0]),
302+
],
303+
[
304+
none_max(bounds[1][0], point[1]),
305+
none_max(bounds[1][1], point[0]),
306+
],
307+
]
308+
return bounds
309+
280310
class TopoJson(MacroElement):
281311
def __init__(self, data, object_path):
282312
"""
@@ -286,10 +316,13 @@ def __init__(self, data, object_path):
286316
super(TopoJson, self).__init__()
287317
self._name = 'TopoJson'
288318
if 'read' in dir(data):
319+
self.embed = True
289320
self.data = data.read()
290321
elif type(data) is dict:
322+
self.embed = True
291323
self.data = json.dumps(data)
292324
else:
325+
self.embed = False
293326
self.data = data
294327

295328
self.object_path = object_path
@@ -315,6 +348,38 @@ def render(self, **kwargs):
315348
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"), # noqa
316349
name='topojson')
317350

351+
def _get_self_bounds(self):
352+
"""Computes the bounds of the object itself (not including it's children)
353+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
354+
"""
355+
if not self.embed:
356+
raise ValueError('Cannot compute bounds of non-embedded TopoJSON.')
357+
358+
data = json.loads(self.data)
359+
360+
xmin,xmax,ymin,ymax = None, None, None, None
361+
362+
for arc in data['arcs']:
363+
x,y = 0,0
364+
for dx, dy in arc:
365+
x += dx
366+
y += dy
367+
xmin = none_min(x, xmin)
368+
xmax = none_max(x, xmax)
369+
ymin = none_min(y, ymin)
370+
ymax = none_max(y, ymax)
371+
return [
372+
[
373+
data['transform']['translate'][0] + data['transform']['scale'][0] * xmin,
374+
data['transform']['translate'][1] + data['transform']['scale'][1] * ymin,
375+
],
376+
[
377+
data['transform']['translate'][0] + data['transform']['scale'][0] * xmax,
378+
data['transform']['translate'][1] + data['transform']['scale'][1] * ymax,
379+
]
380+
381+
]
382+
318383
class ColorScale(MacroElement):
319384
def __init__(self, color_domain, color_code, caption=""):
320385
"""
@@ -344,7 +409,6 @@ def render(self, **kwargs):
344409
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"), # noqa
345410
name='d3')
346411

347-
348412
class MarkerCluster(Layer):
349413
"""Adds a MarkerCluster layer on the map."""
350414
def __init__(self, overlay=True, control=True):
@@ -569,6 +633,23 @@ def __init__(self, locations, color=None, weight=None,
569633
{% endmacro %}
570634
""") # noqa
571635

636+
def _get_self_bounds(self):
637+
"""Computes the bounds of the object itself (not including it's children)
638+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
639+
"""
640+
bounds = [[None, None], [None, None]]
641+
for point in iter_points(self.data):
642+
bounds = [
643+
[
644+
none_min(bounds[0][0], point[0]),
645+
none_min(bounds[0][1], point[1]),
646+
],
647+
[
648+
none_max(bounds[1][0], point[0]),
649+
none_max(bounds[1][1], point[1]),
650+
],
651+
]
652+
return bounds
572653

573654
class MultiPolyLine(MacroElement):
574655
def __init__(self, locations, color=None, weight=None,
@@ -616,6 +697,23 @@ def __init__(self, locations, color=None, weight=None,
616697
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
617698
{% endmacro %}
618699
""") # noqa
700+
def _get_self_bounds(self):
701+
"""Computes the bounds of the object itself (not including it's children)
702+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
703+
"""
704+
bounds = [[None, None], [None, None]]
705+
for point in iter_points(self.data):
706+
bounds = [
707+
[
708+
none_min(bounds[0][0], point[0]),
709+
none_min(bounds[0][1], point[1]),
710+
],
711+
[
712+
none_max(bounds[1][0], point[0]),
713+
none_max(bounds[1][1], point[1]),
714+
],
715+
]
716+
return bounds
619717

620718
class CustomIcon(Icon):
621719
def __init__(self, icon_image, icon_size=None, icon_anchor=None,

folium/map.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
from .element import Element, Figure, MacroElement, Html
1818

19-
2019
class Map(MacroElement):
2120
def __init__(self, location=None, width='100%', height='100%',
2221
left="0%", top="0%", position='relative',
@@ -243,7 +242,6 @@ def __init__(self, tiles='OpenStreetMap', name=None,
243242
{% endmacro %}
244243
""")
245244

246-
247245
class FeatureGroup(Layer):
248246
def __init__(self, name=None, overlay=True, control=True):
249247
"""
@@ -273,7 +271,6 @@ def __init__(self, name=None, overlay=True, control=True):
273271
{% endmacro %}
274272
""")
275273

276-
277274
class LayerControl(MacroElement):
278275
"""Adds a layer control to the map."""
279276
def __init__(self):
@@ -374,7 +371,6 @@ def __init__(self, color='blue', icon_color='white', icon='info-sign',
374371
{% endmacro %}
375372
""")
376373

377-
378374
class Marker(MacroElement):
379375
def __init__(self, location, popup=None, icon=None):
380376
"""Create a simple stock Leaflet marker on the map, with optional
@@ -421,6 +417,11 @@ def __init__(self, location, popup=None, icon=None):
421417
{% endmacro %}
422418
""")
423419

420+
def _get_self_bounds(self):
421+
"""Computes the bounds of the object itself (not including it's children)
422+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
423+
"""
424+
return [[self.location[0], self.location[1]], [self.location[0], self.location[1]]]
424425

425426
class Popup(Element):
426427
def __init__(self, html=None, max_width=300):
@@ -469,7 +470,6 @@ def render(self, **kwargs):
469470
self._template.render(this=self, kwargs=kwargs)),
470471
name=self.get_name())
471472

472-
473473
class FitBounds(MacroElement):
474474
def __init__(self, bounds, padding_top_left=None,
475475
padding_bottom_right=None, padding=None, max_zoom=None):
@@ -491,7 +491,6 @@ def __init__(self, bounds, padding_top_left=None,
491491
the same value.
492492
max_zoom: int, default None
493493
Maximum zoom to be used.
494-
495494
"""
496495
super(FitBounds, self).__init__()
497496
self._name = 'FitBounds'

folium/plugins/boat_marker.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010
import json
1111
from jinja2 import Template
1212

13-
from folium.element import JavascriptLink, MacroElement, Figure
13+
from folium.element import JavascriptLink, Figure
14+
from folium.map import Marker
1415

15-
16-
class BoatMarker(MacroElement):
16+
class BoatMarker(Marker):
1717
"""Adds a BoatMarker layer on the map."""
18-
def __init__(self, position=None, heading=0,
19-
wind_heading=None, wind_speed=0, **kwargs):
18+
def __init__(self, location, popup=None, icon=None,
19+
heading=0, wind_heading=None, wind_speed=0, **kwargs):
2020
"""Creates a BoatMarker plugin to append into a map with
2121
Map.add_plugin.
2222
2323
Parameters
2424
----------
25-
position: tuple of length 2, default None
25+
location: tuple of length 2, default None
2626
The latitude and longitude of the marker.
2727
If None, then the middle of the map is used.
2828
@@ -36,9 +36,8 @@ def __init__(self, position=None, heading=0,
3636
wind_speed: int, default 0
3737
Speed of the wind in knots.
3838
"""
39-
super(BoatMarker, self).__init__()
39+
super(BoatMarker, self).__init__(location, popup=popup, icon=icon)
4040
self._name = 'BoatMarker'
41-
self.position = None if position is None else tuple(position)
4241
self.heading = heading
4342
self.wind_heading = wind_heading
4443
self.wind_speed = wind_speed
@@ -47,7 +46,7 @@ def __init__(self, position=None, heading=0,
4746
self._template = Template(u"""
4847
{% macro script(this, kwargs) %}
4948
var {{this.get_name()}} = L.boatMarker(
50-
[{{this.position[0]}},{{this.position[1]}}],
49+
[{{this.location[0]}},{{this.location[1]}}],
5150
{{this.kwargs}}).addTo({{this._parent.get_name()}});
5251
{{this.get_name()}}.setHeadingWind({{this.heading}}, {{this.wind_speed}}, {{this.wind_heading}});
5352
{% endmacro %}

folium/plugins/heat_map.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from folium.element import JavascriptLink, Figure
1313
from folium.map import TileLayer
14-
14+
from folium.utilities import none_min, none_max
1515

1616
class HeatMap(TileLayer):
1717
def __init__(self, data, name=None, min_opacity=0.5, max_zoom=18,
@@ -79,3 +79,21 @@ def render(self, **kwargs):
7979
figure.header.add_children(
8080
JavascriptLink("https://leaflet.github.io/Leaflet.heat/dist/leaflet-heat.js"), # noqa
8181
name='leaflet-heat.js')
82+
83+
def _get_self_bounds(self):
84+
"""Computes the bounds of the object itself (not including it's children)
85+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
86+
"""
87+
bounds = [[None,None],[None,None]]
88+
for point in self.data:
89+
bounds = [
90+
[
91+
none_min(bounds[0][0], point[1]),
92+
none_min(bounds[0][1], point[0]),
93+
],
94+
[
95+
none_max(bounds[1][0], point[1]),
96+
none_max(bounds[1][1], point[0]),
97+
],
98+
]
99+
return bounds

folium/plugins/image_overlay.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,9 @@ def __init__(self, image, bounds, opacity=1., attr=None,
136136
).addTo({{this._parent.get_name()}});
137137
{% endmacro %}
138138
""")
139+
140+
def _get_self_bounds(self):
141+
"""Computes the bounds of the object itself (not including it's children)
142+
in the form [[lat_min, lon_min], [lat_max, lon_max]]
143+
"""
144+
return self.bounds

0 commit comments

Comments
 (0)