Skip to content

Commit 6b99292

Browse files
authored
Merge pull request #151 from getcake/heatmap
2 parents ade6679 + 1eb4032 commit 6b99292

File tree

2 files changed

+80
-18
lines changed

2 files changed

+80
-18
lines changed

flask_googlemaps/__init__.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def __init__(
5555
layer="",
5656
map_ids=None,
5757
bicycle_layer=False,
58+
heatmap_data=None,
59+
heatmap_layer=False,
5860
**kwargs
5961
):
6062
# type: (...) -> None
@@ -68,7 +70,7 @@ def __init__(
6870
self.zoom = zoom
6971
self.maptype = maptype
7072
self.markers = [] # type: List[Any]
71-
self.map_ids = map_ids,
73+
self.map_ids = map_ids
7274
self.build_markers(markers)
7375
self.rectangles = [] # type: List[Any]
7476
self.build_rectangles(rectangles)
@@ -102,6 +104,9 @@ def __init__(
102104
self.styles = styles
103105
self.layer = layer
104106
self.bicycle_layer = bicycle_layer
107+
self.heatmap_layer = heatmap_layer
108+
self.heatmap_data = []
109+
self.build_heatmap(heatmap_data, heatmap_layer)
105110

106111
def build_markers(self, markers):
107112
# type: (Optional[Union[Dict, List, Tuple]]) -> None
@@ -149,7 +154,7 @@ def add_marker(self, lat=None, lng=None, **kwargs):
149154

150155
def build_rectangles(self, rectangles):
151156
# type: (Optional[List[Union[List, Tuple, Tuple[Tuple], Dict]]]) -> None
152-
""" Process data to construct rectangles
157+
"""Process data to construct rectangles
153158
154159
This method is built from the assumption that the rectangles parameter
155160
is a list of:
@@ -227,7 +232,7 @@ def build_rectangle_dict(
227232
fill_opacity=0.3, # type: float
228233
):
229234
# type: (...) -> Dict
230-
""" Set a dictionary with the javascript class Rectangle parameters
235+
"""Set a dictionary with the javascript class Rectangle parameters
231236
232237
This function sets a default drawing configuration if the user just
233238
pass the rectangle bounds, but also allows to set each parameter
@@ -260,7 +265,7 @@ def build_rectangle_dict(
260265

261266
def add_rectangle(self, north=None, west=None, south=None, east=None, **kwargs):
262267
# type: (Optional[float], Optional[float], Optional[float], Optional[float], **Any) -> None
263-
""" Adds a rectangle dict to the Map.rectangles attribute
268+
"""Adds a rectangle dict to the Map.rectangles attribute
264269
265270
The Google Maps API describes a rectangle using the LatLngBounds
266271
object, which defines the bounds to be drawn. The bounds use the
@@ -307,7 +312,7 @@ def add_rectangle(self, north=None, west=None, south=None, east=None, **kwargs):
307312

308313
def build_circles(self, circles):
309314
# type: (Optional[List[Union[List, Tuple, Dict]]]) -> None
310-
""" Process data to construct rectangles
315+
"""Process data to construct rectangles
311316
312317
This method is built from the assumption that the circles parameter
313318
is a list of:
@@ -359,7 +364,7 @@ def build_circle_dict(
359364
clickable=True, # type: bool
360365
):
361366
# type: (...) -> Dict
362-
""" Set a dictionary with the javascript class Circle parameters
367+
"""Set a dictionary with the javascript class Circle parameters
363368
364369
This function sets a default drawing configuration if the user just
365370
pass the rectangle bounds, but also allows to set each parameter
@@ -394,7 +399,7 @@ def build_circle_dict(
394399

395400
def add_circle(self, center_lat=None, center_lng=None, radius=None, **kwargs):
396401
# type: (Optional[float], Optional[float], Optional[float], **Any) -> None
397-
""" Adds a circle dict to the Map.circles attribute
402+
"""Adds a circle dict to the Map.circles attribute
398403
399404
The circle in a sphere is called "spherical cap" and is defined in the
400405
Google Maps API by at least the center coordinates and its radius, in
@@ -436,7 +441,7 @@ def add_circle(self, center_lat=None, center_lng=None, radius=None, **kwargs):
436441

437442
def build_polylines(self, polylines):
438443
# type: (Optional[List[Union[List, Tuple, Dict]]]) -> None
439-
""" Process data to construct polylines
444+
"""Process data to construct polylines
440445
441446
This method is built from the assumption that the polylines parameter
442447
is a list of:
@@ -500,7 +505,7 @@ def build_polyline_dict(
500505
self, path, stroke_color="#FF0000", stroke_opacity=0.8, stroke_weight=2
501506
):
502507
# type: (List[Dict], str, float, int) -> Dict
503-
""" Set a dictionary with the javascript class Polyline parameters
508+
"""Set a dictionary with the javascript class Polyline parameters
504509
505510
This function sets a default drawing configuration if the user just
506511
pass the polyline path, but also allows to set each parameter
@@ -532,7 +537,7 @@ def build_polyline_dict(
532537

533538
def add_polyline(self, path=None, **kwargs):
534539
# type: (Optional[List[Dict]], **Any) -> None
535-
""" Adds a polyline dict to the Map.polylines attribute
540+
"""Adds a polyline dict to the Map.polylines attribute
536541
537542
The Google Maps API describes a polyline as a "linear overlay of
538543
connected line segments on the map". The linear paths are defined
@@ -577,7 +582,7 @@ def add_polyline(self, path=None, **kwargs):
577582

578583
def build_polygons(self, polygons):
579584
# type: (Optional[List[Union[List, Tuple, Dict]]]) -> None
580-
""" Process data to construct polygons
585+
"""Process data to construct polygons
581586
582587
This method is built from the assumption that the polygons parameter
583588
is a list of:
@@ -649,7 +654,7 @@ def build_polygon_dict(
649654
fill_opacity=0.3, # type: float
650655
):
651656
# type: (...) -> Dict
652-
""" Set a dictionary with the javascript class Polygon parameters
657+
"""Set a dictionary with the javascript class Polygon parameters
653658
654659
This function sets a default drawing configuration if the user just
655660
pass the polygon path, but also allows to set each parameter
@@ -687,7 +692,7 @@ def build_polygon_dict(
687692

688693
def add_polygon(self, path=None, **kwargs):
689694
# type: (Optional[List[Dict]], **Any) -> None
690-
""" Adds a polygon dict to the Map.polygons attribute
695+
"""Adds a polygon dict to the Map.polygons attribute
691696
692697
The Google Maps API describes a polyline as a "linear overlay of
693698
connected line segments on the map" and "form a closed loop and define
@@ -733,6 +738,37 @@ def add_polygon(self, path=None, **kwargs):
733738

734739
self.polygons.append(kwargs)
735740

741+
def build_heatmap(self, heatmap_data, heatmap_layer):
742+
# type: (list, bool) -> dict
743+
if not heatmap_layer:
744+
return
745+
if heatmap_layer and not heatmap_data:
746+
raise AttributeError("heatmap_later requires 'heatmap_data'")
747+
if not isinstance(heatmap_data, (list)):
748+
raise AttributeError(
749+
"heatmap_data only accepts a list of dicts with keys 'lat' 'lng' and their corresponding values"
750+
)
751+
for hm in heatmap_data:
752+
if isinstance(hm, dict):
753+
self.add_heatmap(**hm)
754+
else:
755+
raise AttributeError(
756+
"elements of list 'heatmap_data' must be a dict of keys 'lat' and 'lng' with their corresponding values"
757+
)
758+
759+
def add_heatmap(self, lat=None, lng=None, **kwargs):
760+
# type: (Optional[float], Optional[float], **Any) -> None
761+
if lat is not None:
762+
kwargs["lat"] = lat
763+
if lng is not None:
764+
kwargs["lng"] = lng
765+
if "lat" not in kwargs or "lng" not in kwargs:
766+
raise AttributeError("heatmap_data requires 'lat' and 'lng' values")
767+
if len(kwargs) > 2:
768+
raise AttributeError("heatmap_data can only contain 'lat' and 'lng' values")
769+
770+
self.heatmap_data.append(kwargs)
771+
736772
def render(self, *args, **kwargs):
737773
# type: (*Any, **Any) -> Text
738774
return render_template(*args, **kwargs)
@@ -773,10 +809,12 @@ def as_json(self):
773809
def verify_lat_lng_coordinates(self, lat, lng):
774810
if not (90 >= lat >= -90):
775811
raise AttributeError(
776-
"Latitude must be between -90 and 90 degrees inclusive.")
812+
"Latitude must be between -90 and 90 degrees inclusive."
813+
)
777814
if not (180 >= lng >= -180):
778815
raise AttributeError(
779-
"Longitude must be between -180 and 180 degrees inclusive.")
816+
"Longitude must be between -180 and 180 degrees inclusive."
817+
)
780818

781819
return (lat, lng)
782820

flask_googlemaps/templates/googlemaps/gmapjs.html

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
{% if not is_googlemaps_loaded() %} {% if GOOGLEMAPS_KEY %}
1+
{% if not is_googlemaps_loaded() and not gmap.heatmap_layer %} {% if GOOGLEMAPS_KEY %}
22
<script src="//maps.googleapis.com/maps/api/js?key={{GOOGLEMAPS_KEY}}&map_ids={{gmap.map_ids}}&language={{gmap.language}}&region={{gmap.region}}" type="text/javascript"></script>
33
{% else %}
44
<script src="//maps.googleapis.com/maps/api/js?language={{gmap.language}}&region={{gmap.region}}" type="text/javascript"></script>
5-
{% endif %} {{ set_googlemaps_loaded() }} {% endif %} {% if gmap.cluster %}
5+
{% endif %} {{ set_googlemaps_loaded() }} {% endif %}{% if gmap.cluster %}
66
<script src="//cdnjs.cloudflare.com/ajax/libs/js-marker-clusterer/1.0.0/markerclusterer_compiled.js" type="text/javascript"></script>
7-
{% endif %}
7+
{% endif %}{% if gmap.heatmap_layer %}{% if GOOGLEMAPS_KEY %}
8+
<script src="//maps.googleapis.com/maps/api/js?key={{GOOGLEMAPS_KEY}}&map_ids={{gmap.map_ids}}&language={{gmap.language}}&region={{gmap.region}}&libraries=visualization" type="text/javascript"></script>
9+
{% endif %}{% endif%}
10+
811

912
<style type="text/css">
1013
#{{gmap.identifier}} { {{gmap.style}} }
@@ -13,6 +16,7 @@
1316
<script type="text/javascript">
1417
var {{gmap.varname}} = null;
1518
var {{gmap.varname}}_markers = [];
19+
var {{gmap.varname}}_heatmap_data = [];
1620
var {{gmap.varname}}_rectangles = [];
1721
var {{gmap.varname}}_circles = [];
1822
var {{gmap.varname}}_polygons = [];
@@ -61,6 +65,21 @@
6165
{% endif %}
6266

6367

68+
// add heatmap layer
69+
{% if gmap.heatmap_layer %}
70+
var heatmapData = [];
71+
var raw_heatmap_data = {{gmap.heatmap_data|tojson|safe}};
72+
for(i=0; i < {{gmap.heatmap_data|length}}; i++) {
73+
pos = new google.maps.LatLng(raw_heatmap_data[i].lat, raw_heatmap_data[i].lng);
74+
heatmapData.push(pos);
75+
}
76+
var heatmap = new google.maps.visualization.HeatmapLayer({
77+
data: heatmapData
78+
});
79+
heatmap.setMap({{gmap.varname}});
80+
81+
{% endif %}
82+
6483
// add gmap markers
6584
var raw_markers = {{gmap.markers|tojson|safe}};
6685
for(i=0; i<{{gmap.markers|length}};i++) {
@@ -72,6 +91,7 @@
7291
label: raw_markers[i].label ? raw_markers[i].label : null
7392
});
7493

94+
7595
if(raw_markers[i].infobox)
7696
{
7797
google.maps.event.addListener(
@@ -80,6 +100,7 @@
80100
getInfoCallback({{gmap.varname}}, raw_markers[i].infobox)
81101
);
82102
}
103+
83104
if("{{gmap.report_markerClickpos}}" === "True" )
84105
{
85106
google.maps.event.addListener(
@@ -89,12 +110,15 @@
89110
);
90111
}
91112
{{gmap.varname}}_markers[i] = tempMarker;
113+
92114
}
93115

116+
94117
{% if gmap.report_clickpos %}
95118
google.maps.event.addListener(
96119
{{gmap.varname}},
97120
'click',
121+
98122
function(event) { clickposCallback('{{gmap.clickpos_uri}}', event.latLng) }
99123
);
100124
{% endif %}

0 commit comments

Comments
 (0)