Build a nearby business search service with Google Maps Platform (JavaScript)

1. Before you begin

Learn to use Google Maps Platform Maps and Places APIs to build a local business search, which geolocates the user and shows interesting places nearby. The app integrates geolocation, Place Details, Place Photos, and more.

Prerequisites

  • Basic knowledge of HTML, CSS, and JavaScript
  • A project with a billing account (follow instructions in the next step if you don't have this).
  • For the enablement step below, you will need to enable Maps JavaScript API and Places API.
  • An API key for the project above.

Get started with Google Maps Platform

If you haven't used Google Maps Platform before, follow the Get Started with Google Maps Platform guide or watch the Getting Started with Google Maps Platform playlist to complete the following steps:

  1. Create a billing account.
  2. Create a project.
  3. Enable Google Maps Platform APIs and SDKs (listed in the previous section).
  4. Generate an API key.

What you'll do

  • Build a webpage that displays a Google map
  • Centered the map on the user's location
  • Find nearby places and display the results as clickable markers
  • Fetch and show more details about each place

ae1caf211daa484d.png

What you'll need

  • A web browser, such as Google Chrome (recommended), Firefox, Safari, or Internet Explorer
  • Your favorite text or code editor

Get the sample code

  1. Open your command-line interface (Terminal on MacOS or Command Prompt on Windows) and download the sample code with this command:
git clone https://github.com/googlecodelabs/google-maps-nearby-search-js/

If that doesn't work, click the following button to download all the code for this codelab, then unzip the file:

Download the code

  1. Change into the directory you just cloned or downloaded.
cd google-maps-nearby-search-js

The stepN folders contain the desired end state of each step of this codelab. They are there for reference. Do all your coding work in the directory called work.

2. Create a map with a default center

There are three steps to creating a Google map on your web page:

  1. Create an HTML page
  2. Add a map
  3. Paste in your API key

1. Create an HTML page

Below is the map created in this step. The map is centered on the Sydney Opera House in Sydney, Australia. If the user denies permission to get their location, the map defaults to this location and still provides interesting search results.

569b9781658fec74.png

  1. Change directories into your work/ folder. Throughout the rest of the codelab, make your edits in the version in the work/ folder.
cd work
  1. In the work/ directory, use your text editor to create a blank file called index.html.
  2. Copy the following code into index.html.

index.html

<!DOCTYPE html>
<html>

<head>
  <title>Sushi Finder</title>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
  <meta charset="utf-8">
  <style>
    /* Always set the map height explicitly to define the size of the div
     * element that contains the map. */
    #map {
      height: 100%;
      background-color: grey;
    }

    /* Optional: Makes the sample page fill the window. */
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    /* TODO: Step 4A1: Make a generic sidebar. */
  </style>
</head>

<body>
  <!-- TODO: Step 4A2: Add a generic sidebar -->

  <!-- Map appears here -->
  <div id="map"></div>

  <!-- TODO: Step 1B, Add a map -->
</body>

</html>
  1. Open the file index.html in your web browser.
open index.html

2. Add a map

This section shows you how to load the Maps JavaScript API into your web page and write your own JavaScript that uses the API to add a map to the web page.

  1. Add this script code where you see <!-- TODO: Step 1B, Add a map --> after the map div and before the close </body> tag.

step1/index.html

<!-- TODO: Step 1B, Add a map -->
<script>
    /* Note: This example requires that you consent to location sharing when
     * prompted by your browser. If you see the error "Geolocation permission
     * denied.", it means you probably did not give permission for the browser * to locate you. */

    /* TODO: Step 2, Geolocate your user
     * Replace the code from here to the END TODO comment with new code from
     * codelab instructions. */
    let pos;
    let map;
    function initMap() {
        // Set the default location and initialize all variables
        pos = {lat: -33.857, lng: 151.213};
        map = new google.maps.Map(document.getElementById('map'), {
            center: pos,
            zoom: 15
        });
    }
    /* END TODO: Step 2, Geolocate your user */
</script>

<!-- TODO: Step 1C, Get an API key -->
<!-- TODO: Step 3A, Load the Places Library -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>

3. Paste in your API key

  1. In the line after <!-- TODO: Step 1C, Get an API key -->, copy and replace the value of the key parameter in the script source URL with the API key you created during the pre-requisites.

step1/index.html

<!-- TODO: Step 1C, Get an API key -->
<!-- TODO: Step 3A, Load the Places Library -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
  1. Save the HTML file you've been working on.

Test it

Reload your browser view of the file you've been editing. You should see a map appear now where the grey rectangle was before. If you see an error message instead, make sure you have replaced "YOUR_API_KEY" in the final <script> tag with your own API key. See above for how to get an API key if you don't already have one.

Full sample code

The full code for this project up to this point is available on Github.

3. Geolocate your user

Next, you want to display the geographic location of the user or device on a Google map using your browser's HTML5 Geolocation feature along with the Maps JavaScript API.

Here is an example of a map that displays your geographic location if you were browsing from Mountain View, California:

1dbb3fec117cd895.png

What is geolocation?

Geolocation refers to the identification of the geographic location of a user or computing device through a variety of data-collection mechanisms. Typically, most geolocation services use network routing addresses or internal GPS devices to determine this location. This app uses the web browser's W3C Geolocation standard navigator.geolocation property to determine the user's location.

Try it yourself

Replace the code between the comments TODO: Step 2, Geolocate your user and END TODO: Step 2, Geolocate your user with the following code:

step2/index.html

/* TODO: Step 2, Geolocate your user
    * Replace the code from here to the END TODO comment with this code
    * from codelab instructions. */
let pos;
let map;
let bounds;
let infoWindow;
let currentInfoWindow;
let service;
let infoPane;
function initMap() {
    // Initialize variables
    bounds = new google.maps.LatLngBounds();
    infoWindow = new google.maps.InfoWindow;
    currentInfoWindow = infoWindow;
    /* TODO: Step 4A3: Add a generic sidebar */

    // Try HTML5 geolocation
    if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(position => {
        pos = {
        lat: position.coords.latitude,
        lng: position.coords.longitude
        };
        map = new google.maps.Map(document.getElementById('map'), {
        center: pos,
        zoom: 15
        });
        bounds.extend(pos);

        infoWindow.setPosition(pos);
        infoWindow.setContent('Location found.');
        infoWindow.open(map);
        map.setCenter(pos);

        /* TODO: Step 3B2, Call the Places Nearby Search */
    }, () => {
        // Browser supports geolocation, but user has denied permission
        handleLocationError(true, infoWindow);
    });
    } else {
    // Browser doesn't support geolocation
    handleLocationError(false, infoWindow);
    }
}

// Handle a geolocation error
function handleLocationError(browserHasGeolocation, infoWindow) {
    // Set default location to Sydney, Australia
    pos = {lat: -33.856, lng: 151.215};
    map = new google.maps.Map(document.getElementById('map'), {
    center: pos,
    zoom: 15
    });

    // Display an InfoWindow at the map center
    infoWindow.setPosition(pos);
    infoWindow.setContent(browserHasGeolocation ?
    'Geolocation permissions denied. Using default location.' :
    'Error: Your browser doesn\'t support geolocation.');
    infoWindow.open(map);
    currentInfoWindow = infoWindow;

    /* TODO: Step 3B3, Call the Places Nearby Search */
}
/* END TODO: Step 2, Geolocate your user */
/* TODO: Step 3B1, Call the Places Nearby Search */

Test it

  1. Save your file.
  2. Reload your page.

Your browser should now ask you for permission to share your location with the app.

  1. Click Block one time to see if it handles the error gracefully and remains centered on Sydney.
  2. Reload again and click Allow to see if the geolocation works and moves the map to your current location.

Full sample code

The full code for this project up to this point is available on Github.

4. Search for nearby places

A Nearby Search lets you search for places within a specified area by keyword or type. A Nearby Search must always include a location, which can be specified in one of two ways:

  • A LatLngBounds object defining a rectangular search area
  • A circular area defined as the combination of the location property - specifying the center of the circle as a LatLng object — and a radius, measured in meters

Initiate a Nearby Search with a call to the PlacesService nearbySearch() method, which will return an array of PlaceResult objects.

A. Load the Places Library

First, to access the Places Library services, update your script source URL to introduce the libraries parameter and add places as a value.

step3/index.html

<!-- TODO: Step 3A, Load the Places Library -->
<script async defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap">

B. Call the Places Nearby Search request and handle the response

Next, form a PlaceSearch Request. The minimum required fields are:

The minimum required fields are:

  • bounds, which must be a google.maps.LatLngBounds object defining the rectangular search area, or a location and a radius; the former takes a google.maps.LatLng object, and the latter takes a simple integer that represents the circle's radius in meters. The maximum allowed radius is 50 000 meters. Note that when rankBy is set to DISTANCE, you must specify a location but you cannot specify a radius or bounds.
  • A keyword to be matched against all available fields, including but not limited to name, type, and address, as well as customer reviews and other third-party content, or a type, which restricts the results to places matching the specified type. Only one type may be specified (if more than one type is provided, then all types following the first entry are ignored). See the list of supported types.

For this codelab, you will use the user's current position as the location for the search and rank results by distance.

  1. Add the following at the comment TODO: Step 3B1 to write two functions to call the search and handle the response.

The keyword sushi is used as the search term, but you can change it. The code to define the createMarkers function is provided in the next section.

step3/index.html

/* TODO: Step 3B1, Call the Places Nearby Search */
// Perform a Places Nearby Search Request
function getNearbyPlaces(position) {
    let request = {
    location: position,
    rankBy: google.maps.places.RankBy.DISTANCE,
    keyword: 'sushi'
    };

    service = new google.maps.places.PlacesService(map);
    service.nearbySearch(request, nearbyCallback);
}

// Handle the results (up to 20) of the Nearby Search
function nearbyCallback(results, status) {
    if (status == google.maps.places.PlacesServiceStatus.OK) {
    createMarkers(results);
    }
}

/* TODO: Step 3C, Generate markers for search results */
  1. Add this line to the end of the initMap function at the comment TODO: Step 3B2.
/* TODO: Step 3B2, Call the Places Nearby Search */
// Call Places Nearby Search on user's location
getNearbyPlaces(pos);
  1. Add this line to the end of the handleLocationError function at the comment TODO: Step 3B3.
/* TODO: Step 3B3, Call the Places Nearby Search */
// Call Places Nearby Search on the default location
getNearbyPlaces(pos);

C. Generate markers for search results

A marker identifies a location on a map. By default, a marker uses a standard image. For information about customizing marker images, see Markers.

The google.maps.Marker constructor takes a single Marker options object literal, specifying the initial properties of the marker.

The following fields are particularly important and commonly set when constructing a marker:

  • position (required) specifies a LatLng identifying the initial location of the marker.
  • map (optional) specifies the Map on which to place the marker. If you do not specify the map upon construction of the marker, the marker is created, but is not attached to (or displayed on) the map. You may add the marker later by calling the marker's setMap() method.
  • Add the following code after the comment TODO: Step 3C to set the position, map and title for one marker per place returned in the response. You also use the extend method of the bounds variable to ensure that the center and all the markers are visible on the map.

step3/index.html

/* TODO: Step 3C, Generate markers for search results */
// Set markers at the location of each place result
function createMarkers(places) {
    places.forEach(place => {
    let marker = new google.maps.Marker({
        position: place.geometry.location,
        map: map,
        title: place.name
    });

    /* TODO: Step 4B: Add click listeners to the markers */

    // Adjust the map bounds to include the location of this marker
    bounds.extend(place.geometry.location);
    });
    /* Once all the markers have been placed, adjust the bounds of the map to
    * show all the markers within the visible area. */
    map.fitBounds(bounds);
}

/* TODO: Step 4C: Show place details in an info window */

Test it

  1. Save and reload the page, and click Allow to give geolocation permissions.

You should see up to 20 red markers around the center location of the map.

  1. Reload the page again and block geolocation permissions this time.

Do you still get results at the default center of your map (in the sample, the default is in Sydney, Australia)?

Full sample code

The full code for this project up to this point is available on Github.

5. Show Place Details on demand

Once you have a place's Place ID (delivered as one of the fields in the results of your Nearby Search), you can request additional details about the place, such as its complete address, phone number, and user ratings and reviews. In this codelab, you'll make a sidebar to display rich Place Details and make the markers interactive so the user can select places to view details.

A. Make a generic sidebar

You need a place to display Place Details, so here's some simple code for a sidebar that you can use to slide out and display the place details when the user clicks on a marker.

  1. Add the following code to the style tag after the comment TODO: Step 4A1:

step4/index.html

/* TODO: Step 4A1: Make a generic sidebar */
/* Styling for an info pane that slides out from the left. 
    * Hidden by default. */
#panel {
    height: 100%;
    width: null;
    background-color: white;
    position: fixed;
    z-index: 1;
    overflow-x: hidden;
    transition: all .2s ease-out;
}

.open {
    width: 250px;
}

/* Styling for place details */
.hero {
    width: 100%;
    height: auto;
    max-height: 166px;
    display: block;
}

.place,
p {
    font-family: 'open sans', arial, sans-serif;
    padding-left: 18px;
    padding-right: 18px;
}

.details {
    color: darkslategrey;
}

a {
    text-decoration: none;
    color: cadetblue;
}
  1. In the body section just before the map div, add a div for the details panel.
<!-- TODO: Step 4A2: Add a generic sidebar -->
<!-- The slide-out panel for showing place details -->
<div id="panel"></div>
  1. In the initMap() function after the TODO: Step 4A3 comment, initialize the infoPane variable like this:
/* TODO: Step 4A3: Add a generic sidebar */
infoPane = document.getElementById('panel');

B. Add click listeners to the markers

  1. In the createMarkers function, add a click listener to each marker as you create them.

The click listener fetches details about the place associated with that marker and calls the function to display the details.

  1. Paste the following code inside the createMarkers function at the code comment TODO: Step 4B.

The showDetails method is implemented in the next section.

step4/index.html

/* TODO: Step 4B: Add click listeners to the markers */
// Add click listener to each marker
google.maps.event.addListener(marker, 'click', () => {
    let request = {
    placeId: place.place_id,
    fields: ['name', 'formatted_address', 'geometry', 'rating',
        'website', 'photos']
    };

    /* Only fetch the details of a place when the user clicks on a marker.
    * If we fetch the details for all place results as soon as we get
    * the search response, we will hit API rate limits. */
    service.getDetails(request, (placeResult, status) => {
    showDetails(placeResult, marker, status)
    });
});

In the addListener request, the placeId property specifies a single place for the details request and the fields property is an array of field names for information you want returned about the place. For a full list of fields you can request, see PlaceResult interface.

C. Show Place Details in an info window

An info window displays content (usually text or images) in a dialog above a given location on a map. The info window has a content area and a tapered stem. The tip of the stem is attached to a specified location on the map. Typically, info windows are attached to markers, but you can also attach an info window to a specific latitude/longitude.

  1. Add the following code at the comment TODO: Step 4C to create an InfoWindow that displays the name and rating of the business, and attaches that window to the marker.

You define showPanel in the next section for displaying details in a sidebar.

step4/index.html

/* TODO: Step 4C: Show place details in an info window */
// Builds an InfoWindow to display details above the marker
function showDetails(placeResult, marker, status) {
    if (status == google.maps.places.PlacesServiceStatus.OK) {
    let placeInfowindow = new google.maps.InfoWindow();
    placeInfowindow.setContent('<div><strong>' + placeResult.name +
        '</strong><br>' + 'Rating: ' + placeResult.rating + '</div>');
    placeInfowindow.open(marker.map, marker);
    currentInfoWindow.close();
    currentInfoWindow = placeInfowindow;
    showPanel(placeResult);
    } else {
    console.log('showDetails failed: ' + status);
    }
}

/* TODO: Step 4D: Load place details in a sidebar */

D. Load place details in a sidebar

Use the same details returned in the PlaceResult object to populate another div. In this sample, use infoPane which is an arbitrary variable name for the div with the ID "panel". Each time the user clicks on a new marker, this code closes the sidebar if it was already open, erases the old details, adds the new details, and opens the sidebar.

  1. Add the following code after the comment TODO: Step 4D.

step4/index.html

/* TODO: Step 4D: Load place details in a sidebar */
// Displays place details in a sidebar
function showPanel(placeResult) {
    // If infoPane is already open, close it
    if (infoPane.classList.contains("open")) {
    infoPane.classList.remove("open");
    }

    // Clear the previous details
    while (infoPane.lastChild) {
    infoPane.removeChild(infoPane.lastChild);
    }

    /* TODO: Step 4E: Display a Place Photo with the Place Details */

    // Add place details with text formatting
    let name = document.createElement('h1');
    name.classList.add('place');
    name.textContent = placeResult.name;
    infoPane.appendChild(name);
    if (placeResult.rating != null) {
    let rating = document.createElement('p');
    rating.classList.add('details');
    rating.textContent = `Rating: ${placeResult.rating} \u272e`;
    infoPane.appendChild(rating);
    }
    let address = document.createElement('p');
    address.classList.add('details');
    address.textContent = placeResult.formatted_address;
    infoPane.appendChild(address);
    if (placeResult.website) {
    let websitePara = document.createElement('p');
    let websiteLink = document.createElement('a');
    let websiteUrl = document.createTextNode(placeResult.website);
    websiteLink.appendChild(websiteUrl);
    websiteLink.title = placeResult.website;
    websiteLink.href = placeResult.website;
    websitePara.appendChild(websiteLink);
    infoPane.appendChild(websitePara);
    }

    // Open the infoPane
    infoPane.classList.add("open");
}

E. Display a Place Photo with the Place Details

The getDetails result returns an array of up to 10 photos associated with the placeId. Here, you display the first photo above the place name in the sidebar.

  1. Place this code before the creation of the name element if you want the photo to appear at the top of the sidebar.

step4/index.html

/* TODO: Step 4E: Display a Place Photo with the Place Details */
// Add the primary photo, if there is one
if (placeResult.photos != null) {
    let firstPhoto = placeResult.photos[0];
    let photo = document.createElement('img');
    photo.classList.add('hero');
    photo.src = firstPhoto.getUrl();
    infoPane.appendChild(photo);
}

Test it

  1. Save and reload the page in your browser and allow geolocation permissions.
  2. Click on a marker to see the info window pop up from the marker displaying a few details and the sidebar slide out from the left to display more details.
  3. Test whether the search also works if you reload and deny geolocation permissions. Edit your search keyword for a different query and explore the result returned for that search.

ae1caf211daa484d.png

Full sample code

The full code for this project up to this point is available on Github.

6. Congratulations

Congratulations! You used many features of the Maps JavaScript API, including the Places Library.

What we've covered

Learn more

To do even more with maps, explore the Maps JavaScript API documentation and Places Library documentation, both of which contain guides, tutorials, the API reference, more code samples, and support channels. Some popular features are Importing Data into Maps, Start Styling Your Map, and adding the Street View Service.

Which type of codelab would you most like us to build next?

More examples of using rich Places information More codelabs using the Maps Platform JavaScript API More codelabs for Android More codelabs for iOS Visualizing location-based data on maps Custom styling of maps Using StreetView

Is the codelab you want not listed above? Request it with a new issue here.