2020.04.14 更新:昨天讀到了一篇文章:Why I’m not making COVID19 visualizations, and why you (probably) shouldn’t either。想了一下後覺得,確實,其實現有資料很難說是準確的,再加上不論確診人數增加或減少,我們一般人看到其實都是讓自己的心情七上八下而已。今天開始決定關閉 Demo 頁,這篇就當作是 Google Maps API 的筆記教學。
2020.03.30 更新:根據資料來源 JHU CSSE公告,他們不再更新 recovered 的資料,因此 Demo 頁面中,August 在今天也刪掉了所有「康復」的部份。
2020.02.13 更新:
新增段落「城市、國家名英翻中」。
新增段落「繪製圖表」。
新增段落「infowindow 監聽按鈕 click 事件」。
2020.02.10 更新:因為資料源今天公告改更新在 GitHub上,Google Sheets 只會更新到今天以前,因此本篇針對取資料、整理資料的部份,分為上、下二大段。
上段是接 Google Sheets 的、下段是接 GitHub 上 CSV 格式的。
因為前陣子 Sheets 的部份常有變動,不是增加列數就是減個欄位,上段用 Sheets 的截圖會跟實際有所出入,在此告知。
有用到的筆記文列表
這一篇是整合了之前幾篇筆記文,最後會實作出在 Google Map 上丟標記,標記這次新冠肺炎目前的分佈圖。
整理一下有用到的技術,都是之前寫過的幾篇筆記文,條列如下:
Google Maps API 部份
取得資料部份
繪製圖表部份
Google Sheets 部份
取得、整理資料
本段的資料來源是 Google Sheets,用 Google Apps Script 來取得資料。
Sheets 表格是長這樣:
Google Maps API 要丟上 Marker,必須要有經緯度,我們要存下經緯度。
表共有三個 Sheets:Confirmed、Recovered、Death。
處理資料時就要三個表的資料都取得、整理後,再回傳給 Client 端。
August 寫在 Google Apps Script 的程式碼是這樣子的,處理了要資料、整理資料、回傳資料這三個部份:
部署為網路應用程式後,用 Postman GET 產出的網址,得到的結果如下:
確認有了經緯度跟三個數值,接下來就是把經緯度丟到 Google Map 上。
將資料寫進 Google Maps API
這邊實作產出的 Google Map,是一般地圖 + 標記 + 熱圖,有幾個考量:
- 調用 API、畫出地圖要時間,在頁面加上一層 loading,才不會一進來看到一片白。
- 這次最嚴重的地方在湖北,map 的中心點就設為湖北。(2020.3 月改成義大利)
- 視覺重心在分佈的城市,因此地圖樣式要客製,隱藏大部份的景點標示 + 介面按鈕。
loading 效果
loading 的效果做了一個呼吸燈的樣子,先畫出一個蓋版的漸層,animation
的 keyframe
就是改變這個漸層的透明度。
CSS 如下:
繪製地圖、客製樣式
首先,確認地圖中心點在湖北(2020.3 月改成義大利):
let center = { lat: 30.97564, lng: 112.2707 };
接著開始把資料寫給 API:
把控制地圖的 UI 關掉,只留全螢幕的按鈕。
styles 是要寫客製的樣式,因為很長,這邊不列出來,最後可以從原始碼裡看到。
styles 的設定可以參考官方的說明文件:featureType
地圖上放置標記、info window、熱圖
這三件事寫在一起,因為都是在同一個迴圈內一起完成的。
由於資料的格式是 Object,先用了一行:
let keys = Object.keys(res);
把所有的 Key 都先用成陣列,在用這個陣列跑 forEach
迴圈。
這邊是丟資料給 API 的程式碼:
一併在迴圈中處理要給 Heat Map 的資料,這邊是接 Heat Map API 的程式碼:
最後要記得把 loading 給拿掉,就完成了這次的實作。
看了圖後才發現,這次竟然連芬蘭都有1個確診,也太可怕。
原始碼
接 Google Sheets 當資料部份的原始碼放在 Gist 上了,記得替換掉 token:
https://gist.github.com/letswritetw/f386028c675c43250722ed49d5d572b6
GitHub CSV 部份
取得資料
對方更新在 GitHub 上的資料格式是 CSV,進到 GitHub 的 專案 後會看到二個資料夾,都是這次肺炎的全球資料,只是整理的性質不同:
daily_case_updates 點進去,會是按日期來分檔案。
time_series 點進去,會像上段取 Google Sheets 一樣分為 confirmed、recovered、death 三個檔,這個是本篇實作會用的,所以選擇這個資料夾:
比方我們要取 time_series_2019-ncov-Confirmed.csv 這個的資料,點進去後,會看到 GitHub 把 CSV 檔給顯示成完整的表格:
要怎麼取得這個 CSV 的 URL 呢?點選上圖標紅框的「Row」就可以了,就會進到這個網址:
原本 August 也疑惑這個網址能不能取得資料,後來用 Postman 先用個 GET,竟然就真的取到了:
既然 Postman 取得到,那用 Google Apps Scripts也行,要煩惱的就是如何將 CSV 檔轉成 JSON 的格式。
轉換 CSV 成 JSON
如果 Google 一下,會看見很多搜尋結果都指向同一種寫法,如下:
接著就會看見地圖開始報錯,畫不出來,原因就出在這一行:
var headers = lines[0].split(',');
資料源裡面的城市名,有些本身就帶有 ,
,像是:Seattle, WA、Chicago, IL。
一用 ,
來切,就會一起切了出去。
由於 August 目前對正則表達式還沒有很熟,所以就用了比較笨的方法,就是先把 ,
給替換成 --
後,再來 split(',')
。修改過的並在 GAS 上執行的程式碼如下:
有了把 CSV 轉成 JSON 的 function 後,接下來在 GAS 上就是取得資料、整理並回傳。在 GAS 上寫的完整程式碼如下
一樣部署為網路應用程式後,用 Postman GET 的結果如圖:
可以看到跟在 Google Sheets 取到的資料排法不太一樣,每個城市/國家就是一個陣列。
將資料寫進 Google Maps API
這一段大致上跟上段 Google Sheets 的部份相同,不同的點在於 Client 取得 GAS 回傳的資料後,怎麼來整理?
以下是 August 整理的方式,可以參考:
上段的程式碼處理了整理資料、丟資料到 Google Maps API。
需要說明的是,這邊改用 Vue.js 來寫,才會看到 map
改成了 this.map
,很多變數也都加了 this
,因為都用成了 Vue.js 的 data
。
另外實作產出的頁面,這幾日也完成了右下方新增列表的卡片,點擊卡片上的城市/國家名時,會移到該城市/國家,並打開 infowindow 看數據的功能。才會多寫一個 responseData
來依照確診數排序。
城市、國家名英翻中
資料源的城市、國家名都是拼英的,不是中文,所以常常看到一些地名會沒概念是在哪裡,在地圖上看還好,因為還可以從地圖上標示的中文看到,但如果是看列表的話就不方便。
原本想直接找個套件來用,但由於套件得包含大部份的文字,因此往往很大一包,最小的一個在 npm 上有看到 3 點多 MB 的。
最後決定用手動整理對照表的方式來翻,笨了點,但檔案才不會過大拖垮讀取速度。
首先是列出一個陣列,裡面是一個個的物件,Key 是拼英,Value 是中文,列完後是這樣:
https://gist.github.com/letswritetw/3e9b4040e752bacacc57233f05cb4e70
接著,在取資料時,就寫一個翻譯的 function:
這種自己手寫對照表的,好處是檔案小,讀取快,壞處就是如果資料源有新增城市/國家,就每次都要再補。
為了防止資料面有新增時,程式這邊沒有列進對照中而出現空值,因此最後也寫了對照不到就回傳原拼音,以免列表中看不見文字。
2020.03.12 更新:
因為城市數愈來愈多,今天看破四百了,都把對照表寫進頁面裡會很長。
從今日開始,把對照表寫進 Google Sheets 裡,頁面實際是抓 Sheets 裡的資料來對照翻譯。
August 先用 Sheets 原有的翻譯功能來翻,翻起來很怪的再去 Google 查城市中文名。四百多筆有點多,如果有看到很怪的中文名,就代表還未更新到,請見諒。
繪製圖表
最後開發完成的,是點擊地圖上的標記後,加入一個「開啟圖表」按鈕,點下去會生成一個照日期來繪製的圖表,像這樣:
圖表的部份,這邊直接使用套件,推薦一套很好用又開源的 Chart.js。
它的參數好懂,又有許多的範例可以參考,繪製出來的圖表也漂亮。
圖表這邊比較麻煩的部份,就是要把資料的格式整理成圖表要的。以 Chart.js 來說,需要的格式是這樣:
data: { labels: ['January', 'February', 'March'], datasets: [{ label: 'My First dataset', backgroundColor: 'rgb(255, 99, 132)', borderColor: 'rgb(255, 99, 132)', data: [0, 10, 5] }] }
先建出一個 labels
,是圖表下方的說明,接著在 datasets.data
寫出對應這個 labels 的值。像是 August 的圖表預計放上確診、康復、死亡三個,那在 datasets
的陣列出就得寫三個物件進去。
整理資料成圖表要的格式就不列出了,用幾個迴圈整理就行。
這邊要特別寫的是,因為「開啟圖表」的按鈕是放在 infowindow上,要怎麼讓 infowindow 中的按鈕可以監聽 click
事件?
infowindow 監聽按鈕 click 事件
在跑迴圈時,infowindow 的 content 就支援 HTML,因此寫成以下:
在 button
的部份直接寫入 id="info-btn-${id}"
,讓每一個 infowindow 中的按鈕都可以有一個獨立的 id。
接著,是要監聽 infowindow 本身的事件,infowindow 有一個事件叫「domready
」,就是這個 infowindow 在 DOM 上就緒時,我們就寫一個當 domready
後,聽這個按鈕的 click
就行了,如下:
就這樣幾行就完成了監聽 infowindow 中的按鈕 click 事件。
請問有沒有抓取csv資料版本的程式碼?
沒有喔