def __init__(self, data, styledict, name=None, overlay=True, control=True, show=True, init_timestamp_index=0, highlight=True): super(TimeSliderChoropleth, self).__init__(name=name, overlay=overlay, control=control, show=show) self.data = GeoJson.process_data(GeoJson({}), data) if not isinstance(styledict, dict): raise ValueError('styledict must be a dictionary, got {!r}'.format(styledict)) # noqa for val in styledict.values(): if not isinstance(val, dict): raise ValueError('Each item in styledict must be a dictionary, got {!r}'.format(val)) # noqa # Make set of timestamps. timestamps = set() for feature in styledict.values(): timestamps.update(set(feature.keys())) timestamps = sorted(list(timestamps)) self.timestamps = timestamps self.styledict = styledict self.highlight = highlight if init_timestamp_index >= 0: assert init_timestamp_index < len(timestamps), ( 'init_timestamp_index cannot be {} since it is greater than the' ' number of timestamps, which is {}.' ).format(init_timestamp_index, len(timestamps)) else: assert -len(timestamps) <= init_timestamp_index, ( 'When init_timestamp_index is negative, it must be in the range' ' `[-len(timestamps), -1]` but got {} instead.' ).format(init_timestamp_index) self.init_timestamp_index = init_timestamp_index
def __init__(self, data, styledict, name=None, overlay=True, control=True, show=True): super(TimeSliderChoropleth, self).__init__(name=name, overlay=overlay, control=control, show=show) self.data = GeoJson.process_data(GeoJson({}), data) if not isinstance(styledict, dict): raise ValueError('styledict must be a dictionary, got {!r}'.format( styledict)) # noqa for val in styledict.values(): if not isinstance(val, dict): raise ValueError( 'Each item in styledict must be a dictionary, got {!r}'. format(val)) # noqa # Make set of timestamps. timestamps = set() for feature in styledict.values(): timestamps.update(set(feature.keys())) timestamps = sorted(list(timestamps)) self.timestamps = timestamps self.styledict = styledict
def test_json_request(self): """Test requests for remote GeoJSON files.""" self.m = folium.Map(zoom_start=4) # Adding remote GeoJSON as additional layer. GeoJson(remote_url, smooth_factor=0.5).add_to(self.m) self.m._parent.render() bounds = self.m.get_bounds() assert bounds == [[18.948267, -178.123152], [71.351633, 173.304726]], bounds # noqa
def choropleth(self, geo_data, data=None, columns=None, key_on=None, threshold_scale=None, fill_color='blue', fill_opacity=0.6, line_color='black', line_weight=1, line_opacity=1, name=None, legend_name='', topojson=None, reset=False, smooth_factor=None, highlight=None): """ Apply a GeoJSON overlay to the map. Plot a GeoJSON overlay on the base map. There is no requirement to bind data (passing just a GeoJSON plots a single-color overlay), but there is a data binding option to map your columnar data to different feature objects with a color scale. If data is passed as a Pandas DataFrame, the "columns" and "key-on" keywords must be included, the first to indicate which DataFrame columns to use, the second to indicate the layer in the GeoJSON on which to key the data. The 'columns' keyword does not need to be passed for a Pandas series. Colors are generated from color brewer (http://colorbrewer2.org/) sequential palettes on a D3 threshold scale. The scale defaults to the following quantiles: [0, 0.5, 0.75, 0.85, 0.9]. A custom scale can be passed to `threshold_scale` of length <=6, in order to match the color brewer range. TopoJSONs can be passed as "geo_data", but the "topojson" keyword must also be passed with the reference to the topojson objects to convert. See the topojson.feature method in the TopoJSON API reference: https://github.com/topojson/topojson/wiki/API-Reference Parameters ---------- geo_data: string/object URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON geometries data: Pandas DataFrame or Series, default None Data to bind to the GeoJSON. columns: dict or tuple, default None If the data is a Pandas DataFrame, the columns of data to be bound. Must pass column 1 as the key, and column 2 the values. key_on: string, default None Variable in the GeoJSON file to bind the data to. Must always start with 'feature' and be in JavaScript objection notation. Ex: 'feature.id' or 'feature.properties.statename'. threshold_scale: list, default None Data range for D3 threshold scale. Defaults to the following range of quantiles: [0, 0.5, 0.75, 0.85, 0.9], rounded to the nearest order-of-magnitude integer. Ex: 270 rounds to 200, 5600 to 6000. fill_color: string, default 'blue' Area fill color. Can pass a hex code, color name, or if you are binding data, one of the following color brewer palettes: 'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu', 'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'. fill_opacity: float, default 0.6 Area fill opacity, range 0-1. line_color: string, default 'black' GeoJSON geopath line color. line_weight: int, default 1 GeoJSON geopath line weight. line_opacity: float, default 1 GeoJSON geopath line opacity, range 0-1. legend_name: string, default empty string Title for data legend. topojson: string, default None If using a TopoJSON, passing "objects.yourfeature" to the topojson keyword argument will enable conversion to GeoJSON. reset: boolean, default False Remove all current geoJSON layers, start with new layer smooth_factor: float, default None How much to simplify the polyline on each zoom level. More means better performance and smoother look, and less means more accurate representation. Leaflet defaults to 1.0. highlight: boolean, default False Enable highlight functionality when hovering over a GeoJSON area. Returns ------- GeoJSON data layer in obj.template_vars Examples -------- >>> m.choropleth(geo_data='us-states.json', line_color='blue', ... line_weight=3) >>> m.choropleth(geo_data='geo.json', data=df, ... columns=['Data 1', 'Data 2'], ... key_on='feature.properties.myvalue', ... fill_color='PuBu', ... threshold_scale=[0, 20, 30, 40, 50, 60]) >>> m.choropleth(geo_data='countries.json', ... topojson='objects.countries') >>> m.choropleth(geo_data='geo.json', data=df, ... columns=['Data 1', 'Data 2'], ... key_on='feature.properties.myvalue', ... fill_color='PuBu', ... threshold_scale=[0, 20, 30, 40, 50, 60], ... highlight=True) """ if threshold_scale and len(threshold_scale) > 6: raise ValueError if data is not None and not color_brewer(fill_color): raise ValueError('Please pass a valid color brewer code to ' 'fill_local. See docstring for valid codes.') # Create color_data dict if hasattr(data, 'set_index'): # This is a pd.DataFrame color_data = data.set_index(columns[0])[columns[1]].to_dict() elif hasattr(data, 'to_dict'): # This is a pd.Series color_data = data.to_dict() elif data: color_data = dict(data) else: color_data = None # Compute color_domain if threshold_scale: color_domain = list(threshold_scale) elif color_data: # To avoid explicit pandas dependency ; changed default behavior. data_min = min(color_data.values()) data_max = max(color_data.values()) if data_min == data_max: data_min = (data_min if data_min < 0 else 0 if data_min > 0 else -1) data_max = (data_max if data_max > 0 else 0 if data_max < 0 else 1) data_min, data_max = (1.01 * data_min - 0.01 * data_max, 1.01 * data_max - 0.01 * data_min) nb_class = 6 color_domain = [ data_min + i * (data_max - data_min) * 1. / nb_class for i in range(1 + nb_class) ] else: color_domain = None if color_domain and key_on: key_on = key_on[8:] if key_on.startswith('feature.') else key_on color_range = color_brewer(fill_color, n=len(color_domain)) def get_by_key(obj, key): return (obj.get(key, None) if len(key.split('.')) <= 1 else get_by_key(obj.get(key.split('.')[0], None), '.'.join( key.split('.')[1:]))) def color_scale_fun(x): return color_range[len([ u for u in color_domain if get_by_key(x, key_on) in color_data and u <= color_data[get_by_key(x, key_on)] ])] else: def color_scale_fun(x): return fill_color def style_function(x): return { 'weight': line_weight, 'opacity': line_opacity, 'color': line_color, 'fillOpacity': fill_opacity, 'fillColor': color_scale_fun(x) } def highlight_function(x): return { 'weight': line_weight + 2, 'fillOpacity': fill_opacity + .2 } if topojson: geo_json = TopoJson(geo_data, topojson, name=name, style_function=style_function, smooth_factor=smooth_factor) else: geo_json = GeoJson( geo_data, name=name, style_function=style_function, smooth_factor=smooth_factor, highlight_function=highlight_function if highlight else None) self.add_child(geo_json) # Create ColorMap. if color_domain: brewed = color_brewer(fill_color, n=len(color_domain)) color_scale = StepColormap( brewed[1:len(color_domain)], index=color_domain, vmin=color_domain[0], vmax=color_domain[-1], caption=legend_name, ) self.add_child(color_scale)
def choropleth(self, geo_data, data=None, columns=None, key_on=None, bins=6, fill_color='blue', nan_fill_color='black', fill_opacity=0.6, nan_fill_opacity=None, line_color='black', line_weight=1, line_opacity=1, name=None, legend_name='', topojson=None, reset=False, smooth_factor=None, highlight=None, **kwargs): """ Apply a GeoJSON overlay to the map. Plot a GeoJSON overlay on the base map. There is no requirement to bind data (passing just a GeoJSON plots a single-color overlay), but there is a data binding option to map your columnar data to different feature objects with a color scale. If data is passed as a Pandas DataFrame, the "columns" and "key-on" keywords must be included, the first to indicate which DataFrame columns to use, the second to indicate the layer in the GeoJSON on which to key the data. The 'columns' keyword does not need to be passed for a Pandas series. Colors are generated from color brewer (http://colorbrewer2.org/) sequential palettes. By default, linear binning is used between the min and the max of the values. Custom binning can be achieved with the `bins` parameter. TopoJSONs can be passed as "geo_data", but the "topojson" keyword must also be passed with the reference to the topojson objects to convert. See the topojson.feature method in the TopoJSON API reference: https://github.com/topojson/topojson/wiki/API-Reference Parameters ---------- geo_data: string/object URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON geometries data: Pandas DataFrame or Series, default None Data to bind to the GeoJSON. columns: dict or tuple, default None If the data is a Pandas DataFrame, the columns of data to be bound. Must pass column 1 as the key, and column 2 the values. key_on: string, default None Variable in the `geo_data` GeoJSON file to bind the data to. Must start with 'feature' and be in JavaScript objection notation. Ex: 'feature.id' or 'feature.properties.statename'. bins: int or sequence of scalars or str, default 6 If `bins` is an int, it defines the number of equal-width bins between the min and the max of the values. If `bins` is a sequence, it directly defines the bin edges. For more information on this parameter, have a look at numpy.histogram function. fill_color: string, default 'blue' Area fill color. Can pass a hex code, color name, or if you are binding data, one of the following color brewer palettes: 'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu', 'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'. nan_fill_color: string, default 'black' Area fill color for nan or missing values. Can pass a hex code, color name. fill_opacity: float, default 0.6 Area fill opacity, range 0-1. nan_fill_opacity: float, default fill_opacity Area fill opacity for nan or missing values, range 0-1. line_color: string, default 'black' GeoJSON geopath line color. line_weight: int, default 1 GeoJSON geopath line weight. line_opacity: float, default 1 GeoJSON geopath line opacity, range 0-1. legend_name: string, default empty string Title for data legend. topojson: string, default None If using a TopoJSON, passing "objects.yourfeature" to the topojson keyword argument will enable conversion to GeoJSON. reset: boolean, default False Remove all current geoJSON layers, start with new layer smooth_factor: float, default None How much to simplify the polyline on each zoom level. More means better performance and smoother look, and less means more accurate representation. Leaflet defaults to 1.0. highlight: boolean, default False Enable highlight functionality when hovering over a GeoJSON area. Returns ------- GeoJSON data layer in obj.template_vars Examples -------- >>> m.choropleth(geo_data='us-states.json', line_color='blue', ... line_weight=3) >>> m.choropleth(geo_data='geo.json', data=df, ... columns=['Data 1', 'Data 2'], ... key_on='feature.properties.myvalue', ... fill_color='PuBu', ... bins=[0, 20, 30, 40, 50, 60]) >>> m.choropleth(geo_data='countries.json', ... topojson='objects.countries') >>> m.choropleth(geo_data='geo.json', data=df, ... columns=['Data 1', 'Data 2'], ... key_on='feature.properties.myvalue', ... fill_color='PuBu', ... bins=[0, 20, 30, 40, 50, 60], ... highlight=True) """ if data is not None and not color_brewer(fill_color): raise ValueError('Please pass a valid color brewer code to ' 'fill_local. See docstring for valid codes.') if nan_fill_opacity is None: nan_fill_opacity = fill_opacity if 'threshold_scale' in kwargs: if kwargs['threshold_scale'] is not None: bins = kwargs['threshold_scale'] warnings.warn( 'choropleth `threshold_scale` parameter is now depreciated ' 'in favor of the `bins` parameter.', DeprecationWarning) # Create color_data dict if hasattr(data, 'set_index'): # This is a pd.DataFrame color_data = data.set_index(columns[0])[columns[1]].to_dict() elif hasattr(data, 'to_dict'): # This is a pd.Series color_data = data.to_dict() elif data: color_data = dict(data) else: color_data = None if color_data is not None and key_on is not None: real_values = np.array(list(color_data.values())) real_values = real_values[~np.isnan(real_values)] _, bin_edges = np.histogram(real_values, bins=bins) bins_min, bins_max = min(bin_edges), max(bin_edges) if np.any((real_values < bins_min) | (real_values > bins_max)): raise ValueError( 'All values are expected to fall into one of the provided ' 'bins (or to be Nan). Please check the `bins` parameter ' 'and/or your data.') # We add the colorscale nb_bins = len(bin_edges) - 1 color_range = color_brewer(fill_color, n=nb_bins) color_scale = StepColormap(color_range, index=bin_edges, vmin=bins_min, vmax=bins_max, caption=legend_name) self.add_child(color_scale) # then we 'correct' the last edge for numpy digitize # (we add a very small amount to fake an inclusive right interval) increasing = bin_edges[0] <= bin_edges[-1] bin_edges[-1] = np.nextafter(bin_edges[-1], (1 if increasing else -1) * np.inf) key_on = key_on[8:] if key_on.startswith('feature.') else key_on def get_by_key(obj, key): return (obj.get(key, None) if len(key.split('.')) <= 1 else get_by_key(obj.get(key.split('.')[0], None), '.'.join( key.split('.')[1:]))) def color_scale_fun(x): key_of_x = get_by_key(x, key_on) if key_of_x not in color_data.keys(): return nan_fill_color, nan_fill_opacity value_of_x = color_data[key_of_x] if np.isnan(value_of_x): return nan_fill_color, nan_fill_opacity color_idx = np.digitize(value_of_x, bin_edges, right=False) - 1 return color_range[color_idx], fill_opacity else: def color_scale_fun(x): return fill_color, fill_opacity def style_function(x): color, opacity = color_scale_fun(x) return { 'weight': line_weight, 'opacity': line_opacity, 'color': line_color, 'fillOpacity': opacity, 'fillColor': color } def highlight_function(x): return { 'weight': line_weight + 2, 'fillOpacity': fill_opacity + .2 } if topojson: geo_json = TopoJson(geo_data, topojson, name=name, style_function=style_function, smooth_factor=smooth_factor) else: geo_json = GeoJson( geo_data, name=name, style_function=style_function, smooth_factor=smooth_factor, highlight_function=highlight_function if highlight else None) self.add_child(geo_json)