def folium_add_map_title(title: str, folium_map: folium.Map): """Adds a map title""" html = ''' <div style=”position: fixed; bottom: 50px; left: 50px; width: 100px; height: 90px; border:2px solid grey; z-index:9999; font-size:10pt; “>{}</div>'''.format(title) folium_map.get_root().html.add_child(folium.Element(html))
def _save_map_to_png(m: folium.Map, filepath='mymap', delay=3): """ Saves a screenshot of a folium.Map object as a png. Similar to folium's existing m._to_png but saves to disk. WARNING - This is a private method because it required a non-essential dependency - you need to install geckodriver - see README for details. """ try: options = webdriver.firefox.options.Options() options.add_argument('--headless') driver = webdriver.Firefox(options=options) except: raise AssertionError( 'You need to install geckodriver - see README for details') html = m.get_root().render() with _tmp_html(html) as fname: # We need the tempfile to avoid JS security issues. driver.get(f'file:///{fname}') driver.maximize_window() time.sleep(delay) driver.save_screenshot(filepath) driver.quit()
def write_file(self, f: BinaryIO, m: Map): html: str = m.get_root().render() f.write(html)
def write_file(self, f: BinaryIO, m: Map): html: str = m.get_root().render() html_b64: bytes = base64.b64encode(html.encode()) f.write(html_b64)
def add_map_legend(m: Map, title: str, items: tuple | Sequence[tuple]): """ Adds a legend for a folium map. Parameters ---------- m : Map Represents a folium map. title : str Represents the title of the legend items : list of tuple Represents the color and name of the legend items References ---------- https://github.com/python-visualization/folium/issues/528#issuecomment-421445303 Examples -------- >>> import folium >>> from pymove.utils.visual import add_map_legend >>> df lat lon datetime id 0 39.984094 116.319236 2008-10-23 05:53:05 1 1 39.984198 116.319322 2008-10-23 05:53:06 1 2 39.984224 116.319402 2008-10-23 05:53:11 1 3 39.984211 116.319389 2008-10-23 05:53:16 2 4 39.984217 116.319422 2008-10-23 05:53:21 2 >>> m = folium.Map(location=[df.lat.median(), df.lon.median()]) >>> folium.PolyLine(mdf[['lat', 'lon']], color='red').add_to(m) >>> pm.visual.add_map_legend(m, 'Color by ID', [(1, 'red')]) >>> m.get_root().to_dict() { "name": "Figure", "id": "1d32230cd6c54b19b35ceaa864e61168", "children": { "map_6f1abc8eacee41e8aa9d163e6bbb295f": { "name": "Map", "id": "6f1abc8eacee41e8aa9d163e6bbb295f", "children": { "openstreetmap": { "name": "TileLayer", "id": "f58c3659fea348cb828775f223e1e6a4", "children": {} }, "poly_line_75023fd7df01475ea5e5606ddd7f4dd2": { "name": "PolyLine", "id": "75023fd7df01475ea5e5606ddd7f4dd2", "children": {} } } }, "map_legend": { # legend element "name": "MacroElement", "id": "72911b4418a94358ba8790aab93573d1", "children": {} } }, "header": { "name": "Element", "id": "e46930fc4152431090b112424b5beb6a", "children": { "meta_http": { "name": "Element", "id": "868e20baf5744e82baf8f13a06849ecc", "children": {} } } }, "html": { "name": "Element", "id": "9c4da9e0aac349f594e2d23298bac171", "children": {} }, "script": { "name": "Element", "id": "d092078607c04076bf58bd4593fa1684", "children": {} } } """ item = "<li><span style='background:%s;'></span>%s</li>" list_items = '\n'.join([item % (c, n) for (n, c) in items]) template = """ {{% macro html(this, kwargs) %}} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script> $( function() {{ $( "#maplegend" ).draggable({{ start: function (event, ui) {{ $(this).css({{ right: "auto", top: "auto", bottom: "auto" }}); }} }}); }}); </script> </head> <body> <div id='maplegend' class='maplegend' style='position: absolute; z-index:9999; border:2px solid grey; background-color:rgba(255, 255, 255, 0.8); border-radius:6px; padding: 10px; font-size:14px; right: 20px; bottom: 20px;'> <div class='legend-title'> {} </div> <div class='legend-scale'> <ul class='legend-labels'> {} </ul> </div> </div> </body> </html> <style type='text/css'> .maplegend .legend-title {{ text-align: left; margin-bottom: 5px; font-weight: bold; font-size: 90%; }} .maplegend .legend-scale ul {{ margin: 0; margin-bottom: 5px; padding: 0; float: left; list-style: none; }} .maplegend .legend-scale ul li {{ font-size: 80%; list-style: none; margin-left: 0; line-height: 18px; margin-bottom: 2px; }} .maplegend ul.legend-labels li span {{ display: block; float: left; height: 16px; width: 30px; margin-right: 5px; margin-left: 0; border: 1px solid #999; }} .maplegend .legend-source {{ font-size: 80%; color: #777; clear: both; }} .maplegend a {{ color: #777; }} </style> {{% endmacro %}}""".format(title, list_items) macro = MacroElement() macro._template = Template(template) m.get_root().add_child(macro, name='map_legend')
def add_map_legend(m: Map, title: Text, items: List[Tuple]): """ Adds a legend for a folium map. Parameters ---------- m : Map Represents a folium map. title : str Represents the title of the legend items : list of tuple Represents the color and name of the legend items References ---------- https://github.com/python-visualization/folium/issues/528#issuecomment-421445303 """ item = "<li><span style='background:%s;'></span>%s</li>" list_items = '\n'.join([item % (c, n) for (n, c) in items]) template = """ {{% macro html(this, kwargs) %}} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script> $( function() {{ $( "#maplegend" ).draggable({{ start: function (event, ui) {{ $(this).css({{ right: "auto", top: "auto", bottom: "auto" }}); }} }}); }}); </script> </head> <body> <div id='maplegend' class='maplegend' style='position: absolute; z-index:9999; border:2px solid grey; background-color:rgba(255, 255, 255, 0.8); border-radius:6px; padding: 10px; font-size:14px; right: 20px; bottom: 20px;'> <div class='legend-title'> {} </div> <div class='legend-scale'> <ul class='legend-labels'> {} </ul> </div> </div> </body> </html> <style type='text/css'> .maplegend .legend-title {{ text-align: left; margin-bottom: 5px; font-weight: bold; font-size: 90%; }} .maplegend .legend-scale ul {{ margin: 0; margin-bottom: 5px; padding: 0; float: left; list-style: none; }} .maplegend .legend-scale ul li {{ font-size: 80%; list-style: none; margin-left: 0; line-height: 18px; margin-bottom: 2px; }} .maplegend ul.legend-labels li span {{ display: block; float: left; height: 16px; width: 30px; margin-right: 5px; margin-left: 0; border: 1px solid #999; }} .maplegend .legend-source {{ font-size: 80%; color: #777; clear: both; }} .maplegend a {{ color: #777; }} </style> {{% endmacro %}}""".format(title, list_items) macro = MacroElement() macro._template = Template(template) m.get_root().add_child(macro)
def build_map(v_map: folium.Map, json_data: str, data_map: dict, colours: dict, v_caption: str, v_caption2: str, map_property: str) -> folium.Map: """ This function is the one that builds the map in v_map :param v_map: Folium map object we will use as the base to build the regions and paint them :param json_data: Route to the json we will use to divide our map into regions and to paint them :param data_map: A dictionary with the data_coronavirus (we use a data_frame and turn it into a dictionary) :param colours: Dictionary with the ranges and colours where the key is the value and the value is the color associated to that value. It is important to notice that we can mix string values that will be static values (no range for them) and ranges. We will have to put all string/static values first in the map and them the number values that will be paired to make ranges. Also important to notice, the keys are the values and the values are the color a string with the hexadecimal color (#XXXXXX) or a valid color name :param v_caption: Text we will put in the legend for the linear colours in the map :param v_caption2: Text we will put in the legend for the static colours in the map :param map_property: Property in feature we will use to identify the zones defined in the json :return: the map divided in regions, with the regions painted and coloured according to the values we indicated in the dictionary dataMap """ # We color the map in v_map using the feature GeoJson # and add it to the map folium.GeoJson( data=json_data, style_function=lambda feature: { 'fillColor': get_color(feature, data_map, colours, map_property), 'fillOpacity': 0.7, 'color': None, 'weight': 1, }).add_to(v_map) # We create a legend for the colours which we have painted our map with colours_keys = list(colours.keys()) # coloursValues = list(colours.values()) linear_legend_keys = [] linear_legend_values = [] nonlinear_legend_keys = [] nonlinear_legend_values = [] num_value = True for v_colour in colours_keys: if isinstance(v_colour, (int, float)): linear_legend_values.append(colours[v_colour]) linear_legend_keys.append(v_colour) else: nonlinear_legend_keys.append(v_colour) nonlinear_legend_values.append(colours[v_colour]) num_value = False # We create a colormap object to be used as legend if len(linear_legend_keys) > 1 and len(linear_legend_values) > 1: colormap = branca.colormap.LinearColormap(linear_legend_values) colormap = colormap.to_step(index=linear_legend_keys) colormap.caption = v_caption # We add the colormap to our map colormap.add_to(v_map) # We this value corresponds to an static value, we will add it to the legend for static values if not num_value: html_legend = """ <div style='position: fixed;bottom:50px;left:50px;border:2px solid grey;z-index:9999; font-size:14px'> """ + v_caption2 for key in nonlinear_legend_keys: html_legend += '<br> ' + key + ' ' html_legend += '<svg width="40" height="11">' color = colours[key].replace(' ', "") rgb_color = list(int(color[i:i + 2], 16) for i in (1, 3, 5)) html_legend += ' <rect width="40" height="11" style="fill:rgb({0},{1},{2});stroke-width:3;stroke:' \ 'rgb(0,0,0)" />'.format(rgb_color[0], rgb_color[1], rgb_color[2]) html_legend += '</svg>' html_legend += """ </div> """ v_map.get_root().html.add_child(folium.Element(html_legend)) return v_map
def path_plot(self, channel=None, map_type='dynamic', devices='all', start_date=None, end_date=None, options=dict()): ''' Creates a folium map showing a path Parameters ------- channel: String None If None, shows path, otherwise, colored path with channel mapping map_type: String 'dynamic' 'dynamic' or 'static'. Whether is a dinamic map or not devices: list or 'all' List of devices to include, or 'all' from self.devices channel: String The channel to make the map from start_date, end_date: String Date convertible string options: dict() Possible keys are (default otherwise) location: list [41.400818, 2.1825157] Center map location tiles: (String) 'Stamen Toner' Tiles for the folium.Map zoom: (float) 2.5 Zoom to start with in folium.Map period: 'String' '1W' Period for 'dynamic' map radius: float 10 Circle radius for icon fillOpacity: float 1 (<1) Fill opacity for the icon stroke: 'String' 'false' 'true' or 'false'. For icon's stroke icon: 'String' 'circle' A valid folium.Map icon style Returns ------- Folium.Map object ''' # Set defaults options = dict_fmerge(config._map_def_opt, options) # Make features features = [] if devices == 'all': mdev = self.devices else: mdev = list() for device in devices: if device in self.devices: mdev.append(device) else: std_out(f'Device {device} not found, ignoring', 'WARNING') if len(mdev) == 0: std_out('Requested devices not in test', 'ERROR') return None for device in mdev: chs = ['GPS_LAT', 'GPS_LONG'] if channel is not None: if channel not in self.devices[str(device)].readings.columns: std_out( f'Channel {channel} not in columns: {self.devices[str(device)].readings.columns}', 'ERROR') return None # Get bins minmax = False if not options['minmax']: if all([key not in channel for key in config._channel_bins]): std_out( f'Requested channel {channel} not in config mapped bins {config._channel_bins.keys()}.Using min/max mapping', 'WARNING') minmax = True else: minmax = True if minmax: bins = linspace( self.devices[str(device)].readings[channel].min(), self.devices[str(device)].readings[channel].max(), config._channel_bin_n) else: for bname in config._channel_bins.keys(): if bname in channel: bins = config._channel_bins[bname] break chs.append(channel) # Create copy dfc = self.devices[str(device)].readings[chs].copy() # Resample and cleanup # TODO THIS CAN INPUT SOME MADE UP READINGS dfc = clean(dfc.resample(options['period']).mean(), 'fill') # Make color column legend_labels = None if channel is not None: dfc['COLOR'] = cut(dfc[channel], bins, labels =\ config._map_colors_palette) # Make legend labels legend_labels = {} for ibin in range(len(bins) - 1): legend_labels[f'{round(bins[ibin],2)} : {round(bins[ibin+1],2)}'] =\ config._map_colors_palette[ibin] else: dfc['COLOR'] = config._map_colors_palette[0] if start_date is not None: dfc = dfc[dfc.index > start_date] if end_date is not None: dfc = dfc[dfc.index < end_date] # Add point for each date for date in dfc.index: if date == dfc.index[-1]: break times = [] color = str(dfc.loc[date, 'COLOR']) if color == 'nan' or isnan(dfc.loc[date, 'GPS_LONG'])\ or isnan(dfc.loc[date, 'GPS_LAT']): std_out(f'Skipping point {date}', 'WARNING') continue geometry = { 'type': 'LineString', 'coordinates': [[dfc.loc[date, 'GPS_LONG'], dfc.loc[date, 'GPS_LAT']], [ dfc.loc[date + dfc.index.freq, 'GPS_LONG'], dfc.loc[date + dfc.index.freq, 'GPS_LAT'] ]], } properties = { 'icon': options['icon'], 'iconstyle': { 'fillColor': color, 'fillOpacity': options['fillOpacity'], 'stroke': options['stroke'], 'radius': options['radius'] }, 'device': device, 'timestamp': date.strftime('%Y-%m-%dT%H:%M:%S'), "coordinates": [ dfc.loc[date + dfc.index.freq, 'GPS_LAT'], dfc.loc[date + dfc.index.freq, 'GPS_LONG'] ], 'style': { 'color': color, 'stroke-width': options['stroke-width'], 'fillOpacity': options['fillOpacity'] } } # Add reading to tooltip if channel is not None: properties['channel'] = channel properties['value'] = dfc.loc[date, channel] if map_type == 'dynamic': properties['times'] = [ date.strftime('%Y-%m-%dT%H:%M:%S'), (date + dfc.index.freq).strftime('%Y-%m-%dT%H:%M:%S') ] features.append({ 'type': 'Feature', 'geometry': geometry, 'properties': properties }) featurecol = {'type': 'FeatureCollection', 'features': features} # Make map if options['location'] == 'average': avg_long = dfc['GPS_LONG'].mean() avg_lat = dfc['GPS_LAT'].mean() loc = [avg_lat, avg_long] else: loc = options['location'] m = Map( location=loc, tiles=options['tiles'], zoom_start=options['zoom'], ) if map_type == 'static': # TODO WORKAROUND UNTIL GEOJSON ACCEPTS MARKERS if options['markers']: for feature in features: Circle(location=[ feature['geometry']['coordinates'][0][1], feature['geometry']['coordinates'][0][0] ], fill='true', radius=feature['properties']['iconstyle']['radius'], color=feature['properties']['iconstyle']['fillColor'], fill_opacity=feature['properties']['iconstyle'] ['fillOpacity']).add_to(m) if channel is not None: fields = ["device", "channel", "timestamp", "coordinates", "value"] aliases = [ "Device:", "Sensor:", "Timestamp:", "Coordinates:", "Reading:" ] else: fields = ["device", "timestamp", "coordinates"] aliases = ["Device:", "Timestamp:", "Coordinates:"] popup = GeoJsonPopup( fields=fields, aliases=aliases, localize=True, labels=True, max_width=800, ) tooltip = GeoJsonTooltip( fields=fields, aliases=aliases, localize=True, sticky=True, labels=True, style=""" background-color: #F0EFEF; border: 1px solid gray; border-radius: 1px; box-shadow: 2px; """, max_width=800, ) GeoJson( featurecol, tooltip=tooltip, popup=popup, style_function=lambda x: { 'color': x['properties']['style']['color'], 'weight': x['properties']['style']['stroke-width'], 'fillOpacity': x['properties']['style']['fillOpacity'] }, ).add_to(m) elif map_type == 'dynamic': TimestampedGeoJson(featurecol, period='PT' + convert_rollup(options['period']), add_last_point=True, auto_play=False, loop=False, max_speed=options['max_speed'], loop_button=True, time_slider_drag_update=True).add_to(m) else: std_out(f'Not supported map type {map_type}', 'ERROR') return None if options['minimap']: minimap = MiniMap(toggle_display=True, tile_layer=options['tiles']) minimap.add_to(m) if options['legend'] and not legend_labels is None: templateLoader = FileSystemLoader(searchpath=join(dirname(__file__),\ 'templates')) templateEnv = Environment(loader=templateLoader) template = templateEnv.get_template("map_legend.html") filled_map_legend = template.render(legend_labels=legend_labels) map_legend_html = '{% macro html(this, kwargs) %}'+\ filled_map_legend+\ '{% endmacro %}' legend = element.MacroElement() legend._template = element.Template(map_legend_html) m.get_root().add_child(legend) return m
def add_categorical_legend( folium_map: fl.Map, title: str, colors: List[str], labels: List[str], ) -> fl.Map: """ Given a Folium map, add to it a categorical legend with the given title, colors, and corresponding labels. The given colors and labels will be listed in the legend from top to bottom. Return the resulting map. Based on `this example <http://nbviewer.jupyter.org/gist/talbertc-usgs/18f8901fc98f109f2b71156cf3ac81cd>`_. """ # Error check if len(colors) != len(labels): raise ValueError( "colors and labels must have the same length.") color_by_label = zip(labels, colors, RSRPSamples, RSRPPercents) # Make legend HTML template = f""" {{% macro html(this, kwargs) %}} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div id='maplegend' class='maplegend'> <div class='legend-title'>{title}</div> <div class='legend-scale'> <ul class='legend-labels'> """ for label, color, count, percent in color_by_label: template += f"<div class='clr' style='background:{color}'></div>" template += f"<span>{label}</span></span>{count}</span>{percent}" template += """ </ul> </div> </div> </body> </html> <style type='text/css'> .maplegend { position: absolute; z-index:9999; background-color: rgba(255, 255, 255, 1); border-radius: 5px; border: 2px solid #bbb; padding: 10px; font-size:12px; right: 20px; top: 20px; width: 210px; opacity: 0.9; } .maplegend .legend-title { text-align: left; margin-bottom: 5px; font-weight: bold; font-size: 90%; } .maplegend .legend-scale ul { #whole legend margin: 0; margin-bottom: 5px; padding: 0; float: left; list-style: none; } .maplegend .legend-scale ul li { #descriptions font-size: 80%; list-style: none; margin-left: 10; line-height: 18px; margin-bottom: 2px; width: auto; } .maplegend ul.legend-labels span { #RSRP labels display: block; float: left; height: 16px; width: 90px; margin-right: 5px; margin-left: 0; border: 0px solid #ccc; } .maplegend .clr { #colors display: block; float: left; height: 16px; width: 20px; margin-right: 5px; margin-left: 0; border: 0px solid #ccc; } .maplegend .legend-source { font-size: 80%; color: #777; clear: both; } .maplegend a { color: #777; } </style> {% endmacro %} """ macro = bc.element.MacroElement() macro._template = bc.element.Template(template) folium_map.get_root().add_child(macro) return folium_map