# 初探 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 標準請多多指教 🙏**