Place Autocomplete Data API

歐洲經濟區 (EEA) 開發人員

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 屬性指定最多五個地點類型。如未指定類型,系統會傳回所有類型的地點。

參閱 API 參考資料

取得 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。假如您重複使用工作階段符記,系統會將工作階段視為無效,並針對相關要求收費 (因為系統會將其視為未提供工作階段符記)。

範例

使用者輸入查詢時,系統每隔幾次按鍵就會呼叫一次自動完成要求 (而非每個字元),並傳回可能的結果清單。使用者從結果清單中選取項目時,該選取項目會計為一項要求,而搜尋期間的所有要求會一併計為一項要求。如果使用者選取地點,系統不會收取搜尋查詢費用,只會針對地點資料要求收費。如果使用者在工作階段開始後幾分鐘內未選取任何項目,系統只會收取搜尋查詢費用。

從應用程式的角度來看,事件流程如下:

  1. 使用者開始輸入查詢,搜尋「法國巴黎」。
  2. 偵測到使用者輸入內容後,應用程式會建立新的工作階段符記「Token A」。
  3. 使用者輸入時,API 會每隔幾個字元發出自動完成要求,並為每個字元顯示新的潛在結果清單:
    「P」
    「Par」
    「Paris」
    「Paris, Fr」
  4. 使用者選取項目時:
    • 查詢產生的所有要求都會分組,並以單一要求的形式新增至以「Token A」表示的工作階段。
    • 系統會將使用者的選取項目計為 Place Detail 要求,並加入以「符記 A」表示的工作階段。
  5. 工作階段結束,應用程式會捨棄「權杖 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 要求分組。

TypeScript

let titleElement;
let resultsContainerElement;
let inputElement;

let newestRequestId = 0;

// Add an initial request body.
const request = {
    input: '',
    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',
};

function init() {
    titleElement = document.getElementById('title');
    resultsContainerElement = document.getElementById('results');
    inputElement = document.querySelector('input');
    inputElement.addEventListener('input', makeAutocompleteRequest);
    refreshToken(request);
}

async function makeAutocompleteRequest(inputEvent) {
    // Reset elements and exit if an empty string is received.
    if (inputEvent.target.value == '') {
        titleElement.innerText = '';
        resultsContainerElement.replaceChildren();
        return;
    }

    // Add the latest char sequence to the request.
    request.input = inputEvent.target.value;

    // To avoid race conditions, store the request ID and compare after the request.
    const requestId = ++newestRequestId;

    // Fetch autocomplete suggestions and show them in a list.
    // @ts-ignore
    const { suggestions } = await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);

    // If the request has been superseded by a newer request, do not render the output.
    if (requestId !== newestRequestId) return;

    titleElement.innerText = `Query predictions for "${request.input}"`;

    // Clear the list first.
    resultsContainerElement.replaceChildren();

    for (const suggestion of suggestions) {
        const placePrediction = suggestion.placePrediction;

        // Create a link for the place, add an event handler to fetch the place.
        const a = document.createElement('a');
        a.addEventListener('click', () => {
            onPlaceSelected(placePrediction!.toPlace());
        });
        a.innerText = placePrediction!.text.toString();

        // Create a new list item element.
        const li = document.createElement('li');
        li.appendChild(a);
        resultsContainerElement.appendChild(li);
    }
}

// Event handler for clicking on a suggested place.
async function onPlaceSelected(place) {
    await place.fetchFields({
        fields: ['displayName', 'formattedAddress'],
    });
    const placeText = document.createTextNode(`${place.displayName}: ${place.formattedAddress}`);
    resultsContainerElement.replaceChildren(placeText);
    titleElement.innerText = 'Selected Place:';
    inputElement.value = '';
    refreshToken(request);
}

// Helper function to refresh the session token.
function refreshToken(request) {
    // Create a new session token and add it to the request.
    request.sessionToken = new google.maps.places.AutocompleteSessionToken();
}

declare global {
    interface Window {
      init: () => void;
    }
  }
  window.init = init;

JavaScript

let titleElement;
let resultsContainerElement;
let inputElement;
let newestRequestId = 0;
// Add an initial request body.
const request = {
    input: '',
    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',
};
function init() {
    titleElement = document.getElementById('title');
    resultsContainerElement = document.getElementById('results');
    inputElement = document.querySelector('input');
    inputElement.addEventListener('input', makeAutocompleteRequest);
    refreshToken(request);
}
async function makeAutocompleteRequest(inputEvent) {
    // Reset elements and exit if an empty string is received.
    if (inputEvent.target.value == '') {
        titleElement.innerText = '';
        resultsContainerElement.replaceChildren();
        return;
    }
    // Add the latest char sequence to the request.
    request.input = inputEvent.target.value;
    // To avoid race conditions, store the request ID and compare after the request.
    const requestId = ++newestRequestId;
    // Fetch autocomplete suggestions and show them in a list.
    // @ts-ignore
    const { suggestions } = await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
    // If the request has been superseded by a newer request, do not render the output.
    if (requestId !== newestRequestId)
        return;
    titleElement.innerText = `Query predictions for "${request.input}"`;
    // Clear the list first.
    resultsContainerElement.replaceChildren();
    for (const suggestion of suggestions) {
        const placePrediction = suggestion.placePrediction;
        // Create a link for the place, add an event handler to fetch the place.
        const a = document.createElement('a');
        a.addEventListener('click', () => {
            onPlaceSelected(placePrediction.toPlace());
        });
        a.innerText = placePrediction.text.toString();
        // Create a new list item element.
        const li = document.createElement('li');
        li.appendChild(a);
        resultsContainerElement.appendChild(li);
    }
}
// Event handler for clicking on a suggested place.
async function onPlaceSelected(place) {
    await place.fetchFields({
        fields: ['displayName', 'formattedAddress'],
    });
    const placeText = document.createTextNode(`${place.displayName}: ${place.formattedAddress}`);
    resultsContainerElement.replaceChildren(placeText);
    titleElement.innerText = 'Selected Place:';
    inputElement.value = '';
    refreshToken(request);
}
// Helper function to refresh the session token.
function refreshToken(request) {
    // Create a new session token and add it to the request.
    request.sessionToken = new google.maps.places.AutocompleteSessionToken();
}
window.init = 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;
}

a {
  cursor: pointer;
  text-decoration: underline;
  color: blue;
}

input {
  width: 300px;
}

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>
  </head>
  <body>
    <input id="input" type="text" placeholder="Search for a place..." />
    <div id="title"></div>
    <ul id="results"></ul>
    <img
      class="powered-by-google"
      src="https://storage.googleapis.com/geo-devrel-public-buckets/powered_by_google_on_white.png"
      alt="Powered by Google"
    />

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8&callback=init&libraries=places&v=weekly"
      defer
    ></script>
  </body>
</html>

試用範例