<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();
Sản phẩm hoặc tính năng này đang ở giai đoạn Xem trước (trước khi phát hành công khai) và chúng tôi không còn hỗ trợ khách hàng mới nữa.
Các sản phẩm và tính năng trước giai đoạn phát hành rộng rãi có thể bị hạn chế về dịch vụ hỗ trợ, đồng thời các thay đổi đối với các sản phẩm và tính năng trước giai đoạn phát hành rộng rãi có thể không tương thích với các phiên bản trước giai đoạn phát hành rộng rãi khác. Các Sản phẩm/dịch vụ ở giai đoạn trước khi phát hành rộng rãi tuân theo Điều khoản dành riêng cho dịch vụ của Nền tảng Google Maps . Để biết thêm thông tin, hãy xem nội dung mô tả về giai đoạn ra mắt .
Trước khi sử dụng Location Selection API để tìm kiếm điểm đón xe, hãy làm theo hướng dẫn để tích hợp với thư viện ứng dụng .
Dịch vụ Lựa chọn vị trí cung cấp 3 API để chọn vị trí nhận và trả xe: FindNearbyPlaces, FindPickupPointsForPlace và FindPickupPointsForLocation.
Sử dụng FindNearbyPlaces để tìm những địa điểm gần vị trí tìm kiếm hoặc vị trí thiết bị. Các địa điểm được xếp hạng theo khoảng cách đến vị trí và mức độ nổi bật của địa điểm đó đối với dịch vụ đi chung xe. FindNearbyPlaces trả về danh sách những địa điểm có thể hiển thị để người dùng có thể lựa chọn phù hợp nhất. Sau khi chọn một địa điểm, hãy dùng FindPickupPointsForPlace để tìm nạp điểm đến lấy hàng cho địa điểm đã chọn.
Sử dụng FindPickupPointsForLocation để tìm nạp các địa điểm và điểm đến lấy hàng liên quan ở gần vị trí tìm kiếm hoặc vị trí của thiết bị trong cùng một lệnh gọi RPC.
Mỗi điểm đến lấy hàng đều được liên kết với một địa điểm. Các điểm đón được xếp hạng theo khoảng cách đến vị trí và mức độ nổi bật của các điểm đó đối với dịch vụ đi chung xe. Xin lưu ý rằng điểm đến lấy hàng cho nhiều địa điểm sẽ được đặt hàng cùng nhau. FindPickupPointsForLocation kết hợp FindNearbyPlaces và FindPickupPointsForPlace. Ví dụ: giả sử vị trí yêu cầu ở gần Địa điểm P1, P2 và P3. Nếu điểm đến lấy hàng phù hợp nhất T1 được liên kết với Địa điểm P2 và điểm đến lấy hàng phù hợp tiếp theo được liên kết với Địa điểm P1, thì kết quả sẽ có thứ tự [T1:P2, T2:P1, ...].
Thứ hạng của điểm đến lấy hàng phụ thuộc vào tiêu chí được cung cấp trong yêu cầu.
Để biết thêm thông tin, hãy xem bài viết Tối ưu hoá việc đón cho nhiều chuyến đi .
Tìm kiếm theo vị trí
Nếu bạn muốn hiển thị các địa điểm cho người dùng trước khi chọn điểm đến lấy hàng hoặc hiển thị các địa điểm lân cận bằng cách kéo một ghim, hãy sử dụng lệnh gọi RPC sau:
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);
Lệnh gọi RPC trả về một danh sách phản hồi được xếp hạng về địa điểm đáp ứng các tiêu chí đầu vào, được sắp xếp theo sự kết hợp giữa khoảng cách và mức độ nổi bật. Bạn có thể để hành khách chọn một địa điểm hoặc sử dụng kết quả đầu tiên và chuyển sang bước chọn điểm đón. Mỗi phản hồi về địa điểm đều có một place_id riêng biệt có thể dùng trong FindPickupPointsForPlaceRequest để tìm nạp điểm đến lấy hàng. Để biết thêm thông tin, hãy xem phần Tìm kiếm theo mã địa điểm .
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);
Lệnh gọi RPC trả về một danh sách được xếp hạng gồm các điểm đến lấy hàng đáp ứng tiêu chí đầu vào, được sắp xếp theo sự kết hợp giữa khoảng cách và mức độ nổi bật. Lệnh gọi RPC này kết hợp FindNearbyPlaces và FindPickupPointsForPlaceRequest, đồng thời có thể được dùng thay vì kết hợp hai lệnh gọi còn lại.
Tìm kiếm theo mã địa điểm
Bạn có thể lấy place_id bằng cách sử dụng FindNearbyPlaces hoặc bằng cách sử dụng dịch vụ Autocomplete của Places API. Sau đó, hãy sử dụng lệnh gọi RPC sau đây để cung cấp các điểm đến lấy hàng tối ưu cho địa điểm đã chỉ định:
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 trả về PickupPointResponses kèm theo vĩ độ và kinh độ cho các điểm mà người lái xe có thể đón hành khách.
Tối ưu hoá việc đón khách cho nhiều chuyến đi
Đối với các chuyến đi chung hoặc chuyến đi liên tiếp, FindPickupPointsForPlace hỗ trợ sắp xếp các điểm đón theo DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION. Điều này cho phép bạn trả về một điểm đón cho chuyến đi tiếp theo được tối ưu hoá bằng tuyến đường của chuyến đi hiện tại của người lái xe.
Ví dụ:
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);
Hiển thị đường viền, lối vào và lối ra của toà nhà
Trong thông báo phản hồi proto về địa điểm, trường associatedCompounds xác định các hợp chất liên kết với địa điểm. Thông báo kết hợp chứa 3 loại thông tin:
Loại hợp chất – một trong 4 lựa chọn
compoundBuilding – một toà nhà độc lập, chẳng hạn như trung tâm mua sắm hoặc siêu thị.
compoundSection – một khu phức hợp nằm trong một khu phức hợp lớn hơn, chẳng hạn như một cửa hàng duy nhất trong trung tâm mua sắm.
compoundGrounds – mọi thứ liên quan đến một compoundBuilding, chẳng hạn như một trung tâm mua sắm, bãi đậu xe và mọi toà nhà khác trong bãi đậu xe đó.
unrecognized – giá trị mặc định
Hình học – toạ độ của đa giác được phác thảo, được lưu trữ trong cấu trúc GeoJSON trong trường displayBoundary. Các toạ độ này được dùng để tạo đường viền của phần, toà nhà hoặc khu đất.
Lối vào – toạ độ vĩ độ và kinh độ của tất cả các lối vào và lối ra được xác định vị trí.
Lựa chọn vị trí trả về mọi loại hợp chất được liên kết với vị trí tìm kiếm. Nếu vị trí tìm kiếm nằm trong một cửa hàng cụ thể trong trung tâm mua sắm, thì Location Selection sẽ trả về:
* cửa hàng cụ thể, lối vào và lối ra của cửa hàng, cũng như đường viền của cửa hàng
* toà nhà phức hợp (trung tâm mua sắm), lối vào và lối ra của toà nhà, cũng như đường viền của trung tâm mua sắm
* khu đất phức hợp (trung tâm mua sắm + bãi đỗ xe), lối vào và lối ra của khu đất, cũng như đường viền của toàn bộ khu đất
Hình ảnh này cho thấy cả 3 loại hợp chất đang được trả về.
Hình ảnh này cho thấy một số vị trí bên trong sân bay, với ranh giới của cả một toà nhà trong sân bay và ranh giới của sân bay cũng như tất cả các khu vực liên quan.