"""
Visualization auxiliary operations.
add_map_legend,
generate_color,
rgb,
hex_rgb,
cmap_hex_color,
get_cmap,
save_wkt
"""
from __future__ import annotations
from typing import Sequence
from branca.element import MacroElement, Template
from folium import Map
from matplotlib.cm import get_cmap as _get_cmap
from matplotlib.colors import Colormap, ListedColormap, rgb2hex
from numpy.random import randint
from pandas import DataFrame
from pymove.utils.constants import COLORS, LATITUDE, LONGITUDE, TRAJ_ID
[docs]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')
[docs]def generate_color() -> str:
"""
Generates a random color.
Returns
-------
Random HEX color
Examples
--------
>>> from pymove.utils.visual import generate_color
>>> print(generate_color(), type(generate_color()))
'#E0FFFF' <class 'str'>
>>> print(generate_color(), type(generate_color()))
'#808000' <class 'str'>
"""
return COLORS[randint(0, len(COLORS))]
[docs]def rgb(rgb_colors: tuple[float, float, float]) -> tuple[int, int, int]:
"""
Return a tuple of integers, as used in AWT/Java plots.
Parameters
----------
rgb_colors : tuple
Represents a tuple with three positions that correspond to the percentage red,
green and blue colors.
Returns
-------
tuple
Represents a tuple of integers that correspond the colors values.
Examples
--------
>>> from pymove.utils.visual import rgb
>>> print(rgb((0.1, 0.2, 0.7)), type(rgb((0.1, 0.2, 0.7))))
(51, 178, 25) <class 'tuple'>
>>> print(rgb((0.5, 0.4, 0.1)), type(rgb((0.5, 0.4, 0.1))))
(102, 25, 127) <class 'tuple'>
"""
blue = rgb_colors[0]
red = rgb_colors[1]
green = rgb_colors[2]
return int(red * 255), int(green * 255), int(blue * 255)
[docs]def hex_rgb(rgb_colors: tuple[float, float, float]) -> str:
"""
Return a hex str, as used in Tk plots.
Parameters
----------
rgb_colors : tuple
Represents a tuple with three positions that correspond to the percentage red,
green and blue colors
Returns
-------
str
Represents a color in hexadecimal format
Examples
--------
>>> from pymove.utils.visual import hex_rgb
>>> print(hex_rgb((0.1, 0.2, 0.7)), type(hex_rgb((0.1, 0.2, 0.7))))
'#33B219' <class 'str'>
>>> print(hex_rgb((0.5, 0.4, 0.1)), type(hex_rgb((0.5, 0.4, 0.1))))
'#66197F' <class 'str'>
"""
return '#%02X%02X%02X' % rgb(rgb_colors)
[docs]def cmap_hex_color(cmap: ListedColormap, i: int) -> str:
"""
Convert a Colormap to hex color.
Parameters
----------
cmap : ListedColormap
Represents the Colormap
i : int
List color index
Returns
-------
str
Represents corresponding hex str
Examples
--------
>>> from pymove.utils.visual import cmap_hex_color
>>> import matplotlib.pyplot as plt
>>> jet = plt.get_cmap('jet') # This comand generates a Linear Segmented Colormap
>>> print(cmap_hex_color(jet, 0))
'#000080'
>>> print(cmap_hex_color(jet, 1))
'#000084'
"""
return rgb2hex(cmap(i))
[docs]def get_cmap(cmap: str) -> Colormap:
"""
Returns a matplotlib colormap instance.
Parameters
----------
cmap : str
name of the colormar
Returns
-------
Colormap
matplotlib colormap
Examples
--------
>>> from pymove.utils.visual import get_cmap
>>> print(get_cmap('Greys')
<matplotlib.colors.LinearSegmentedColormap object at 0x7f743fc04bb0>
"""
return _get_cmap(cmap)
[docs]def save_wkt(
move_data: DataFrame, filename: str, label_id: str = TRAJ_ID
):
"""
Save a visualization in a map in a new file .wkt.
Parameters
----------
move_data : DataFrame
Input trajectory data
filename : str
Represents the filename
label_id : str
Represents column name of trajectory id
Returns
-------
File: A file.wkt that contains geometric points that build a map visualization
Examples
--------
>>> from pymove.utils.visual import save_wkt
>>> df.head()
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
>>> save_wkt(df, 'test.wkt', 'id')
>>> with open('test.wtk') as f:
>>> print(f.read())
'id;linestring'
'1;LINESTRING(116.319236 39.984094,116.319322 39.984198,116.319402 39.984224)'
'2;LINESTRING(116.319389 39.984211,116.319422 39.984217)'
"""
wtk = '%s;linestring\n' % label_id
ids = move_data[label_id].unique()
for id_ in ids:
move_df = move_data[move_data[label_id] == id_]
curr_str = '%s;LINESTRING(' % id_
curr_str += ','.join(
f'{x[0]} {x[1]}'
for x in move_df[[LONGITUDE, LATITUDE]].values
)
curr_str += ')\n'
wtk += curr_str
with open(filename, 'w') as f:
f.write(wtk)