Skip to content

Commit 42eefa2

Browse files
committed
feat: add support for spiderifying markers in FeatureGroups
1 parent d8c6018 commit 42eefa2

File tree

3 files changed

+130
-36
lines changed

3 files changed

+130
-36
lines changed

docs/user_guide/plugins/overlapping_marker_spiderfier.md

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
The `OverlappingMarkerSpiderfier` is a plugin for Folium that helps manage overlapping markers by "spiderfying" them when clicked, making it easier to select individual markers.
44

5+
## Using with Markers
6+
57
```{code-cell} ipython3
68
import folium
79
from folium.plugins import OverlappingMarkerSpiderfier
@@ -17,13 +19,89 @@ for i in range(20):
1719
).add_to(m)
1820
1921
# Add the OverlappingMarkerSpiderfier plugin
20-
oms = OverlappingMarkerSpiderfier(options={
21-
"keepSpiderfied": True, # Markers remain spiderfied after clicking
22-
"nearbyDistance": 20, # Distance for clustering markers in pixel
23-
"circleSpiralSwitchover": 10, # Threshold for switching between circle and spiral
24-
"legWeight": 2.0 # Line thickness for spider legs
25-
})
22+
oms = OverlappingMarkerSpiderfier(
23+
keep_spiderfied=True, # Markers remain spiderfied after clicking
24+
nearby_distance=20, # Distance for clustering markers in pixel
25+
circle_spiral_switchover=10, # Threshold for switching between circle and spiral
26+
leg_weight=2.0 # Line thickness for spider legs
27+
)
2628
oms.add_to(m)
2729
2830
m
2931
```
32+
33+
## Using with FeatureGroups
34+
35+
```{code-cell} ipython3
36+
import folium
37+
from folium.plugins import OverlappingMarkerSpiderfier
38+
39+
# Create a map
40+
m = folium.Map(location=[45.05, 3.05], zoom_start=13)
41+
42+
# Create a FeatureGroup
43+
feature_group = folium.FeatureGroup(name='Feature Group')
44+
45+
# Add markers to the FeatureGroup
46+
for i in range(10):
47+
folium.Marker(
48+
location=[45.05 + i * 0.0001, 3.05 + i * 0.0001],
49+
popup=f"Feature Group Marker {i}"
50+
).add_to(feature_group)
51+
52+
# Add the FeatureGroup to the map
53+
feature_group.add_to(m)
54+
55+
# Initialize OverlappingMarkerSpiderfier
56+
oms = OverlappingMarkerSpiderfier()
57+
oms.add_to(m)
58+
59+
m
60+
```
61+
62+
## Using with FeatureGroupSubGroups
63+
64+
```{code-cell} ipython3
65+
import folium
66+
from folium.plugins import OverlappingMarkerSpiderfier, FeatureGroupSubGroup
67+
68+
# Create a map
69+
m = folium.Map(location=[45.05, 3.05], zoom_start=13)
70+
71+
# Create a main FeatureGroup
72+
main_group = folium.FeatureGroup(name='Main Group')
73+
74+
# Create sub-groups
75+
sub_group1 = FeatureGroupSubGroup(main_group, name='Sub Group 1')
76+
sub_group2 = FeatureGroupSubGroup(main_group, name='Sub Group 2')
77+
78+
# Add markers to the first sub-group
79+
for i in range(10):
80+
folium.Marker(
81+
location=[45.05 + i * 0.0001, 3.05 + i * 0.0001],
82+
popup=f"Sub Group 1 Marker {i}"
83+
).add_to(sub_group1)
84+
85+
# Add markers to the second sub-group
86+
for i in range(10, 20):
87+
folium.Marker(
88+
location=[45.06 + (i - 10) * 0.0001, 3.06 + (i - 10) * 0.0001],
89+
popup=f"Sub Group 2 Marker {i}"
90+
).add_to(sub_group2)
91+
92+
# Add sub-groups to the map
93+
sub_group1.add_to(m)
94+
sub_group2.add_to(m)
95+
96+
# Add the main group to the map
97+
main_group.add_to(m)
98+
99+
# Initialize OverlappingMarkerSpiderfier
100+
oms = OverlappingMarkerSpiderfier()
101+
oms.add_to(m)
102+
103+
# Add the LayerControl plugin
104+
folium.LayerControl().add_to(m)
105+
106+
m
107+
```
Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from typing import Optional
2+
13
from jinja2 import Template
24

3-
from folium.elements import JSCSSMixin, MacroElement
5+
from folium.elements import Element, JSCSSMixin, MacroElement
6+
from folium.map import Marker
47
from folium.utilities import parse_options
58

69

@@ -12,26 +15,24 @@ class OverlappingMarkerSpiderfier(JSCSSMixin, MacroElement):
1215
When a user clicks on a cluster of overlapping markers, they spread out in a 'spider' pattern, making each marker
1316
individually accessible.
1417
15-
Markers must be added to the map **before** calling `oms.add_to(map)`.
16-
The plugin identifies and manages all markers already present on the map.
18+
Markers are automatically identified and managed by the plugin, so there is no need to add them separately.
19+
Simply add the plugin to the map using `oms.add_to(map)`.
1720
1821
Parameters
1922
----------
20-
options : dict, optional
21-
The options to configure the spiderfier behavior:
22-
- keepSpiderfied : bool, default True
23-
If true, markers stay spiderfied after clicking
24-
- nearbyDistance : int, default 20
25-
Pixels away from a marker that is considered overlapping
26-
- legWeight : float, default 1.5
27-
Weight of the spider legs
28-
- circleSpiralSwitchover : int, optional
29-
Number of markers at which to switch from circle to spiral pattern
23+
keep_spiderfied : bool, default True
24+
If true, markers stay spiderfied after clicking.
25+
nearby_distance : int, default 20
26+
Pixels away from a marker that is considered overlapping.
27+
leg_weight : float, default 1.5
28+
Weight of the spider legs.
29+
circle_spiral_switchover : int, optional
30+
Number of markers at which to switch from circle to spiral pattern.
3031
3132
Example
3233
-------
3334
>>> oms = OverlappingMarkerSpiderfier(
34-
... options={"keepSpiderfied": True, "nearbyDistance": 30, "legWeight": 2.0}
35+
... keep_spiderfied=True, nearby_distance=30, leg_weight=2.0
3536
... )
3637
>>> oms.add_to(map)
3738
"""
@@ -53,7 +54,6 @@ class OverlappingMarkerSpiderfier(JSCSSMixin, MacroElement):
5354
{%- for marker in this.markers %}
5455
oms.addMarker({{ marker.get_name() }});
5556
{%- endfor %}
56-
5757
} catch (error) {
5858
console.error('Error initializing OverlappingMarkerSpiderfier:', error);
5959
}
@@ -70,20 +70,35 @@ class OverlappingMarkerSpiderfier(JSCSSMixin, MacroElement):
7070
]
7171

7272
def __init__(
73-
self,
74-
keep_spiderfied: bool = True,
75-
nearby_distance: int = 20,
76-
leg_weight: float = 1.5,
77-
circle_spiral_switchover: int = 9,
78-
**kwargs
73+
self,
74+
keep_spiderfied: bool = True,
75+
nearby_distance: int = 20,
76+
leg_weight: float = 1.5,
77+
circle_spiral_switchover: int = 9,
78+
**kwargs
7979
):
8080
super().__init__()
8181
self._name = "OverlappingMarkerSpiderfier"
82-
default_options = {
83-
"keepSpiderfied": True,
84-
"nearbyDistance": 20,
85-
"legWeight": 1.5,
86-
}
87-
if options:
88-
default_options.update(options)
89-
self.options = parse_options(**default_options, **kwargs)
82+
self.options = parse_options(
83+
keep_spiderfied=keep_spiderfied,
84+
nearby_distance=nearby_distance,
85+
leg_weight=leg_weight,
86+
circle_spiral_switchover=circle_spiral_switchover,
87+
**kwargs
88+
)
89+
90+
def add_to(
91+
self, parent: Element, name: Optional[str] = None, index: Optional[int] = None
92+
) -> Element:
93+
self._parent = parent
94+
self.markers = self._get_all_markers(parent)
95+
super().add_to(parent, name=name, index=index)
96+
97+
def _get_all_markers(self, element: Element) -> list:
98+
markers = []
99+
for child in element._children.values():
100+
if isinstance(child, Marker):
101+
markers.append(child)
102+
elif hasattr(child, "_children"):
103+
markers.extend(self._get_all_markers(child))
104+
return markers

tests/plugins/test_overlapping_marker_spiderfier.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ def test_overlapping_marker_spiderfier_integration():
105105
"""
106106
m = Map([45.05, 3.05], zoom_start=14)
107107
oms = OverlappingMarkerSpiderfier(
108-
options={"keepSpiderfied": True, "nearbyDistance": 20}
108+
keep_spiderfied=True,
109+
nearby_distance=20,
109110
)
110111
oms.add_to(m)
111112

0 commit comments

Comments
 (0)