這次的直播要做一個互動地圖,當滑鼠滑過的時候顯示當地天氣資訊!
這次的直播內容主要有幾個部分:
- 取得地圖的 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




