Place Autocomplete 데이터 API

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;

자동 완성 예상 검색어 제한

기본적으로 Place Autocomplete는 사용자 위치 근처의 예상 검색어에 편중된 모든 장소 유형을 표시하며 사용자가 선택한 장소의 사용 가능한 모든 데이터 필드를 가져옵니다. 결과를 제한하거나 상세 검색하여 더 관련성 높은 예상 검색어를 표시하려면 Place Autocomplete 옵션을 설정하세요.

결과를 제한하면 자동 완성 위젯이 제한 지역 밖의 모든 결과를 무시합니다. 일반적인 방법은 결과를 지도 경계로 제한하는 것입니다. 결과를 상세 검색하면 자동 완성 위젯이 지정된 지역 내 결과를 표시하지만 일부 일치 항목이 해당 지역을 벗어날 수 있습니다.

origin 속성을 사용하여 목적지까지의 최단 거리 거리를 계산할 출발지를 지정합니다. 이 값을 생략하면 거리는 반환되지 않습니다.

includedPrimaryTypes 속성을 사용하여 최대 5개의 장소 유형을 지정합니다. 유형을 지정하지 않으면 모든 유형의 장소가 반환됩니다.

API 참조를 확인하세요.

장소 세부정보 가져오기

장소 예상 결과에서 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;

세션 토큰

세션 토큰은 사용자 자동 완성 검색의 쿼리 및 선택 단계를 결제 목적의 개별 세션으로 그룹화합니다. 사용자가 입력을 시작하면 세션이 시작됩니다. 사용자가 장소를 선택하고 장소 세부정보에 대한 호출이 이루어지면 세션이 종료됩니다.

새 세션 토큰을 만들어 요청에 추가하려면 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"],
});
await place.fetchFields({
    fields: ['displayName'],
  });

AutocompleteSessionToken의 새 인스턴스를 만들어 다음 세션을 위한 세션 토큰을 만듭니다.

세션 토큰 권장사항:

  • 모든 Place Autocomplete 호출에 세션 토큰을 사용합니다.
  • 각 세션에 대해 새 토큰을 생성합니다.
  • 새 세션마다 고유한 세션 토큰을 전달합니다. 둘 이상의 세션에 동일한 토큰을 사용하면 각 요청에 대해 개별적으로 요금이 청구됩니다.

필요한 경우 요청에서 자동 완성 세션 토큰을 생략할 수 있습니다. 세션 토큰이 생략된 경우 각 요청에 대한 요금이 별도로 청구되어 Autocomplete - Per Request SKU가 트리거됩니다. 세션 토큰을 재사용하면 세션이 유효하지 않은 것으로 간주되어 세션 토큰이 제공되지 않은 것처럼 요청에 대한 요금이 청구됩니다.

사용자가 쿼리를 입력하면 자동 완성 요청이 문자 단위가 아닌 몇 번의 키 입력마다 호출되고 가능한 결과 목록이 반환됩니다. 사용자가 결과 목록에서 항목을 선택하면 선택 항목이 요청으로 계산되고, 검색 중에 수행된 모든 요청은 번들로 묶여 단일 요청으로 계산됩니다. 사용자가 장소를 선택하면 검색어를 무료로 사용할 수 있으며 장소 데이터 요청에 대해서만 요금이 청구됩니다. 사용자가 세션을 시작하고 몇 분 내에 선택하지 않으면 검색어에 대해서만 요금이 청구됩니다.

앱의 관점에서 볼 때 이벤트의 흐름은 다음과 같습니다.

  1. 사용자가 '프랑스 파리'를 검색하기 위해 검색어를 입력하기 시작합니다.
  2. 사용자 입력을 감지하면 앱은 새로운 세션 토큰인 '토큰 A'를 만듭니다.
  3. 사용자가 입력할 때 API는 몇 글자마다 자동 완성 요청을 실행하며 각 문자에 대해 잠재적인 결과의 새로운 목록을 표시합니다.
    'P'
    'Par'
    'Paris',
    'Paris, Fr'
  4. 사용자가 선택하는 경우:
    • 쿼리에서 발생하는 모든 요청은 그룹화되어 '토큰 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>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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>

샘플 사용해 보기

세션으로 자동 완성 입력 미리 입력

이 예에서는 사용자 쿼리를 기반으로 fetchAutocompleteSuggestions()를 호출하고 응답으로 예상 장소 목록을 표시하고 마지막으로 선택한 장소의 장소 세부정보를 가져오는 방법을 보여줍니다. 또한 이 예에서는 최종 장소 세부정보 요청과 함께 세션 토큰을 사용하여 사용자 쿼리를 그룹화하는 방법도 보여줍니다.

TypeScript

let title;
let results;
let input;
let token;

// Add an initial request body.
let 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",
};

async function init() {
    token = new google.maps.places.AutocompleteSessionToken();

    title = document.getElementById('title');
    results = document.getElementById('results');
    input = document.querySelector("input");
    input.addEventListener("input", makeAcRequest);
    request = refreshToken(request) as any;
}

async function makeAcRequest(input) {
    // Reset elements and exit if an empty string is received.
    if (input.target.value == '') {
        title.innerText = '';
        results.replaceChildren();
        return;
    }

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

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

    title.innerText = 'Query predictions for "' + request.input + '"';

    // Clear the list first.
    results.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 element.
        const li = document.createElement('li');
        li.appendChild(a);
        results.appendChild(li);
    }
}

// Event handler for clicking on a suggested place.
async function onPlaceSelected(place) {
    await place.fetchFields({
        fields: ['displayName', 'formattedAddress'],
    });
    let placeText = document.createTextNode(place.displayName + ': ' + place.formattedAddress);
    results.replaceChildren(placeText);
    title.innerText = 'Selected Place:';
    input.value = '';
    refreshToken(request);
}

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

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

JavaScript

let title;
let results;
let input;
let token;
// Add an initial request body.
let 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",
};

async function init() {
  token = new google.maps.places.AutocompleteSessionToken();
  title = document.getElementById("title");
  results = document.getElementById("results");
  input = document.querySelector("input");
  input.addEventListener("input", makeAcRequest);
  request = refreshToken(request);
}

async function makeAcRequest(input) {
  // Reset elements and exit if an empty string is received.
  if (input.target.value == "") {
    title.innerText = "";
    results.replaceChildren();
    return;
  }

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

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

  title.innerText = 'Query predictions for "' + request.input + '"';
  // Clear the list first.
  results.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 element.
    const li = document.createElement("li");

    li.appendChild(a);
    results.appendChild(li);
  }
}

// Event handler for clicking on a suggested place.
async function onPlaceSelected(place) {
  await place.fetchFields({
    fields: ["displayName", "formattedAddress"],
  });

  let placeText = document.createTextNode(
    place.displayName + ": " + place.formattedAddress,
  );

  results.replaceChildren(placeText);
  title.innerText = "Selected Place:";
  input.value = "";
  refreshToken(request);
}

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

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>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=init&libraries=places&v=weekly"
      defer
    ></script>
  </body>
</html>

샘플 사용해 보기