Skip to content

Commit 45d81cf

Browse files
authored
check coordinates (#679)
1 parent 9a7d65c commit 45d81cf

6 files changed

Lines changed: 83 additions & 30 deletions

File tree

folium/features.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +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, Popup
20+
from folium.utilities import _validate_coordinates, _validate_location
2021

2122
from jinja2 import Template
2223

@@ -133,7 +134,10 @@ class RegularPolygonMarker(Marker):
133134
def __init__(self, location, color='black', opacity=1, weight=2,
134135
fill_color='blue', fill_opacity=1,
135136
number_of_sides=4, rotation=0, radius=15, popup=None):
136-
super(RegularPolygonMarker, self).__init__(location, popup=popup)
137+
super(RegularPolygonMarker, self).__init__(
138+
_locations_tolist(location),
139+
popup=popup
140+
)
137141
self._name = 'RegularPolygonMarker'
138142
self.color = color
139143
self.opacity = opacity
@@ -819,7 +823,10 @@ class Circle(Marker):
819823
"""
820824
def __init__(self, location, radius=500, color='black',
821825
fill_color='black', fill_opacity=0.6, popup=None):
822-
super(Circle, self).__init__(location, popup=popup)
826+
super(Circle, self).__init__(
827+
_validate_location(location),
828+
popup=popup
829+
)
823830
self._name = 'Circle'
824831
self.radius = radius
825832
self.color = color
@@ -928,7 +935,10 @@ def __init__(self, bounds, color='black', weight=1, fill_color='black',
928935
... )
929936
930937
"""
931-
super(RectangleMarker, self).__init__(bounds, popup=popup)
938+
super(RectangleMarker, self).__init__(
939+
_validate_coordinates(bounds),
940+
popup=popup
941+
)
932942
self._name = 'RectangleMarker'
933943
self.color = color
934944
self.weight = weight
@@ -986,10 +996,7 @@ def __init__(self, locations, color='black', weight=1, fill_color='black',
986996
... fill_opacity=0.5, popup='Tokyo, Japan'))
987997
988998
"""
989-
super(PolygonMarker, self).__init__(
990-
_locations_tolist(locations),
991-
popup=popup
992-
)
999+
super(PolygonMarker, self).__init__(locations, popup=popup)
9931000
self._name = 'PolygonMarker'
9941001
self.color = color
9951002
self.weight = weight
@@ -1089,7 +1096,7 @@ def __init__(self, locations, color=None, weight=None,
10891096
opacity=None, popup=None):
10901097
super(PolyLine, self).__init__()
10911098
self._name = 'PolyLine'
1092-
self.data = _locations_tolist(locations)
1099+
self.data = _validate_coordinates(locations)
10931100
self.color = color
10941101
self.weight = weight
10951102
self.opacity = opacity

folium/map.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from branca.element import CssLink, Element, Figure, Html, JavascriptLink, MacroElement # noqa
2121
from branca.utilities import _parse_size
2222

23+
from folium.utilities import _validate_coordinates, _validate_location
24+
2325
from jinja2 import Environment, PackageLoader, Template
2426

2527
from six import binary_type, text_type
@@ -61,24 +63,6 @@
6163
]
6264

6365

64-
def _validate_location(location):
65-
"""Validates and formats location values before setting"""
66-
if type(location) not in [list, tuple]:
67-
raise TypeError('Expected tuple/list for location, got '
68-
'{!r}'.format(location))
69-
70-
if len(location) != 2:
71-
raise ValueError('Expected two values for location [lat, lon], '
72-
'got {}'.format(len(location)))
73-
74-
try:
75-
location = [float(val) for val in location]
76-
except:
77-
raise ValueError('Location values must be valid numeric values, '
78-
'got {!r}'.format(location))
79-
return location
80-
81-
8266
class LegacyMap(MacroElement):
8367
"""Create a Map with Folium and Leaflet.js
8468
@@ -668,11 +652,14 @@ class Marker(MacroElement):
668652
--------
669653
>>> Marker(location=[45.5, -122.3], popup='Portland, OR')
670654
>>> Marker(location=[45.5, -122.3], popup=folium.Popup('Portland, OR'))
655+
671656
"""
672657
def __init__(self, location, popup=None, icon=None):
673658
super(Marker, self).__init__()
674659
self._name = 'Marker'
675-
self.location = location
660+
# Must be _validate_coordinates b/c some markers are defined with
661+
# multiple coordinates values, like Polygons.
662+
self.location = _validate_coordinates(location)
676663
if icon is not None:
677664
self.add_child(icon)
678665
if isinstance(popup, text_type) or isinstance(popup, binary_type):

folium/plugins/boat_marker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from branca.element import Figure, JavascriptLink
88

99
from folium.map import Marker
10+
from folium.utilities import _validate_location
1011

1112
from jinja2 import Template
1213

@@ -35,7 +36,11 @@ class BoatMarker(Marker):
3536
"""
3637
def __init__(self, location, popup=None, icon=None,
3738
heading=0, wind_heading=None, wind_speed=0, **kwargs):
38-
super(BoatMarker, self).__init__(location, popup=popup, icon=icon)
39+
super(BoatMarker, self).__init__(
40+
_validate_location(location),
41+
popup=popup,
42+
icon=icon
43+
)
3944
self._name = 'BoatMarker'
4045
self.heading = heading
4146
self.wind_heading = wind_heading

folium/plugins/fast_marker_cluster.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import (absolute_import, division, print_function)
44

55
from folium.plugins.marker_cluster import MarkerCluster
6+
from folium.utilities import _validate_coordinates
67

78
from jinja2 import Template
89

@@ -34,7 +35,7 @@ class FastMarkerCluster(MarkerCluster):
3435
def __init__(self, data, callback=None):
3536
super(FastMarkerCluster, self).__init__([])
3637
self._name = 'FastMarkerCluster'
37-
self._data = data
38+
self._data = _validate_coordinates(data)
3839

3940
if callback is None:
4041
self._callback = ('var callback;\n' +

folium/plugins/heat_map.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from branca.utilities import none_max, none_min
99

1010
from folium.map import TileLayer
11+
from folium.utilities import _isnan
1112

1213
from jinja2 import Template
1314

@@ -41,9 +42,11 @@ class HeatMap(TileLayer):
4142
def __init__(self, data, name=None, min_opacity=0.5, max_zoom=18,
4243
max_val=1.0, radius=25, blur=15, gradient=None, overlay=True):
4344
super(TileLayer, self).__init__(name=name)
45+
if _isnan(data):
46+
raise ValueError('data cannot contain NaNs, '
47+
'got:\n{!r}'.format(data))
4448
self._name = 'HeatMap'
4549
self.tile_name = name if name is not None else self.get_name()
46-
4750
self.data = [[x for x in line] for line in data]
4851
self.min_opacity = min_opacity
4952
self.max_zoom = max_zoom

folium/utilities.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import (absolute_import, division, print_function)
2+
3+
import math
4+
5+
6+
def _validate_location(location):
7+
"""Validates and formats location values before setting."""
8+
if _isnan(location):
9+
raise ValueError('Location values cannot contain NaNs, '
10+
'got {!r}'.format(location))
11+
if type(location) not in [list, tuple]:
12+
raise TypeError('Expected tuple/list for location, got '
13+
'{!r}'.format(location))
14+
15+
if len(location) != 2:
16+
raise ValueError('Expected two values for location [lat, lon], '
17+
'got {}'.format(len(location)))
18+
location = _locations_tolist(location)
19+
return location
20+
21+
22+
def _validate_coordinates(coordinates):
23+
"""Validates multiple coordinates for the various markers in folium."""
24+
if _isnan(coordinates):
25+
raise ValueError('Location values cannot contain NaNs, '
26+
'got:\n{!r}'.format(coordinates))
27+
coordinates = _locations_tolist(coordinates)
28+
return coordinates
29+
30+
31+
def _locations_tolist(x):
32+
"""Transforms recursively a list of iterables into a list of list."""
33+
if hasattr(x, '__iter__'):
34+
return list(map(_locations_tolist, x))
35+
else:
36+
return x
37+
38+
39+
def _flatten(container):
40+
for i in container:
41+
if isinstance(i, (list, tuple)):
42+
for j in _flatten(i):
43+
yield j
44+
else:
45+
yield i
46+
47+
48+
def _isnan(values):
49+
"""Check if there are NaNs values in the iterable."""
50+
return any(math.isnan(value) for value in _flatten(values))

0 commit comments

Comments
 (0)