Place Autocomplete Data API 可讓您以程式輔助方式擷取地點預測結果,藉此建立自訂自動完成體驗,並提供比自動完成小工具更精細的控制程度。本指南說明如何使用 Place Autocomplete Data API,根據使用者查詢發出自動完成要求。
以下範例顯示簡化的自動完成整合。輸入搜尋查詢 (例如「披薩」或「夏威夷生魚飯」),然後按一下選取所需結果。
Autocomplete 要求
自動完成要求會接收查詢輸入字串,並傳回地點預測結果清單。如要提出自動完成要求,請呼叫 fetchAutocompleteSuggestions()
,並傳遞含有必要屬性的要求。input
屬性包含要搜尋的字串;在一般應用程式中,這個值會在使用者輸入查詢時更新。要求應包含 sessionToken
,用於計費。
下列程式碼片段顯示如何建立要求主體、新增工作階段權杖,然後呼叫 fetchAutocompleteSuggestions()
取得 PlacePrediction
清單。
// Add an initial request body. let request = { input: "Tadi", locationRestriction: { west: -122.44, north: 37.8, east: -122.39, south: 37.78, }, origin: { lat: 37.7893, lng: -122.4039 }, includedPrimaryTypes: ["restaurant"], language: "en-US", region: "us", }; // Create a session token. const token = new AutocompleteSessionToken(); // Add the token to the request. // @ts-ignore request.sessionToken = token;
限制 Autocomplete 預測結果
根據預設,Place Autocomplete 會顯示所有地點類型 (優先顯示使用者所在位置附近的預測結果),並擷取使用者所選地點的所有可用資料欄位。您可以限制或自訂調整結果,將 Place Autocomplete 選項設為顯示更相關的預測結果。
限制結果後,Autocomplete 小工具會略過超出限制區域的所有結果。常見做法是將結果限制在地圖範圍內。自訂調整結果會讓 Autocomplete 小工具顯示指定區域內的結果,但有些相符項目可能會超出這個區域。
使用 origin
屬性指定起點,以便計算與目的地的測地距離。如果省略這個值,系統就不會傳回距離。
使用 includedPrimaryTypes
屬性指定最多五個地點類型。如未指定類型,系統會傳回所有類型的地點。
取得 Place Details
如要從地點預測結果傳回 Place
物件,請先呼叫 toPlace()
,然後在產生的 Place
物件上呼叫 fetchFields()
(系統會自動納入地點預測的會期 ID)。呼叫 fetchFields()
會結束自動完成工作階段。
let place = suggestions[0].placePrediction.toPlace(); // Get first predicted place. await place.fetchFields({ fields: ["displayName", "formattedAddress"], }); const placeInfo = document.getElementById("prediction"); placeInfo.textContent = "First predicted place: " + place.displayName + ": " + place.formattedAddress;
工作階段符記
工作階段符記會將使用者自動完成搜尋的查詢和選取階段歸入不同的工作階段,以用於計費。工作階段會在使用者開始輸入時啟動。 使用者選取地點並呼叫 Place Details 時,工作階段就會結束。
如要建立新的工作階段符記並新增至要求,請建立 AutocompleteSessionToken
的例項,然後將要求的 sessionToken
屬性設為使用符記,如以下程式碼片段所示:
// Create a session token. const token = new AutocompleteSessionToken(); // Add the token to the request. // @ts-ignore request.sessionToken = token;
呼叫 fetchFields()
時,工作階段就會結束。建立 Place
執行個體後,您不必將工作階段權杖傳遞至 fetchFields()
,因為系統會自動處理這項作業。
await place.fetchFields({ fields: ["displayName", "formattedAddress"], });
建立 AutocompleteSessionToken
的新例項,為下一個工作階段建立工作階段符記。
工作階段符記建議:
- 在所有 Place Autocomplete 呼叫中使用工作階段符記。
- 為每個工作階段產生新的符記。
- 請務必針對每個新的工作階段傳遞不重複的工作階段符記。如果多個工作階段使用同一個符記,則每個要求會分別計費。
您可以選擇在要求中省略自動完成工作階段符記。如果省略工作階段符記,系統會針對每個要求分別收費,並觸發 Autocomplete - Per Request SKU。假如您重複使用工作階段符記,系統會將工作階段視為無效,並針對相關要求收費 (因為系統會將其視為未提供工作階段符記)。
範例
使用者輸入查詢時,系統每隔幾次按鍵就會呼叫一次自動完成要求 (而非每個字元),並傳回可能的結果清單。使用者從結果清單中選取項目時,系統會將該選取項目視為一項要求,並將搜尋期間的所有要求一併計為一項要求。如果使用者選取地點,系統不會收取搜尋查詢費用,只會針對地點資料要求收費。如果使用者在工作階段開始後幾分鐘內未選取任何項目,系統只會收取搜尋查詢費用。
從應用程式的角度來看,事件流程如下:
- 使用者開始輸入查詢,搜尋「法國巴黎」。
- 偵測到使用者輸入內容後,應用程式會建立新的工作階段符記「Token A」。
- 使用者輸入時,API 會每隔幾個字元發出自動完成要求,並為每個字元顯示新的潛在結果清單:
「P」
「Par」
「Paris」
「Paris, Fr」
- 使用者選取項目時:
- 查詢產生的所有要求都會分組,並以單一要求的形式新增至「權杖 A」代表的工作階段。
- 系統會將使用者的選取項目計為 Place Detail 要求,並加入以「符記 A」表示的工作階段。
- 工作階段結束,應用程式會捨棄「權杖 A」。
完整程式碼範例
本節提供完整範例,說明如何使用 Place Autocomplete Data API。地點自動完成預測
以下範例示範如何針對輸入內容「Tadi」呼叫 fetchAutocompleteSuggestions()
,然後對第一個預測結果呼叫 toPlace()
,接著呼叫 fetchFields()
取得地點詳細資料。
TypeScript
/** * Demonstrates making a single request for Place predictions, then requests Place Details for the first result. */ async function init() { // @ts-ignore const { Place, AutocompleteSessionToken, AutocompleteSuggestion } = await google.maps.importLibrary("places") as google.maps.PlacesLibrary; // Add an initial request body. let request = { input: "Tadi", locationRestriction: { west: -122.44, north: 37.8, east: -122.39, south: 37.78 }, origin: { lat: 37.7893, lng: -122.4039 }, includedPrimaryTypes: ["restaurant"], language: "en-US", region: "us", }; // Create a session token. const token = new AutocompleteSessionToken(); // Add the token to the request. // @ts-ignore request.sessionToken = token; // Fetch autocomplete suggestions. const { suggestions } = await AutocompleteSuggestion.fetchAutocompleteSuggestions(request); const title = document.getElementById('title') as HTMLElement; title.appendChild(document.createTextNode('Query predictions for "' + request.input + '":')); for (let suggestion of suggestions) { const placePrediction = suggestion.placePrediction; // Create a new list element. const listItem = document.createElement('li'); const resultsElement = document.getElementById("results") as HTMLElement; listItem.appendChild(document.createTextNode(placePrediction.text.toString())); resultsElement.appendChild(listItem); } let place = suggestions[0].placePrediction.toPlace(); // Get first predicted place. await place.fetchFields({ fields: ['displayName', 'formattedAddress'], }); const placeInfo = document.getElementById("prediction") as HTMLElement; placeInfo.textContent = 'First predicted place: ' + place.displayName + ': ' + place.formattedAddress; } init();
JavaScript
/** * Demonstrates making a single request for Place predictions, then requests Place Details for the first result. */ async function init() { // @ts-ignore const { Place, AutocompleteSessionToken, AutocompleteSuggestion } = await google.maps.importLibrary("places"); // Add an initial request body. let request = { input: "Tadi", locationRestriction: { west: -122.44, north: 37.8, east: -122.39, south: 37.78, }, origin: { lat: 37.7893, lng: -122.4039 }, includedPrimaryTypes: ["restaurant"], language: "en-US", region: "us", }; // Create a session token. const token = new AutocompleteSessionToken(); // Add the token to the request. // @ts-ignore request.sessionToken = token; // Fetch autocomplete suggestions. const { suggestions } = await AutocompleteSuggestion.fetchAutocompleteSuggestions(request); const title = document.getElementById("title"); title.appendChild( document.createTextNode('Query predictions for "' + request.input + '":'), ); for (let suggestion of suggestions) { const placePrediction = suggestion.placePrediction; // Create a new list element. const listItem = document.createElement("li"); const resultsElement = document.getElementById("results"); listItem.appendChild( document.createTextNode(placePrediction.text.toString()), ); resultsElement.appendChild(listItem); } let place = suggestions[0].placePrediction.toPlace(); // Get first predicted place. await place.fetchFields({ fields: ["displayName", "formattedAddress"], }); const placeInfo = document.getElementById("prediction"); placeInfo.textContent = "First predicted place: " + place.displayName + ": " + place.formattedAddress; } init();
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; }
HTML
<html> <head> <title>Place Autocomplete Data API Predictions</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="title"></div> <ul id="results"></ul> <p><span id="prediction"></span></p> <img class="powered-by-google" src="https://storage.googleapis.com/geo-devrel-public-buckets/powered_by_google_on_white.png" alt="Powered by Google" /> <!-- prettier-ignore --> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg", v: "weekly"});</script> </body> </html>
測試範例
使用工作階段的 Place Autocomplete 預先輸入功能
這個範例將說明下列概念:
- 根據使用者查詢呼叫
fetchAutocompleteSuggestions()
,並顯示預測地點清單做為回應。 - 使用工作階段符記,將使用者查詢與最終的 Place Details 要求分組。
- 擷取所選地點的 Place Details,並顯示標記。
- 使用控制項插槽,將 UI 元素巢狀內嵌在
gmp-map
元素中。
TypeScript
const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; let innerMap: google.maps.Map; let marker: google.maps.marker.AdvancedMarkerElement; let titleElement = document.querySelector('.title') as HTMLElement; let resultsContainerElement = document.querySelector('.results') as HTMLElement; let inputElement = document.querySelector('input') as HTMLInputElement; let tokenStatusElement = document.querySelector('.token-status') as HTMLElement; let newestRequestId = 0; let tokenCount = 0; // Create an initial request body. const request: google.maps.places.AutocompleteRequest = { input: '', includedPrimaryTypes: [ 'restaurant', 'cafe', 'museum', 'park', 'botanical_garden', ], } async function init() { await google.maps.importLibrary('maps'); innerMap = mapElement.innerMap; innerMap.setOptions({ mapTypeControl: false, }); // Update request center and bounds when the map bounds change. google.maps.event.addListener(innerMap, 'bounds_changed', async () => { request.locationRestriction = innerMap.getBounds(); request.origin = innerMap.getCenter(); }); inputElement.addEventListener('input', makeAutocompleteRequest); } async function makeAutocompleteRequest(inputEvent) { // To avoid race conditions, store the request ID and compare after the request. const requestId = ++newestRequestId; const { AutocompleteSuggestion } = (await google.maps.importLibrary( 'places' )) as google.maps.PlacesLibrary; if (!inputEvent.target?.value) { titleElement.textContent = ''; resultsContainerElement.replaceChildren(); return; } // Add the latest char sequence to the request. request.input = (inputEvent.target as HTMLInputElement).value; // Fetch autocomplete suggestions and show them in a list. const { suggestions } = await AutocompleteSuggestion.fetchAutocompleteSuggestions(request); // If the request has been superseded by a newer request, do not render the output. if (requestId !== newestRequestId) return; titleElement.innerText = `Place predictions for "${request.input}"`; // Clear the list first. resultsContainerElement.replaceChildren(); for (const suggestion of suggestions) { const placePrediction = suggestion.placePrediction; if (!placePrediction) { continue; } // Create a link for the place, add an event handler to fetch the place. // We are using a button element to take advantage of its a11y capabilities. const placeButton = document.createElement('button'); placeButton.addEventListener('click', () => { onPlaceSelected(placePrediction.toPlace()); }); placeButton.textContent = placePrediction.text.toString(); placeButton.classList.add('place-button'); // Create a new list item element. const li = document.createElement('li'); li.appendChild(placeButton); resultsContainerElement.appendChild(li); } } // Event handler for clicking on a suggested place. async function onPlaceSelected(place: google.maps.places.Place) { const { AdvancedMarkerElement } = (await google.maps.importLibrary( 'marker' )) as google.maps.MarkerLibrary; await place.fetchFields({ fields: ['displayName', 'formattedAddress', 'location'], }); resultsContainerElement.textContent = `${place.displayName}: ${place.formattedAddress}`; titleElement.textContent = 'Selected Place:'; inputElement.value = ''; await refreshToken(); // Remove the previous marker, if it exists. if (marker) { marker.remove(); } // Create a new marker. marker = new AdvancedMarkerElement({ map: innerMap, position: place.location, title: place.displayName, }) // Center the map on the selected place. if (place.location) { innerMap.setCenter(place.location); innerMap.setZoom(15); } } // Helper function to refresh the session token. async function refreshToken() { const { AutocompleteSessionToken } = (await google.maps.importLibrary( 'places' )) as google.maps.PlacesLibrary; // Increment the token counter. tokenCount++; // Create a new session token and add it to the request. request.sessionToken = new AutocompleteSessionToken(); tokenStatusElement.textContent = `Session token count: ${tokenCount}`; } init();
JavaScript
const mapElement = document.querySelector('gmp-map'); let innerMap; let marker; let titleElement = document.querySelector('.title'); let resultsContainerElement = document.querySelector('.results'); let inputElement = document.querySelector('input'); let tokenStatusElement = document.querySelector('.token-status'); let newestRequestId = 0; let tokenCount = 0; // Create an initial request body. const request = { input: '', includedPrimaryTypes: [ 'restaurant', 'cafe', 'museum', 'park', 'botanical_garden', ], }; async function init() { await google.maps.importLibrary('maps'); innerMap = mapElement.innerMap; innerMap.setOptions({ mapTypeControl: false, }); // Update request center and bounds when the map bounds change. google.maps.event.addListener(innerMap, 'bounds_changed', async () => { request.locationRestriction = innerMap.getBounds(); request.origin = innerMap.getCenter(); }); inputElement.addEventListener('input', makeAutocompleteRequest); } async function makeAutocompleteRequest(inputEvent) { // To avoid race conditions, store the request ID and compare after the request. const requestId = ++newestRequestId; const { AutocompleteSuggestion } = (await google.maps.importLibrary('places')); if (!inputEvent.target?.value) { titleElement.textContent = ''; resultsContainerElement.replaceChildren(); return; } // Add the latest char sequence to the request. request.input = inputEvent.target.value; // Fetch autocomplete suggestions and show them in a list. const { suggestions } = await AutocompleteSuggestion.fetchAutocompleteSuggestions(request); // If the request has been superseded by a newer request, do not render the output. if (requestId !== newestRequestId) return; titleElement.innerText = `Place predictions for "${request.input}"`; // Clear the list first. resultsContainerElement.replaceChildren(); for (const suggestion of suggestions) { const placePrediction = suggestion.placePrediction; if (!placePrediction) { continue; } // Create a link for the place, add an event handler to fetch the place. // We are using a button element to take advantage of its a11y capabilities. const placeButton = document.createElement('button'); placeButton.addEventListener('click', () => { onPlaceSelected(placePrediction.toPlace()); }); placeButton.textContent = placePrediction.text.toString(); placeButton.classList.add('place-button'); // Create a new list item element. const li = document.createElement('li'); li.appendChild(placeButton); resultsContainerElement.appendChild(li); } } // Event handler for clicking on a suggested place. async function onPlaceSelected(place) { const { AdvancedMarkerElement } = (await google.maps.importLibrary('marker')); await place.fetchFields({ fields: ['displayName', 'formattedAddress', 'location'], }); resultsContainerElement.textContent = `${place.displayName}: ${place.formattedAddress}`; titleElement.textContent = 'Selected Place:'; inputElement.value = ''; await refreshToken(); // Remove the previous marker, if it exists. if (marker) { marker.remove(); } // Create a new marker. marker = new AdvancedMarkerElement({ map: innerMap, position: place.location, title: place.displayName, }); // Center the map on the selected place. if (place.location) { innerMap.setCenter(place.location); innerMap.setZoom(15); } } // Helper function to refresh the session token. async function refreshToken() { const { AutocompleteSessionToken } = (await google.maps.importLibrary('places')); // Increment the token counter. tokenCount++; // Create a new session token and add it to the request. request.sessionToken = new AutocompleteSessionToken(); tokenStatusElement.textContent = `Session token count: ${tokenCount}`; } init();
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } .place-button { height: 3rem; width: 100%; background-color: transparent; text-align: left; border: none; cursor: pointer; } .place-button:focus-visible { outline: 2px solid #0056b3; border-radius: 2px; } .input { width: 300px; font-size: small; margin-bottom: 1rem; } /* Styles for the floating panel */ .controls { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); font-family: sans-serif; font-size: small; margin: 12px; padding: 1rem; } .title { font-weight: bold; margin-top: 1rem; margin-bottom: 0.5rem; } .results { list-style-type: none; margin: 0; padding: 0; } .results li:not(:last-child) { border-bottom: 1px solid #ddd; } .results li:hover { background-color: #eee; }
HTML
<html> <head> <title>Place Autocomplete Data API Session</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> <!-- prettier-ignore --> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly"});</script> </head> <body> <gmp-map center="37.7893, -122.4039" zoom="12" map-id="DEMO_MAP_ID"> <div class="controls" slot="control-inline-start-block-start" > <input type="text" class="input" placeholder="Search for a place..." autocomplete="off" /><!-- Turn off the input's own autocomplete (not supported by all browsers).--> <div class="token-status"></div> <div class="title"></div> <ol class="results"></ol> </div> </gmp-map> </body> </html>