Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add leaflet-realtime plugin #1848

Merged
merged 14 commits into from
Jan 2, 2024
2 changes: 2 additions & 0 deletions folium/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from folium.plugins.pattern import CirclePattern, StripePattern
from folium.plugins.polyline_offset import PolyLineOffset
from folium.plugins.polyline_text_path import PolyLineTextPath
from folium.plugins.realtime import Realtime
from folium.plugins.scroll_zoom_toggler import ScrollZoomToggler
from folium.plugins.search import Search
from folium.plugins.semicircle import SemiCircle
Expand Down Expand Up @@ -54,6 +55,7 @@
"MousePosition",
"PolyLineTextPath",
"PolyLineOffset",
"Realtime",
"ScrollZoomToggler",
"Search",
"SemiCircle",
Expand Down
90 changes: 90 additions & 0 deletions folium/plugins/realtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from branca.element import MacroElement
from jinja2 import Template

from folium.elements import JSCSSMixin
from folium.utilities import JsCode, parse_options


class Realtime(JSCSSMixin, MacroElement):
"""Put realtime data on a Leaflet map: live tracking GPS units,
sensor data or just about anything.

Based on: https://github.com/perliedman/leaflet-realtime

Parameters
----------
start : bool, default True
Should automatic updates be enabled when layer is added
on the map and stopped when layer is removed from the map
interval : int, default 60000
Automatic update interval, in milliseconds
getFeatureId : function, default returns `feature.properties.id`
Conengmo marked this conversation as resolved.
Show resolved Hide resolved
Function to get an identifier uniquely identify a feature over time
Conengmo marked this conversation as resolved.
Show resolved Hide resolved
updateFeature : function
Conengmo marked this conversation as resolved.
Show resolved Hide resolved
Used to update an existing feature's layer;
by default, points (markers) are updated, other layers are discarded
and replaced with a new, updated layer.
Allows to create more complex transitions,
for example, when a feature is updated
removeMissing : bool, default False
Should missing features between updates been automatically
removed from the layer

Other parameters are passed to the GeoJson layer, so you can pass
`style`, `pointToLayer` and/or `onEachFeature`.

Examples
--------
>>> from folium.utilities import JsCode
>>> m = folium.Map()
>>> rt = Realtime(
... "https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_regions_elevation_points.geojson",
... getFeatureId=JsCode("function(f) { return f.properties.name; }"),
... interval=10000,
... )
>>> rt.add_to(m)
"""

_template = Template(
"""
{% macro script(this, kwargs) %}
var options = {{this.options|tojson}};
{% for key, value in this.functions.items() %}
options["{{key}}"] = {{ value }};
{% endfor %}
var {{ this.get_name() }} = new L.realtime(
{{ this.src|tojson }},
options
);
{{ this._parent.get_name() }}.addLayer(
{{ this.get_name() }}._container);
Conengmo marked this conversation as resolved.
Show resolved Hide resolved
{% endmacro %}
"""
)

default_js = [
(
"Leaflet_Realtime_js",
"https://cdnjs.cloudflare.com/ajax/libs/leaflet-realtime/2.2.0/leaflet-realtime.js", # NoQA
)
]

def __init__(self, src, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather explicitly name all the arguments here that are also in the docstring. That way it's more transparent for users and IDE's what the arguments of this function are. We can add type hints for each argument.

The JS functions are already added to options separately in the JS part, so that can still happen there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the arguments from the docstring explicit. I made them default None because I did not want them to shadow the defaults from leaflet-realtime.

Conengmo marked this conversation as resolved.
Show resolved Hide resolved
super().__init__()
self._name = "Realtime"
self.src = src

# extract JsCode objects
self.functions = {}
for key, value in kwargs.items():
if isinstance(value, JsCode):
self.functions[key] = value.js_code

# and remove them from kwargs
for key in self.functions:
kwargs.pop(key)

# the container is special, as we
# do not allow it to be set (yet)
# from python
self.options = parse_options(container=None, **kwargs)
Conengmo marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions folium/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,15 @@ def get_and_assert_figure_root(obj: Element) -> Figure:
figure, Figure
), "You cannot render this Element if it is not in a Figure."
return figure


# See:
# https://github.com/andfanilo/streamlit-echarts/blob/master/streamlit_echarts/frontend/src/utils.js
# Thanks andfanilo
class JsCode:
def __init__(self, js_code: str):
"""Wrapper around a js function
Args:
js_code (str): javascript function code as str
"""
self.js_code = js_code