Skip to content

Commit 2500719

Browse files
committed
Adding TreeLayerControl for route, cluster, point and geojson; adding geojson test; adding maximize, adding zoom to bounds
1 parent 1e76e90 commit 2500719

15 files changed

+1021
-154
lines changed

nextplot/cluster.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
import plotly.graph_objects as go
77
import scipy.spatial
8+
from folium import plugins
89

910
from . import common, types
1011

@@ -289,18 +290,21 @@ def plot(
289290
if not map_file:
290291
map_file = base_name + ".map.html"
291292
print(f"Plotting map to {map_file}")
292-
m = common.create_map(
293+
m, base_tree = common.create_map(
293294
(bbox.max_x + bbox.min_x) / 2.0,
294295
(bbox.max_y + bbox.min_y) / 2.0,
295296
custom_map_tile,
296297
)
297298
plot_groups = {}
299+
group_names = {}
298300

299301
# Plot the clusters themselves
300302
for i, cluster in enumerate(clusters):
301303
if len(cluster.points) <= 0:
302304
continue
303-
plot_groups[i] = folium.FeatureGroup(f"Cluster {i+1}")
305+
layer_name = f"Cluster {i+1}"
306+
plot_groups[i] = folium.FeatureGroup(name=layer_name)
307+
group_names[plot_groups[i]] = layer_name
304308
text = (
305309
"<p>"
306310
+ f"Cluster: {i} / {len(clusters)}</br>"
@@ -331,8 +335,29 @@ def plot(
331335
for k in plot_groups:
332336
plot_groups[k].add_to(m)
333337

338+
# Add button to expand the map to fullscreen
339+
plugins.Fullscreen(
340+
position="topright",
341+
title="Expand me",
342+
title_cancel="Exit me",
343+
).add_to(m)
344+
345+
# Create overlay tree for advanced control of route/unassigned layers
346+
overlay_tree = {
347+
"label": "Overlays",
348+
"select_all_checkbox": "Un/select all",
349+
"children": [
350+
{
351+
"label": "Clusters",
352+
"select_all_checkbox": True,
353+
"collapsed": True,
354+
"children": [{"label": group_names[v], "layer": v} for v in plot_groups.values()],
355+
}
356+
],
357+
}
358+
334359
# Add control for all layers and write file
335-
folium.LayerControl().add_to(m)
360+
plugins.TreeLayerControl(base_tree=base_tree, overlay_tree=overlay_tree).add_to(m)
336361

337362
# Fit map to bounds
338363
m.fit_bounds([[bbox.min_y, bbox.min_x], [bbox.max_y, bbox.max_x]])

nextplot/common.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -340,31 +340,56 @@ def bounding_box(points) -> types.BoundingBox:
340340
return types.BoundingBox(min(pos_x), max(pos_x), min(pos_y), max(pos_y))
341341

342342

343-
def create_map(lon: float, lat: float, custom_layers: list[str] = None) -> folium.Map:
343+
def create_map(lon: float, lat: float, custom_layers: list[str] = None) -> tuple[folium.Map, dict[str, any]]:
344344
"""
345-
Creates a default folium map focused on the given coordinates.
345+
Creates a default folium map focused on the given coordinates. Furthermore, it
346+
returns a tree structure that can be used to select different base layers
347+
using the TreeLayerControl.
346348
"""
347349
m = folium.Map(
348350
location=[lat, lon],
349-
tiles="cartodb positron",
350351
zoomSnap=0.25,
351352
zoomDelta=0.25,
352353
wheelPxPerZoomLevel=180,
353354
)
354-
folium.TileLayer("cartodbdark_matter").add_to(m)
355-
folium.TileLayer("openstreetmap").add_to(m)
355+
356+
tile_layers = [
357+
("openstreetmap", folium.TileLayer("openstreetmap").add_to(m)),
358+
("cartodbdark_matter", folium.TileLayer("cartodbdark_matter").add_to(m)),
359+
("cartodb positron", folium.TileLayer("cartodb positron").add_to(m)),
360+
]
361+
356362
if custom_layers:
357363
for layer in custom_layers:
358364
if layer.startswith("http"):
359365
elements = layer.split(",")
360-
folium.TileLayer(
361-
tiles=elements[0],
362-
name=elements[1],
363-
attr=elements[2],
364-
).add_to(m)
366+
if len(elements) != 3:
367+
raise Exception(f"Invalid custom layer definition: {layer}. Expected <url>,<name>,<attribution>")
368+
tile_layers.append(
369+
(
370+
elements[1],
371+
folium.TileLayer(
372+
tiles=elements[0],
373+
name=elements[1],
374+
attr=elements[2],
375+
).add_to(m),
376+
)
377+
)
365378
else:
366379
raise Exception(f"Invalid custom layer definition: {layer}. Expected <url>,<name>,<attribution>")
367-
return m
380+
381+
base_tree = {
382+
"label": "Base Layers",
383+
"children": [
384+
{
385+
"label": "Tiles",
386+
"radioGroup": "tiles",
387+
"children": [{"label": name, "layer": layer} for name, layer in tile_layers],
388+
},
389+
],
390+
}
391+
392+
return m, base_tree
368393

369394

370395
# ==================== Color handling

nextplot/geojson.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import folium
44
import jsonpath_ng
5+
from folium import plugins
56
from folium.elements import JSCSSMixin
67
from folium.map import Layer
78
from jinja2 import Template
@@ -23,14 +24,14 @@ def arguments(parser):
2324
type=str,
2425
nargs="?",
2526
default="",
26-
help="path to the geojson file to plot",
27+
help="path to the GeoJSON file to plot",
2728
)
2829
parser.add_argument(
2930
"--jpath_geojson",
3031
type=str,
3132
nargs="?",
3233
default="",
33-
help="JSON path to the geojson elements (XPATH like,"
34+
help="JSON path to the GeoJSON elements (XPATH like,"
3435
+ " see https://goessner.net/articles/JsonPath/,"
3536
+ ' example: "state.routes[*].geojson")',
3637
)
@@ -149,27 +150,56 @@ def plot(
149150
bbox_sw[0], bbox_sw[1] = min(bbox_sw[0], sw[0]), min(bbox_sw[1], sw[1])
150151
bbox_ne[0], bbox_ne[1] = max(bbox_ne[0], ne[0]), max(bbox_ne[1], ne[1])
151152

152-
# Make map plot of routes
153+
# Make map plot of geojson data
153154
map_file = output_map
154155
if not map_file:
155156
map_file = base_name + ".map.html"
156157
print(f"Plotting map to {map_file}")
157-
m = common.create_map(
158+
m, base_tree = common.create_map(
158159
(bbox_sw[1] + bbox_ne[1]) / 2.0,
159160
(bbox_sw[0] + bbox_ne[0]) / 2.0,
160161
custom_map_tile,
161162
)
163+
plot_groups = {}
164+
group_names = {}
162165
for i, gj in enumerate(geojsons):
163-
group = folium.FeatureGroup(f"geojson {i}")
166+
group_name = f"GeoJSON {i}"
167+
plot_groups[i] = folium.FeatureGroup(name=group_name)
168+
group_names[plot_groups[i]] = group_name
164169
if style:
165-
StyledGeoJson(gj).add_to(group)
170+
StyledGeoJson(gj).add_to(plot_groups[i])
166171
else:
167-
folium.GeoJson(gj).add_to(group)
168-
group.add_to(m)
172+
folium.GeoJson(gj).add_to(plot_groups[i])
173+
plot_groups[i].add_to(m)
174+
175+
# Add button to expand the map to fullscreen
176+
plugins.Fullscreen(
177+
position="topright",
178+
title="Expand me",
179+
title_cancel="Exit me",
180+
).add_to(m)
181+
182+
# Create overlay tree for advanced control of route/unassigned layers
183+
overlay_tree = {
184+
"label": "Overlays",
185+
"select_all_checkbox": "Un/select all",
186+
"children": [
187+
{
188+
"label": "GeoJSONs",
189+
"select_all_checkbox": True,
190+
"collapsed": True,
191+
"children": [{"label": group_names[v], "layer": v} for v in plot_groups.values()],
192+
}
193+
],
194+
}
169195

170196
# Add control for all layers and write file
171-
folium.LayerControl().add_to(m)
197+
plugins.TreeLayerControl(base_tree=base_tree, overlay_tree=overlay_tree).add_to(m)
198+
199+
# Fit bounds
172200
m.fit_bounds([sw, ne])
201+
202+
# Save map
173203
m.save(map_file)
174204

175205

nextplot/point.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import folium
66
import plotly.graph_objects as go
7+
from folium import plugins
78

89
from . import common, types
910

@@ -219,12 +220,20 @@ def plot(
219220
if not map_file:
220221
map_file = base_name + ".map.html"
221222
print(f"Plotting map to {map_file}")
222-
m = common.create_map(
223+
m, base_tree = common.create_map(
223224
(bbox.max_x + bbox.min_x) / 2.0,
224225
(bbox.max_y + bbox.min_y) / 2.0,
225226
custom_map_tile,
226227
)
227-
for ps in points:
228+
plot_groups = {}
229+
group_names = {}
230+
231+
for i, ps in enumerate(points):
232+
if len(ps.points) <= 0:
233+
continue
234+
layer_name = f"Point group {i+1}"
235+
plot_groups[i] = folium.FeatureGroup(name=layer_name)
236+
group_names[plot_groups[i]] = layer_name
228237
for point in ps.points:
229238
d = point.desc.replace("\n", "<br/>").replace(r"`", r"\`")
230239
popup_text = folium.Html(
@@ -246,10 +255,35 @@ def plot(
246255
fillOpacity=1.0,
247256
)
248257
marker.options["fillOpacity"] = 1.0
249-
marker.add_to(m)
258+
marker.add_to(plot_groups[i])
259+
260+
# Add all grouped parts to the map
261+
for g in plot_groups:
262+
plot_groups[g].add_to(m)
263+
264+
# Add button to expand the map to fullscreen
265+
plugins.Fullscreen(
266+
position="topright",
267+
title="Expand me",
268+
title_cancel="Exit me",
269+
).add_to(m)
270+
271+
# Create overlay tree for advanced control of route/unassigned layers
272+
overlay_tree = {
273+
"label": "Overlays",
274+
"select_all_checkbox": "Un/select all",
275+
"children": [
276+
{
277+
"label": "Point groups",
278+
"select_all_checkbox": True,
279+
"collapsed": True,
280+
"children": [{"label": group_names[v], "layer": v} for v in plot_groups.values()],
281+
}
282+
],
283+
}
250284

251285
# Add control for all layers and write file
252-
folium.LayerControl().add_to(m)
286+
plugins.TreeLayerControl(base_tree=base_tree, overlay_tree=overlay_tree).add_to(m)
253287

254288
# Fit bounds
255289
m.fit_bounds([[bbox.min_y, bbox.min_x], [bbox.max_y, bbox.max_x]])

nextplot/route.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,19 +343,22 @@ def create_map(
343343
bbox = common.bounding_box([r.points for r in routes])
344344

345345
# Make map plot of routes
346-
m = common.create_map(
346+
m, base_tree = common.create_map(
347347
(bbox.max_x + bbox.min_x) / 2.0,
348348
(bbox.max_y + bbox.min_y) / 2.0,
349349
custom_map_tile,
350350
)
351351
route_groups = {}
352+
route_layer_names = {}
352353
unassigned_group = folium.FeatureGroup("Unassigned")
353354

354355
# Plot the routes themselves
355356
for i, route in enumerate(routes):
356357
if len(route.points) <= 0:
357358
continue
358-
route_groups[route] = folium.FeatureGroup(f"Route {i+1}")
359+
layer_name = f"Route {i+1}"
360+
route_groups[route] = folium.FeatureGroup(layer_name)
361+
route_layer_names[route_groups[route]] = layer_name
359362
plot_map_route(
360363
route_groups[route],
361364
route,
@@ -450,8 +453,33 @@ def create_map(
450453
if len(unassigned) > 0:
451454
unassigned_group.add_to(m)
452455

456+
# Add button to expand the map to fullscreen
457+
plugins.Fullscreen(
458+
position="topright",
459+
title="Expand me",
460+
title_cancel="Exit me",
461+
).add_to(m)
462+
463+
# Create overlay tree for advanced control of route/unassigned layers
464+
overlay_tree = {
465+
"label": "Overlays",
466+
"select_all_checkbox": "Un/select all",
467+
"children": [],
468+
}
469+
if len(unassigned) > 0:
470+
overlay_tree["children"].append({"label": "Unassigned", "layer": unassigned_group})
471+
if len(route_groups) > 0:
472+
overlay_tree["children"].append(
473+
{
474+
"label": "Routes",
475+
"select_all_checkbox": True,
476+
"collapsed": True,
477+
"children": [{"label": route_layer_names[v], "layer": v} for v in route_groups.values()],
478+
},
479+
)
480+
453481
# Add control for all layers and write file
454-
folium.LayerControl().add_to(m)
482+
plugins.TreeLayerControl(base_tree=base_tree, overlay_tree=overlay_tree).add_to(m)
455483

456484
# Fit map to bounds
457485
m.fit_bounds([[bbox.min_y, bbox.min_x], [bbox.max_y, bbox.max_x]])

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ classifiers = [
1414
dependencies = [
1515
"argcomplete>=2.0.0",
1616
"colorutils>=0.3.0",
17-
"folium>=0.12.1",
17+
"folium @ git+https://github.com/python-visualization/folium@b80e7e92",
1818
"jsonpath_ng>=1.5.3",
1919
"kaleido>=0.2.1",
2020
"numpy>=1.22.3",

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
argcomplete==2.0.0
22
colorutils==0.3.0
3-
folium==0.16.0
3+
folium @ git+https://github.com/python-visualization/folium@b80e7e92
44
jsonpath_ng==1.6.1
55
kaleido==0.2.1
66
numpy==1.26.4

0 commit comments

Comments
 (0)