以Folium實現時序熱點圖圖層疊加與經緯度打點分群顯示

Folium、Python、HeatMapWithTime、MarkerCluster。

Reference:https://github.com/python-visualization/folium/issues/1062https://blog.yeshuanova.com/2017/10/python-visulization-folium/

Folium通常被用來實現資料視覺化,最近剛好在執行一些大數據視覺化的專案有用到,藉這機會將幾個比較難實現與稀有的作法記錄下來。

會特別紀錄這個是因為這項顯示方式需要額外實現一些code才有辦法達成。

以下將會從資料引入→簡單熱點圖→時序熱點圖→多時序熱點圖共用同一個時間控制軸逐一呈現+markerCluster打點Aggregate呈現。

我們先將實驗的資料引入並轉成list,資料內容長這樣:

資料樣貌
import folium
from folium.plugins import HeatMap
from folium.plugins import HeatMapWithTime
import pandas as pd
import numpy as np

fileName01 = "heatMapWithTimePlus//sampleData1.csv"
fileName02 = "heatMapWithTimePlus//sampleData2.csv"
fileName03 = "heatMapWithTimePlus//sampleData3.csv"
fileName04 = "heatMapWithTimePlus//sampleData4.csv"
fileName05 = "heatMapWithTimePlus//sampleData5.csv"
fileName06 = "heatMapWithTimePlus//sampleData6.csv"
df01 = pd.read_csv(fileName01, delimiter=",", header = None)
df02 = pd.read_csv(fileName02, delimiter=",", header = None)
df03 = pd.read_csv(fileName03, delimiter=",", header = None)
df04 = pd.read_csv(fileName04, delimiter=",", header = None)
df05 = pd.read_csv(fileName05, delimiter=",", header = None)
df06 = pd.read_csv(fileName06, delimiter=",", header = None)
heatData01 = df01.values.tolist()
heatData02 = df02.values.tolist()
heatData03 = df03.values.tolist()
heatData04 = df04.values.tolist()
heatData05 = df05.values.tolist()
heatData06 = df06.values.tolist()

引入後,就可以拿一個檔案當作熱點圖先打出來看看,結果如下:

fmap = folium.Map(location=[23.712326, 120.804037], zoom_start=12, control_scale=True)
fmap.add_child(HeatMap(data=heatData01))
fmap
熱點圖

接著我們拿heatData01~heatData03的資料分別代表06:00、08:00、10:00的資料,產生時序熱點圖,結果如下:

Heat_data = []
filterCount = 1 #過濾條件
#Heat_data.append(df01[df01.iloc[:, 2]>filterCount].values.tolist()) #過濾資料用
Heat_data.append(df01.values.tolist())
Heat_data.append(df02.values.tolist())
Heat_data.append(df03.values.tolist())

heatMapWithTimeIndex = ['06:00','08:00','10:00']

fmap = folium.Map(location=[23.712326, 120.804037], zoom_start=12, control_scale=True)
fmap.add_child(HeatMapWithTime(Heat_data,radius=30,index=heatMapWithTimeIndex)) # 顯示連續熱度圖  
fmap
時序熱點圖

接著就是很多人也在詢問的,多張時序熱點圖打在同一張地圖上,但如果沒客製化會發生什麼事情?就像下面這樣,會多一個控制軸:

Heat_data = []
Heat_data2 = []
filterCount = 1 #過濾條件
#Heat_data.append(df01[df01.iloc[:, 2]>filterCount].values.tolist()) #過濾資料用
Heat_data.append(df01.values.tolist())
Heat_data.append(df02.values.tolist())
Heat_data.append(df03.values.tolist())
Heat_data2.append(df04.values.tolist())
Heat_data2.append(df05.values.tolist())
Heat_data2.append(df06.values.tolist())

heatMapWithTimeIndex = ['06:00','08:00','10:00']

fmap = folium.Map(location=[23.712326, 120.804037], zoom_start=12, control_scale=True)
fmap.add_child(HeatMapWithTime(Heat_data,radius=30,index=heatMapWithTimeIndex)) # 顯示連續熱度圖  
fmap.add_child(HeatMapWithTime(Heat_data2,radius=30,index=heatMapWithTimeIndex)) # 顯示連續熱度圖  
fmap
多一個控制軸

但我們只需要一個控制軸,並搭配想要的時序熱點圖做切換控制,此時必須額外刻一個template去處理,將第一個heatmap之後的內容使用這個template加入。

#https://github.com/python-visualization/folium/issues/1062
#heatMapWithTime打在同一個圖層上使用同一個scroll bar

from jinja2 import Template
from folium.map import Layer
class HeatMapWithTimeAdditional(Layer):
    _template = Template("""
        {% macro script(this, kwargs) %}
            var {{this.get_name()}} = new TDHeatmap({{ this.data }},
                {heatmapOptions: {
                    radius: {{this.radius}},
                    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 %}
    """)

    def __init__(self, data, name, radius=15,
                 min_opacity=0, max_opacity=0.6,
                 scale_radius=False, gradient=None, use_local_extrema=False,
                 overlay=True, control=True, show=True):
        super(HeatMapWithTimeAdditional, self).__init__(
            name=name, overlay=overlay, control=control, show=show
        )
        self._name = name
        self.data = data

        # Heatmap settings.
        self.radius = radius
        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

接著我們將兩個時序熱點圖加入,同時加入一個markerCluster到地圖上顯示(目的是一併呈現Folium將點自動aggregate顯示的功能),markFlag.csv使用的內容格式同sampleData系列。

markerGroup = folium.FeatureGroup(control=True, show=False)
markerCluster = folium.plugins.MarkerCluster(name='markFlag', show=False).add_to(markerGroup)

#加入markFlag
markFlag = "heatMapWithTimePlus//markFlag.csv"
with open(markFlag, encoding="utf-8") as fr:
    for line in fr:
        each = line[:-1].split(',')
        
        lat    = each[0]
        lng    = each[1]
        number = each[2]
        div = folium.plugins.BeautifyIcon(
                             icon="arrow-down",
                             icon_shape="marker",
                             number=str(number), #顯示檔案第三欄的數字
                             text_color="black",
                             background_color="white"
        )
        folium.Marker(location=[lat,lng],icon=div).add_to(markerCluster)

fmap = folium.Map(location=[23.712326, 120.804037], zoom_start=12, control_scale=True)
fmap.add_child(HeatMapWithTime(Heat_data,radius=30,index=heatMapWithTimeIndex,name='時序熱點圖1', show=False)) # 顯示連續熱度圖
#第二個heatmapwithtime要用hack的方式
HeatMapWithTimeAdditional(Heat_data2, name='時序熱點圖2', show=False).add_to(fmap)
fmap.add_child(markerCluster)
folium.LayerControl(collapsed=False).add_to(fmap)

fmap
單控制軸控制多個時序熱點圖

這樣就完成了。