Skip to content

Commit 01ec8aa

Browse files
author
Martin Journois
committed
GeoJsonStyle->kwarg GeoJson.style_function, Map.geo_json->Map.choropleth
1 parent a7365bb commit 01ec8aa

6 files changed

Lines changed: 114 additions & 151 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ nosetests.xml
4444
#Virtualenv
4545
ENV
4646
.env
47+
48+
# Tests products
49+
.cache
50+

folium/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
Popup, TileLayer)
1111

1212
from folium.features import (ClickForMarker, ColorScale, CustomIcon, DivIcon,
13-
GeoJson, GeoJsonStyle, ImageOverlay, LatLngPopup,
13+
GeoJson, ImageOverlay, LatLngPopup,
1414
MarkerCluster, MultiPolyLine, PolyLine, Vega,
1515
RegularPolygonMarker, TopoJson, WmsTileLayer)
1616

folium/features.py

Lines changed: 34 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -166,20 +166,22 @@ def render(self, **kwargs):
166166

167167

168168
class GeoJson(MacroElement):
169-
def __init__(self, data):
169+
def __init__(self, data, style_function=None):
170170
"""
171171
Creates a GeoJson plugin to append into a map with
172172
Map.add_plugin.
173173
174174
Parameters
175175
----------
176176
data: file, dict or str.
177-
The GeoJSON data you want to plot.
178-
* If file, then data will be read in the file and fully
179-
embedded in Leaflet's JavaScript.
180-
* If dict, then data will be converted to JSON and embedded
181-
in the JavaScript.
182-
* If str, then data will be passed to the JavaScript as-is.
177+
The GeoJSON data you want to plot.
178+
* If file, then data will be read in the file and fully
179+
embedded in Leaflet's JavaScript.
180+
* If dict, then data will be converted to JSON and embedded
181+
in the JavaScript.
182+
* If str, then data will be passed to the JavaScript as-is.
183+
style_function: function, default None
184+
A function mapping a GeoJson Feature to a style dict.
183185
184186
Examples
185187
--------
@@ -190,22 +192,43 @@ def __init__(self, data):
190192
>>> # Providing string.
191193
>>> GeoJson(open('foo.json').read())
192194
195+
>>> # Providing a style_function that put all states in green, but Alabama in blue.
196+
>>> style_function=lambda x: {'fillColor': '#0000ff' if x['properties']['name']=='Alabama' else '#00ff00'}
197+
>>> GeoJson(geojson, style_function=style_function)
193198
"""
194199
super(GeoJson, self).__init__()
195200
self._name = 'GeoJson'
196201
if 'read' in dir(data):
197-
self.data = data.read()
202+
self.data = json.load(data)
198203
elif type(data) is dict:
199-
self.data = json.dumps(data)
200-
else:
201204
self.data = data
205+
else:
206+
self.data = json.loads(data)
207+
208+
if 'features' not in self.data.keys():
209+
# Catch case when GeoJSON is just a single Feature or a geometry.
210+
if not (isinstance(self.data, dict) and 'geometry' in self.data.keys()):
211+
# Catch case when GeoJSON is just a geometry.
212+
self.data = {'type' : 'Feature', 'geometry' : self.data}
213+
self.data = {'type' : 'FeatureCollection', 'features' : [self.data]}
214+
215+
if style_function is None:
216+
style_function = lambda x: {}
217+
self.style_function = style_function
202218

203219
self._template = Template(u"""
204220
{% macro script(this, kwargs) %}
205-
var {{this.get_name()}} = L.geoJson({{this.data}}).addTo({{this._parent.get_name()}});
221+
var {{this.get_name()}} = L.geoJson({{this.style_data()}})
222+
.addTo({{this._parent.get_name()}})
223+
.setStyle(function(feature) {return feature.properties.style;});
206224
{% endmacro %}
207225
""") # noqa
208226

227+
def style_data(self):
228+
for feature in self.data['features']:
229+
feature.setdefault('properties',{}).setdefault('style',{}).update(
230+
self.style_function(feature))
231+
return json.dumps(self.data)
209232

210233
class TopoJson(MacroElement):
211234
def __init__(self, data, object_path):
@@ -245,81 +268,6 @@ def render(self, **kwargs):
245268
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"), # noqa
246269
name='topojson')
247270

248-
249-
class GeoJsonStyle(MacroElement):
250-
def __init__(self, color_domain, color_code, color_data=None,
251-
key_on='feature.properties.color',
252-
weight=1, opacity=1, color='black',
253-
fill_opacity=0.6, dash_array=0):
254-
"""
255-
TODO docstring here
256-
257-
"""
258-
super(GeoJsonStyle, self).__init__()
259-
self._name = 'GeoJsonStyle'
260-
261-
self.color_domain = color_domain
262-
self.color_range = color_brewer(color_code, n=len(color_domain))
263-
self.color_data = json.dumps(color_data)
264-
self.key_on = key_on
265-
266-
self.weight = weight
267-
self.opacity = opacity
268-
self.color = color
269-
self.fill_color = color_code
270-
self.fill_opacity = fill_opacity
271-
self.dash_array = dash_array
272-
273-
self._template = Template(u"""
274-
{% macro script(this, kwargs) %}
275-
{% if not this.color_range %}
276-
var {{this.get_name()}} = {
277-
color_function : function(feature) {
278-
return '{{this.fill_color}}';
279-
},
280-
};
281-
{%else%}
282-
var {{this.get_name()}} = {
283-
color_scale : d3.scale.threshold()
284-
.domain({{this.color_domain}})
285-
.range({{this.color_range}}),
286-
color_data : {{this.color_data}},
287-
color_function : function(feature) {
288-
{% if this.color_data=='null' %}
289-
return this.color_scale({{this.key_on}});
290-
{% else %}
291-
return
292-
this.color_scale(this.color_data[{{this.key_on}}]);
293-
{% endif %}
294-
},
295-
};
296-
{%endif%}
297-
298-
{{this._parent.get_name()}}.setStyle(function(feature) {
299-
return {
300-
fillColor: {{this.get_name()}}.color_function(feature),
301-
weight: {{this.weight}},
302-
opacity: {{this.opacity}},
303-
color: '{{this.color}}',
304-
fillOpacity: {{this.fill_opacity}},
305-
dashArray: '{{this.dash_array}}'
306-
};
307-
});
308-
{% endmacro %}
309-
""")
310-
311-
def render(self, **kwargs):
312-
super(GeoJsonStyle, self).render(**kwargs)
313-
314-
figure = self.get_root()
315-
assert isinstance(figure, Figure), ("You cannot render this Element "
316-
"if it's not in a Figure.")
317-
318-
figure.header.add_children(
319-
JavascriptLink("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"), # noqa
320-
name='d3')
321-
322-
323271
class ColorScale(MacroElement):
324272
def __init__(self, color_domain, color_code, caption=""):
325273
"""
@@ -477,7 +425,6 @@ def __init__(self, location, radius=500, color='black',
477425
{% endmacro %}
478426
""")
479427

480-
481428
class LatLngPopup(MacroElement):
482429
def __init__(self):
483430
"""

folium/folium.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .map import Map as _Map
1717
from .map import Icon, Marker, Popup, FitBounds
1818
from .features import (WmsTileLayer, RegularPolygonMarker, Vega, GeoJson,
19-
GeoJsonStyle, CircleMarker, LatLngPopup,
19+
CircleMarker, LatLngPopup,
2020
ClickForMarker, ColorScale, TopoJson, PolyLine,
2121
MultiPolyLine, ImageOverlay)
2222
from .utilities import color_brewer, write_png
@@ -442,10 +442,17 @@ def add_plugin(self, plugin):
442442
#
443443
# self.template_vars.update({'fit_bounds': fit_bounds_str.strip()})
444444

445-
def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
445+
def geo_json(self, *args, **kwargs):
446+
"""This method is deprecated and will be removed in v0.2.1. See
447+
`Map.choropleth` instead.
448+
"""
449+
warnings.warn('This method is deprecated. Please use Map.choropleth instead.')
450+
return self.choropleth(*args, **kwargs)
451+
452+
def choropleth(self, geo_path=None, geo_str=None, data_out='data.json',
446453
data=None, columns=None, key_on=None, threshold_scale=None,
447454
fill_color='blue', fill_opacity=0.6, line_color='black',
448-
line_weight=1, line_opacity=1, legend_name=None,
455+
line_weight=1, line_opacity=1, legend_name="",
449456
topojson=None, reset=False):
450457
"""Apply a GeoJSON overlay to the map.
451458
@@ -506,8 +513,8 @@ def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
506513
GeoJSON geopath line weight.
507514
line_opacity: float, default 1
508515
GeoJSON geopath line opacity, range 0-1.
509-
legend_name: string, default None
510-
Title for data legend. If not passed, defaults to columns[1].
516+
legend_name: string, default empty string
517+
Title for data legend.
511518
topojson: string, default None
512519
If using a TopoJSON, passing "objects.yourfeature" to the topojson
513520
keyword argument will enable conversion to GeoJSON.
@@ -520,17 +527,14 @@ def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
520527
521528
Example
522529
-------
523-
>>> m.geo_json(geo_path='us-states.json', line_color='blue',
530+
>>> m.choropleth(geo_path='us-states.json', line_color='blue',
524531
line_weight=3)
525-
>>> m.geo_json(geo_path='geo.json', data=df,
532+
>>> m.choropleth(geo_path='geo.json', data=df,
526533
columns=['Data 1', 'Data 2'],
527534
key_on='feature.properties.myvalue', fill_color='PuBu',
528535
threshold_scale=[0, 20, 30, 40, 50, 60])
529-
>>> m.geo_json(geo_path='countries.json', topojson='objects.countries')
536+
>>> m.choropleth(geo_path='countries.json', topojson='objects.countries')
530537
"""
531-
warnings.warn("%s is deprecated. Use %s instead" %
532-
("geo_json", "add_children(GeoJson)"),
533-
FutureWarning, stacklevel=2)
534538

535539
if threshold_scale and len(threshold_scale) > 6:
536540
raise ValueError
@@ -546,11 +550,6 @@ def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
546550
else:
547551
geo_data = {}
548552

549-
if topojson:
550-
geo_json = TopoJson(geo_data, topojson)
551-
else:
552-
geo_json = GeoJson(geo_data)
553-
554553
# Create color_data dict
555554
if hasattr(data, 'set_index'):
556555
# This is a pd.DataFrame
@@ -587,20 +586,38 @@ def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
587586
color_domain = [data_min+i*(data_max-data_min)*1./nb_class
588587
for i in range(1+nb_class)]
589588
else:
590-
color_domain = [-1, 1]
589+
color_domain = None
590+
591+
if color_domain and key_on:
592+
key_on = key_on[8:] if key_on.startswith('feature.') else key_on
593+
color_range = color_brewer(fill_color, n=len(color_domain))
594+
get_by_key = lambda obj,key : (obj.get(key,None) if len(key.split('.'))<=1
595+
else get_by_key(obj.get(key.split('.')[0],None),
596+
'.'.join(key.split('.')[1:])))
597+
color_scale_fun = lambda x: color_range[len([u for u in color_domain
598+
if u<=color_data[get_by_key(x,key_on)]])]
599+
else:
600+
color_scale_fun = lambda x: fill_color
591601

592-
# Create GeoJsonStyle.
593-
geo_json_style = GeoJsonStyle(
594-
color_domain, fill_color, color_data=color_data, key_on=key_on,
595-
weight=line_weight, opacity=line_opacity, color=line_color,
596-
fill_opacity=fill_opacity)
602+
style_function = lambda x: {
603+
"weight" : line_weight,
604+
"opactiy": line_opacity,
605+
"color" : line_color,
606+
"fillOpacity" : fill_opacity,
607+
"fillColor" : color_scale_fun(x)
608+
}
597609

598-
# Create ColorScale
599-
color_scale = ColorScale(color_domain, fill_color, caption=legend_name)
610+
if topojson:
611+
geo_json = TopoJson(geo_data, topojson)
612+
else:
613+
geo_json = GeoJson(geo_data, style_function=style_function)
600614

601-
geo_json.add_children(geo_json_style)
602615
self.add_children(geo_json)
603-
self.add_children(color_scale)
616+
617+
# Create ColorScale.
618+
if color_domain:
619+
color_scale = ColorScale(color_domain, fill_color, caption=legend_name)
620+
self.add_children(color_scale)
604621

605622
def image_overlay(self, data, opacity=0.25, min_lat=-90.0, max_lat=90.0,
606623
min_lon=-180.0, max_lon=180.0, origin='upper',

tests/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Tests products
2+
data.png
3+
map.html

0 commit comments

Comments
 (0)