Files
Buffteks-Website/buffteks/lib/python3.12/site-packages/folium/plugins/timestamped_geo_json.py
2025-05-08 21:10:14 -05:00

253 lines
8.9 KiB
Python

import json
from branca.element import MacroElement
from jinja2 import Template
from folium.elements import JSCSSMixin
from folium.folium import Map
from folium.utilities import get_bounds, parse_options
class TimestampedGeoJson(JSCSSMixin, MacroElement):
"""
Creates a TimestampedGeoJson plugin from timestamped GeoJSONs to append
into a map with Map.add_child.
A geo-json is timestamped if:
* it contains only features of types LineString, MultiPoint, MultiLineString,
Polygon and MultiPolygon.
* each feature has a 'times' property with the same length as the
coordinates array.
* each element of each 'times' property is a timestamp in ms since epoch,
or in ISO string.
Eventually, you may have Point features with a 'times' property being an
array of length 1.
Parameters
----------
data: file, dict or str.
The timestamped geo-json data you want to plot.
* If file, then data will be read in the file and fully embedded in
Leaflet's javascript.
* If dict, then data will be converted to json and embedded in the
javascript.
* If str, then data will be passed to the javascript as-is.
transition_time: int, default 200.
The duration in ms of a transition from between timestamps.
loop: bool, default True
Whether the animation shall loop.
auto_play: bool, default True
Whether the animation shall start automatically at startup.
add_last_point: bool, default True
Whether a point is added at the last valid coordinate of a LineString.
period: str, default "P1D"
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
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.
Format: ISO8601 Duration
ex: 'P1M' 1/month, 'P1D' 1/day, 'PT1H' 1/hour, and 'PT1M' 1/minute
Examples
--------
>>> TimestampedGeoJson(
... {
... "type": "FeatureCollection",
... "features": [
... {
... "type": "Feature",
... "geometry": {
... "type": "LineString",
... "coordinates": [[-70, -25], [-70, 35], [70, 35]],
... },
... "properties": {
... "times": [1435708800000, 1435795200000, 1435881600000],
... "tooltip": "my tooltip text",
... },
... }
... ],
... }
... )
See https://github.com/socib/Leaflet.TimeDimension for more information.
"""
_template = Template(
"""
{% macro script(this, kwargs) %}
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: function(date){
var newdate = new moment(date);
console.log(newdate)
return newdate.format("{{this.date_options}}");
}
});
{{this._parent.get_name()}}.timeDimension = L.timeDimension(
{
period: {{ this.period|tojson }},
}
);
var timeDimensionControl = new L.Control.TimeDimensionCustom(
{{ this.options|tojson }}
);
{{this._parent.get_name()}}.addControl(this.timeDimensionControl);
var geoJsonLayer = L.geoJson({{this.data}}, {
pointToLayer: function (feature, latLng) {
if (feature.properties.icon == 'marker') {
if(feature.properties.iconstyle){
return new L.Marker(latLng, {
icon: L.icon(feature.properties.iconstyle)});
}
//else
return new L.Marker(latLng);
}
if (feature.properties.icon == 'circle') {
if (feature.properties.iconstyle) {
return new L.circleMarker(latLng, feature.properties.iconstyle)
};
//else
return new L.circleMarker(latLng);
}
//else
return new L.Marker(latLng);
},
style: function (feature) {
return feature.properties.style;
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
if (feature.properties.tooltip) {
layer.bindTooltip(feature.properties.tooltip);
}
}
})
var {{this.get_name()}} = L.timeDimension.layer.geoJson(
geoJsonLayer,
{
updateTimeDimension: true,
addlastPoint: {{ this.add_last_point|tojson }},
duration: {{ this.duration }},
}
).addTo({{this._parent.get_name()}});
{% endmacro %}
"""
) # noqa
default_js = [
(
"jquery2.0.0",
"https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js",
),
(
"jqueryui1.10.2",
"https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js",
),
(
"iso8601",
"https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js",
),
(
"leaflet.timedimension",
"https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.min.js",
),
# noqa
(
"moment",
"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js",
),
]
default_css = [
(
"highlight.js_css",
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css",
),
(
"leaflet.timedimension_css",
"https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.control.css",
),
]
def __init__(
self,
data,
transition_time=200,
loop=True,
auto_play=True,
add_last_point=True,
period="P1D",
min_speed=0.1,
max_speed=10,
loop_button=False,
date_options="YYYY-MM-DD HH:mm:ss",
time_slider_drag_update=False,
duration=None,
speed_slider=True,
):
super().__init__()
self._name = "TimestampedGeoJson"
if "read" in dir(data):
self.embed = True
self.data = data.read()
elif type(data) is dict:
self.embed = True
self.data = json.dumps(data)
else:
self.embed = False
self.data = data
self.add_last_point = bool(add_last_point)
self.period = period
self.date_options = date_options
self.duration = "undefined" if duration is None else '"' + duration + '"'
self.options = parse_options(
position="bottomleft",
min_speed=min_speed,
max_speed=max_speed,
auto_play=auto_play,
loop_button=loop_button,
time_slider_drag_update=time_slider_drag_update,
speed_slider=speed_slider,
player_options={
"transitionTime": int(transition_time),
"loop": loop,
"startOver": True,
},
)
def render(self, **kwargs):
assert isinstance(
self._parent, Map
), "TimestampedGeoJson can only be added to a Map object."
super().render(**kwargs)
def _get_self_bounds(self):
"""
Computes the bounds of the object itself (not including it's children)
in the form [[lat_min, lon_min], [lat_max, lon_max]].
"""
if not self.embed:
raise ValueError("Cannot compute bounds of non-embedded GeoJSON.")
data = json.loads(self.data)
if "features" not in data.keys():
# Catch case when GeoJSON is just a single Feature or a geometry.
if not (isinstance(data, dict) and "geometry" in data.keys()):
# Catch case when GeoJSON is just a geometry.
data = {"type": "Feature", "geometry": data}
data = {"type": "FeatureCollection", "features": [data]}
return get_bounds(data, lonlat=True)