חיפוש טקסט (חדש)

בחירת פלטפורמה: Android iOS JavaScript Web Service
מפתחים באזור הכלכלי האירופי (EEA)

התכונה "חיפוש טקסט (חדש)" מקבלת שאילתת טקסט ומחזירה רשימה של מקומות תואמים.

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

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

חיפוש מקומות באמצעות שאילתת טקסט

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

TypeScript

const request = {
    textQuery: query,
    fields: ['displayName', 'location', 'businessStatus'],
    includedType: '', // Restrict query to a specific type (leave blank for any).
    useStrictTypeFiltering: true,
    locationRestriction: map.getBounds(),
    isOpenNow: true,
    language: 'en-US',
    maxResultCount: 8,
    minRating: 1, // Specify a minimum rating.
    region: 'us',
};

const { places } = await Place.searchByText(request);

JavaScript

const request = {
    textQuery: query,
    fields: ['displayName', 'location', 'businessStatus'],
    includedType: '', // Restrict query to a specific type (leave blank for any).
    useStrictTypeFiltering: true,
    locationRestriction: map.getBounds(),
    isOpenNow: true,
    language: 'en-US',
    maxResultCount: 8,
    minRating: 1, // Specify a minimum rating.
    region: 'us',
};

const { places } = await Place.searchByText(request);
  • מציינים שאילתת טקסט או מספר טלפון לחיפוש באמצעות הפרמטר textQuery.
  • משתמשים בפרמטר fields (חובה) כדי לציין רשימה מופרדת בפסיקים של שדה נתונים אחד או יותר בקאמל קייס. השדות שאתם מבקשים קובעים את מחיר הבקשה. איך בוחרים שדות נתונים
  • משתמשים בפרמטר includedType כדי להחזיר רק תוצאות מהסוג שצוין.
  • אפשר להשתמש ב-locationBias או ב-locationRestriction כדי להטות או להגביל את תוצאות החיפוש של חיפוש הטקסט לאזור ספציפי.
לרשימת המאפיינים המלאה

אם השאילתה מכילה מספר טלפון, צריך להגדיר את פרמטר האזור. לדוגמה, אם אתם משתמשים במספר טלפון כדי לחפש מקום ביפן, והדומיין שממנו נשלחת הבקשה הוא jp, אתם צריכים להגדיר את הפרמטר region לערך "jp". אם משמיטים את region מהבקשה, ה-API יוגדר כברירת מחדל לאזור ארצות הברית ('us').

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

דוגמה

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

TypeScript

let map: google.maps.Map;
let markers: Record<string, google.maps.marker.AdvancedMarkerElement> = {};
let infoWindow: google.maps.InfoWindow;

async function init() {
    const [{ Map, InfoWindow }, { ControlPosition }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const center = { lat: 37.4161493, lng: -122.0812166 };
    map = new Map(document.getElementById('map')!, {
        center,
        zoom: 11,
        mapTypeControl: false,
        mapId: 'DEMO_MAP_ID',
    });

    const textInput = document.getElementById('text-input') as HTMLInputElement;
    const textInputButton = document.getElementById(
        'text-input-button'
    ) as HTMLButtonElement;
    const card = document.getElementById('text-input-card')!;
    map.controls[ControlPosition.TOP_LEFT].push(card);

    textInputButton.addEventListener('click', () => {
        void findPlaces(textInput.value);
    });

    textInput.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
            void findPlaces(textInput.value);
        }
    });

    infoWindow = new InfoWindow();
}

async function findPlaces(query: string) {
    const [{ Place }, { AdvancedMarkerElement }] = await Promise.all([
        google.maps.importLibrary('places'),
        google.maps.importLibrary('marker'),
    ]);

    const request = {
        textQuery: query,
        fields: ['displayName', 'location', 'businessStatus'],
        includedType: '', // Restrict query to a specific type (leave blank for any).
        useStrictTypeFiltering: true,
        locationRestriction: map.getBounds(),
        isOpenNow: true,
        language: 'en-US',
        maxResultCount: 8,
        minRating: 1, // Specify a minimum rating.
        region: 'us',
    };

    const { places } = await Place.searchByText(request);

    if (places.length) {
        const { LatLngBounds } = await google.maps.importLibrary('core');
        const bounds = new LatLngBounds();

        // First remove all existing markers.
        for (const id in markers) {
            markers[id].map = null;
        }
        markers = {};

        // Loop through and get all the results.
        places.forEach((place: google.maps.places.Place) => {
            const marker = new AdvancedMarkerElement({
                map,
                position: place.location,
                title: place.displayName,
                gmpClickable: true,
            });
            markers[place.id] = marker;

            marker.addEventListener('gmp-click', () => {
                map.panTo(place.location!);
                updateInfoWindow(place.displayName!, place.id, marker);
            });

            if (place.location != null) {
                bounds.extend(place.location);
            }
        });

        map.fitBounds(bounds);
    } else {
        console.log('No results');
    }
}

// Helper function to create an info window.
function updateInfoWindow(
    title: string | Element | null,
    content: string | Element | null,
    anchor: google.maps.marker.AdvancedMarkerElement
) {
    infoWindow.setContent(content);
    infoWindow.setHeaderContent(title);
    infoWindow.open({
        map,
        anchor,
        shouldFocus: false,
    });
}

void init();

JavaScript

let map;
let markers = {};
let infoWindow;

async function init() {
    const [{ Map, InfoWindow }, { ControlPosition }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const center = { lat: 37.4161493, lng: -122.0812166 };
    map = new Map(document.getElementById('map'), {
        center,
        zoom: 11,
        mapTypeControl: false,
        mapId: 'DEMO_MAP_ID',
    });

    const textInput = document.getElementById('text-input');
    const textInputButton = document.getElementById('text-input-button');
    const card = document.getElementById('text-input-card');
    map.controls[ControlPosition.TOP_LEFT].push(card);

    textInputButton.addEventListener('click', () => {
        void findPlaces(textInput.value);
    });

    textInput.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
            void findPlaces(textInput.value);
        }
    });

    infoWindow = new InfoWindow();
}

async function findPlaces(query) {
    const [{ Place }, { AdvancedMarkerElement }] = await Promise.all([
        google.maps.importLibrary('places'),
        google.maps.importLibrary('marker'),
    ]);

    const request = {
        textQuery: query,
        fields: ['displayName', 'location', 'businessStatus'],
        includedType: '', // Restrict query to a specific type (leave blank for any).
        useStrictTypeFiltering: true,
        locationRestriction: map.getBounds(),
        isOpenNow: true,
        language: 'en-US',
        maxResultCount: 8,
        minRating: 1, // Specify a minimum rating.
        region: 'us',
    };

    const { places } = await Place.searchByText(request);

    if (places.length) {
        const { LatLngBounds } = await google.maps.importLibrary('core');
        const bounds = new LatLngBounds();

        // First remove all existing markers.
        for (const id in markers) {
            markers[id].map = null;
        }
        markers = {};

        // Loop through and get all the results.
        places.forEach((place) => {
            const marker = new AdvancedMarkerElement({
                map,
                position: place.location,
                title: place.displayName,
                gmpClickable: true,
            });
            markers[place.id] = marker;

            marker.addEventListener('gmp-click', () => {
                map.panTo(place.location);
                updateInfoWindow(place.displayName, place.id, marker);
            });

            if (place.location != null) {
                bounds.extend(place.location);
            }
        });

        map.fitBounds(bounds);
    } else {
        console.log('No results');
    }
}

// Helper function to create an info window.
function updateInfoWindow(title, content, anchor) {
    infoWindow.setContent(content);
    infoWindow.setHeaderContent(title);
    infoWindow.open({
        map,
        anchor,
        shouldFocus: false,
    });
}

void 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;
}

#text-input-card {
    width: 25%;
    background-color: #fff;
    border-radius: 5px;
    box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
    margin: 10px;
    padding: 5px;
    font-family: Roboto, sans-serif;
    font-size: large;
    font-weight: bold;
}

#text-input {
    width: 100%;
    padding: 10px;
    margin: 0;
    box-sizing: border-box;
}

#text-input-button {
    display: inline-block;
    margin-top: 0.5rem;
    width: auto;
    padding: 0.6rem 0.75rem;
    font-size: 0.875rem;
    font-weight: 500;
    color: #fff;
    background-color: #2563eb;
    border: none;
    border-radius: 0.375rem;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out;
    text-align: center;
}

HTML

<html>
    <head>
        <title>Text Search</title>

        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (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: "GOOGLE_MAPS_API_KEY"
            });
        </script>
    </head>
    <body>
        <div id="text-input-card">
            <input
                type="text"
                id="text-input"
                placeholder="Search for a place" />
            <input type="button" id="text-input-button" value="Search" />
        </div>
        <div id="map"></div>
    </body>
</html>

בחירת שדות נתונים

השדות שאתם מבקשים קובעים את המחיר של כל בקשת חיפוש. הבקשה מחויבת פעם אחת, לפי המק"ט הגבוה ביותר מבין השדות המבוקשים. לדוגמה, אם מוסיפים את השדה reviews ל- fields , כל הבקשה מחויבת לפי מק"ט חיפוש טקסט Enterprise + Atmosphere.

כדי לשמור על בקשות החיפוש ב-Text Search Pro, צריך לבקש רק את השדות שמוצגים בתוצאות החיפוש, כמו id,‏ displayName,‏ formattedAddress ו-location.

כשמשתמש בוחר מקום, מפעילים את fetchFields() במקום הזה כדי לקבל שדות כמו reviews,‏ nationalPhoneNumber או websiteURI. כל קריאה ל-fetchFields() היא בקשה נפרדת ל-Place Details, והחיוב עליה מתבצע לפי המק"ט הגבוה ביותר מבין השדות שהיא מבקשת. הגישה הזו חוסכת כסף בהתאם למספר המקומות שהמשתמשים פותחים.

השדות שמפעילים כל מק"ט

  • השדות הבאים מפעילים את המק"ט חיפוש טקסט Essentials (IDs Only)‎:

    id

  • השדות הבאים מפעילים את מק"ט חיפוש טקסט Pro:

    accessibilityOptions
    addressComponents
    adrFormatAddress
    businessStatus
    displayName
    formattedAddress
    googleMapsUri
    iconBackgroundColor
    location
    photos
    plusCode
    svgIconMaskURI
    types
    utcOffsetMinutes
    viewport

  • השדות הבאים מפעילים את מק"ט Enterprise לחיפוש טקסט:

    internationalPhoneNumber
    nationalPhoneNumber
    priceLevel
    rating
    regularOpeningHours
    userRatingCount
    websiteURI

  • השדות הבאים מפעילים את המק"ט "חיפוש טקסט – Enterprise + Atmosphere":

    allowsDogs
    editorialSummary
    evChargeOptions
    fuelOptions
    hasCurbsidePickup
    hasDelivery
    hasDineIn
    hasLiveMusic
    hasMenuForChildren
    hasOutdoorSeating
    hasRestroom
    hasTakeout
    hasWifi
    isGoodForChildren
    isGoodForGroups
    isGoodForWatchingSports
    isReservable
    parkingOptions
    paymentOptions
    reviews
    servesBeer
    servesBreakfast
    servesBrunch
    servesCocktails
    servesCoffee
    servesDessert
    servesDinner
    servesLunch
    servesVegetarianFood
    servesWine

לגבי המק"ט של כל שדה, ראו שדות נתונים של מקומות.