From ad03fd76a35e35c049a53bdf5e543e7818fb934e Mon Sep 17 00:00:00 2001 From: Hans Then Date: Sat, 16 Dec 2023 15:56:14 +0100 Subject: [PATCH 01/14] Implemented the leaflet-realtime plugin Based on: https://github.com/perliedman/leaflet-realtime --- folium/plugins/__init__.py | 2 + folium/plugins/realtime.py | 88 ++++++++++++++++++++++++++++++++++++++ folium/utilities.py | 12 ++++++ 3 files changed, 102 insertions(+) create mode 100644 folium/plugins/realtime.py diff --git a/folium/plugins/__init__.py b/folium/plugins/__init__.py index f4f168e2b..93d2bfc14 100644 --- a/folium/plugins/__init__.py +++ b/folium/plugins/__init__.py @@ -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 @@ -54,6 +55,7 @@ "MousePosition", "PolyLineTextPath", "PolyLineOffset", + "Realtime", "ScrollZoomToggler", "Search", "SemiCircle", diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py new file mode 100644 index 000000000..ab1c9203b --- /dev/null +++ b/folium/plugins/realtime.py @@ -0,0 +1,88 @@ +from branca.element import MacroElement +from jinja2 import Template + +from folium.elements import JSCSSMixin +from folium.utilities import parse_options, JsCode + + +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` + Function to get an identifier uniquely identify a feature over time + updateFeature : function + 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); + {% 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): + 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) diff --git a/folium/utilities.py b/folium/utilities.py index 942206930..2a82e3a56 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -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 From 85afba5f8096ccdae104363b01105ed7e95a9ff4 Mon Sep 17 00:00:00 2001 From: Hans Then Date: Sat, 16 Dec 2023 17:07:53 +0100 Subject: [PATCH 02/14] Fix for failing pre-commit hooks in origin --- folium/plugins/realtime.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index ab1c9203b..eb6b08b1e 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -2,7 +2,7 @@ from jinja2 import Template from folium.elements import JSCSSMixin -from folium.utilities import parse_options, JsCode +from folium.utilities import JsCode, parse_options class Realtime(JSCSSMixin, MacroElement): @@ -37,9 +37,11 @@ class Realtime(JSCSSMixin, MacroElement): -------- >>> 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 = 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) """ From a2af509ca584de95dfbd5820d88b2ae8c2f26493 Mon Sep 17 00:00:00 2001 From: Hans Then Date: Wed, 27 Dec 2023 18:11:40 +0100 Subject: [PATCH 03/14] Updated after review comments --- folium/plugins/realtime.py | 71 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index eb6b08b1e..9111d2ee2 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -2,7 +2,7 @@ from jinja2 import Template from folium.elements import JSCSSMixin -from folium.utilities import JsCode, parse_options +from folium.utilities import JsCode, camelize, parse_options class Realtime(JSCSSMixin, MacroElement): @@ -13,33 +13,45 @@ class Realtime(JSCSSMixin, MacroElement): Parameters ---------- + source : + 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.utilities.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 - getFeatureId : function, default returns `feature.properties.id` + get_feature_id : folium.utilities.JsCode + A function with a geojson `feature` as parameter + default returns `feature.properties.id` Function to get an identifier uniquely identify a feature over time - updateFeature : function + update_feature : folium.utilities.JsCode + A 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 - removeMissing : bool, default False + remove_missing : 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`. + `style`, `point_to_layer` and/or `on_each_feature`. Examples -------- >>> from folium.utilities import JsCode - >>> m = folium.Map() + >>> m = folium.Map(location=[40.73, -73.94], zoom_start=12) >>> 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; }"), + ... "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) @@ -52,8 +64,13 @@ class Realtime(JSCSSMixin, MacroElement): {% for key, value in this.functions.items() %} 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 -%} options ); {{ this._parent.get_name() }}.addLayer( @@ -69,22 +86,36 @@ class Realtime(JSCSSMixin, MacroElement): ) ] - def __init__(self, src, **kwargs): + def __init__( + self, + source, + start=None, + interval=None, + get_feature_id=None, + update_feature=None, + remove_missing=None, + **kwargs + ): super().__init__() self._name = "Realtime" - self.src = src + self.src = source + + if start is not None: + kwargs["start"] = start + if interval is not None: + 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 + if remove_missing is not None: + kwargs["remove_missing"] = remove_missing # extract JsCode objects self.functions = {} - for key, value in kwargs.items(): + for key, value in list(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) + self.functions[camelize(key)] = value.js_code + 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) + self.options = parse_options(**kwargs) From a83c15525ae74ec75cb31901affc5faf96cd972b Mon Sep 17 00:00:00 2001 From: Hans Then Date: Thu, 28 Dec 2023 10:06:44 +0100 Subject: [PATCH 04/14] Add documentation for the realtime plugin --- docs/user_guide/plugins/realtime.md | 129 ++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/user_guide/plugins/realtime.md diff --git a/docs/user_guide/plugins/realtime.md b/docs/user_guide/plugins/realtime.md new file mode 100644 index 000000000..b83597c5f --- /dev/null +++ b/docs/user_guide/plugins/realtime.md @@ -0,0 +1,129 @@ +```{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. + +Parameters +---------- +source : + 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.utilities.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 : folium.utilities.JsCode + A function with a geojson `feature` as parameter + default returns `feature.properties.id` + Function to get an identifier uniquely identify a feature over time +update_feature : folium.utilities.JsCode + A 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 parameters are passed to the `L.GeoJson` object, so you can pass + `style`, `point_to_layer` and/or `on_each_feature`. + + +```{code-cell} ipython3 +from folium.utilities 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 +``` + +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. + + +```{code-cell} ipython3 +import folium +from folium.utilities import JsCode +from folium.plugins import Realtime + +m = folium.Map() + +source = 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 +``` + +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.utilities 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" + +rt = 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) +rt.add_to(m) +m +``` From c9ecc0df259c505b413ff4502d14414ffa2a3109 Mon Sep 17 00:00:00 2001 From: Hans Then Date: Thu, 28 Dec 2023 10:09:48 +0100 Subject: [PATCH 05/14] Also update TOC --- docs/user_guide/plugins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user_guide/plugins.rst b/docs/user_guide/plugins.rst index 00c3bd246..e8b372608 100644 --- a/docs/user_guide/plugins.rst +++ b/docs/user_guide/plugins.rst @@ -23,6 +23,7 @@ Plugins plugins/pattern plugins/polyline_offset plugins/polyline_textpath_and_antpath + plugins/realtime plugins/scroll_zoom_toggler plugins/search plugins/semi_circle From f22367ce8770fca564bbba7fcc93cd4fd540e160 Mon Sep 17 00:00:00 2001 From: Hans Then Date: Thu, 28 Dec 2023 10:18:49 +0100 Subject: [PATCH 06/14] Fix layout --- docs/user_guide/plugins/realtime.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/user_guide/plugins/realtime.md b/docs/user_guide/plugins/realtime.md index b83597c5f..3bdce63ee 100644 --- a/docs/user_guide/plugins/realtime.md +++ b/docs/user_guide/plugins/realtime.md @@ -24,15 +24,19 @@ source : * a dict that is passed to javascript's `fetch` function for fetching the data * a folium.utilities.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 : folium.utilities.JsCode A function with a geojson `feature` as parameter default returns `feature.properties.id` Function to get an identifier uniquely identify a feature over time + update_feature : folium.utilities.JsCode A function with a geojson `feature` as parameter Used to update an existing feature's layer; @@ -40,6 +44,7 @@ update_feature : folium.utilities.JsCode 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 From 9a06c1d14e91023e07863c8c8d21bc40a004479e Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:43:22 +0100 Subject: [PATCH 07/14] remove noqa --- folium/plugins/realtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index 9111d2ee2..ad1243e4e 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -82,7 +82,7 @@ class Realtime(JSCSSMixin, MacroElement): default_js = [ ( "Leaflet_Realtime_js", - "https://cdnjs.cloudflare.com/ajax/libs/leaflet-realtime/2.2.0/leaflet-realtime.js", # NoQA + "https://cdnjs.cloudflare.com/ajax/libs/leaflet-realtime/2.2.0/leaflet-realtime.js", ) ] From b5d35d2f3d26fe2950d3b39d9371eb5943d5e9bf Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:47:13 +0100 Subject: [PATCH 08/14] don't use `options` var name --- folium/plugins/realtime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index ad1243e4e..0c3325261 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -60,9 +60,9 @@ class Realtime(JSCSSMixin, MacroElement): _template = Template( """ {% macro script(this, kwargs) %} - var options = {{this.options|tojson}}; + var {{ this.get_name() }}_options = {{ this.options|tojson }}; {% for key, value in this.functions.items() %} - options["{{key}}"] = {{ value }}; + {{ this.get_name() }}_options["{{key}}"] = {{ value }}; {% endfor %} var {{ this.get_name() }} = new L.realtime( @@ -71,7 +71,7 @@ class Realtime(JSCSSMixin, MacroElement): {% else -%} {{ this.src.js_code }}, {% endif -%} - options + {{ this.get_name() }}_options ); {{ this._parent.get_name() }}.addLayer( {{ this.get_name() }}._container); From 966a356fc4ba12ff2499d17febeb0e3165de8a77 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:47:52 +0100 Subject: [PATCH 09/14] use default arguments, add typing --- folium/plugins/realtime.py | 49 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index 0c3325261..5aabcdec0 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -1,3 +1,5 @@ +from typing import Optional, Union + from branca.element import MacroElement from jinja2 import Template @@ -13,34 +15,36 @@ class Realtime(JSCSSMixin, MacroElement): Parameters ---------- - source : + 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.utilities.JsCode object in case you need more freedom. - start : bool, default True + * a `folium.utilities.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 + interval: int, default 60000 Automatic update interval, in milliseconds - get_feature_id : folium.utilities.JsCode - A function with a geojson `feature` as parameter + get_feature_id: JsCode, optional + A JS function with a geojson `feature` as parameter default returns `feature.properties.id` - Function to get an identifier uniquely identify a feature over time - update_feature : folium.utilities.JsCode - A function with a geojson `feature` as parameter + 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 + remove_missing: 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`, `point_to_layer` and/or `on_each_feature`. + + Other keyword arguments are passed to the GeoJson layer, so you can pass + `style`, `point_to_layer` and/or `on_each_feature`. Examples -------- @@ -88,28 +92,25 @@ class Realtime(JSCSSMixin, MacroElement): def __init__( self, - source, - start=None, - interval=None, - get_feature_id=None, - update_feature=None, - remove_missing=None, + 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 - if start is not None: - kwargs["start"] = start - if interval is not None: - kwargs["interval"] = interval + 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 - if remove_missing is not None: - kwargs["remove_missing"] = remove_missing + kwargs["remove_missing"] = remove_missing # extract JsCode objects self.functions = {} From 0e8a043cf54efe873b99d537c75543eeab3fbb3b Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:48:17 +0100 Subject: [PATCH 10/14] Update JsCode docstring for in docs --- folium/utilities.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/folium/utilities.py b/folium/utilities.py index 2a82e3a56..afc4fc531 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -412,13 +412,8 @@ def get_and_assert_figure_root(obj: Element) -> Figure: return figure -# See: -# https://github.com/andfanilo/streamlit-echarts/blob/master/streamlit_echarts/frontend/src/utils.js -# Thanks andfanilo class JsCode: + """Wrapper around Javascript code.""" + 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 From 3fe3f68289af68b0aa3c81548f19a7251b1d9769 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:48:30 +0100 Subject: [PATCH 11/14] Add JsCode to docs --- docs/reference.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reference.rst b/docs/reference.rst index 0b3c7d9c7..5ed5f7c32 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -31,6 +31,12 @@ Other map features .. automodule:: folium.features +Utilities +--------------------- + +.. autoclass:: folium.utilities.JsCode + + Plugins -------------------- .. automodule:: folium.plugins From 44dc0e0ce918661294a1b3725e7f95ec0203fac0 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:48:42 +0100 Subject: [PATCH 12/14] remove parameters from docs --- docs/user_guide/plugins/realtime.md | 35 ----------------------------- 1 file changed, 35 deletions(-) diff --git a/docs/user_guide/plugins/realtime.md b/docs/user_guide/plugins/realtime.md index 3bdce63ee..51029f732 100644 --- a/docs/user_guide/plugins/realtime.md +++ b/docs/user_guide/plugins/realtime.md @@ -16,41 +16,6 @@ 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. -Parameters ----------- -source : - 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.utilities.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 : folium.utilities.JsCode - A function with a geojson `feature` as parameter - default returns `feature.properties.id` - Function to get an identifier uniquely identify a feature over time - -update_feature : folium.utilities.JsCode - A 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 parameters are passed to the `L.GeoJson` object, so you can pass - `style`, `point_to_layer` and/or `on_each_feature`. ```{code-cell} ipython3 From 12af4446221f85be2f0c5df8fc7e709eae2a4dcc Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:49:06 +0100 Subject: [PATCH 13/14] slight tweaks to docs --- docs/user_guide/plugins/realtime.md | 78 +++++++++++++++++------------ 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/docs/user_guide/plugins/realtime.md b/docs/user_guide/plugins/realtime.md index 51029f732..ac1a3c9ae 100644 --- a/docs/user_guide/plugins/realtime.md +++ b/docs/user_guide/plugins/realtime.md @@ -17,6 +17,10 @@ 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.utilities import JsCode @@ -30,9 +34,12 @@ 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. +International Space Station using a public API. ```{code-cell} ipython3 @@ -43,40 +50,43 @@ from folium.plugins import Realtime m = folium.Map() source = 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 - } - }] - }; + 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); -} + .then(responseHandler) + .catch(errorHandler); + } """) -rt = Realtime(source, - interval=10000) +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 @@ -90,10 +100,12 @@ 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" -rt = 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) -rt.add_to(m) +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 ``` From 4756ecbdbdc2c6bcb3c80401cc4b0f3657499291 Mon Sep 17 00:00:00 2001 From: Frank <33519926+Conengmo@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:01:24 +0100 Subject: [PATCH 14/14] import JsCode in init --- docs/user_guide/plugins/realtime.md | 7 +++---- folium/__init__.py | 2 ++ folium/plugins/realtime.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/user_guide/plugins/realtime.md b/docs/user_guide/plugins/realtime.md index ac1a3c9ae..7e5d315c5 100644 --- a/docs/user_guide/plugins/realtime.md +++ b/docs/user_guide/plugins/realtime.md @@ -23,7 +23,7 @@ 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.utilities import JsCode +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", @@ -44,12 +44,11 @@ International Space Station using a public API. ```{code-cell} ipython3 import folium -from folium.utilities import JsCode from folium.plugins import Realtime m = folium.Map() -source = JsCode(""" +source = folium.JsCode(""" function(responseHandler, errorHandler) { var url = 'https://api.wheretheiss.at/v1/satellites/25544'; @@ -94,7 +93,7 @@ means that you can also pass parameters which you would typically pass to an ```{code-cell} ipython3 import folium -from folium.utilities import JsCode +from folium import JsCode from folium.plugins import Realtime m = folium.Map(location=[40.73, -73.94], zoom_start=12) diff --git a/folium/__init__.py b/folium/__init__.py index 305e9e98b..01a0b6607 100644 --- a/folium/__init__.py +++ b/folium/__init__.py @@ -40,6 +40,7 @@ Tooltip, ) from folium.raster_layers import TileLayer, WmsTileLayer +from folium.utilities import JsCode from folium.vector_layers import Circle, CircleMarker, Polygon, PolyLine, Rectangle try: @@ -79,6 +80,7 @@ "IFrame", "Icon", "JavascriptLink", + "JsCode", "LatLngPopup", "LayerControl", "LinearColormap", diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index 5aabcdec0..429a40955 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -21,7 +21,7 @@ class Realtime(JSCSSMixin, MacroElement): * 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.utilities.JsCode` object in case you need more freedom. + * 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 @@ -48,7 +48,7 @@ class Realtime(JSCSSMixin, MacroElement): Examples -------- - >>> from folium.utilities import JsCode + >>> 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",