From 4350f328046ebb07a0fca60d87b5a4396ee2bdaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Senanur=20=C3=87etin?= Date: Tue, 27 Oct 2026 16:42:09 +0300 Subject: [PATCH 1/3] feat(TimestampedGeoJson): allow marker-only animation without path redraw --- folium/plugins/timestamped_geo_json.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/folium/plugins/timestamped_geo_json.py b/folium/plugins/timestamped_geo_json.py index 11d1deae63..44457666e7 100644 --- a/folium/plugins/timestamped_geo_json.py +++ b/folium/plugins/timestamped_geo_json.py @@ -47,6 +47,10 @@ class TimestampedGeoJson(JSCSSMixin, MacroElement): Used to construct the array of available times starting from the first available time. Format: ISO8601 Duration ex: 'P1M' 1/month, 'P1D' 1/day, 'PT1H' 1/hour, and 'PT1M' 1/minute + show_path: bool, default True + Whether to draw animated LineString paths. Set to False to animate + only the last-point markers when add_last_point=True. The source paths + are still used to determine timestamps and marker positions. duration: str, default None Period of time which the features will be shown on the map after their time has passed. If None, all previous times will be shown. @@ -131,6 +135,14 @@ class TimestampedGeoJson(JSCSSMixin, MacroElement): } }) + {% if not this.show_path %} + // Keep full source features for time indexing, but skip recreating + // their polylines in the animated frame layers. + geoJsonLayer.options.filter = function (feature) { + return feature.geometry.type !== "LineString"; + }; + {% endif %} + var {{this.get_name()}} = L.timeDimension.layer.geoJson( geoJsonLayer, { @@ -191,6 +203,7 @@ def __init__( time_slider_drag_update=False, duration=None, speed_slider=True, + show_path=True, ): super().__init__() self._name = "TimestampedGeoJson" @@ -205,6 +218,7 @@ def __init__( self.embed = False self.data = data self.add_last_point = bool(add_last_point) + self.show_path = bool(show_path) self.period = period self.date_options = date_options self.duration = "undefined" if duration is None else '"' + duration + '"' From 7199878964a254e9f6d1a2cb23cb32a6f2f00b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Senanur=20=C3=87etin?= Date: Tue, 27 Oct 2026 16:42:25 +0300 Subject: [PATCH 2/3] test(TimestampedGeoJson): preserve timestamps while hiding animated path --- tests/plugins/test_timestamped_geo_json.py | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/plugins/test_timestamped_geo_json.py b/tests/plugins/test_timestamped_geo_json.py index 24ee9a12dc..34363916d8 100644 --- a/tests/plugins/test_timestamped_geo_json.py +++ b/tests/plugins/test_timestamped_geo_json.py @@ -197,3 +197,37 @@ def test_timestamped_geo_json(): bounds = m.get_bounds() assert bounds == [[-53.0, -158.0], [50.0, 158.0]], bounds + + +def test_timestamped_geo_json_can_animate_markers_without_paths(): + data = { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": {"type": "LineString", "coordinates": [[1, 2], [3, 4]]}, + "properties": {"times": [1435708800000, 1435795200000], "icon": "marker"}, + }], + } + + map_with_paths = folium.Map(location=[2, 1]) + plugins.TimestampedGeoJson(data).add_to(map_with_paths) + original = map_with_paths.get_root().render() + assert "geoJsonLayer.options.filter" not in original + + map_with_markers = folium.Map(location=[2, 1]) + plugin = plugins.TimestampedGeoJson( + data, show_path=False, add_last_point=True, + ).add_to(map_with_markers) + html = map_with_markers.get_root().render() + + # Initial GeoJSON must receive every LineString so it can index all times. + # The frame-layer filter is installed only afterwards, and suppresses + # creating a polyline while allowing the generated last-point marker. + initial_layer = html.index("var geoJsonLayer = L.geoJson(") + frame_filter = html.index("geoJsonLayer.options.filter = function") + animated_layer = html.index("L.timeDimension.layer.geoJson(") + assert initial_layer < frame_filter < animated_layer + assert 'feature.geometry.type !== "LineString"' in html + assert "addlastPoint: true" in html + assert '"coordinates": [[1, 2], [3, 4]]' in html + assert plugin.show_path is False From 512b9b2af52a0e15c17238e010cda355f096b84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Senanur=20=C3=87etin?= Date: Wed, 28 Oct 2026 01:58:49 +0300 Subject: [PATCH 3/3] style: format TimestampedGeoJson regression test with Black --- tests/plugins/test_timestamped_geo_json.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/plugins/test_timestamped_geo_json.py b/tests/plugins/test_timestamped_geo_json.py index 34363916d8..9cbbeb052e 100644 --- a/tests/plugins/test_timestamped_geo_json.py +++ b/tests/plugins/test_timestamped_geo_json.py @@ -202,11 +202,16 @@ def test_timestamped_geo_json(): def test_timestamped_geo_json_can_animate_markers_without_paths(): data = { "type": "FeatureCollection", - "features": [{ - "type": "Feature", - "geometry": {"type": "LineString", "coordinates": [[1, 2], [3, 4]]}, - "properties": {"times": [1435708800000, 1435795200000], "icon": "marker"}, - }], + "features": [ + { + "type": "Feature", + "geometry": {"type": "LineString", "coordinates": [[1, 2], [3, 4]]}, + "properties": { + "times": [1435708800000, 1435795200000], + "icon": "marker", + }, + } + ], } map_with_paths = folium.Map(location=[2, 1]) @@ -216,7 +221,9 @@ def test_timestamped_geo_json_can_animate_markers_without_paths(): map_with_markers = folium.Map(location=[2, 1]) plugin = plugins.TimestampedGeoJson( - data, show_path=False, add_last_point=True, + data, + show_path=False, + add_last_point=True, ).add_to(map_with_markers) html = map_with_markers.get_root().render()