Skip to content

Commit 964cf97

Browse files
Add a custom container to the realtime plugin (#1869)
* Add cluster override * Add tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add container argument to documentation * Fix incorrect import * Update realtime.py No idea why this was gone in this branch. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 309aa08 commit 964cf97

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

docs/user_guide/plugins/realtime.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,27 @@ Realtime(
108108
109109
m
110110
```
111+
112+
## Using a MarkerCluster as a container
113+
114+
The subway stations in the previous example are not easy to distinguish at lower zoom levels.
115+
It is possible to use a custom container for the GeoJson. In this example we use a MarkerCluster.
116+
```{code-cell} ipython3
117+
import folium
118+
from folium import JsCode
119+
from folium.plugins import Realtime, MarkerCluster
120+
121+
m = folium.Map(location=[40.73, -73.94], zoom_start=12)
122+
source = "https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson"
123+
124+
container = MarkerCluster().add_to(m)
125+
Realtime(
126+
source,
127+
get_feature_id=JsCode("(f) => { return f.properties.objectid }"),
128+
point_to_layer=JsCode("(f, latlng) => { return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2})}"),
129+
container=container,
130+
interval=10000,
131+
).add_to(m)
132+
133+
m
134+
```

folium/plugins/realtime.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from typing import Union
1+
from typing import Optional, Union
22

33
from branca.element import MacroElement
44
from jinja2 import Template
55

66
from folium.elements import JSCSSMixin
7+
from folium.map import Layer
78
from folium.utilities import JsCode, camelize, parse_options
89

910

@@ -41,7 +42,11 @@ class Realtime(JSCSSMixin, MacroElement):
4142
remove_missing: bool, default False
4243
Should missing features between updates been automatically
4344
removed from the layer
44-
45+
container: Layer, default GeoJson
46+
The container will typically be a `FeatureGroup`, `MarkerCluster` or
47+
`GeoJson`, but it can be anything that generates a javascript
48+
L.LayerGroup object, i.e. something that has the methods
49+
`addLayer` and `removeLayer`.
4550
4651
Other keyword arguments are passed to the GeoJson layer, so you can pass
4752
`style`, `point_to_layer` and/or `on_each_feature`. Make sure to wrap
@@ -70,6 +75,11 @@ class Realtime(JSCSSMixin, MacroElement):
7075
{{ this.get_name() }}_options["{{key}}"] = {{ value }};
7176
{% endfor %}
7277
78+
{% if this.container -%}
79+
{{ this.get_name() }}_options["container"]
80+
= {{ this.container.get_name() }};
81+
{% endif -%}
82+
7383
var {{ this.get_name() }} = new L.realtime(
7484
{% if this.src is string or this.src is mapping -%}
7585
{{ this.src|tojson }},
@@ -99,11 +109,13 @@ def __init__(
99109
get_feature_id: Union[JsCode, str, None] = None,
100110
update_feature: Union[JsCode, str, None] = None,
101111
remove_missing: bool = False,
112+
container: Optional[Layer] = None,
102113
**kwargs
103114
):
104115
super().__init__()
105116
self._name = "Realtime"
106117
self.src = source
118+
self.container = container
107119

108120
kwargs["start"] = start
109121
kwargs["interval"] = interval

tests/plugins/test_realtime.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Test Realtime
3+
------------------
4+
"""
5+
6+
from jinja2 import Template
7+
8+
import folium
9+
from folium.plugins import MarkerCluster, Realtime
10+
from folium.utilities import JsCode, normalize
11+
12+
13+
def test_realtime():
14+
m = folium.Map(location=[40.73, -73.94], zoom_start=12)
15+
source = "https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson"
16+
17+
container = MarkerCluster().add_to(m)
18+
19+
rt = Realtime(
20+
source,
21+
get_feature_id=JsCode("(f) => { return f.properties.objectid }"),
22+
point_to_layer=JsCode(
23+
"(f, latlng) => { return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2})}"
24+
),
25+
container=container,
26+
interval=10000,
27+
)
28+
rt.add_to(m)
29+
30+
tmpl_for_expected = Template(
31+
"""
32+
{% macro script(this, kwargs) %}
33+
var {{ this.get_name() }}_options = {{ this.options|tojson }};
34+
{% for key, value in this.functions.items() %}
35+
{{ this.get_name() }}_options["{{key}}"] = {{ value }};
36+
{% endfor %}
37+
38+
{% if this.container -%}
39+
{{ this.get_name() }}_options["container"]
40+
= {{ this.container.get_name() }};
41+
{% endif -%}
42+
43+
var {{ this.get_name() }} = new L.realtime(
44+
{% if this.src is string or this.src is mapping -%}
45+
{{ this.src|tojson }},
46+
{% else -%}
47+
{{ this.src.js_code }},
48+
{% endif -%}
49+
{{ this.get_name() }}_options
50+
);
51+
{{ this._parent.get_name() }}.addLayer(
52+
{{ this.get_name() }}._container);
53+
{% endmacro %}
54+
"""
55+
)
56+
expected = normalize(tmpl_for_expected.render(this=rt))
57+
58+
out = normalize(m._parent.render())
59+
60+
# We verify that imports
61+
assert (
62+
'<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-realtime/2.2.0/leaflet-realtime.js"></script>' # noqa
63+
in out
64+
) # noqa
65+
66+
assert expected in out

0 commit comments

Comments
 (0)