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

320 lines
12 KiB
Python

from branca.element import Element, Figure
from jinja2 import Template
from folium.elements import JSCSSMixin
from folium.map import Layer
from folium.utilities import none_max, none_min
class HeatMapWithTime(JSCSSMixin, Layer):
"""
Create a HeatMapWithTime layer
Parameters
----------
data: list of list of points of the form [lat, lng] or [lat, lng, weight]
The points you want to plot. The outer list corresponds to the various time
steps in sequential order. (weight is in (0, 1] range and defaults to 1 if
not specified for a point)
index: Index giving the label (or timestamp) of the elements of data. Should have
the same length as data, or is replaced by a simple count if not specified.
name : string, default None
The name of the Layer, as it will appear in LayerControls.
radius: default 15.
The radius used around points for the heatmap.
blur: default 0.8.
Blur strength used for the heatmap. Must be between 0 and 1.
min_opacity: default 0
The minimum opacity for the heatmap.
max_opacity: default 0.6
The maximum opacity for the heatmap.
scale_radius: default False
Scale the radius of the points based on the zoom level.
gradient: dict, default None
Match point density values to colors. Color can be a name ('red'),
RGB values ('rgb(255,0,0)') or a hex number ('#FF0000').
use_local_extrema: default False
Defines whether the heatmap uses a global extrema set found from the input data
OR a local extrema (the maximum and minimum of the currently displayed view).
auto_play: default False
Automatically play the animation across time.
display_index: default True
Display the index (usually time) in the time control.
index_steps: default 1
Steps to take in the index dimension between animation steps.
min_speed: default 0.1
Minimum fps speed for animation.
max_speed: default 10
Maximum fps speed for animation.
speed_step: default 0.1
Step between different fps speeds on the speed slider.
position: default 'bottomleft'
Position string for the time slider. Format: 'bottom/top'+'left/right'.
overlay : bool, default True
Adds the layer as an optional overlay (True) or the base layer (False).
control : bool, default True
Whether the Layer will be included in LayerControls.
show: bool, default True
Whether the layer will be shown on opening (only for overlays).
"""
_template = Template(
"""
{% macro script(this, kwargs) %}
var times = {{this.times}};
{{this._parent.get_name()}}.timeDimension = L.timeDimension(
{times : times, currentTime: new Date(1)}
);
var {{this._control_name}} = new L.Control.TimeDimensionCustom({{this.index}}, {
autoPlay: {{this.auto_play}},
backwardButton: {{this.backward_button}},
displayDate: {{this.display_index}},
forwardButton: {{this.forward_button}},
limitMinimumRange: {{this.limit_minimum_range}},
limitSliders: {{this.limit_sliders}},
loopButton: {{this.loop_button}},
maxSpeed: {{this.max_speed}},
minSpeed: {{this.min_speed}},
playButton: {{this.play_button}},
playReverseButton: {{this.play_reverse_button}},
position: "{{this.position}}",
speedSlider: {{this.speed_slider}},
speedStep: {{this.speed_step}},
styleNS: "{{this.style_NS}}",
timeSlider: {{this.time_slider}},
timeSliderDrapUpdate: {{this.time_slider_drap_update}},
timeSteps: {{this.index_steps}}
})
.addTo({{this._parent.get_name()}});
var {{this.get_name()}} = new TDHeatmap({{this.data}},
{heatmapOptions: {
radius: {{this.radius}},
blur: {{this.blur}},
minOpacity: {{this.min_opacity}},
maxOpacity: {{this.max_opacity}},
scaleRadius: {{this.scale_radius}},
useLocalExtrema: {{this.use_local_extrema}},
defaultWeight: 1,
{% if this.gradient %}gradient: {{ this.gradient }}{% endif %}
}
})
.addTo({{this._parent.get_name()}});
{% endmacro %}
"""
)
default_js = [
(
"iso8601",
"https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js",
),
(
"leaflet.timedimension.min.js",
"https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.min.js",
),
(
"heatmap.min.js",
"https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/pa7_hm.min.js",
),
(
"leaflet-heatmap.js",
"https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/pa7_leaflet_hm.min.js",
),
]
default_css = [
(
"leaflet.timedimension.control.min.css",
"https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.control.css",
)
]
def __init__(
self,
data,
index=None,
name=None,
radius=15,
blur=0.8,
min_opacity=0,
max_opacity=0.6,
scale_radius=False,
gradient=None,
use_local_extrema=False,
auto_play=False,
display_index=True,
index_steps=1,
min_speed=0.1,
max_speed=10,
speed_step=0.1,
position="bottomleft",
overlay=True,
control=True,
show=True,
):
super().__init__(name=name, overlay=overlay, control=control, show=show)
self._name = "HeatMap"
self._control_name = self.get_name() + "Control"
# Input data.
self.data = data
self.index = (
index if index is not None else [str(i) for i in range(1, len(data) + 1)]
)
if len(self.data) != len(self.index):
raise ValueError(
"Input data and index are not of compatible lengths."
) # noqa
self.times = list(range(1, len(data) + 1))
# Heatmap settings.
self.radius = radius
self.blur = blur
self.min_opacity = min_opacity
self.max_opacity = max_opacity
self.scale_radius = "true" if scale_radius else "false"
self.use_local_extrema = "true" if use_local_extrema else "false"
self.gradient = gradient
# Time dimension settings.
self.auto_play = "true" if auto_play else "false"
self.display_index = "true" if display_index else "false"
self.min_speed = min_speed
self.max_speed = max_speed
self.position = position
self.speed_step = speed_step
self.index_steps = index_steps
# Hard coded defaults for simplicity.
self.backward_button = "true"
self.forward_button = "true"
self.limit_sliders = "true"
self.limit_minimum_range = 5
self.loop_button = "true"
self.speed_slider = "true"
self.time_slider = "true"
self.play_button = "true"
self.play_reverse_button = "true"
self.time_slider_drap_update = "false"
self.style_NS = "leaflet-control-timecontrol"
def render(self, **kwargs):
super().render(**kwargs)
figure = self.get_root()
assert isinstance(
figure, Figure
), "You cannot render this Element if it is not in a Figure."
figure.header.add_child(
Element(
"""
<script>
var TDHeatmap = L.TimeDimension.Layer.extend({
initialize: function(data, options) {
var heatmapCfg = {
radius: 15,
blur: 0.8,
maxOpacity: 1.,
scaleRadius: false,
useLocalExtrema: false,
latField: 'lat',
lngField: 'lng',
valueField: 'count',
defaultWeight : 1,
};
heatmapCfg = $.extend({}, heatmapCfg, options.heatmapOptions || {});
var layer = new HeatmapOverlay(heatmapCfg);
L.TimeDimension.Layer.prototype.initialize.call(this, layer, options);
this._currentLoadedTime = 0;
this._currentTimeData = {
data: []
};
this.data= data;
this.defaultWeight = heatmapCfg.defaultWeight || 1;
},
onAdd: function(map) {
L.TimeDimension.Layer.prototype.onAdd.call(this, map);
map.addLayer(this._baseLayer);
if (this._timeDimension) {
this._getDataForTime(this._timeDimension.getCurrentTime());
}
},
_onNewTimeLoading: function(ev) {
this._getDataForTime(ev.time);
return;
},
isReady: function(time) {
return (this._currentLoadedTime == time);
},
_update: function() {
this._baseLayer.setData(this._currentTimeData);
return true;
},
_getDataForTime: function(time) {
delete this._currentTimeData.data;
this._currentTimeData.data = [];
var data = this.data[time-1];
for (var i = 0; i < data.length; i++) {
this._currentTimeData.data.push({
lat: data[i][0],
lng: data[i][1],
count: data[i].length>2 ? data[i][2] : this.defaultWeight
});
}
this._currentLoadedTime = time;
if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
this._update();
}
this.fire('timeload', {
time: time
});
}
});
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
initialize: function(index, options) {
var playerOptions = {
buffer: 1,
minBufferReady: -1
};
options.playerOptions = $.extend({}, playerOptions, options.playerOptions || {});
L.Control.TimeDimension.prototype.initialize.call(this, options);
this.index = index;
},
_getDisplayDateFormat: function(date){
return this.index[date.getTime()-1];
}
});
</script>
""", # noqa
template_name="timeControlScript",
)
)
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]].
"""
bounds = [[None, None], [None, None]]
for point in self.data:
bounds = [
[
none_min(bounds[0][0], point[0]),
none_min(bounds[0][1], point[1]),
],
[
none_max(bounds[1][0], point[0]),
none_max(bounds[1][1], point[1]),
],
]
return bounds