forked from python-visualization/folium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add leaflet-realtime plugin (python-visualization#1848)
* Implemented the leaflet-realtime plugin Based on: https://github.com/perliedman/leaflet-realtime * Fix for failing pre-commit hooks in origin * Updated after review comments * Add documentation for the realtime plugin * Also update TOC * Fix layout * remove noqa * don't use `options` var name * use default arguments, add typing * Update JsCode docstring for in docs * Add JsCode to docs * remove parameters from docs * slight tweaks to docs * import JsCode in init --------- Co-authored-by: Frank <[email protected]>
- Loading branch information
Showing
7 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
```{code-cell} ipython3 | ||
--- | ||
nbsphinx: hidden | ||
--- | ||
import folium | ||
import folium.plugins | ||
``` | ||
|
||
# Realtime plugin | ||
|
||
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 | ||
|
||
This plugin functions much like an `L.GeoJson` layer, for | ||
which the geojson data is periodically polled from a url. | ||
|
||
|
||
## Simple example | ||
|
||
In this example we use a static geojson, whereas normally you would have a | ||
url that actually updates in real time. | ||
|
||
```{code-cell} ipython3 | ||
from folium import JsCode | ||
m = folium.Map(location=[40.73, -73.94], zoom_start=12) | ||
rt = folium.plugins.Realtime( | ||
"https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson", | ||
get_feature_id=JsCode("(f) => { return f.properties.objectid; }"), | ||
interval=10000, | ||
) | ||
rt.add_to(m) | ||
m | ||
``` | ||
|
||
|
||
## Javascript function as source | ||
|
||
For more complicated scenarios, such as when the underlying data source does not return geojson, you can | ||
write a javascript function for the `source` parameter. In this example we track the location of the | ||
International Space Station using a public API. | ||
|
||
|
||
```{code-cell} ipython3 | ||
import folium | ||
from folium.plugins import Realtime | ||
m = folium.Map() | ||
source = folium.JsCode(""" | ||
function(responseHandler, errorHandler) { | ||
var url = 'https://api.wheretheiss.at/v1/satellites/25544'; | ||
fetch(url) | ||
.then((response) => { | ||
return response.json().then((data) => { | ||
var { id, longitude, latitude } = data; | ||
return { | ||
'type': 'FeatureCollection', | ||
'features': [{ | ||
'type': 'Feature', | ||
'geometry': { | ||
'type': 'Point', | ||
'coordinates': [longitude, latitude] | ||
}, | ||
'properties': { | ||
'id': id | ||
} | ||
}] | ||
}; | ||
}) | ||
}) | ||
.then(responseHandler) | ||
.catch(errorHandler); | ||
} | ||
""") | ||
rt = Realtime(source, interval=10000) | ||
rt.add_to(m) | ||
m | ||
``` | ||
|
||
|
||
## Customizing the layer | ||
|
||
The leaflet-realtime plugin typically uses an `L.GeoJson` layer to show the data. This | ||
means that you can also pass parameters which you would typically pass to an | ||
`L.GeoJson` layer. With this knowledge we can change the first example to display | ||
`L.CircleMarker` objects. | ||
|
||
```{code-cell} ipython3 | ||
import folium | ||
from folium import JsCode | ||
from folium.plugins import Realtime | ||
m = folium.Map(location=[40.73, -73.94], zoom_start=12) | ||
source = "https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson" | ||
Realtime( | ||
source, | ||
get_feature_id=JsCode("(f) => { return f.properties.objectid }"), | ||
point_to_layer=JsCode("(f, latlng) => { return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2})}"), | ||
interval=10000, | ||
).add_to(m) | ||
m | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from typing import Optional, Union | ||
|
||
from branca.element import MacroElement | ||
from jinja2 import Template | ||
|
||
from folium.elements import JSCSSMixin | ||
from folium.utilities import JsCode, camelize, 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 | ||
---------- | ||
source: str, dict, JsCode | ||
The source can be one of: | ||
* a string with the URL to get data from | ||
* a dict that is passed to javascript's `fetch` function | ||
for fetching the data | ||
* a `folium.JsCode` object in case you need more freedom. | ||
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 | ||
get_feature_id: JsCode, optional | ||
A JS function with a geojson `feature` as parameter | ||
default returns `feature.properties.id` | ||
Function to get an identifier to uniquely identify a feature over time | ||
update_feature: JsCode, optional | ||
A JS function with a geojson `feature` as parameter | ||
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 | ||
remove_missing: bool, default False | ||
Should missing features between updates been automatically | ||
removed from the layer | ||
Other keyword arguments are passed to the GeoJson layer, so you can pass | ||
`style`, `point_to_layer` and/or `on_each_feature`. | ||
Examples | ||
-------- | ||
>>> from folium import JsCode | ||
>>> m = folium.Map(location=[40.73, -73.94], zoom_start=12) | ||
>>> rt = Realtime( | ||
... "https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson", | ||
... get_feature_id=JsCode("(f) => { return f.properties.objectid; }"), | ||
... point_to_layer=JsCode( | ||
... "(f, latlng) => { return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2})}" | ||
... ), | ||
... interval=10000, | ||
... ) | ||
>>> rt.add_to(m) | ||
""" | ||
|
||
_template = Template( | ||
""" | ||
{% macro script(this, kwargs) %} | ||
var {{ this.get_name() }}_options = {{ this.options|tojson }}; | ||
{% for key, value in this.functions.items() %} | ||
{{ this.get_name() }}_options["{{key}}"] = {{ value }}; | ||
{% endfor %} | ||
var {{ this.get_name() }} = new L.realtime( | ||
{% if this.src is string or this.src is mapping -%} | ||
{{ this.src|tojson }}, | ||
{% else -%} | ||
{{ this.src.js_code }}, | ||
{% endif -%} | ||
{{ this.get_name() }}_options | ||
); | ||
{{ this._parent.get_name() }}.addLayer( | ||
{{ this.get_name() }}._container); | ||
{% endmacro %} | ||
""" | ||
) | ||
|
||
default_js = [ | ||
( | ||
"Leaflet_Realtime_js", | ||
"https://cdnjs.cloudflare.com/ajax/libs/leaflet-realtime/2.2.0/leaflet-realtime.js", | ||
) | ||
] | ||
|
||
def __init__( | ||
self, | ||
source: Union[str, dict, JsCode], | ||
start: bool = True, | ||
interval: int = 60000, | ||
get_feature_id: Optional[JsCode] = None, | ||
update_feature: Optional[JsCode] = None, | ||
remove_missing: bool = False, | ||
**kwargs | ||
): | ||
super().__init__() | ||
self._name = "Realtime" | ||
self.src = source | ||
|
||
kwargs["start"] = start | ||
kwargs["interval"] = interval | ||
if get_feature_id is not None: | ||
kwargs["get_feature_id"] = get_feature_id | ||
if update_feature is not None: | ||
kwargs["update_feature"] = update_feature | ||
kwargs["remove_missing"] = remove_missing | ||
|
||
# extract JsCode objects | ||
self.functions = {} | ||
for key, value in list(kwargs.items()): | ||
if isinstance(value, JsCode): | ||
self.functions[camelize(key)] = value.js_code | ||
kwargs.pop(key) | ||
|
||
self.options = parse_options(**kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters