# 初探 Google Maps API
當專案需要用到地圖時,應該首選都是 Google Map 提供的 API 服務。本文會先從 API 的介紹開始,慢慢進入如何應用到專案,接著詳細介紹在專案中使用到的 API,最後以一個概論作結。
## Google Maps JavaScript API V3 介紹
因應不同的需求,主要可以分成七大類 API:
| API | 說明 | 範例 |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| [❶Maps](https://developers.google.com/maps/documentation/javascript/reference/map) | 顧名思義就是呼喚出那張地圖時所要使用的。自訂地圖樣式等。 | ![](https://i.imgur.com/nWprZ4w.png) |
| [❷Drawing on the map](https://developers.google.com/maps/documentation/javascript/reference/marker) | 想在地圖上顯示出指定地點的 Marker 或甚至點擊 Marker 後要跳出一個 Info Window 嗎?內容都在這裡。 | ![](https://i.imgur.com/ioUFTSI.png) |
| [❸Street View](https://developers.google.com/maps/documentation/javascript/reference/street-view) | 街景服務 | 略 |
| [❹Places](https://developers.google.com/maps/documentation/javascript/reference/places-widget) | 取得地點詳細資訊、經緯度變換地點都得用這組 API,也是本次會`重點介紹的項目`! | 詳見文章以下介紹 |
| [❺Routes](https://developers.google.com/maps/documentation/javascript/reference/directions) | 導航路線相關。 | 略 |
| [❻Local Context (beta)](https://developers.google.com/maps/documentation/javascript/reference/local-context-map-view#LocalContextMapViewOptions) | Local Context 將地圖、路徑規劃、地點 (Maps, Routes, Places) 功能,透過一支 API 全部整合,一次提供 3 種功能,似乎很讚,但還在 beta 中。 | 略 |
| [❼Journey Sharing (beta)](https://developers.google.com/maps/documentation/javascript/reference/journey-sharing-map-view) | 顧名思義就是呼喚出那張地圖時所要使用的。自訂地圖樣式等。 | 略 |
在之前接觸的專案中,主要使用的主要是 ❶、❷,也就是使用 Google 地圖,並把相關店家的資訊(例如車咕嚕中洗車場地點)放置 `Marker` 在地圖上,點擊後會跳出店家詳細資訊及預約的 `Info Window`。
而本文主要想著重介紹的是 ❹,也是最近偉士牌需求中的購車頁優化,會需要依照使用者的 input 去移動到對應的城市,並顯示城市的名稱,其實就是模擬 Google Map 的搜尋功能,但實際做起來需要熟悉 API 的混用。
## 需求
1. 輸入 Enter 後將地圖定位至該縣市/區域並在列表顯示該搜尋區域**所在縣市**的名稱及所有分店。
1. 移動地圖至其他縣市時,地圖:顯示可見地區的分店地標;列表:顯示地圖中心**所在縣市**的所有分店
我們來拆解一下這個需求的實作步驟看看。
### 需求 ❶ 拆解 — [影片](https://drive.google.com/file/d/1wqfCXx_kfyBHZPa1c4wtlrocI-saMhrh/view?usp=sharing)
1. 使用者輸入欲搜尋的區域,例如「豐原」、「綠島」
2. 把這個 input 傳給 API,得到 response
3. response 應該就有郵遞區號,直接用這個去 mapping 城市表找出豐原是在臺中市;綠島是在臺東縣
4. 地圖上呈現以豐原為中心的畫面、列表上印出臺中市
### 需求 ❷ 拆解 — [影片](https://drive.google.com/file/d/1O1sOXF4bphrm2tjHh0mSXQAcW62xAU4x/view?usp=sharing)
1. 使用者從 A 縣市拖曳到 B 縣市
2. 監聽 DragEnd,發現地圖中心有改變就取得現在中心的經緯度丟到 API
3. 從 response 整理出目前是在哪一個縣市
4. 畫面改變
## 實作
首先,遵循 [guide](https://developers.google.com/maps/documentation/javascript/get-api-key) 取得一組 API Key。
由於 Google Maps Platform 只提供原生 JS 或 TS,我們可以直接使用好心人士包裝成 [React 專用的套件](https://www.npmjs.com/package/@react-google-maps/api)。
### 基礎建設
**※ 程式碼會省略 input 的 component**
```javascript=
import React, { useState } from 'react'
import { GoogleMap, useLoadScript } from '@react-google-maps/api'
const CustomGoogleMap = () => {
// 載入 Places API 所需要的 libraries
const [libraries] = useState(['places'])
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: GOOGLE_MAP_API_KEY,
libraries,
})
// 定義地圖的 style、各種控制等
const renderMap = () => {
const options = {
disableDefaultUI: true,
zoomControl: true,
scaleControl: true,
styles: mapStyle,
}
//todo1 處理使用者輸入的資料,實現需求❶
//todo2:處理拖曳結束後的事件,實現需求❷
return (
<>
<GoogleMap
mapContainerStyle={{
width: '100%',
height: '320px',
}}
center={center}
zoom={12}
options={options}
onLoad={handleLoad}
onDragEnd={handleCenterChanged}
/>
</>
)
}
if (loadError) {
return <h1>Map cannot be loaded right now, sorry.</h1>
}
return isLoaded ? renderMap() : null
}
```
<!-- ![](https://i.imgur.com/JblBhuc.png) -->
### 需求 ❶
做好前置作業後,來實作需求 ❶ 吧!這邊我們需要運用到 Places 裡 [Autocomplete()](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service) 來取得預測值,接著再將這個值丟給 [Geocoder()](https://developers.google.com/maps/documentation/javascript/reference/geocoder) 轉換成經緯度,才能夠將中心點設為 input 的區域。如下圖 1:
```javascript=
// 處理使用者輸入的資料,實現需求❶
const handleKeyPress = event => {
if (event.key === 'Enter') {
const maps = window.google.maps;
const sessionToken = new maps.places.AutocompleteSessionToken();
const service = new maps.places.AutocompleteService();
const request = {
input: userInput,
sessionToken,
language: 'zh-TW', // 限定回傳語言為臺灣繁體中文
types: [
'administrative_area_level_1',
'administrative_area_level_2',
'administrative_area_level_3',
], // 限定回傳區域為 1~3 級行政區
};
service.getPlacePredictions(request, predictions => {
const geocoder = new window.google.maps.Geocoder();
geocoder.geocode({ placeId: predictions[0].place_id }, responses => {
// 取得 input data 的經緯度後將地圖中間設為該值
setCenter({
lat: responses[0].geometry.location.lat(),
lng: responses[0].geometry.location.lng(),
});
// 取得郵遞區號以用來 mapping 城市,例如 110 則對應到臺北市信義區
setZipCode(responses[0].address_components.slice(-1)[0].long_name);
});
});
// 使用 webview 開啟時需再按下 enter 後使用 blur() 以讓 portable device 鍵盤自動收起
event.target.blur();
}
};
```
<!-- ![1](https://i.imgur.com/HAZuMTH.png) -->
這樣第一個需求就完成囉~可以回顧上面的影片。
### 需求 ❷
做完第一項之後,這個就很好理解了!直接看 code。
```javascript=
const handleCenterChanged = () => {
// ❶將拖曳結束後的中心點放到 Geocoder() 取得經緯度
// ❷將資料處理後得到郵遞區號去做城市 mapping
};
//....
<GoogleMap onDragEnd={handleCenterChanged}>
```
<!-- ![](https://i.imgur.com/eQLlQe6.png) -->
🎉🎉🎉🎉🎉 完成囉! 🎉🎉🎉🎉🎉
## 可以再優化的部分
- 當 response 沒有回傳郵遞區號時,該如何去找到對應的城市?
- 如果使用者輸入非 1~3 級行政區(例如:陽明山),是否需提示 Alert?
## 結語
當初這個功能其實摸索了很久,主要在於 Google ~~很有商業頭腦地~~把每一隻 API 回傳的 data 區分的很細,導致你要去組合兩三隻才能夠得到你要的結果。
每一隻 API 都是[分開計費](https://mapsplatform.google.com/intl/ja_ALL/pricing/)的!如有專案需求可能會需要在開發前與 PM 討論,確認客戶接不接受收費方式,畢竟如果每一個拖曳都要打 API,即使有豐沛的免費額度也不得不注意~
## 參考資料
> [Google Maps JavaScript API V3 Reference](https://developers.google.com/maps/documentation/javascript/reference) > [Places Autocomplete Service](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service) > [Geocoder](https://developers.google.com/maps/documentation/javascript/reference/geocoder) > [@react-google-maps/api](https://www.npmjs.com/package/@react-google-maps/api)
**PS: 以上程式碼如果凌亂或不符合大家的 code 標準請多多指教 🙏**