這次的直播要做一個互動地圖,當滑鼠滑過的時候顯示當地天氣資訊!
這次的直播內容主要有幾個部分:
- 取得地圖的 svg 的檔案,並修改成我們可以用的檔案類型。
- 取得台灣的地圖資料,包含行政區域名稱與代號,並把資料對應到地圖上。
- 做出互動的頁面,包含滑鼠滑過時的移動、變色,還有右側的資料顯示。
筆記會著重在 3. 程式實作的部分,並把相關的資料與圖片連同程式碼一起放在 github 上面,供大家參考💻。
要跟著影片一起做也沒問題:
程式實作
主要會用到的工具與知識:
- HTML, CSS 與 JavaScript 的基礎觀念
- svg 的基本操作
- Vue.js 框架的操作
- 使用 axios 串接中央氣象局 open data API
讀取地圖,並操作樣式
要在我們的頁面讀取 svg 有幾種方法,使用 <img>
tag 讀取檔案,或是直接把 <svg>
包覆的內容直接貼在 html 裡面,但是如果直接讀取檔案的話就沒有辦法對 svg 進行操作,所以我們選擇後者。
svg 檔案的格式跟 html 很像,都是一層一層的往下排列,不過相對於 html 裡面的內容是 <html>
包覆所有物件,svg 則是在 <svg> 包覆繪圖軟體裡面使用的「群組」<g>
、「路徑」<path>
、「線段」<line>
或是 <ploygon>
、<circle>
圖形等元件。
讀進 svg 的 html 大概會長這樣:
<html> <head> ... </head> <body> <svg data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 595.28 841.89"> <defs /> <title> image.svg </title> <path id="161d ... 09.27h0Z" /> ... <path id="41139c2a ... 1.22-1.29Z" /> </svg> </body> </html>
我們先在 CSS 加上一些樣式與高度的限制,才不會讓圖片太大。另外把 svg 裡面的 path 元件加上顏色跟滑鼠移過(hover)時的變化。
這邊要注意,用 css 操作 svg 顏色的方式跟一般 dom 元件的方式不太一樣,如果要改變 path
的填色,不能用 background
,而是要用 fill
,線條的話不是 border
,而是 stroke
喔!想要知道更多 svg 的屬性與使用方式可以參考這個 CSS-TRICK 的整理:SVG Properties and CSS。
:root { --color-gold: #B99362; } body { background-color: #222 } svg { height: 100vh; } path { stroke: white; fill: transparent; transition: 0.5s; cursor: pointer; } path:hover { fill: var(--color-gold); transform: translate(-5px, -5px); }
到這邊,我們就有一個 hover 時會變色與區域浮起來的地圖囉!
篩選滑鼠移動過地區的地理資料
緊接著我們需要把地圖畫面跟資料連在一起,當滑動過某個地區的時候,要先知道現在滑鼠在哪一個縣市,才有辦法在畫面上顯示相對應的地理資訊~
我們先觀察一下 svg 裡面各個縣市 <path>
的結構,以台北市為例:
<path id="1e48e0bb-8964-4121-b347-b900162cf771" data-name="taipei_city" class="96fdfe13-4732-40bb-9e9c-cdc6e310fcb9" d="M466.27,77.17,465.42,79l-.85.85-.24.49-.85,1v1.83l-1.22.73L462,85.47l.49,1.59,1.22.85,3.9.49,2.44,2.32,1,1.83.12,5.61-1.83,2.56-1.22,1.1-.61,1.34.37,3.54.73,1.46,1.46-.12,1.34-.73.85-1.22.85-.12,2,.85,1.1.85.49,1.34v1.83l.85,1.46,1.58,1.1,1.71.12.73,2.93,2.56,1.71,6.83.73,1.46-.61h2l.12-1.22-3.29-1.59-.24-1.71.12-1.46-.85-2.8v-1.58l.85-1.34,1.22-.73,3.41.24,1.1-.73,1.46-.37,4.63.24.37-1-1.1-.49-1.58.49L497.86,103l-3.29-2.44L494,99.25l.85-1.1-.24-1.34-1-1,.73-.85,1.59-3.29-.37-3.17L490.29,84l.37-1.59,1-1.1-.37-1.22-2-1.71-.73-1.1-.12-3.54-2-2V70l.49-1.34V66.81l-3.17.24-1.34-2.44-1-1.1-1.71,2.68-1.34.61-.61,1.34-2.56,1.46-.61,1.59-1.1.73-1.71.12-1.34.61-2.07,2.44-.61,1.59-1.59.48h0Z" />
發現其實可以直接在 <path>
裡面加上自己定義的 data-* attribute
,如此一來,就可以用類似 jQuery 的 attr()
方法,甚至是透過 DOM element 的 data
屬性取得這個名字的內容。
舉例而言,我們可以在直接在瀏覽器加上某個縣市的 onmouseover
監聽器,並在滑鼠移動的時候印出地圖位置的 data-name
的值,就可以這樣做:
// 使用原生 JavaScript 的寫法 // 先抓取台北市的 path 物件 const el_taipei_city = document.getElementById('1e48e0bb-8964-4121-b347-b900162cf771') // 加上監聽器,打印出我們要的 data-* attribute 內容 el_taipei_city.onmouseover = function({console.log(this.dataset.name)} // 結果 -> taipei_city
有了地圖的資訊之後,我們只要拿著這個地點的名稱去比對現有的天氣資料,就可以把相對應的資料放到畫面上了。地理資訊的資料型態長這樣:
var place_data=[ { tag: "taipei_city", place: "臺北市", low: 16, high: 24, weather: "Rainy" }, { tag: "new_taipei_city", place: "新北市", low: 15, high: 22, weather: "Rainy" } ... ]
假設拿著 taipei_city
字串,想要從 place_data
取得整個台北市的物件要怎麼做呢? 我們可以用 for 迴圈,一個一個比對,但是其實 JavaScript 有提供我們更簡潔的寫法,就是陣列的 filter
方法,我們可以直接回傳陣列裡面中,判斷函式結果為 true 的物件。
像是這樣:Do re mi so ~
current_place_obj = place_data.filter((obj)=>obj.tag === 'taipei_city')[0]
因此,我們可以很快速的獲得 tag 是 taipei_city
的整個物件,要小心 filter
方法回傳的也是一個陣列,所以如果要取值的話,要取第0項喔。
將資料用 Vue 綁定,並渲染在畫面上
這邊比較需要注意的地方有兩個,第一個是需要在組件 mounted 的時候把所有的 <path>
tag 加上滑鼠移動的監聽器,當滑鼠移動的時候,就更新當前選擇到的 data-name
屬性更新到組件的 filter
資料上。第二個是可以利用 Vue 的 computed
計算屬性即時根據 filter
的變動即時更新要顯示當前資料的物件 now_area
。
const app = new Vue({ el: '#app', mounted() { paths = document.querySelectorAll('path'); let _this = this // 把這個 vm 本身存在 _this,以供後續函式內部使用 paths.forEach(e => { e.onmouseover = function () { _this.filter = this.dataset.name } }) }, data: () => { return { filter: '', place_data: null, } }, computed: { now_area() { let result = place_data.filter((obj) => obj.tag === this.filter) if (result.length == 0) { return null } else { return result[0] } } }, })
最後快速的加上標題與內容的樣式,就完成這次的作品了!🎉🎉🎉
加碼小單元-串接中央氣象局的 API 顯示實際的溫度與氣象
我們選擇使用中央氣象局氣象資料開放平台提供的資料,先註冊帳號後到這個網址:https://opendata.cwb.gov.tw/user/authkey,點擊下圖中的取得授權碼之後右邊就會出現你的 API token,要好好保管他這份資料呦。之後只要使用這個 token 就可以通行無阻的拿到我們需要的資料了~
從開發指南可以看到呼叫 API 的規範大致上是長這樣:
※ URL: https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/{dataid}?Authorization={apikey}&format={format}
{dataid} 為各資料集代碼 (參照:資料清單) ex.F-A0012-001
{apikey} 為會員帳號對應之授權碼 ex.CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678
{format} 為資料格式,請參照各資料集頁面確認可下載之檔案格式 ex.XML、CAP、JSON、ZIP、KMZ、GRIB2
※ 範例:https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-A0012-001?Authorization=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678&format=XML
並請加入快取功能,如上述所示。
因為我們需要的是以JSON格式顯現的鄉鎮天氣預報-台灣未來1週天氣預報,因此呼叫的規格大概是這樣: let url = 'https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-D0047-091?Authorization=你的API_token&downloadType=WEB&format=JSON'
我們使用 axios
的 get 方法拿取資料看看 axios.get(url).then(data => {console.log(data)})
,可以拿到這個樣子的資料,打開之後發現我們需要的就是在 dataset -> locations 裡面的 location 陣列資料。
再往下看可以看出他的結構,locationName
是縣市名稱,descriptionName
是這個數值的名稱,這才發現,原來氣象局的資料還會依照時段區分,我們尋求最簡單的作法,直接抓離現在最近的時段就好。
weatherElement 裡面有很多資料提供的數值,如果不清楚意思的話也可以查閱檔案的欄位說明表,雖然文件通常又臭又長,但是好好讀一下都能省下不少開發的時間。最複雜的解析部分已經完成,接下來只要把資料源跟篩選方式改成從api抓回來的資料就可以了!
接下來先把 call 到的資料存到 data 的 weather_data 中,另外把 filter
方法換成 find
,因為我們只需要第一個符合條件的結果。把回傳的資料格式修整一下,符合原本的資料型態就可以直接呈現囉~
mounted() { axios.get(url).then(data => { console.log(data) this.weather_data = data.data.cwbopendata.dataset.locations.location }) ...
now_area() { let data = {} let result = this.weather_data.find((obj) => { return obj.locationName === this.filter }) if (result) { let high = result.weatherElement.find(el => el.elementName === 'MaxT').time[0].elementValue.value let low = result.weatherElement.find(el => el.elementName === 'MinT').time[0].elementValue.value let weather = result.weatherElement.find(el => el.elementName === 'Wx').time[0].elementValue[0].value data = { place: this.filter, low: low, high: high, weather: weather } } return data }
這樣就大功告成了!專案的原始碼可以在這邊查看:https://github.com/Monoame-Design/bosscoding-examples