Place Autocomplete Data API

מפתחים באזור הכלכלי האירופי (EEA)

‫Place Autocomplete Data API מאפשר לכם לאחזר תחזיות של מקומות באופן פרוגרמטי, כדי ליצור חוויות השלמה אוטומטית מותאמות אישית עם רמת שליטה גבוהה יותר מזו שאפשרה ווידג'ט ההשלמה האוטומטית. במדריך הזה נסביר איך להשתמש ב-Place Autocomplete Data API כדי לשלוח בקשות להשלמה אוטומטית על סמך שאילתות של משתמשים.

בדוגמה הבאה מוצג שילוב פשוט של השלמה אוטומטית. מזינים את שאילתת החיפוש, למשל 'פיצה' או 'פוקי', ואז לוחצים על התוצאה הרצויה כדי לבחור בה.

בקשות להשלמה אוטומטית

בקשה להשלמה אוטומטית מקבלת מחרוזת קלט של שאילתה ומחזירה רשימה של חיזויים של מקומות. כדי לשלוח בקשה להשלמה אוטומטית, קוראים ל-method‏ 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;

הגבלת ההצעות להשלמה אוטומטית

כברירת מחדל, ההשלמה האוטומטית של מקומות מציגה את כל סוגי המקומות, עם הטיה לחיזויים בקרבת המיקום של המשתמש, ומאחזרת את כל שדות הנתונים הזמינים למקום שנבחר על ידי המשתמש. הגדרת אפשרויות להשלמה אוטומטית של מקומות כדי להציג תחזיות רלוונטיות יותר, על ידי הגבלת התוצאות או הטייתן.

הגבלת התוצאות גורמת לווידג'ט ההשלמה האוטומטית להתעלם מתוצאות שנמצאות מחוץ לאזור ההגבלה. נהוג להגביל את התוצאות לגבולות המפה. הטיה של תוצאות מאפשרת לווידג'ט ההשלמה האוטומטית להציג תוצאות באזור שצוין, אבל יכול להיות שחלק מההתאמות יהיו מחוץ לאזור הזה.

משתמשים במאפיין origin כדי לציין את נקודת המוצא שממנה יחושב המרחק הגיאודזי אל היעד. אם לא מציינים את הערך הזה, המרחק לא מוחזר.

משתמשים במאפיין includedPrimaryTypes כדי לציין עד חמישה סוגי מקומות. אם לא מציינים סוגים, יוחזרו מקומות מכל הסוגים.

הפניית API

קבלת פרטי מקום

כדי להחזיר אובייקט Place מתוצאת תחזית של מקום, קודם קוראים ל-toPlace() ואז קוראים ל-fetchFields() באובייקט Place שמתקבל (מזהה הסשן מתחזית המקום נכלל באופן אוטומטי). התקשרות אל 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"],
});

יוצרים טוקן סשן לסשן הבא על ידי יצירת מופע חדש של AutocompleteSessionToken.

המלצות לטוקן סשן:

  • מומלץ להשתמש באסימוני סשן לכל הקריאות ל-Place Autocomplete.
  • צריך ליצור טוקן חדש לכל סשן.
  • צריך להעביר טוקן ייחודי של סשן לכל סשן חדש. שימוש באותו אסימון ליותר מסשן אחד יגרום לחיוב נפרד על כל בקשה.

אפשר להשמיט את טוקן הסשן של ההשלמה האוטומטית מבקשה. אם לא מציינים את טוקן הסשן, כל בקשה מחויבת בנפרד, והשימוש יחויב לפי מק"ט השלמה אוטומטית – לכל בקשה. אם משתמשים מחדש בטוקן של סשן, הסשן נחשב לא תקף והבקשות מחויבות כאילו לא סופק טוקן של סשן.

דוגמה

בזמן שהמשתמש מקליד שאילתה, נשלחת בקשה להשלמה אוטומטית אחרי כל כמה הקשות (לא אחרי כל תו), ומוחזרת רשימה של תוצאות אפשריות. כשמשתמש בוחר משהו מרשימת התוצאות, הבחירה נחשבת כבקשה, וכל הבקשות שנשלחות במהלך החיפוש נארזות יחד ונספרות כבקשה אחת. אם המשתמש בוחר מקום, שאילתת החיפוש זמינה ללא תשלום, ורק בקשת הנתונים של המקום מחויבת. אם המשתמש לא בוחר תוך כמה דקות מתחילת הסשן, הוא יחויב רק על שאילתת החיפוש.

מנקודת המבט של אפליקציה, רצף האירועים הוא כזה:

  1. משתמש מתחיל להקליד שאילתה כדי לחפש את 'פריז, צרפת'.
  2. כשמזוהה קלט משתמש, האפליקציה יוצרת אסימון סשן חדש, Token A.
  3. בזמן שהמשתמש מקליד, ה-API שולח בקשה להשלמה אוטומטית אחרי כל כמה תווים, ומציג רשימה חדשה של תוצאות אפשריות לכל אחד מהם:
    'P'
    'Par'
    'Paris'
    'Paris, Fr'
  4. כשהמשתמש בוחר אפשרות:
    • כל הבקשות שנובעות מהשאילתה מקובצות ונוספות לסשן שמיוצג על ידי Token A, כבקשה אחת.
    • הבחירה של המשתמש נספרת כבקשה לפרטי מקום, והיא מתווספת לסשן שמיוצג על ידי Token A.
  5. הסשן מסתיים והאפליקציה מבטלת את האסימון A.
מידע על חיוב על סשנים

קוד לדוגמה מלא

בקטע הזה יש דוגמאות מלאות שמראות איך להשתמש ב-Place Autocomplete Data API .

הצעות להשלמת החיפוש של מקומות

בדוגמה הבאה מוסבר איך להפעיל את השיטה fetchAutocompleteSuggestions() כדי לקבל את התוצאה הראשונה של החיזוי עבור הקלט 'Tadi', ואז להפעיל את השיטה toPlace() כדי לקבל את פרטי המקום.fetchFields()

TypeScript

async function init() {
  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 + '":')
  );

  const resultsElement = document.getElementById("results") as HTMLElement;

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

    // Create a new list element.
    const listItem = document.createElement("li");

    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

async function init() {
    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 + '":'));
    const resultsElement = document.getElementById("results");
    for (let suggestion of suggestions) {
        const placePrediction = suggestion.placePrediction;
        // Create a new list element.
        const listItem = document.createElement("li");
        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>
    <!-- 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>
    <div id="title"></div>
    <ul id="results"></ul>
    <p><span id="prediction"></span></p>
    <img
      class="powered-by-google"
      src="./powered_by_google_on_white.png"
      alt="Powered by Google"
    />

  </body>
</html>

דוגמה לניסיון

השלמה אוטומטית של מקומות עם חיפוש תוך כדי הקלדה באמצעות סשנים

בדוגמה הזו אפשר לראות את המושגים הבאים:

  • התקשרות אל fetchAutocompleteSuggestions() על סמך שאילתות של משתמשים והצגת רשימה של מקומות חזויים בתגובה.
  • שימוש באסימוני סשן כדי לקבץ שאילתת משתמש עם בקשת הפרטים הסופית של המקום.
  • שליפת פרטי המקום שנבחר והצגת סמן.
  • שימוש ב-control slotting כדי להטמיע רכיבי ממשק משתמש ברכיב 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. 
 */
gmp-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>

דוגמה לניסיון