Mini Taiwan 3D

Mini Taiwan 3D

前言

草薙昭彦 2019 年推出了超強視覺化專案:Mini Tokyo 3D,還很佛心的開源。原本就對鐵路資料視覺化滿有興趣,又最近剛好看到這個完成度如此高的東東,當然要來研究一下其背後程式架構設計,看看是否有機會套用台灣鐵路資料上去。於是寫了本篇記錄。

資料分析

Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.
– Fred Brooks

首先要來研究一下,Mini Tokyo 3D 是如何存儲火車相關資料,看了一下原始檔可以總結出以下類別(跟火車無關的先不列出):

  • train-timetables/ 按車次編排的時刻表,可選加入直通運轉車次資料。
  • coordinates.json 記錄軌道位置資訊,格式跟 GeoJson 不太一樣。
  • poi.json 車站出口資訊。
  • rail-direction.json 記錄車次走向的翻譯文。
  • railway.json 記錄軌道車站列表。
  • station-groups.json 儲存車站繪製時,需要要黏在一起的車站群。
  • stations.json 記錄所有車站資訊(繪製時程式會自動將車站座標移動到軌道線上)。
  • train-types.json 記錄車種別翻譯文。
  • train-vehicles.json 記錄車廂繪製顏色。顏色列表依序指定:車頂、側邊中線、側邊下緣、前後面。

名稱取得相當直覺。接下來要來理解其中的 ID 配置。

線路繪製需要:每一條線路分配一條軌道 ID。相同車站為不同軌各自分配車站 ID,再使用 Station Groups 做分群。

時刻表資料需要:同一輛車在不同線路不同運行日下需要各自分配時刻表 ID。若這輛車會接續其他線路繼續行駛,則使用 nt(next)跟 pt(previous)連結時刻表 ID。該車輛可以額外設定終點站 ID、運行走向 ID、車種 ID(如區間、快速等)、車廂 ID(預設跟軌道同色)等資訊。時刻表需要在 tt 中設定每一個車站的到站時刻,而終點站只需要設定抵達時刻。

自己猜半天之後,發現原始碼 data-classes/ 底下的註解還滿完整的。日本鐵路系統相當複雜,一定程度上可以 Cover 掉所有鐵路運行情形,想說速速將資料全部換成台灣的就可以搞定上線⋯⋯殊不知轉檔地獄才正要開始。

台灣資料轉換

台灣 TDX 給的資料格式相當參差不齊啊(崩潰!還有寫錯字。結果轉檔花了蠻多時間處理,底下記錄下一些雷點。

台鐵的複複線?

台鐵的線路座標資料不確定是根據什麼基準量測,蠻多很接近的小片段,方向也不一致。需要自己寫解析器去按線段各自整理成一長條完整的座標線。演算法很簡單:

  1. 減少整體線段點數以提升繪製效能。折角超過 1 度或是距離超過 250 公尺才拿取下一點。
  2. 挑一條最長的做為線段串連起點,重複以下動作直到找不到下一條可以銜接:
    • 頭尾接龍:取頭尾最靠近且幾乎為接續直線的線段往下拼湊(若線段走向相反要順便反向)
    • 平行線接續:在頭尾處尋找非常靠近且幾乎平行的線,從中砍一刀,將目前線段接上去。然後捨棄另一半幾乎重疊的線段。

搞定!

<台鐵線路複雜示意圖>
台鐵成追線跟兩條西部幹線交匯處。(大圓為線段起點)

台鐵的類直通運轉

在日本,列車路線是按照行駛區間來區分路線名稱,原則上都有自己獨立軌道,就算和別人共用軌道也會另外取一條線路名稱,例如:東京上野 Line。但台鐵目前只有按照行走地理位置區分成:西部幹線(WL)、海線(WL-C)、南迴線(SL)、⋯⋯等,也就沒有另外為每個區間命名。若直接拿地理路線當成行駛區間路線會遇到一些問題。

例如,台鐵許多南北橫跨超長區間運營的列車。於是寫了一段演算法,將長途列車拆分成區間與區間的直通運轉。

台鐵循環區間??

又例如,海線 1006 自強號會依序行駛以下時刻表(前綴為時刻表 ID):

  • WL.1006 西部幹線。指定 ntWL-C.1006
  • WL-C.1006 海線。指定 ptWL.1006ntWL.1006
  • WL.1006 再回到西部幹線。指定 ptWL-C.1006

前面分析過,同一天的同一班車會給定唯一 ID,又接續的前後時刻表會分別指定 ntpt。於是這種運行模式就會造成頭尾時刻表配到相同時刻表 ID 的問題,程式就無法辨識 WL-C.1006 是第一段還是最後一段。後來決定把西部幹線從台中切一刀,分成西部幹線南北兩段,以解決此問題。

直達車找嘸線路

後來又發現花蓮(位於東部幹線)有直達車開往南港(位於西部幹線-北段)。這會造成我的時刻表切割演算法出問題,因為中途站宜蘭到下一班南港之間,沒有共用路線可以銜接。於是乎,我又把東部幹線擅自延長到台北車站。

車站分群

延長路線時,需要為延伸段額外增加車站點。其實就是複製現有的,再修改一下車站 ID。最後要記得把這些車站在 Station Groups 中再次分組到同一群,否則滑鼠會點選不到重疊的車站。該 JSON 檔案中,使用了 2 層 List 來記錄相連的車站。最內層緊鄰的群將繪製成一坨長圓形。而外面層的則會為其繪製連通道連接。

另外,台鐵有兩個台北車站。除了基本的「台北」之外,也要記得把環島列車用的「臺北-環島」跟台北放同一群。

    軌道繪製內差設定

    高鐵的資料最為乾淨,台鐵的整理完之後,大概 2 個晚上就搞定。但為求地圖美觀,又額外花許多時間去處理線路重疊問題,包含以下區間:

    • 南港至浮洲地下段
    • 台鐵六家接近區間
    • 台鐵沙崙接近區間
    • 台鐵新左營接近區間

    從原本 Mini Tokyo 3D 成品中,可以觀察出 Zoom Level 越遠(數值越小)時,線路間隔會相應變寬,以避免緊鄰線路打架。研究了一番,發現竟然是程式直接內差計算出來的,共軌部分只需要存一組座標線——例如:前綴為 Base. 的軌道 ID,此特殊軌道只有被引用時會繪製,其他的只要再標記頭尾跟偏移量即可!原作者這塊處理的真的相當不錯,實在佩服。底下設定方式完全是我自己研究原始碼實驗出來的,有誤請多見諒。

    首先,一條線路可以拆成多段連續座標線,記錄於 sublines 陣列中。留意拆線段的時候,需要保持相同走向,否則火車追蹤功能會顛倒。又,每一小條 Subline 可以設定不同 type,整理如下表。

    Subline Type意義詳細說明
    main使用 coords 中指定的座標陣列繪製線路。若指定 startend,內差計算會在 Zoom Level 需要時,根據指定軌道 ID 將頭尾偏移 offset 整數倍。可分別加入 zoom 參數以指定分界 Zoom Level。
    subcoords 只需指定頭尾 2 點座標。中間線段會使用 startend 指定的軌道 ID 來計算。可選設定 offset(預設為 0 表示使用重疊的線路位置)及 altitude 以調整海拔。
    hybridZoom Out 時使用 sub 方式;Zoom In 時使用 main 方式來繪製線路。可指定 zoom 參數以設定 Zoom Level 的切換點。需指定 startend,Zoom Out 時會根據設定偏移整條路線 offset 倍。

    來看調整前後差異。

    <台鐵六家接近線間距調整比較圖>
    台鐵六家接近線間距調整。

    做為參考,上圖中台鐵六家線路線形狀設定為:

    {
        "id": "TR.LJ",
        "sublines": [
            {
                "type": "main",
                "coords": [新竹, ..., 竹中],
                "end": {"railway": "THSR.Main", "offset": 1, "zoom": 16}
            },
            {
                "type": "hybrid",
                "coords": [竹中, ..., 六家],
                "zoom": 16,
                "start": {"railway": "THSR.Main", "offset": 1, "zoom": 16},
                "end": {"railway": "THSR.Main", "offset": 1, "zoom": 16}
            }
        ]
    }

    這裡需要留意第一條 main 路線要設定 end 資訊,用以無縫接軌下一條 hybrid 線段。

    鐵路地下化!

    subline 設定中指定 altitude-1 的話,該鐵路線段就會繪製到地底下。altitude 設定若放在 subline 底下第一層表示設定整段海拔。若設定在 startend 中表示起終點的線路起伏。

    看成果!

    <台鐵高雄地下化區間截圖>
    台鐵高雄地下化區間。

    做為參考,上圖中台鐵高雄段路線形狀設定為:

    {
        "id": "TR.WL-S",
        "sublines": [
            { 台中, ..., 新左營 },
            {
                "coords": [新左營, ..., 後庄],
                "altitude": -1,
                "start": {"altitude": 0},
                "end": {"altitude": 0},
             },
             { 後庄, ..., 屏東 }
        ]
    }

    時刻表差異處理

    運行日調整。在日本,時刻表主要分成 3 個時段:平日、土(週六)、日祝(週日及國定假日)。但翻了一圈台鐵時刻表,發現 API 是一到日一天一天給說當天有沒有開。例如:196 車次是五六日開;191 車次是一六日開⋯⋯啊~~運行日模式太多,乾脆拆分成一天一天記錄好了。此改動需要額外修改原始碼中:獲取當天時刻表、跟製作每日時刻表的相關函式。

    又,台北捷運已經方便到乘客可以無視車次。結果從 API 拿到的質料也只剩下每一站的發車時刻表。只好再自己寫解析器,參考站間運行時間資料,將站間時刻表黏成一列一列捷運班車。車次就自己隨意流水號,然後車種全部設定成「區間」。另外,因為文湖線無法取得時刻表,所以就先讓它空晾在一邊。

    成果 Demo

    Mini Taiwan 3D

    題外話。由於台北的火車全部地下化,地上檢視時其實看不太到火車在跑 XD 又若切換成地下模式檢視,變成看不到建築物(台北 101 QQ)。無論如何都會使得截圖看起來頗無聊 >< 這就是無腦地下化之後的宿命嗎⋯⋯最喜歡看火車窗外風景的說。

    相似專案

    資料來源

    原始碼

    從 Mini Tokyo 3D 專案 Fork 過來修改的版本。master 用來持續更新原專案的新改動。台灣修改版本放在 dev/taiwan-map Branch 上。

    ukyouz/mini-taiwan-3d

    未來規劃

    • 新增台灣其他縣市的捷運路線
    • 新增 Live Cam 資料
    • 新增台灣航班資訊
    • 支援動態抓去當日時刻表
    • 支援誤點列車顯示

      References