當我們有了地圖元件後,要怎麼讓標記渲染在指定位置呢?從上一篇文章就可以知道,在地圖上要指定中心點,必須依賴座標;所以在地圖標記的設定上,也是要指定一個座標,然後放置標記。
取得地址座標
那麼該如何取得指定地址的座標,這篇文章會提到使用 Google Map 中的 Geocoding API
和 places API
執行以下需求:
輸入地址執行搜尋
用戶輸入地址完畢後,直接按下搜尋按鈕,該地址透過 Geocoding API 的 geocode
搜尋後如果收到回傳 status GeocoderStatus.OK === true
,則能取得回傳的座標,相反地則是搜尋不成功,前端可以顯示錯誤訊息在頁面上。
輸入部分地址,由 Google Map API 提供選項
用戶輸入部分地址,透過 debounce 判斷使用者停止輸入後,透過 places API 的 AutocompleteService
中的 getPlacePredictions
,提供使用者最多五項關於該輸入地址的預測資料,如果收到回傳 places.PlacesServiceStatus.OK === true
,則顯示選項給使用者,反之則不提供選項,使用者可以選擇重新調整內容或透過搜尋按鈕執行搜尋功能。
輸入地址執行搜尋
這邊參考了Geocoder 文件 ,要 new 一個 instance 來進行操作:
const geocoder = new mapApi.Geocoder()
接下來文件中提到,使用 geocode
method 可以進行相關操作,第一個參數是 request,第二個參數是 callback,
request 的部分,因為需求是地址查找,所以帶入 address 參數。
而回傳的 callback 中也帶色兩個參數,一個是回傳的 result, 另外則是 透過常數表示狀態的 status,在這邊有有效回應的 status 是 OK
,其他則是不同情況下的錯誤或無效狀態,因此我們判斷當 status === mapApi.GeocoderStatus.OK
時,去取得回傳的 result,透過當中的座標渲染標記,並且將座標和 place_id 存於 state 中,當作表單資料的一部分。
相反的,如果 status !== mapApi.GeocoderStatus.OK
,則顯示錯誤訊息於頁面上。
const handleSearch = useCallback(() => {
const geocoder = new mapApi.Geocoder()
geocoder.geocode(
{ address: addressInputRef.current.value },
(results, status) => {
if (status === mapApi.GeocoderStatus.OK) {
const { location } = results[0].geometry
mapInstance.setCenter(location)
renderMarker(mapInstance, mapApi, location, isMarker)
setCoordsResult({
lat: location.lat(),
lng: location.lng()
})
setPlaceId(results[0].place_id)
return setIsMarker(true)
}
return setAddressErrMsg('請輸入有效地址') // 錯誤訊息設定
}
)
}, [isMarker, mapApi, mapInstance, renderMarker])
輸入部分地址,由 Google Map API 提供選項
在這個功能,需要記錄的有兩個部分,第一個是使用 places API 的 Places Autocomplete Service,然後透過其中 getPlacePredictions
的 method,同樣透過第一個 request 參數和第二個 callback 參數進行操作。
const handleAutocomplete = useCallback(() => {
const service = new mapApi.places.AutocompleteService()
const request = {
input: addressSearchInput
}
service.getPlacePredictions(request, (results, status) => {
if (status === mapApi.places.PlacesServiceStatus.OK) {
return setAutoResults(results)
}
console.log('自動完成沒有結果')
return setAutoResults(null)
})
}
, [mapApiLoaded, addressSearchInput, mapApi, isSearched])
而為了避免在輸入途中就觸發地點自動完成功能,多次發送還不具意義的字串進行請求,讓 Google-Map-API 的請求次數過多,這邊使用 debounce(去抖動)的處理方式,判斷當使用者停止進行相同行為經過一定時間後,才觸發指定功能。
可以看到 handleInput
當中,設定 700 毫秒為時間單位,當未達時間內使用者持續觸發輸入行為,會刷新計時器,當停止達到 700 毫秒,才會將 ref value 存於 addressSearchInput state,並觸發 useEffect 依賴條件更改,執行 handleAutocomplete
function。
const handleInput = debounce(() =>
setAddressSearchInput(addressInputRef.current.value)
, 700}
const onInputChange = useCallback(
(e) => {
handleInput()
setValue('storeAddress', e.target.value) // react-hook-form
},
[setValue, handleInput]
)
useEffect(() => {
// 非搜尋狀態且有地址搜尋字詞時,執行自動完成功能
if (addressSearchInput && !isSearched) handleAutocomplete()
// 清空input欄位時,視為非搜尋狀態並清空自動搜尋結果
if (!addressInputRef?.current?.value) {
setAutoResults(null)
setIsSearched(false)
}
}, [addressSearchInput, handleAutocomplete, isSearched])
這樣我們就完成兩種透過地址搜索的功能,接下來將紀錄如何將得到的座標資料渲染成標記在指定的位置上,並且透過拖曳和點擊可以重新渲染標記,取得新的座標和 placeId。