臺北市即時交通流量(VD)之下載與解讀

 

(Focus-on-Air-Quality)

背景

  • 臺北市交通流量VD偵測結果每5分鐘公開在網站上,隨即覆蓋並無歷史數據可供自由下載,因此需設計自動定期下載程式以自行保存數據。
  • 數據之性質、提供、以及連結說明詳交通量數據檔案連結
  • 此處專注在數據檔的下載與解讀。
  • 主控檔TaipeiGetVD_cron.cs包括下載、解壓縮(gzip)、解讀(getVD.py)及儲存等所有程序。
  • 自動執行方式
    • 無限制迴圈。分鐘數為5的倍數時執行,否則休眠1分鐘。此法當系統關機再開時還必須手動方式重新啟動。
    • crontab的設定:也設定每分鐘執行,延用同樣邏輯。重開機後系統自動啟動,不需人工操作。
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
  *  *  *  *  * kuang /home/backup/data/ETC/TaipeiVD/TaipeiGetVD_cron.cs

壓縮檔之下載、解壓縮

下載

  • 每分鐘執行:清除舊檔
  • 每5分鐘執行,也可以*/5方式控制crontab的頻率,此處延用非crontab版本方式,自行研判是否執行。
    • 研判:分鐘數除5的餘數(M=$(( $A % 5 )))為0
  • 以wget來取得檔案
kuang@master /home/backup/data/ETC/TaipeiVD
$ cat TaipeiGetVD_cron.cs
cd /home/backup/data/ETC/TaipeiVD
if [ -f latest ];then rm latest;fi
if [ -f GetVDDATA* ];then rm GetVDDATA*;fi
A=`date +%M`
M=$(( $A % 5 ))
if [ $M = 0 ] ;then
ymd=`date  --rfc-3339='date'`

wget https://tcgbusfs.blob.core.windows.net/blobtisv/GetVDDATA.xml.gz

解壓縮

  • 使用gzip來解壓縮
  • 將xml更名為GetVDATA,符合程式內設
gzip -d GetVDDATA.xml.gz
mv GetVDDATA.xml GetVDDATA

執行內容解讀與儲存

  • 執行getVD.py來解讀內容,存成csv格式
  • 使用cat指令,將每5分鐘的VD數據附加到每日檔案之後
...
Y=`date +%Y`
mkdir -p $Y
...
python getVD.py
cat latest>>$Y/$ymd

VD數據之解讀

xml檔案特性

  • 基本上VD偵測數據是一個xml檔案
    • 其變數名稱會以前後包夾<VARNAME>...</VARNAME>形態,建立名稱與其值的關係,
    • 變數的值也可以包括其他的變數,類似集合的概念,將變數予以分組、建立從屬目錄架構。
    • 由於其數據儲存相對較為緊密,常常用來儲存即時更新之數據,以減少檔案體積。
    • 並無統一格式可遵循、無組織(每一個監測點有不同資料項目數量)、
    • 也不必每一個欄位均有完整的欄位名稱及數值配對、(亦即有不少欄位是從來不會有內容的)、
    • 每一次網頁只有寫一次時間,必須另外加一時間之資料項目與內容到資料庫內。
    • 範例如下
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<VDInfoSet>
    <ExchangeTime>2022/10/12T15:21:02</ExchangeTime>
    <VDInfo>
        <VDData>
            <VDDevice>
                <DeviceID>V3MER00</DeviceID>
                <TimeInterval>5</TimeInterval>
                <TotalOfLane>3</TotalOfLane>
                <LaneData>
                    <LaneNO>0</LaneNO>
                    <Volume>67.0</Volume>
                    <AvgSpeed>39.298508</AvgSpeed>
                    <AvgOccupancy>8.6</AvgOccupancy>
                    <Svolume>12.0</Svolume>
                    <Mvolume>50.0</Mvolume>
                    <Lvolume>5.0</Lvolume>
                </LaneData>
                <LaneData>
                    <LaneNO>1</LaneNO>
                    <Volume>26.0</Volume>
...                    

getVD.py程式說明

  • 使用etree模組來解讀
    • cElementTree與ElementTree差別在實現過程是否使用C語言副程式,這會使程式的記憶體較小一些,效率好一些。
    • 檢查tree的內容(dir(tree))可以使用
      • tree.getroot()、
      • elem.tag、
      • elem.attrib、
      • elem.text等指令來探索要解析的XML檔案,
    • 如範例程式中就是運用[elem.text for elem in tree.iter()]指令,將所有網頁內容(數據內容部分)存成序列ttxt。
  • key element
    • 經過摸索之後,發現監測資料乃是以監測設施id(DeviceID)為中心,
    • 有的設施有多條線道、有的只有單線道,其TotalOfLane不同(ttl),
    • 而其有用的資訊除了時間(須另加)以外,其餘皆以循序方式,接在id的後面(以ist起始),必須以循序的方式逐一讀取。
  • cols(資料表頭)
    • 元件數目雖然在每個監測點會略有不同,但順序總是相同,
    • 數量總是8的倍數(*總車道數TotalOfLane),正好是7個變數與1個無用的LaneData共8個變數位置。
  • dictionary d
    • 順序做好了,剩下就是序列名稱與對應關係了。
    • 此處先宣告9個空白、相同名字的2維序列,依序納入id、總車道數、以及其後的7個變數內容,
    • 這樣在編列成字典時就不需要再繁瑣撰寫,只要逐一將字典update即可。
  • 偵測時間
    • 如前所述,網頁只會在表頭處(第2個項目,即ttxt[1])寫一次時間 ‘ExchangeTime’,而不是每個id都會重複,並不符合資料庫型態,
    • 為了避免在讀取過程中造成干擾,因此在讀完之後再加一個字典項目即可。
  • 最後以csv形式輸出以利檔案整併。

執行結果檔案範例

  • 每5分鐘表頭會重複出現,使用時要記得予以剔除。
DeviceID,TotalOfLane,LaneNO,Volume,AvgSpeed,AvgOccupancy,Svolume,Mvolume,Lvolume,ExchangeTime
V8010A1,3,0,5.0,58.0,0.8,4.0,1.0,0.0,2022/10/13T23:56:02
V8010A1,3,1,19.0,59.210526,5.0,19.0,0.0,0.0,2022/10/13T23:56:02
V8010A1,3,2,14.0,50.785713,2.4,10.0,4.0,0.0,2022/10/13T23:56:02
V1221E0,1,0,13.0,72.23077,1.0,11.0,0.0,2.0,2022/10/13T23:56:02
...
  • 個別站每5分鐘數據
    • 範例偵測站有0~2共3筆道路數據。(3線道)
    • 處理時間約與偵測數據約晚了4分鐘左右
    • 各線道由內向外編號,0車道車速最快,編號越大平均車速越低。
kuang@master /home/backup/data/ETC/TaipeiVD
$ grep VP8GI20 2022/2022-10-14 |H
VP8GI20,3,0,25.0,56.8,5.0,22.0,2.0,1.0,2022/10/13T23:56:02
VP8GI20,3,1,47.0,42.957447,5.4,13.0,32.0,2.0,2022/10/13T23:56:02
VP8GI20,3,2,12.0,24.75,2.2,6.0,5.0,1.0,2022/10/13T23:56:02
VP8GI20,3,0,15.0,48.6,3.4,13.0,1.0,1.0,2022/10/14T00:01:02
VP8GI20,3,1,35.0,45.714287,5.0,20.0,14.0,1.0,2022/10/14T00:01:02
...

歷年執行成果

  • 只要工作站與網路正常連線運作,數據就會自動累積。
  • 2017開始運作之後迄今(2022-10-14)的執行筆數如下
#kuang@master /home/backup/data/ETC/TaipeiVD
$ for i in {17..22};do echo 20$i $(ls 20$i/*|wc -l);done
2017 27
2018 320
2019 365
2020 366
2021 363
2022 268

getVD.py程式碼

kuang@master /home/backup/data/ETC/TaipeiVD
$ cat getVD.py
from pandas import *
import xml.etree.cElementTree as ET
fname='GetVDDATA'
tree=ET.ElementTree(file=fname)
ttxt=[elem.text for elem in tree.iter()]
id=set([elem.text for elem in tree.iter(tag='DeviceID')])
cols=['DeviceID','TotalOfLane','LaneNO','Volume','AvgSpeed','AvgOccupancy','Svolume','Mvolume','Lvolume']
v=[]
for i in xrange(len(cols)):
    v.append([])
for i in id:
    ist=ttxt.index(i)
    ttl=int(ttxt[ist+2])
    for j in xrange(ttl):
        v[0].append(i)
        v[1].append(ttl)
        istt=ist+4+j*8
        for k in xrange(2,len(cols)):
            v[k].append(ttxt[istt+k-2])
d={}
for i in xrange(len(cols)):
    d.update({cols[i]:v[i]})
cols.append('ExchangeTime')
d.update({cols[-1]:[ttxt[1] for x in xrange(len(v[0]))]})
df=DataFrame(d)
df[cols].set_index('DeviceID').to_csv('latest')