<html lang="en">
<head>
<meta charset="utf-8">
<title>Location Selection Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<h1>Location Selection Demo - FindPickupPointsForPlace</h1>
<div class="container">
<section class="form-container">
<form id="form-pups-for-place" name="location-selection">
<label class="form-label" for="placeId">Place ID</label>
<input type="text" id="placeId" name="placeId" value="ChIJwTUa-q_Mj4ARff4yludGH-M" />
<label class="form-label" for="languageCode">Language Code</label>
<input type="text" id="languageCode" name="languageCode" value="en-US" />
<label class="form-label" for="regionCode">Region Code</label>
<input type="text" id="regionCode" name="regionCode" value="US" />
<label class="form-label" for="searchLocation-latitude">Search Location - Latitude</label>
<input type="text" id="searchLocation-latitude" name="searchLocation-latitude" value="37.329472" />
<label class="form-label" for="searchLocation-longitude">Search Location - Longitude</label>
<input type="text" id="searchLocation-longitude" name="searchLocation-longitude" value="-121.890449" />
<label class="form-label" for="orderBy">Order By</label>
<select id="orderBy" name="orderBy">
<option value="DISTANCE_FROM_SEARCH_LOCATION" selected>DISTANCE_FROM_SEARCH_LOCATION</option>
<option value="WALKING_ETA_FROM_SEARCH_LOCATION">WALKING_ETA_FROM_SEARCH_LOCATION</option>
<option value="DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION">DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION</option>
</select>
<label class="form-label" for="destination-latitude">Destination - Latitude</label>
<input type="text" id="destination-latitude" name="destination-latitude" value="" />
<label class="form-label" for="destination-longitude">Destination - Longitude</label>
<input type="text" id="destination-longitude" name="destination-longitude" value="" />
<label class="form-label" for="maxResults">Max Results</label>
<input type="number" id="maxResults" name="maxResults" min="1" value="5" step="1" />
<fieldset>
<legend>Travel Modes</legend>
<div>
<input type="checkbox" id="walking" name="travelModes" value="WALKING" checked>
<label for="walking" class="form-checkbox-label">WALKING</label>
</div>
<div>
<input type="checkbox" id="driving" name="travelModes" value="DRIVING" checked>
<label for="driving" class="form-checkbox-label">DRIVING</label>
</div>
<div>
<input type="checkbox" id="twoWheeler" name="travelModes" value="TWO_WHEELER">
<label for="twoWheeler" class="form-checkbox-label">TWO_WHEELER</label>
</div>
</fieldset>
<label class="form-label" for="computeWalkingEta">Compute Walking ETA</label>
<select id="computeWalkingEta" name="computeWalkingEta" class="boolean">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<label class="form-label" for="computeDrivingEta">Compute Driving ETA</label>
<select id="computeDrivingEta" name="computeDrivingEta" class="boolean">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<input class="submit-button" type="submit" value="Call" />
</form>
</section>
<section>
<div id="map" class="map"></div>
</section>
</div>
<section class="output-container">
<h2>Response</h2>
<pre id="output"></pre>
</section>
</body>
</html>
body {
font-family: 'Google Sans';
}
.container {
display: grid;
grid-template-columns: 30% 1fr;
grid-template-rows: 100%;
grid-column-gap: 20px;
grid-row-gap: 0px;
}
h1 {
font-size: 24px;
margin-top: 20px;
margin-bottom: 20px;
font-weight: bold;
}
h2 {
font-size: 18px;
font-weight: bold;
}
h1,
.form-container,
.output-container {
margin-left: 20px;
}
.map,
.output-container {
margin-right: 20px;
}
.form-container {
border: 1px solid black;
padding: 20px;
}
.map {
border: 1px solid black;
min-height: 800px;
}
.output-container {
margin-top: 20px;
}
#output {
border: 1px solid red;
font-family: 'Google Sans';
min-height: 150px;
}
label:not(.form-checkbox-label), legend {
overflow-wrap: break-word;
font-weight: bold;
}
input:not([type="checkbox"]), select, fieldset {
font-family: 'Google Sans';
width: 100%;
padding: 5px 5px;
margin: 0 0 20px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 8px;
box-sizing: border-box;
}
input[type="submit"] {
min-width: 150px;
background-color: green; /* Blue */
border: none;
color: white;
padding: 15px 15px;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 20px;
width: 50%;
}
input[type="submit"]:hover {
background-color: darkseagreen;
}
input[type="submit"]:active {
background-color: darkseagreen;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
.info-label {
font-weight: bold;
}
const MAPS_API_KEY = ''; // Put your API Key for Maps SDK here
const LS_API_KEY = ''; // Put your API Key for Location Selection APIs here
const MAPS_URL = `https://maps.googleapis.com/maps/api/js?key=${
MAPS_API_KEY}&libraries=places,geometry&callback=initMap`;
const LS_BASE_URL = 'https://locationselection.googleapis.com/v1beta';
const API_URL_PUPS_FOR_PLACE =
`${LS_BASE_URL}:findPickupPointsForPlace?key=${LS_API_KEY}`;
const API_URL_PUPS_FOR_LOCATION =
`${LS_BASE_URL}:findPickupPointsForLocation?key=${LS_API_KEY}`;
const API_URL_NEARBY_PLACES =
`${LS_BASE_URL}:findNearbyPlaces?key=${LS_API_KEY}`;
const FORM_ID_PUPS_FOR_LOCATION = 'form-pups-for-location';
const FORM_ID_PUPS_FOR_PLACE = 'form-pups-for-place';
const FORM_ID_NEARBY_PLACES = 'form-nearby-places';
const FORM_TO_API_URL_MAP = {
[FORM_ID_PUPS_FOR_LOCATION]: API_URL_PUPS_FOR_LOCATION,
[FORM_ID_PUPS_FOR_PLACE]: API_URL_PUPS_FOR_PLACE,
[FORM_ID_NEARBY_PLACES]: API_URL_NEARBY_PLACES,
};
const RED_PIN = 'http://maps.google.com/mapfiles/ms/icons/red-dot.png';
const GREEN_PIN = 'http://maps.google.com/mapfiles/ms/icons/green-dot.png';
const BLUE_PIN = 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png';
const DEFAULT_ZOOM_LEVEL = 18;
// codepoint from https://fonts.google.com/icons
const SEARCH_LOCATION_MARKER = '\ue7f2';
const GOOGLEPLEX = {
lat: 37.422001,
lng: -122.084061
};
let map;
let polyLines = [];
let polygons = [];
let mapMarkers = [];
let entranceMarkers = [];
function loadMap() {
const script = document.createElement('script');
script.src = MAPS_URL;
document.body.appendChild(script);
}
function initMap() {
map = new google.maps.Map(
document.getElementById('map'),
{center: GOOGLEPLEX, zoom: DEFAULT_ZOOM_LEVEL});
}
function setupForm() {
const form = document.getElementsByTagName('form')[0];
form.addEventListener('submit', onFormSubmit);
}
function onFormSubmit(evt) {
evt.preventDefault();
evt.stopPropagation();
const formData = new FormData(evt.target);
fetchAPIResults(formData);
}
function transformFormData(fd) {
let transformedFd = {
localizationPreferences: {},
};
const formId = document.getElementsByTagName('form')[0].id;
if (formId === FORM_ID_PUPS_FOR_LOCATION ||
formId === FORM_ID_PUPS_FOR_PLACE) {
transformedFd = {localizationPreferences: {}, travelModes: []};
}
const addSearchLocation = () => {
if (transformedFd.searchLocation == null) {
transformedFd.searchLocation = {};
}
};
const addDestination = () => {
if (transformedFd.destination == null) {
transformedFd.destination = {};
}
};
fd.forEach((value, key) => {
switch (key) {
case 'travelModes':
transformedFd.travelModes.push(value);
break;
case 'languageCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'regionCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'searchLocation-latitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['latitude'] = value;
}
break;
case 'searchLocation-longitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['longitude'] = value;
}
break;
case 'destination-latitude':
if (value) {
addDestination();
transformedFd.destination['latitude'] = value;
}
break;
case 'destination-longitude':
if (value) {
addDestination();
transformedFd.destination['longitude'] = value;
}
break;
default:
transformedFd[key] = value;
break;
}
});
const json = JSON.stringify(transformedFd, undefined, 2);
return json;
}
async function fetchAPIResults(fd) {
const formId = document.getElementsByTagName('form')[0].id;
const url = FORM_TO_API_URL_MAP[formId];
const transformedFd = transformFormData(fd);
const response = await fetch(url, {method: 'POST', body: transformedFd});
const result = await response.json();
// Display JSON
displayAPIResults(result);
// Update map
let searchLocation = {};
if (JSON.parse(transformedFd).searchLocation) {
searchLocation = {
lat: Number(JSON.parse(transformedFd).searchLocation.latitude),
lng: Number(JSON.parse(transformedFd).searchLocation.longitude),
};
}
switch (formId) {
case FORM_ID_PUPS_FOR_PLACE:
markPickupPointsForPlace(result);
break;
case FORM_ID_PUPS_FOR_LOCATION:
markPickupPointsForLocation(result, searchLocation);
break;
case FORM_ID_NEARBY_PLACES:
markNearbyPlaces(result, searchLocation);
break;
default:
break;
}
}
function displayAPIResults(data) {
const output = document.getElementById('output');
output.textContent = JSON.stringify(data, undefined, 2);
}
function markNearbyPlaces(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const places = [];
for (const placeResult of data.placeResults) {
places.push(placeResult.place);
}
resetMap(searchLocation);
markPlaces(places, searchLocation);
for (const place of places) {
markEntrances(place.associatedCompounds, place);
}
markSearchLocation(searchLocation, '');
for (const place of places) {
mapPolygons(place.associatedCompounds);
}
}
function markPickupPointsForPlace(data) {
if (data.error) {
resetMap();
return;
}
const place = data.placeResult.place;
const pickupPoints = data.pickupPointResults;
const searchLocation = {
lat: place.geometry.location.latitude,
lng: place.geometry.location.longitude
};
resetMap(searchLocation);
markPickupPoints(place, pickupPoints, searchLocation);
markEntrances(place.associatedCompounds, place);
markSearchLocation(searchLocation, place.displayName);
createPolyLinesOneToMany(searchLocation, pickupPoints);
mapPolygons(place.associatedCompounds);
}
function markPickupPointsForLocation(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const placeIdToPlace = {};
// A dict, and the key is placeId(str)s and the value is a list of pups.
const placePickupPoints = {};
data.placeResults.forEach(result => {
placeIdToPlace[result.place.placeId] = result.place;
placePickupPoints[result.place.placeId] = [];
});
data.placePickupPointResults.forEach(result => {
placePickupPoints[result.associatedPlaceId].push(result.pickupPointResult);
})
resetMap(searchLocation);
for (const placeId in placePickupPoints) {
const place = placeIdToPlace[placeId];
const pups = placePickupPoints[placeId];
markEntrances(place.associatedCompounds, place);
markPickupPoints(place, pups, searchLocation);
createPolyLinesOneToMany(searchLocation, pups);
mapPolygons(place.associatedCompounds);
}
// update the marker rank to global order
for (let i = 0; i < mapMarkers.length; i++) {
mapMarkers[i].label = String(i);
}
markSearchLocation(searchLocation, '');
}
function markPlaces(places, searchLocation) {
for (const place of places) {
const placeLocation = place.geometry.location;
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
const marker = new google.maps.Marker({
position: toLatLngLiteral(placeLocation),
animation: google.maps.Animation.DROP,
map: map,
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
}
function markEntrances(compounds, place) {
if (!compounds) {
return;
}
for (const compound of compounds) {
if (!compound.entrances) {
continue;
}
for (const entrance of compound.entrances) {
const entranceMarker = new google.maps.Marker({
position: toLatLngLiteral(entrance.location),
icon: {
url: BLUE_PIN,
},
animation: google.maps.Animation.DROP,
map: map,
});
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
entranceMarker.addListener('click', () => {
infoWindow.open(map, entranceMarker);
});
map.addListener('click', () => {
infoWindow.close();
});
entranceMarkers.push(entranceMarker);
}
}
}
function mapPolygons(many) {
if (!many) {
return;
}
for (const toPoint of many) {
const data = toPoint.geometry.displayBoundary;
if (data == null || data.coordinates == null) {
continue;
}
const value = data.coordinates;
const polyArray = JSON.parse(JSON.stringify(value))[0];
const usedColors = [];
const finalLatLngs = [];
let color = '';
for (let i = 0; i < polyArray.length; ++i) {
if (polyArray[i] != null && polyArray[i].length > 0) {
color = getColor(usedColors);
usedColors.push(color);
if (isArrLatLng(polyArray[i])) {
finalLatLngs.push({lat: polyArray[i][1], lng: polyArray[i][0]});
}
}
}
const poly = new google.maps.Polygon({
strokeColor: color,
strokeOpacity: 0.2,
strokeWeight: 5,
fillColor: color,
fillOpacity: 0.1,
paths: finalLatLngs,
map: map,
});
polygons.push(poly);
}
}
function getColor(usedColors) {
let color = generateStrokeColor();
while (usedColors.includes(color)) {
color = generateStrokeColor();
}
return color;
}
function generateStrokeColor() {
return Math.floor(Math.random() * 16777215).toString(16);
}
function isArrLatLng(currArr) {
if (!currArr || currArr.length !== 2) {
return false;
}
return ((typeof currArr[0]) === 'number') &&
((typeof currArr[1]) === 'number');
}
function toLatLngLiteral(latlng) {
return {lat: latlng.latitude, lng: latlng.longitude};
}
function pickupHasRestrictions(pickupPointData) {
let hasRestrictions = false;
const travelDetails = pickupPointData.travelDetails;
for (let i = 0; i < travelDetails.length; i++) {
if (travelDetails[i].trafficRestriction !== 'NO_RESTRICTION') {
hasRestrictions = true;
}
}
return hasRestrictions;
}
function markPickupPoints(place, pickupPoints, searchLocation) {
for (let i = 0; i < pickupPoints.length; i++) {
const pickupPointData = pickupPoints[i];
const pickupPoint = pickupPoints[i].pickupPoint;
const pupIcon =
pickupHasRestrictions(pickupPointData) ? RED_PIN : GREEN_PIN;
const contentString = createInfoWindow(place, pickupPoint);
const pupInfoWindow = new google.maps.InfoWindow({content: contentString});
const marker = new google.maps.Marker({
position: toLatLngLiteral(pickupPoint.location),
label: {
text: String(i),
fontWeight: 'bold',
fontSize: '20px',
color: '#000'
},
animation: google.maps.Animation.DROP,
map,
icon: {
url: pupIcon,
anchor: new google.maps.Point(14, 43),
labelOrigin: new google.maps.Point(-5, 5)
},
});
marker.addListener('click', () => {
pupInfoWindow.open(map, marker);
});
map.addListener('click', () => {
pupInfoWindow.close();
});
mapMarkers.push(marker);
}
}
function createInfoWindow(place, pickupPoint) {
let result = [];
const addResult = (value, key, map) =>
result.push(`<p><span class="info-label">${key}:</span> ${value}</p>`);
const formatAddress = (address) => address.lines.join(',');
const placeFieldMap = new Map();
if (place !== null) {
placeFieldMap.set('Place', '');
placeFieldMap.set('Name', place.displayName);
placeFieldMap.set('Place ID', place.placeId);
placeFieldMap.set('Address', formatAddress(place.address.formattedAddress));
}
const pickupPointFieldMap = new Map();
if (pickupPoint !== null) {
pickupPointFieldMap.set('Pickup point', '');
pickupPointFieldMap.set('Name', pickupPoint.displayName);
}
placeFieldMap.forEach(addResult);
result.push('<hr/>');
pickupPointFieldMap.forEach(addResult);
return result.join('');
}
function markSearchLocation(location, label) {
const infoWindow =
new google.maps.InfoWindow({content: `<p><b>Name: </b>${label}</p>`});
const marker = new google.maps.Marker({
position: location,
map,
label: {
text: SEARCH_LOCATION_MARKER,
fontFamily: 'Material Icons',
color: '#ffffff',
fontSize: '18px',
fontWeight: 'bold',
},
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
function createPolyLinesOneToMany(one, many) {
const lineSymbol = {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
};
for (const toPoint of many) {
const line = new google.maps.Polyline({
path: [one, toLatLngLiteral(toPoint.pickupPoint.location)],
icons: [
{
icon: lineSymbol,
offset: '100%',
},
],
map: map,
});
polyLines.push(line);
}
}
/******* Reset the map ******/
function deleteMarkers() {
for (const mapMarker of mapMarkers) {
mapMarker.setMap(null);
}
mapMarkers = [];
}
function deletePolyLines() {
for (const polyLine of polyLines) {
polyLine.setMap(null);
}
polyLines = [];
}
function deleteEntranceMarkers() {
for (const entranceMarker of entranceMarkers) {
entranceMarker.setMap(null);
}
entranceMarkers = [];
}
function clearPolygons() {
for (let i = 0; i < polygons.length; i++) {
polygons[i].setMap(null);
}
polygons = [];
}
function resetMap(searchLocation) {
if (searchLocation) {
map.setCenter(searchLocation);
} else {
map.setCenter(GOOGLEPLEX);
}
map.setZoom(DEFAULT_ZOOM_LEVEL);
deleteMarkers();
deletePolyLines();
deleteEntranceMarkers();
clearPolygons();
}
// Initiate map & set form event handlers
loadMap();
setupForm();
<html lang="en">
<head>
<meta charset="utf-8">
<title>Location Selection Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<h1>Location Selection Demo - FindPickupPointsForLocation</h1>
<div class="container">
<section class="form-container">
<form id="form-pups-for-location" name="location-selection">
<label class="form-label" for="languageCode">Language Code</label>
<input type="text" id="languageCode" name="languageCode" value="en-US" />
<label class="form-label" for="regionCode">Region Code</label>
<input type="text" id="regionCode" name="regionCode" value="US" />
<label class="form-label" for="searchLocation-latitude">Search Location - Latitude</label>
<input type="text" id="searchLocation-latitude" name="searchLocation-latitude" value="-23.482049" />
<label class="form-label" for="searchLocation-longitude">Search Location - Longitude</label>
<input type="text" id="searchLocation-longitude" name="searchLocation-longitude" value="-46.602135" />
<label class="form-label" for="orderBy">Order By</label>
<select id="orderBy" name="orderBy">
<option value="DISTANCE_FROM_SEARCH_LOCATION" selected>DISTANCE_FROM_SEARCH_LOCATION</option>
<option value="WALKING_ETA_FROM_SEARCH_LOCATION">WALKING_ETA_FROM_SEARCH_LOCATION</option>
<option value="DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION">DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION</option>
</select>
<label class="form-label" for="destination-latitude">Destination - Latitude</label>
<input type="text" id="destination-latitude" name="destination-latitude" value="" />
<label class="form-label" for="destination-longitude">Destination - Longitude</label>
<input type="text" id="destination-longitude" name="destination-longitude" value="" />
<label class="form-label" for="maxResults">Max Results</label>
<input type="number" id="maxResults" name="maxResults" min="1" value="5" step="1" />
<fieldset>
<legend>Travel Modes</legend>
<div>
<input type="checkbox" id="walking" name="travelModes" value="WALKING" checked>
<label for="walking" class="form-checkbox-label">WALKING</label>
</div>
<div>
<input type="checkbox" id="driving" name="travelModes" value="DRIVING" checked>
<label for="driving" class="form-checkbox-label">DRIVING</label>
</div>
<div>
<input type="checkbox" id="twoWheeler" name="travelModes" value="TWO_WHEELER">
<label for="twoWheeler" class="form-checkbox-label">TWO_WHEELER</label>
</div>
</fieldset>
<label class="form-label" for="computeWalkingEta">Compute Walking ETA</label>
<select id="computeWalkingEta" name="computeWalkingEta" class="boolean">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<label class="form-label" for="computeDrivingEta">Compute Driving ETA</label>
<select id="computeDrivingEta" name="computeDrivingEta" class="boolean">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<input class="submit-button" type="submit" value="Call" />
</form>
</section>
<section>
<div id="map" class="map"></div>
</section>
</div>
<section class="output-container">
<h2>Response</h2>
<pre id="output"></pre>
</section>
</body>
</html>
body {
font-family: 'Google Sans';
}
.container {
display: grid;
grid-template-columns: 30% 1fr;
grid-template-rows: 100%;
grid-column-gap: 20px;
grid-row-gap: 0px;
}
h1 {
font-size: 24px;
margin-top: 20px;
margin-bottom: 20px;
font-weight: bold;
}
h2 {
font-size: 18px;
font-weight: bold;
}
h1,
.form-container,
.output-container {
margin-left: 20px;
}
.map,
.output-container {
margin-right: 20px;
}
.form-container {
border: 1px solid black;
padding: 20px;
}
.map {
border: 1px solid black;
min-height: 800px;
}
.output-container {
margin-top: 20px;
}
#output {
border: 1px solid red;
font-family: 'Google Sans';
min-height: 150px;
}
label:not(.form-checkbox-label), legend {
overflow-wrap: break-word;
font-weight: bold;
}
input:not([type="checkbox"]), select, fieldset {
font-family: 'Google Sans';
width: 100%;
padding: 5px 5px;
margin: 0 0 20px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 8px;
box-sizing: border-box;
}
input[type="submit"] {
min-width: 150px;
background-color: green; /* Blue */
border: none;
color: white;
padding: 15px 15px;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 20px;
width: 50%;
}
input[type="submit"]:hover {
background-color: darkseagreen;
}
input[type="submit"]:active {
background-color: darkseagreen;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
.info-label {
font-weight: bold;
}
const MAPS_API_KEY = ''; // Put your API Key for Maps SDK here
const LS_API_KEY = ''; // Put your API Key for Location Selection APIs here
const MAPS_URL = `https://maps.googleapis.com/maps/api/js?key=${
MAPS_API_KEY}&libraries=places,geometry&callback=initMap`;
const LS_BASE_URL = 'https://locationselection.googleapis.com/v1beta';
const API_URL_PUPS_FOR_PLACE =
`${LS_BASE_URL}:findPickupPointsForPlace?key=${LS_API_KEY}`;
const API_URL_PUPS_FOR_LOCATION =
`${LS_BASE_URL}:findPickupPointsForLocation?key=${LS_API_KEY}`;
const API_URL_NEARBY_PLACES =
`${LS_BASE_URL}:findNearbyPlaces?key=${LS_API_KEY}`;
const FORM_ID_PUPS_FOR_LOCATION = 'form-pups-for-location';
const FORM_ID_PUPS_FOR_PLACE = 'form-pups-for-place';
const FORM_ID_NEARBY_PLACES = 'form-nearby-places';
const FORM_TO_API_URL_MAP = {
[FORM_ID_PUPS_FOR_LOCATION]: API_URL_PUPS_FOR_LOCATION,
[FORM_ID_PUPS_FOR_PLACE]: API_URL_PUPS_FOR_PLACE,
[FORM_ID_NEARBY_PLACES]: API_URL_NEARBY_PLACES,
};
const RED_PIN = 'http://maps.google.com/mapfiles/ms/icons/red-dot.png';
const GREEN_PIN = 'http://maps.google.com/mapfiles/ms/icons/green-dot.png';
const BLUE_PIN = 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png';
const DEFAULT_ZOOM_LEVEL = 18;
// codepoint from https://fonts.google.com/icons
const SEARCH_LOCATION_MARKER = '\ue7f2';
const GOOGLEPLEX = {
lat: 37.422001,
lng: -122.084061
};
let map;
let polyLines = [];
let polygons = [];
let mapMarkers = [];
let entranceMarkers = [];
function loadMap() {
const script = document.createElement('script');
script.src = MAPS_URL;
document.body.appendChild(script);
}
function initMap() {
map = new google.maps.Map(
document.getElementById('map'),
{center: GOOGLEPLEX, zoom: DEFAULT_ZOOM_LEVEL});
}
function setupForm() {
const form = document.getElementsByTagName('form')[0];
form.addEventListener('submit', onFormSubmit);
}
function onFormSubmit(evt) {
evt.preventDefault();
evt.stopPropagation();
const formData = new FormData(evt.target);
fetchAPIResults(formData);
}
function transformFormData(fd) {
let transformedFd = {
localizationPreferences: {},
};
const formId = document.getElementsByTagName('form')[0].id;
if (formId === FORM_ID_PUPS_FOR_LOCATION ||
formId === FORM_ID_PUPS_FOR_PLACE) {
transformedFd = {localizationPreferences: {}, travelModes: []};
}
const addSearchLocation = () => {
if (transformedFd.searchLocation == null) {
transformedFd.searchLocation = {};
}
};
const addDestination = () => {
if (transformedFd.destination == null) {
transformedFd.destination = {};
}
};
fd.forEach((value, key) => {
switch (key) {
case 'travelModes':
transformedFd.travelModes.push(value);
break;
case 'languageCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'regionCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'searchLocation-latitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['latitude'] = value;
}
break;
case 'searchLocation-longitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['longitude'] = value;
}
break;
case 'destination-latitude':
if (value) {
addDestination();
transformedFd.destination['latitude'] = value;
}
break;
case 'destination-longitude':
if (value) {
addDestination();
transformedFd.destination['longitude'] = value;
}
break;
default:
transformedFd[key] = value;
break;
}
});
const json = JSON.stringify(transformedFd, undefined, 2);
return json;
}
async function fetchAPIResults(fd) {
const formId = document.getElementsByTagName('form')[0].id;
const url = FORM_TO_API_URL_MAP[formId];
const transformedFd = transformFormData(fd);
const response = await fetch(url, {method: 'POST', body: transformedFd});
const result = await response.json();
// Display JSON
displayAPIResults(result);
// Update map
let searchLocation = {};
if (JSON.parse(transformedFd).searchLocation) {
searchLocation = {
lat: Number(JSON.parse(transformedFd).searchLocation.latitude),
lng: Number(JSON.parse(transformedFd).searchLocation.longitude),
};
}
switch (formId) {
case FORM_ID_PUPS_FOR_PLACE:
markPickupPointsForPlace(result);
break;
case FORM_ID_PUPS_FOR_LOCATION:
markPickupPointsForLocation(result, searchLocation);
break;
case FORM_ID_NEARBY_PLACES:
markNearbyPlaces(result, searchLocation);
break;
default:
break;
}
}
function displayAPIResults(data) {
const output = document.getElementById('output');
output.textContent = JSON.stringify(data, undefined, 2);
}
function markNearbyPlaces(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const places = [];
for (const placeResult of data.placeResults) {
places.push(placeResult.place);
}
resetMap(searchLocation);
markPlaces(places, searchLocation);
for (const place of places) {
markEntrances(place.associatedCompounds, place);
}
markSearchLocation(searchLocation, '');
for (const place of places) {
mapPolygons(place.associatedCompounds);
}
}
function markPickupPointsForPlace(data) {
if (data.error) {
resetMap();
return;
}
const place = data.placeResult.place;
const pickupPoints = data.pickupPointResults;
const searchLocation = {
lat: place.geometry.location.latitude,
lng: place.geometry.location.longitude
};
resetMap(searchLocation);
markPickupPoints(place, pickupPoints, searchLocation);
markEntrances(place.associatedCompounds, place);
markSearchLocation(searchLocation, place.displayName);
createPolyLinesOneToMany(searchLocation, pickupPoints);
mapPolygons(place.associatedCompounds);
}
function markPickupPointsForLocation(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const placeIdToPlace = {};
// A dict, and the key is placeId(str)s and the value is a list of pups.
const placePickupPoints = {};
data.placeResults.forEach(result => {
placeIdToPlace[result.place.placeId] = result.place;
placePickupPoints[result.place.placeId] = [];
});
data.placePickupPointResults.forEach(result => {
placePickupPoints[result.associatedPlaceId].push(result.pickupPointResult);
})
resetMap(searchLocation);
for (const placeId in placePickupPoints) {
const place = placeIdToPlace[placeId];
const pups = placePickupPoints[placeId];
markEntrances(place.associatedCompounds, place);
markPickupPoints(place, pups, searchLocation);
createPolyLinesOneToMany(searchLocation, pups);
mapPolygons(place.associatedCompounds);
}
// update the marker rank to global order
for (let i = 0; i < mapMarkers.length; i++) {
mapMarkers[i].label = String(i);
}
markSearchLocation(searchLocation, '');
}
function markPlaces(places, searchLocation) {
for (const place of places) {
const placeLocation = place.geometry.location;
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
const marker = new google.maps.Marker({
position: toLatLngLiteral(placeLocation),
animation: google.maps.Animation.DROP,
map: map,
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
}
function markEntrances(compounds, place) {
if (!compounds) {
return;
}
for (const compound of compounds) {
if (!compound.entrances) {
continue;
}
for (const entrance of compound.entrances) {
const entranceMarker = new google.maps.Marker({
position: toLatLngLiteral(entrance.location),
icon: {
url: BLUE_PIN,
},
animation: google.maps.Animation.DROP,
map: map,
});
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
entranceMarker.addListener('click', () => {
infoWindow.open(map, entranceMarker);
});
map.addListener('click', () => {
infoWindow.close();
});
entranceMarkers.push(entranceMarker);
}
}
}
function mapPolygons(many) {
if (!many) {
return;
}
for (const toPoint of many) {
const data = toPoint.geometry.displayBoundary;
if (data == null || data.coordinates == null) {
continue;
}
const value = data.coordinates;
const polyArray = JSON.parse(JSON.stringify(value))[0];
const usedColors = [];
const finalLatLngs = [];
let color = '';
for (let i = 0; i < polyArray.length; ++i) {
if (polyArray[i] != null && polyArray[i].length > 0) {
color = getColor(usedColors);
usedColors.push(color);
if (isArrLatLng(polyArray[i])) {
finalLatLngs.push({lat: polyArray[i][1], lng: polyArray[i][0]});
}
}
}
const poly = new google.maps.Polygon({
strokeColor: color,
strokeOpacity: 0.2,
strokeWeight: 5,
fillColor: color,
fillOpacity: 0.1,
paths: finalLatLngs,
map: map,
});
polygons.push(poly);
}
}
function getColor(usedColors) {
let color = generateStrokeColor();
while (usedColors.includes(color)) {
color = generateStrokeColor();
}
return color;
}
function generateStrokeColor() {
return Math.floor(Math.random() * 16777215).toString(16);
}
function isArrLatLng(currArr) {
if (!currArr || currArr.length !== 2) {
return false;
}
return ((typeof currArr[0]) === 'number') &&
((typeof currArr[1]) === 'number');
}
function toLatLngLiteral(latlng) {
return {lat: latlng.latitude, lng: latlng.longitude};
}
function pickupHasRestrictions(pickupPointData) {
let hasRestrictions = false;
const travelDetails = pickupPointData.travelDetails;
for (let i = 0; i < travelDetails.length; i++) {
if (travelDetails[i].trafficRestriction !== 'NO_RESTRICTION') {
hasRestrictions = true;
}
}
return hasRestrictions;
}
function markPickupPoints(place, pickupPoints, searchLocation) {
for (let i = 0; i < pickupPoints.length; i++) {
const pickupPointData = pickupPoints[i];
const pickupPoint = pickupPoints[i].pickupPoint;
const pupIcon =
pickupHasRestrictions(pickupPointData) ? RED_PIN : GREEN_PIN;
const contentString = createInfoWindow(place, pickupPoint);
const pupInfoWindow = new google.maps.InfoWindow({content: contentString});
const marker = new google.maps.Marker({
position: toLatLngLiteral(pickupPoint.location),
label: {
text: String(i),
fontWeight: 'bold',
fontSize: '20px',
color: '#000'
},
animation: google.maps.Animation.DROP,
map,
icon: {
url: pupIcon,
anchor: new google.maps.Point(14, 43),
labelOrigin: new google.maps.Point(-5, 5)
},
});
marker.addListener('click', () => {
pupInfoWindow.open(map, marker);
});
map.addListener('click', () => {
pupInfoWindow.close();
});
mapMarkers.push(marker);
}
}
function createInfoWindow(place, pickupPoint) {
let result = [];
const addResult = (value, key, map) =>
result.push(`<p><span class="info-label">${key}:</span> ${value}</p>`);
const formatAddress = (address) => address.lines.join(',');
const placeFieldMap = new Map();
if (place !== null) {
placeFieldMap.set('Place', '');
placeFieldMap.set('Name', place.displayName);
placeFieldMap.set('Place ID', place.placeId);
placeFieldMap.set('Address', formatAddress(place.address.formattedAddress));
}
const pickupPointFieldMap = new Map();
if (pickupPoint !== null) {
pickupPointFieldMap.set('Pickup point', '');
pickupPointFieldMap.set('Name', pickupPoint.displayName);
}
placeFieldMap.forEach(addResult);
result.push('<hr/>');
pickupPointFieldMap.forEach(addResult);
return result.join('');
}
function markSearchLocation(location, label) {
const infoWindow =
new google.maps.InfoWindow({content: `<p><b>Name: </b>${label}</p>`});
const marker = new google.maps.Marker({
position: location,
map,
label: {
text: SEARCH_LOCATION_MARKER,
fontFamily: 'Material Icons',
color: '#ffffff',
fontSize: '18px',
fontWeight: 'bold',
},
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
function createPolyLinesOneToMany(one, many) {
const lineSymbol = {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
};
for (const toPoint of many) {
const line = new google.maps.Polyline({
path: [one, toLatLngLiteral(toPoint.pickupPoint.location)],
icons: [
{
icon: lineSymbol,
offset: '100%',
},
],
map: map,
});
polyLines.push(line);
}
}
/******* Reset the map ******/
function deleteMarkers() {
for (const mapMarker of mapMarkers) {
mapMarker.setMap(null);
}
mapMarkers = [];
}
function deletePolyLines() {
for (const polyLine of polyLines) {
polyLine.setMap(null);
}
polyLines = [];
}
function deleteEntranceMarkers() {
for (const entranceMarker of entranceMarkers) {
entranceMarker.setMap(null);
}
entranceMarkers = [];
}
function clearPolygons() {
for (let i = 0; i < polygons.length; i++) {
polygons[i].setMap(null);
}
polygons = [];
}
function resetMap(searchLocation) {
if (searchLocation) {
map.setCenter(searchLocation);
} else {
map.setCenter(GOOGLEPLEX);
}
map.setZoom(DEFAULT_ZOOM_LEVEL);
deleteMarkers();
deletePolyLines();
deleteEntranceMarkers();
clearPolygons();
}
// Initiate map & set form event handlers
loadMap();
setupForm();
<html lang="en">
<head>
<meta charset="utf-8">
<title>Location Selection Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<h1>Location Selection Demo - FindNearbyPlaces</h1>
<div class="container">
<section class="form-container">
<form id="form-nearby-places" name="location-selection">
<label class="form-label" for="languageCode">Language Code</label>
<input type="text" id="languageCode" name="languageCode" value="en-US" />
<label class="form-label" for="regionCode">Region Code</label>
<input type="text" id="regionCode" name="regionCode" value="US" />
<label class="form-label" for="searchLocation-latitude">Search Location - Latitude</label>
<input type="text" id="searchLocation-latitude" name="searchLocation-latitude" value="37.365647" />
<label class="form-label" for="searchLocation-longitude">Search Location - Longitude</label>
<input type="text" id="searchLocation-longitude" name="searchLocation-longitude" value="-121.925356" />
<label class="form-label" for="maxResults">Max Results</label>
<input type="number" id="maxResults" name="maxResults" min="1" value="5" step="1" />
<input class="submit-button" type="submit" value="Call" />
</form>
</section>
<section>
<div id="map" class="map"></div>
</section>
</div>
<section class="output-container">
<h2>Response</h2>
<pre id="output"></pre>
</section>
</body>
</html>
body {
font-family: 'Google Sans';
}
.container {
display: grid;
grid-template-columns: 30% 1fr;
grid-template-rows: 100%;
grid-column-gap: 20px;
grid-row-gap: 0px;
}
h1 {
font-size: 24px;
margin-top: 20px;
margin-bottom: 20px;
font-weight: bold;
}
h2 {
font-size: 18px;
font-weight: bold;
}
h1,
.form-container,
.output-container {
margin-left: 20px;
}
.map,
.output-container {
margin-right: 20px;
}
.form-container {
border: 1px solid black;
padding: 20px;
}
.map {
border: 1px solid black;
min-height: 800px;
}
.output-container {
margin-top: 20px;
}
#output {
border: 1px solid red;
font-family: 'Google Sans';
min-height: 150px;
}
label:not(.form-checkbox-label), legend {
overflow-wrap: break-word;
font-weight: bold;
}
input:not([type="checkbox"]), select, fieldset {
font-family: 'Google Sans';
width: 100%;
padding: 5px 5px;
margin: 0 0 20px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 8px;
box-sizing: border-box;
}
input[type="submit"] {
min-width: 150px;
background-color: green; /* Blue */
border: none;
color: white;
padding: 15px 15px;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 20px;
width: 50%;
}
input[type="submit"]:hover {
background-color: darkseagreen;
}
input[type="submit"]:active {
background-color: darkseagreen;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
.info-label {
font-weight: bold;
}
const MAPS_API_KEY = ''; // Put your API Key for Maps SDK here
const LS_API_KEY = ''; // Put your API Key for Location Selection APIs here
const MAPS_URL = `https://maps.googleapis.com/maps/api/js?key=${
MAPS_API_KEY}&libraries=places,geometry&callback=initMap`;
const LS_BASE_URL = 'https://locationselection.googleapis.com/v1beta';
const API_URL_PUPS_FOR_PLACE =
`${LS_BASE_URL}:findPickupPointsForPlace?key=${LS_API_KEY}`;
const API_URL_PUPS_FOR_LOCATION =
`${LS_BASE_URL}:findPickupPointsForLocation?key=${LS_API_KEY}`;
const API_URL_NEARBY_PLACES =
`${LS_BASE_URL}:findNearbyPlaces?key=${LS_API_KEY}`;
const FORM_ID_PUPS_FOR_LOCATION = 'form-pups-for-location';
const FORM_ID_PUPS_FOR_PLACE = 'form-pups-for-place';
const FORM_ID_NEARBY_PLACES = 'form-nearby-places';
const FORM_TO_API_URL_MAP = {
[FORM_ID_PUPS_FOR_LOCATION]: API_URL_PUPS_FOR_LOCATION,
[FORM_ID_PUPS_FOR_PLACE]: API_URL_PUPS_FOR_PLACE,
[FORM_ID_NEARBY_PLACES]: API_URL_NEARBY_PLACES,
};
const RED_PIN = 'http://maps.google.com/mapfiles/ms/icons/red-dot.png';
const GREEN_PIN = 'http://maps.google.com/mapfiles/ms/icons/green-dot.png';
const BLUE_PIN = 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png';
const DEFAULT_ZOOM_LEVEL = 18;
// codepoint from https://fonts.google.com/icons
const SEARCH_LOCATION_MARKER = '\ue7f2';
const GOOGLEPLEX = {
lat: 37.422001,
lng: -122.084061
};
let map;
let polyLines = [];
let polygons = [];
let mapMarkers = [];
let entranceMarkers = [];
function loadMap() {
const script = document.createElement('script');
script.src = MAPS_URL;
document.body.appendChild(script);
}
function initMap() {
map = new google.maps.Map(
document.getElementById('map'),
{center: GOOGLEPLEX, zoom: DEFAULT_ZOOM_LEVEL});
}
function setupForm() {
const form = document.getElementsByTagName('form')[0];
form.addEventListener('submit', onFormSubmit);
}
function onFormSubmit(evt) {
evt.preventDefault();
evt.stopPropagation();
const formData = new FormData(evt.target);
fetchAPIResults(formData);
}
function transformFormData(fd) {
let transformedFd = {
localizationPreferences: {},
};
const formId = document.getElementsByTagName('form')[0].id;
if (formId === FORM_ID_PUPS_FOR_LOCATION ||
formId === FORM_ID_PUPS_FOR_PLACE) {
transformedFd = {localizationPreferences: {}, travelModes: []};
}
const addSearchLocation = () => {
if (transformedFd.searchLocation == null) {
transformedFd.searchLocation = {};
}
};
const addDestination = () => {
if (transformedFd.destination == null) {
transformedFd.destination = {};
}
};
fd.forEach((value, key) => {
switch (key) {
case 'travelModes':
transformedFd.travelModes.push(value);
break;
case 'languageCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'regionCode':
transformedFd.localizationPreferences[key] = value;
break;
case 'searchLocation-latitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['latitude'] = value;
}
break;
case 'searchLocation-longitude':
if (value) {
addSearchLocation();
transformedFd.searchLocation['longitude'] = value;
}
break;
case 'destination-latitude':
if (value) {
addDestination();
transformedFd.destination['latitude'] = value;
}
break;
case 'destination-longitude':
if (value) {
addDestination();
transformedFd.destination['longitude'] = value;
}
break;
default:
transformedFd[key] = value;
break;
}
});
const json = JSON.stringify(transformedFd, undefined, 2);
return json;
}
async function fetchAPIResults(fd) {
const formId = document.getElementsByTagName('form')[0].id;
const url = FORM_TO_API_URL_MAP[formId];
const transformedFd = transformFormData(fd);
const response = await fetch(url, {method: 'POST', body: transformedFd});
const result = await response.json();
// Display JSON
displayAPIResults(result);
// Update map
let searchLocation = {};
if (JSON.parse(transformedFd).searchLocation) {
searchLocation = {
lat: Number(JSON.parse(transformedFd).searchLocation.latitude),
lng: Number(JSON.parse(transformedFd).searchLocation.longitude),
};
}
switch (formId) {
case FORM_ID_PUPS_FOR_PLACE:
markPickupPointsForPlace(result);
break;
case FORM_ID_PUPS_FOR_LOCATION:
markPickupPointsForLocation(result, searchLocation);
break;
case FORM_ID_NEARBY_PLACES:
markNearbyPlaces(result, searchLocation);
break;
default:
break;
}
}
function displayAPIResults(data) {
const output = document.getElementById('output');
output.textContent = JSON.stringify(data, undefined, 2);
}
function markNearbyPlaces(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const places = [];
for (const placeResult of data.placeResults) {
places.push(placeResult.place);
}
resetMap(searchLocation);
markPlaces(places, searchLocation);
for (const place of places) {
markEntrances(place.associatedCompounds, place);
}
markSearchLocation(searchLocation, '');
for (const place of places) {
mapPolygons(place.associatedCompounds);
}
}
function markPickupPointsForPlace(data) {
if (data.error) {
resetMap();
return;
}
const place = data.placeResult.place;
const pickupPoints = data.pickupPointResults;
const searchLocation = {
lat: place.geometry.location.latitude,
lng: place.geometry.location.longitude
};
resetMap(searchLocation);
markPickupPoints(place, pickupPoints, searchLocation);
markEntrances(place.associatedCompounds, place);
markSearchLocation(searchLocation, place.displayName);
createPolyLinesOneToMany(searchLocation, pickupPoints);
mapPolygons(place.associatedCompounds);
}
function markPickupPointsForLocation(data, searchLocation) {
if (data.error) {
resetMap();
return;
}
const placeIdToPlace = {};
// A dict, and the key is placeId(str)s and the value is a list of pups.
const placePickupPoints = {};
data.placeResults.forEach(result => {
placeIdToPlace[result.place.placeId] = result.place;
placePickupPoints[result.place.placeId] = [];
});
data.placePickupPointResults.forEach(result => {
placePickupPoints[result.associatedPlaceId].push(result.pickupPointResult);
})
resetMap(searchLocation);
for (const placeId in placePickupPoints) {
const place = placeIdToPlace[placeId];
const pups = placePickupPoints[placeId];
markEntrances(place.associatedCompounds, place);
markPickupPoints(place, pups, searchLocation);
createPolyLinesOneToMany(searchLocation, pups);
mapPolygons(place.associatedCompounds);
}
// update the marker rank to global order
for (let i = 0; i < mapMarkers.length; i++) {
mapMarkers[i].label = String(i);
}
markSearchLocation(searchLocation, '');
}
function markPlaces(places, searchLocation) {
for (const place of places) {
const placeLocation = place.geometry.location;
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
const marker = new google.maps.Marker({
position: toLatLngLiteral(placeLocation),
animation: google.maps.Animation.DROP,
map: map,
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
}
function markEntrances(compounds, place) {
if (!compounds) {
return;
}
for (const compound of compounds) {
if (!compound.entrances) {
continue;
}
for (const entrance of compound.entrances) {
const entranceMarker = new google.maps.Marker({
position: toLatLngLiteral(entrance.location),
icon: {
url: BLUE_PIN,
},
animation: google.maps.Animation.DROP,
map: map,
});
const infoWindow =
new google.maps.InfoWindow({content: createInfoWindow(place, null)});
entranceMarker.addListener('click', () => {
infoWindow.open(map, entranceMarker);
});
map.addListener('click', () => {
infoWindow.close();
});
entranceMarkers.push(entranceMarker);
}
}
}
function mapPolygons(many) {
if (!many) {
return;
}
for (const toPoint of many) {
const data = toPoint.geometry.displayBoundary;
if (data == null || data.coordinates == null) {
continue;
}
const value = data.coordinates;
const polyArray = JSON.parse(JSON.stringify(value))[0];
const usedColors = [];
const finalLatLngs = [];
let color = '';
for (let i = 0; i < polyArray.length; ++i) {
if (polyArray[i] != null && polyArray[i].length > 0) {
color = getColor(usedColors);
usedColors.push(color);
if (isArrLatLng(polyArray[i])) {
finalLatLngs.push({lat: polyArray[i][1], lng: polyArray[i][0]});
}
}
}
const poly = new google.maps.Polygon({
strokeColor: color,
strokeOpacity: 0.2,
strokeWeight: 5,
fillColor: color,
fillOpacity: 0.1,
paths: finalLatLngs,
map: map,
});
polygons.push(poly);
}
}
function getColor(usedColors) {
let color = generateStrokeColor();
while (usedColors.includes(color)) {
color = generateStrokeColor();
}
return color;
}
function generateStrokeColor() {
return Math.floor(Math.random() * 16777215).toString(16);
}
function isArrLatLng(currArr) {
if (!currArr || currArr.length !== 2) {
return false;
}
return ((typeof currArr[0]) === 'number') &&
((typeof currArr[1]) === 'number');
}
function toLatLngLiteral(latlng) {
return {lat: latlng.latitude, lng: latlng.longitude};
}
function pickupHasRestrictions(pickupPointData) {
let hasRestrictions = false;
const travelDetails = pickupPointData.travelDetails;
for (let i = 0; i < travelDetails.length; i++) {
if (travelDetails[i].trafficRestriction !== 'NO_RESTRICTION') {
hasRestrictions = true;
}
}
return hasRestrictions;
}
function markPickupPoints(place, pickupPoints, searchLocation) {
for (let i = 0; i < pickupPoints.length; i++) {
const pickupPointData = pickupPoints[i];
const pickupPoint = pickupPoints[i].pickupPoint;
const pupIcon =
pickupHasRestrictions(pickupPointData) ? RED_PIN : GREEN_PIN;
const contentString = createInfoWindow(place, pickupPoint);
const pupInfoWindow = new google.maps.InfoWindow({content: contentString});
const marker = new google.maps.Marker({
position: toLatLngLiteral(pickupPoint.location),
label: {
text: String(i),
fontWeight: 'bold',
fontSize: '20px',
color: '#000'
},
animation: google.maps.Animation.DROP,
map,
icon: {
url: pupIcon,
anchor: new google.maps.Point(14, 43),
labelOrigin: new google.maps.Point(-5, 5)
},
});
marker.addListener('click', () => {
pupInfoWindow.open(map, marker);
});
map.addListener('click', () => {
pupInfoWindow.close();
});
mapMarkers.push(marker);
}
}
function createInfoWindow(place, pickupPoint) {
let result = [];
const addResult = (value, key, map) =>
result.push(`<p><span class="info-label">${key}:</span> ${value}</p>`);
const formatAddress = (address) => address.lines.join(',');
const placeFieldMap = new Map();
if (place !== null) {
placeFieldMap.set('Place', '');
placeFieldMap.set('Name', place.displayName);
placeFieldMap.set('Place ID', place.placeId);
placeFieldMap.set('Address', formatAddress(place.address.formattedAddress));
}
const pickupPointFieldMap = new Map();
if (pickupPoint !== null) {
pickupPointFieldMap.set('Pickup point', '');
pickupPointFieldMap.set('Name', pickupPoint.displayName);
}
placeFieldMap.forEach(addResult);
result.push('<hr/>');
pickupPointFieldMap.forEach(addResult);
return result.join('');
}
function markSearchLocation(location, label) {
const infoWindow =
new google.maps.InfoWindow({content: `<p><b>Name: </b>${label}</p>`});
const marker = new google.maps.Marker({
position: location,
map,
label: {
text: SEARCH_LOCATION_MARKER,
fontFamily: 'Material Icons',
color: '#ffffff',
fontSize: '18px',
fontWeight: 'bold',
},
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
map.addListener('click', () => {
infoWindow.close();
});
mapMarkers.push(marker);
}
function createPolyLinesOneToMany(one, many) {
const lineSymbol = {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
};
for (const toPoint of many) {
const line = new google.maps.Polyline({
path: [one, toLatLngLiteral(toPoint.pickupPoint.location)],
icons: [
{
icon: lineSymbol,
offset: '100%',
},
],
map: map,
});
polyLines.push(line);
}
}
/******* Reset the map ******/
function deleteMarkers() {
for (const mapMarker of mapMarkers) {
mapMarker.setMap(null);
}
mapMarkers = [];
}
function deletePolyLines() {
for (const polyLine of polyLines) {
polyLine.setMap(null);
}
polyLines = [];
}
function deleteEntranceMarkers() {
for (const entranceMarker of entranceMarkers) {
entranceMarker.setMap(null);
}
entranceMarkers = [];
}
function clearPolygons() {
for (let i = 0; i < polygons.length; i++) {
polygons[i].setMap(null);
}
polygons = [];
}
function resetMap(searchLocation) {
if (searchLocation) {
map.setCenter(searchLocation);
} else {
map.setCenter(GOOGLEPLEX);
}
map.setZoom(DEFAULT_ZOOM_LEVEL);
deleteMarkers();
deletePolyLines();
deleteEntranceMarkers();
clearPolygons();
}
// Initiate map & set form event handlers
loadMap();
setupForm();
This product or feature is in Preview (pre-GA) and we are no longer onboarding new customers.
Pre-GA products and features might have limited support, and changes to pre-GA products and
features might not be compatible with other pre-GA versions. Pre-GA Offerings are covered by
the Google Maps Platform Service Specific Terms . For more information,
see the launch stage descriptions .
Before using the Location Selection API to search for ride
pickup points, follow the instructions
to integrate with the client library .
The Location Selection service provides three APIs for pickup and dropoff
selection: FindNearbyPlaces
, FindPickupPointsForPlace
, and
FindPickupPointsForLocation
.
Use FindNearbyPlaces
to fetch places near the search or device location. The
places are ranked by proximity to the location and their prominence for
ridesharing. FindNearbyPlaces
returns a list of places that can be displayed
to let the user make the best selection. Once a place is selected, use
FindPickupPointsForPlace
to fetch pickup points for the selected place.
Use FindPickupPointsForLocation
to fetch places and their associated pickup
points near the search or device location in the same RPC call.
Each pickup point
is associated with a place. The pickup points are ranked by proximity to the
location and their prominence for ridesharing. Note that pickup points for
multiple places are ordered together. FindPickupPointsForLocation
combines
FindNearbyPlaces
and FindPickupPointsForPlace
. For example, say the request
location is near Place P1, P2 and P3. If the best pickup point T1 is associated
with Place P2 and the next best pickup point is associated with Place P1, the
results will have the ordering [T1:P2, T2:P1, ...].
The ranking of pickup points depends on the criteria provided in the request.
For more information, see
Optimizing pickups for multiple trips .
Search by location
If you prefer to display places to the user prior to selection of pickup points,
or display nearby places by dragging a pin, use the following RPC call:
FindNearbyPlacesRequest find_nearby_places_request =
FindNearbyPlacesRequest.newBuilder()
.setLocalizationPreferences(LocalizationPreferences.newBuilder()
// Language used for localizing text such as name or address.
.setLanguageCode("en")
.setRegionCode("US")
.build())
// Rider's location or location of dragged pin.
.setSearchLocation(LatLng.newBuilder().setLatitude(37.365647).setLongitude(-121.925356))
// Number of places requested.
.setMaxResults(3)
.build();
FindNearbyPlacesResponse findNearbyPlacesResponse =
locationSelectionBetaClient.findNearbyPlaces(find_nearby_places_request);
The RPC call returns a ranked list of place responses that satisfy the input
criteria, ordered by a combination of proximity and prominence. You can let the
rider choose a place or use the first result and proceed to the pickup point
selection. Each place response has a unique place_id
that can be used in
FindPickupPointsForPlaceRequest
to fetch pickup points. For more information,
see Search by place id .
FindPickupPointsForLocationRequest FindPickupPointsForLocationRequest =
FindPickupPointsForLocationRequest.newBuilder()
// Language used for localizing text such as name and address.
.setLocalizationPreferences(LocalizationPreferences.newBuilder().setRegionCode("US").setLanguageCode("en"))
// The search location of the rider or the dropped pin.
.setSearchLocation(LatLng.newBuilder().setLatitude(-23.482049).setLongitude(-46.602135))
// The max results returned.
.setMaxResults(5)
// List of travel modes. At least one of the travel modes must be supported by the pickup points.
.addTravelModes(TravelMode.DRIVING)
// Specifies the sorting order of matching pickup points.
.setOrderBy(PickupPointOrder.DISTANCE_FROM_SEARCH_LOCATION)
.build();
FindPickupPointsForLocationResponse FindPickupPointsForLocationResponse =
locationSelectionService.FindPickupPointsForLocation(
RpcClientContext.create(), FindPickupPointsForLocationRequest);
The RPC call returns a ranked list of pickup points that satisfy the input
criteria, ordered by a combination of proximity and prominence. This RPC call
combines FindNearbyPlaces
and FindPickupPointsForPlaceRequest
and can be
used instead of combining the other two calls.
Search by place ID
You can obtain a place_id
using FindNearbyPlaces
or
by using the Places API
Autocomplete service. Then use the following RPC call to provide optimal pickup
points for the specified place:
FindPickupPointsForPlaceRequest findPickupPointsForPlaceRequest =
FindPickupPointsForPlaceRequest.newBuilder()
// Language used for localizing text such as name and address.
.setLocalizationPreferences(LocalizationPreferences.newBuilder().setRegionCode("US").setLanguageCode("en"))
// Place ID of the place for which pickup points are being fetched;
// for example, Hilton Hotel, Downtown San Jose.
.setPlaceId("ChIJwTUa-q_Mj4ARff4yludGH-M")
// List of travel modes. At least one of the travel modes must be supported by the pickup points.
.addTravelModes(TravelMode.DRIVING)
// Rider's location or location of dragged pin.
// It is recommended to use the same location that was used in `FindNearbyPlaces` for better quality.
.setSearchLocation(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
.setOrderBy(PickupPointOrder.DISTANCE_FROM_SEARCH_LOCATION)
.setMaxResults(5)
.build();
FindPickupPointsForPlaceResponse findPickupPointsForPlaceResponse =
locationSelectionBetaClient.findPickupPointsForPlace(findPickupPointsForPlaceRequest);
FindPickupPointsForPlace
returns PickupPointResponses
with latitude and
longitude for the
points where a rider can be picked up.
Optimize pickups for multiple trips
For shared or back-to-back rides, FindPickupPointsForPlace
supports ordering
pickup points by DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION
. This lets you
return a pickup point for the next trip that is optimized with the driver's
current trip route.
Example
FindPickupPointsForPlaceRequest findPickupPointsForPlaceRequest =
FindPickupPointsForPlaceRequest.newBuilder()
// Language used for localizing text such as name and address.
.setLocalizationPreferences(LocalizationPreferences.newBuilder().setRegionCode("US").setLanguageCode("en"))
// Place ID of the place for which pickup points are being fetched;
// for example, Hilton Hotel, Downtown San Jose.
.setPlaceId("ChIJwTUa-q_Mj4ARff4yludGH-M")
// List of travel modes. At least one of the travel modes must be supported by the pickup points.
.addTravelModes(TravelMode.DRIVING)
// Second rider's location or location of dragged pin.
.setSearchLocation(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
// Location of the driver's next drop off after picking up the second
// rider. Note, it is not necessarily the second rider's destination.
.setDestination(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
.setOrderBy(PickupPointOrder.DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION)
.setComputeDrivingEta(true)
.setMaxResults(5)
.build();
FindPickupPointsForPlaceResponse findPickupPointsForPlaceResponse =
locationSelectionBetaClient.findPickupPointsForPlace(findPickupPointsForPlaceRequest);
Display building outlines, entrances, and exits
Within the place proto response message, the
associatedCompounds
field identifies the compounds associated with the place. The compound message
contains three types of information:
Compound type - one of four options
compoundBuilding
- a single standalone building, such as a mall or
supermarket.
compoundSection
- a compound within a larger compound, such as a single
store in a mall.
compoundGrounds
- everything associated with a compoundBuilding
,
such as a mall, its parking lot, and any other buildings
within that parking lot.
unrecognized
- the default value
Geometry - the coordinates of the outlined polygon, stored in a GeoJSON
structure in the displayBoundary
field. These coordinates are used to
construct the outline of the section, building, or grounds.
Entrances - the latitude and longitude coordinates of all located entrances
and exits.
Location Selection returns any compound type associated
with the search location. If the search location is in a particular store in
a mall, Location Selection returns:
* the specific store, its entrances and exits, and the outline of the store
* the compound building (the mall), its entrances and exits, and the outline of the mall
* the compound grounds (the mall + parking lot), their entrances and exits, and the outline of the entire grounds
This image shows all three compound types being returned.
This image shows several locations inside an airport, with the boundary of both
a building within the airport and the boundary of the airport and all of its
related grounds.