/
folium.py
229 lines (199 loc) · 7.31 KB
/
folium.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""Create interactive Leaflet web maps of graphs and routes via folium."""
import json
from warnings import warn
from . import utils_graph
# folium is an optional dependency for the folium plotting functions
try:
import folium
except ImportError: # pragma: no cover
folium = None
def plot_graph_folium(
G,
graph_map=None,
popup_attribute=None,
tiles="cartodbpositron",
zoom=1,
fit_bounds=True,
edge_color=None,
edge_width=None,
edge_opacity=None,
**kwargs,
):
"""
Plot a graph as an interactive Leaflet web map.
Note that anything larger than a small city can produce a large web map
file that is slow to render in your browser.
Parameters
----------
G : networkx.MultiDiGraph
input graph
graph_map : folium.folium.Map
if not None, plot the graph on this preexisting folium map object
popup_attribute : string
edge attribute to display in a pop-up when an edge is clicked
tiles : string
name of a folium tileset
zoom : int
initial zoom level for the map
fit_bounds : bool
if True, fit the map to the boundaries of the graph's edges
edge_color : string
deprecated, do not use, use kwargs instead
edge_width : numeric
deprecated, do not use, use kwargs instead
edge_opacity : numeric
deprecated, do not use, use kwargs instead
kwargs
keyword arguments to pass to folium.PolyLine(), see folium docs for
options (for example `color="#333333", weight=5, opacity=0.7`)
Returns
-------
folium.folium.Map
"""
# deprecation warning
if edge_color is not None: # pragma: no cover
kwargs["color"] = edge_color
warn("`edge_color` has been deprecated and will be removed: use kwargs instead")
if edge_width is not None: # pragma: no cover
kwargs["weight"] = edge_width
warn("`edge_width` has been deprecated and will be removed: use kwargs instead")
if edge_opacity is not None: # pragma: no cover
kwargs["opacity"] = edge_opacity
warn("`edge_opacity` has been deprecated and will be removed: use kwargs instead")
# create gdf of all graph edges
gdf_edges = utils_graph.graph_to_gdfs(G, nodes=False)
return _plot_folium(gdf_edges, graph_map, popup_attribute, tiles, zoom, fit_bounds, **kwargs)
def plot_route_folium(
G,
route,
route_map=None,
popup_attribute=None,
tiles="cartodbpositron",
zoom=1,
fit_bounds=True,
route_color=None,
route_width=None,
route_opacity=None,
**kwargs,
):
"""
Plot a route as an interactive Leaflet web map.
Parameters
----------
G : networkx.MultiDiGraph
input graph
route : list
the route as a list of nodes
route_map : folium.folium.Map
if not None, plot the route on this preexisting folium map object
popup_attribute : string
edge attribute to display in a pop-up when an edge is clicked
tiles : string
name of a folium tileset
zoom : int
initial zoom level for the map
fit_bounds : bool
if True, fit the map to the boundaries of the route's edges
route_color : string
deprecated, do not use, use kwargs instead
route_width : numeric
deprecated, do not use, use kwargs instead
route_opacity : numeric
deprecated, do not use, use kwargs instead
kwargs
keyword arguments to pass to folium.PolyLine(), see folium docs for
options (for example `color="#cc0000", weight=5, opacity=0.7`)
Returns
-------
folium.folium.Map
"""
# deprecation warning
if route_color is not None: # pragma: no cover
kwargs["color"] = route_color
warn("`route_color` has been deprecated and will be removed: use kwargs instead")
if route_width is not None: # pragma: no cover
kwargs["weight"] = route_width
warn("`route_width` has been deprecated and will be removed: use kwargs instead")
if route_opacity is not None: # pragma: no cover
kwargs["opacity"] = route_opacity
warn("`route_opacity` has been deprecated and will be removed: use kwargs instead")
# create gdf of the route edges in order
node_pairs = zip(route[:-1], route[1:])
uvk = ((u, v, min(G[u][v], key=lambda k: G[u][v][k]["length"])) for u, v in node_pairs)
gdf_edges = utils_graph.graph_to_gdfs(G.subgraph(route), nodes=False).loc[uvk]
return _plot_folium(gdf_edges, route_map, popup_attribute, tiles, zoom, fit_bounds, **kwargs)
def _plot_folium(gdf, m, popup_attribute, tiles, zoom, fit_bounds, **kwargs):
"""
Plot a GeoDataFrame of LineStrings on a folium map object.
Parameters
----------
gdf : geopandas.GeoDataFrame
a GeoDataFrame of LineString geometries and attributes
m : folium.folium.Map or folium.FeatureGroup
if not None, plot on this preexisting folium map object
popup_attribute : string
attribute to display in pop-up on-click, if None, no popup
tiles : string
name of a folium tileset
zoom : int
initial zoom level for the map
fit_bounds : bool
if True, fit the map to gdf's boundaries
kwargs
keyword arguments to pass to folium.PolyLine()
Returns
-------
m : folium.folium.Map
"""
# check if we were able to import folium successfully
if folium is None: # pragma: no cover
raise ImportError("folium must be installed to use this optional feature")
# get centroid
x, y = gdf.unary_union.centroid.xy
centroid = (y[0], x[0])
# create the folium web map if one wasn't passed-in
if m is None:
m = folium.Map(location=centroid, zoom_start=zoom, tiles=tiles)
# identify the geometry and popup columns
if popup_attribute is None:
attrs = ["geometry"]
else:
attrs = ["geometry", popup_attribute]
# add each edge to the map
for vals in gdf[attrs].values:
params = dict(zip(["geom", "popup_val"], vals))
pl = _make_folium_polyline(**params, **kwargs)
pl.add_to(m)
# if fit_bounds is True, fit the map to the bounds of the route by passing
# list of lat-lng points as [southwest, northeast]
if fit_bounds and isinstance(m, folium.Map):
tb = gdf.total_bounds
m.fit_bounds([(tb[1], tb[0]), (tb[3], tb[2])])
return m
def _make_folium_polyline(geom, popup_val=None, **kwargs):
"""
Turn LineString geometry into a folium PolyLine with attributes.
Parameters
----------
geom : shapely LineString
geometry of the line
popup_val : string
text to display in pop-up when a line is clicked, if None, no popup
kwargs
keyword arguments to pass to folium.PolyLine()
Returns
-------
pl : folium.PolyLine
"""
# locations is a list of points for the polyline folium takes coords in
# lat,lng but geopandas provides them in lng,lat so we must reverse them
locations = [(lat, lng) for lng, lat in geom.coords]
# create popup if popup_val is not None
if popup_val is None:
popup = None
else:
# folium doesn't interpret html, so can't do newlines without iframe
popup = folium.Popup(html=json.dumps(popup_val))
# create a folium polyline with attributes
pl = folium.PolyLine(locations=locations, popup=popup, **kwargs)
return pl