Learn to use Google Maps Platform's Maps and Places APIs to build a local business search, which geolocates the user and shows interesting places around them. The app integrates location, place details, place photos, and more.
Google Maps Platform brings the wealth of Google's location-based information to your app. Millions of developers use Google Maps Platform to help their users navigate the world around them, on the web and on mobile devices. The Google Maps Platform offers three core products:
In this lab, you're going to build a webpage that displays a Google map centered on the user's location, finds nearby places, and displays the places as clickable markers to show more details about each place.
⌘
key + space or Command-space
, then type terminal
) and run this command:gcloud init
The console prompts you to log in with the credentials already provided on this device. If there are multiple accounts to choose from, view the current account you should use by clicking on the "I/O 19" chrome extension in your browser's Extensions toolbar.
gcloud app create
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:
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
.
This lab is focused on using the Google Maps JavaScript API and its Places Library. Non-relevant concepts and code blocks are glossed over and are provided for you to simply copy and paste.
There are four steps to creating a Google map on your web page:
Below is the map you'll create for 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 will default to this location and still be able to provide interesting search results.
work/
folder. Throughout the rest of the lab, make your edits in the version in the work/
folder. cd work
work/
directory, use your text editor to create a blank file called 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>
Note that this is a very basic page with a single div element and a style element. You can add any content you like to the web page. You should see a large div with a grey background spanning the full visible window.
One of the benefits of using Google Cloud Platform is easy hosting of your apps on App Engine. Since this kiosk is already set up with a Cloud Platform account, this section teaches you how to deploy your site to App Engine.
The work
folder contains an app.yaml
file. This is the file that configures your App Engine app's settings, such as specifying how URL paths correspond to request handlers and static files. For this lab you do not need to modify this file.
Now you are ready to deploy your app to App Engine and host it on Google Cloud Platform.
gcloud app deploy
gcloud app browse
The command launches your web browser and load the URL of your webpage hosted on App Engine, and if the browser doesn't launch it returns a URL that you can copy and paste into your browser manually.
Right now you should see only a gray div filling the browser window.
This section shows you how to load the Maps JavaScript API into your web page, and how to write your own JavaScript that uses the API to add a map to the webpage.
Add this script code where you see <!-- TODO: Step 1B, Add a map -->
after the map
div and before the close </body>
tag.
<!-- 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>
This section explains how to authenticate your app to the Maps JavaScript API and Places API using your own API key.
Go to the Google Cloud Platform Console.
This device should already have a project available for you to use for this lab. Follow these steps to enable the APIs used in this lab and get an API key:
<!-- 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 just created.<!-- 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>
In your Terminal, run gcloud app deploy
(be sure you're still in the work folder that contains app.yaml and the index.html file you've been editing).
Reload your browser view of the App Engine-hosted page. 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 of the index.html file with your own API key. See above for how to get an API key if you don't already have one.
The full code for this project through Step 1 is available on Github.
Next, you'll 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, CA:
Geolocation refers to the identification of the geographic location of a user or computing device via a variety of data collection mechanisms. Typically, most geolocation services use network routing addresses or internal GPS devices to determine this location. This application uses the web browser's W3C Geolocation standard navigator.geolocation property to determine the user's location.
Replace the code between the comment TODO: Step 2, Geolocate your user
and END TODO: Step 2, Geolocate your user
with the code below:
/* 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 */
Save and re-deploy your app with gcloud app deploy
in your Terminal.
If you reload your page, your browser should now ask you for permissions to share your location with the app.
The full code for this project through Step 2 is available on Github.
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:
LatLngBounds
object defining a rectangular search area.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
's nearbySearch()
method, which will return an array of PlaceResult
objects.
First, to access the Places Library services, update your script source URL to introduce the libraries
parameter and add places
as a value.
<!-- 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">
Next, form a PlaceSearch Request. The minimum required fields are:
Either of:
bounds
, which must be a google.maps.LatLngBounds
object defining the rectangular search area; orlocation
and a radius
; the former takes a google.maps.LatLng
object, and the latter takes a simple integer, representing 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.end either of:
keyword
- A term 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.type
- Restricts the results to places matching the specified type. Only one type may be specified (if more than one type is provided, all types following the first entry are ignored). See the list of supported types.For this lab, you will use the user's current position as the location for the search and rank results by distance.
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, titled "Generate markers for search results"./* 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 */
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);
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);
A marker identifies a location on a map. By default, a marker uses a standard image. See the Markers guide for information about customizing marker images.
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 on 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 code below at 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.
/* 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 */
gcloud app deploy
to deploy your code changes.The full code for this project through Step 3 is available on Github.
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, user rating and reviews, etc. In this lab, we'll make a sidebar to display rich place detail information and make the markers interactive so the user can select places to view details.
We need a place to display the place details, so here's some simple code for a sidebar that we can use to slide out and display the place details when the user clicks on a marker.
style
tag at the comment TODO: Step 4A1
:/* 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;
}
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>
initMap()
function at the TODO: Step 4A3
comment, initialize the infoPane
variable like this:/* TODO: Step 4A3: Add a generic sidebar */
infoPane = document.getElementById('panel');
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.
Paste the following code inside the createMarkers
function at the code comment TODO: Step 4B
. The showDetails
method is implemented in the next section.
/* 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 above, 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. A full list of fields you can request is in the PlaceResult
interface documentation.
An info window displays content (usually text or images) in a popup window above the map, at a given location. 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.
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. We will define showPanel
in the next section for displaying details in a sidebar.
/* 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 */
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.
Add the following code at the comment TODO: Step 4D
.
/* 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");
}
The getDetails
result returns an array of up to 10 photos associated with the placeId
. Here, we display the first photo above the place name in the sidebar. Place this code above creation of the "name" element if you want the photo to appear at the top of the sidebar.
/* 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);
}
gcloud app deploy
to deploy your code changes.The full code for this project through Step 4 is available on Github.
Congratulations! You've used many features of the Google Maps JavaScript API, including the Places Library.
You also learned how to use the Google Cloud SDK to add App Engine to a project and deploy your locally developed code as an app hosted on Google Cloud Platform. Talk about leveling up your skills!
To do even more with maps, explore the Maps JavaScript API documentation and Places Library documentation which contain guides, tutorials, the API reference, more code samples, and support channels. Some popular features are Importing Data into Maps, Styling Your Map, and adding the Street View Service.
Is the codelab you want not listed above? Request it with a new issue here.