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))
Example #2
0
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()
Example #3
0
 def write_file(self, f: BinaryIO, m: Map):
     html: str = m.get_root().render()
     f.write(html)
Example #4
0
 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)
Example #5
0
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')
Example #6
0
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)
Example #7
0
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'>
        &nbsp; """ + v_caption2
        for key in nonlinear_legend_keys:
            html_legend += '<br> &nbsp; ' + key + ' &nbsp;'
            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
Example #8
0
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