folium 中线条的动态样式
Dynamic styling of lines in folium
我一直在尝试理解来自 folium 的 TimestampedGeoJson
插件。
我想绘制随时间改变颜色的线条。目前,我所做的是每次需要更改颜色时完全重画一条线,这会带来巨大的开销。
另一个问题是如何在功能中指定时间。目前,我有这个例子:
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=10
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'red'
},
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:20:00'
],
'color': 'blue'
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'style': {
'color': d['color'],
'weight': d['weight'] if 'weight' in d else 5
}
}
}
for d in data
]
TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT1M', add_last_point=True).add_to(m)
m.save('dynamic4.html')
对我来说,第一个日期没有任何意义,但显然它是必需的,否则浏览器将不会绘制任何东西。
所以:
a) 如何在不重新绘制线条的情况下更改样式?
b) 时间是什么意思?如何指定一致的时间顺序?
我会首先尝试单独解决您的问题,然后我会提供一个完整的解决方案来说明我最终会做什么。本质上:
- 更改 TimestampedGeoJson _template 变量以更改 style_function 并将使其能够使样式动态化
- 确保 TimestampedGeoJson 数据中的每个坐标都有一个时间步
- 为避免混淆,尽量不要重叠特征或特征在特定时间步丢失数据
- 我相信在你的场景中,你只有一个特征,但会在不同的时间步改变颜色
解决您的问题:
a) 如何在不重画线条的情况下更改样式?
我认为 folium 本身是不可能的,有必要将 style_function 传递给 TimestampedGeoJson,目前它甚至不是 class init 的参数。这样做似乎很难,因为您需要将 python style_function 翻译成 javascript style_function.
有一个简单的解决方法。在 class definition of TimestampedGeoJson 内部,它使用 _template 变量作为 javascript 代码的字符串模板,因此您可以根据需要调整此模板,但使用 javascript.
class TimestampedGeoJson(MacroElement):
.... hidding lines to save space
_template = Template("""
{% macro script(this, kwargs) %}
.... hidding lines to save space
style: function (feature) {
return feature.properties.style;
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
{% endmacro %}
""") # noqa
..... hidding lines to save space
因此,要在每个时间步更改线条颜色,您可以更改模板的这一部分:
style: function (feature) {
return feature.properties.style;
},
由此:遍历颜色数组
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[0]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
更改它以便在每个时间步更新 properties.style 内的颜色。
b) 时间是什么意思?如何指定一致的时间顺序?
TimestampedGeoJson 正在使用库 Leaflet.TimeDimension,所以 TimestampedGeoJson 对应 L.TimeDimension.Layer.GeoJSON.
从该文档中您得到
"coordTimes, times or linestringTimestamps: array of times that can be associated with a geometry (datestrings or ms). In the case of a LineString, it must have as many items as coordinates in the LineString."
所以本质上要保持一致,只需确保
1.对于每个特征,时间大小与坐标相同,并且
2. 使用有效的日期字符串或 ms 格式
3. 如果您的日期增加一个常数,请将您的期间设置为该值
综上所述,我主要在你之前的例子中做了改动:
1) 添加了一个带有新 style_function 的 _template 变量并更改了 TimestampedGeoJson 默认模板
2) 稍微改变了两个特征的坐标,以表明您设置的两个特征是重叠的,并且在某些时间步长中,在某些时间步长中,只定义了第一个特征,后来只定义了第二个特征,所以它让人感到困惑每个时间步都在发生。
3) 为每个特征添加了要循环的颜色列表
from jinja2 import Template
_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) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[currIdx+1]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
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 %}
""")
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=9
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'brown',
'colors':["black","orange","pink"],
},
{
'coordinates': [
[-4.058876661, 43.11843382],
[-4.936537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'blue',
'colors':["red","yellow","green"],
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'color': d["color"],
'colors':d["colors"]
}
}
for d in data
]
t=TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT10H', add_last_point=True)
t._template=_template
t.add_to(m)
m.save('original.html')
我一直在尝试理解来自 folium 的 TimestampedGeoJson
插件。
我想绘制随时间改变颜色的线条。目前,我所做的是每次需要更改颜色时完全重画一条线,这会带来巨大的开销。
另一个问题是如何在功能中指定时间。目前,我有这个例子:
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=10
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'red'
},
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:20:00'
],
'color': 'blue'
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'style': {
'color': d['color'],
'weight': d['weight'] if 'weight' in d else 5
}
}
}
for d in data
]
TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT1M', add_last_point=True).add_to(m)
m.save('dynamic4.html')
对我来说,第一个日期没有任何意义,但显然它是必需的,否则浏览器将不会绘制任何东西。
所以:
a) 如何在不重新绘制线条的情况下更改样式? b) 时间是什么意思?如何指定一致的时间顺序?
我会首先尝试单独解决您的问题,然后我会提供一个完整的解决方案来说明我最终会做什么。本质上:
- 更改 TimestampedGeoJson _template 变量以更改 style_function 并将使其能够使样式动态化
- 确保 TimestampedGeoJson 数据中的每个坐标都有一个时间步
- 为避免混淆,尽量不要重叠特征或特征在特定时间步丢失数据
- 我相信在你的场景中,你只有一个特征,但会在不同的时间步改变颜色
解决您的问题:
a) 如何在不重画线条的情况下更改样式?
我认为 folium 本身是不可能的,有必要将 style_function 传递给 TimestampedGeoJson,目前它甚至不是 class init 的参数。这样做似乎很难,因为您需要将 python style_function 翻译成 javascript style_function.
有一个简单的解决方法。在 class definition of TimestampedGeoJson 内部,它使用 _template 变量作为 javascript 代码的字符串模板,因此您可以根据需要调整此模板,但使用 javascript.
class TimestampedGeoJson(MacroElement):
.... hidding lines to save space
_template = Template("""
{% macro script(this, kwargs) %}
.... hidding lines to save space
style: function (feature) {
return feature.properties.style;
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
{% endmacro %}
""") # noqa
..... hidding lines to save space
因此,要在每个时间步更改线条颜色,您可以更改模板的这一部分:
style: function (feature) {
return feature.properties.style;
},
由此:遍历颜色数组
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[0]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
更改它以便在每个时间步更新 properties.style 内的颜色。
b) 时间是什么意思?如何指定一致的时间顺序?
TimestampedGeoJson 正在使用库 Leaflet.TimeDimension,所以 TimestampedGeoJson 对应 L.TimeDimension.Layer.GeoJSON.
从该文档中您得到
"coordTimes, times or linestringTimestamps: array of times that can be associated with a geometry (datestrings or ms). In the case of a LineString, it must have as many items as coordinates in the LineString."
所以本质上要保持一致,只需确保 1.对于每个特征,时间大小与坐标相同,并且 2. 使用有效的日期字符串或 ms 格式 3. 如果您的日期增加一个常数,请将您的期间设置为该值
综上所述,我主要在你之前的例子中做了改动: 1) 添加了一个带有新 style_function 的 _template 变量并更改了 TimestampedGeoJson 默认模板
2) 稍微改变了两个特征的坐标,以表明您设置的两个特征是重叠的,并且在某些时间步长中,在某些时间步长中,只定义了第一个特征,后来只定义了第二个特征,所以它让人感到困惑每个时间步都在发生。
3) 为每个特征添加了要循环的颜色列表
from jinja2 import Template
_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) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[currIdx+1]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
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 %}
""")
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=9
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'brown',
'colors':["black","orange","pink"],
},
{
'coordinates': [
[-4.058876661, 43.11843382],
[-4.936537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'blue',
'colors':["red","yellow","green"],
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'color': d["color"],
'colors':d["colors"]
}
}
for d in data
]
t=TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT10H', add_last_point=True)
t._template=_template
t.add_to(m)
m.save('original.html')