<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();
Location Selection API를 사용하여 차량 승차 지점을 검색하기 전에 안내 에 따라 클라이언트 라이브러리와 통합하세요.
Location Selection 서비스는 승차 및 하차 지점 선택을 위한 세 가지 API(FindNearbyPlaces, FindPickupPointsForPlace, FindPickupPointsForLocation)를 제공합니다.
FindNearbyPlaces를 사용하여 검색 또는 기기 위치 근처의 장소를 가져옵니다. 장소는 위치와의 근접성과 차량 공유의 중요도에 따라 순위가 지정됩니다. FindNearbyPlaces 는 사용자에게 표시하여 최적의 선택을 할 수 있도록 하는 장소 목록을 반환합니다. 장소를 선택한 후에는 FindPickupPointsForPlace를 사용하여 선택한 장소의 승차 지점을 가져옵니다.
FindPickupPointsForLocation을 사용하여 동일한 RPC 호출에서 검색 또는 기기 위치 근처의 장소와 연결된 승차 지점을 가져옵니다.
각 승차 지점은 장소와 연결됩니다. 승차 지점은 위치와의 근접성과 차량 공유의 중요도에 따라 순위가 지정됩니다. 여러 장소의 승차 지점은 함께 정렬됩니다. FindPickupPointsForLocation 은 FindNearbyPlaces와 FindPickupPointsForPlace를 결합합니다. 예를 들어 요청 위치가 장소 P1, P2, P3 근처에 있다고 가정해 보겠습니다. 최적의 승차 지점 T1이 장소 P2와 연결되고 다음으로 좋은 승차 지점이 장소 P1과 연결된 경우 결과는 [T1:P2, T2:P1, ...] 순서로 표시됩니다.
승차 지점의 순위는 요청에 제공된 기준에 따라 다릅니다. 자세한 내용은 여러 이동을 위한 승차 지점 최적화를 참고하세요.
위치 검색
승차 지점을 선택하기 전에 사용자에게 장소를 표시하거나 핀을 드래그하여 주변 장소를 표시하려면 다음 RPC 호출을 사용하세요.
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);
RPC 호출은 근접성과 중요도의 조합으로 정렬된 입력 기준을 충족하는 순위가 지정된 장소 응답 목록을 반환합니다. 라이더가 장소를 선택하도록 하거나 첫 번째 결과를 사용하여 승차 지점 선택으로 진행할 수 있습니다. 각 장소 응답에는 FindPickupPointsForPlaceRequest에서 승차 지점을 가져오는 데 사용할 수 있는 고유한 place_id가 있습니다. 자세한 내용은
장소 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);
RPC 호출은 근접성과 중요도의 조합으로 정렬된 입력 기준을 충족하는 순위가 지정된 승차 지점 목록을 반환합니다. 이 RPC 호출은 FindNearbyPlaces와 FindPickupPointsForPlaceRequest를 결합하며 다른 두 호출을 결합하는 대신 사용할 수 있습니다.
장소 ID로 검색
FindNearbyPlaces를 사용하거나 Places API Autocomplete 서비스를 사용하여 place_id를 가져올 수 있습니다. 그런 다음 다음 RPC 호출을 사용하여 지정된 장소에 최적의 승차 지점을 제공합니다.
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 는 라이더가 승차할 수 있는 지점의 위도와 경도가 포함된 PickupPointResponses를 반환합니다.
여러 이동을 위한 승차 지점 최적화
공유 또는 연속 이동의 경우 FindPickupPointsForPlace는 DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION으로 승차 지점 정렬을 지원합니다. 이렇게 하면 운전자의 현재 이동 경로에 맞게 최적화된 다음 이동의 승차 지점을 반환할 수 있습니다.
예
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);
건물 윤곽선, 출입구 표시
장소 프로토 응답 메시지 내에서
associatedCompounds
필드는 장소와 연결된 화합물을 식별합니다. 화합물 메시지에는 세 가지 유형의 정보가 포함되어 있습니다.
- 화합물 유형 - 다음 네 가지 옵션 중 하나입니다.
compoundBuilding- 쇼핑몰 또는 슈퍼마켓과 같은 단일 독립형 건물입니다.compoundSection- 쇼핑몰의 단일 매장과 같이 더 큰 화합물 내의 화합물입니다.compoundGrounds-compoundBuilding과 연결된 모든 것입니다. 쇼핑몰, 주차장, 해당 주차장 내의 기타 건물과 같습니다.unrecognized- 기본값입니다.
- 도형 -
displayBoundary필드의 GeoJSON 구조에 저장된 윤곽선이 있는 다각형의 좌표입니다. 이러한 좌표는 섹션, 건물 또는 부지의 윤곽선을 구성하는 데 사용됩니다. - 출입구 - 모든 위치한 출입구의 위도 및 경도 좌표입니다.
Location Selection은 검색 위치와 연결된 화합물 유형을 반환합니다. 검색 위치가 쇼핑몰의 특정 매장에 있는 경우 Location Selection은 다음을 반환합니다. * 특정 매장, 출입구, 매장 윤곽선 * 복합 건물 (쇼핑몰), 출입구, 쇼핑몰 윤곽선 * 복합 부지 (쇼핑몰 + 주차장), 출입구, 전체 부지 윤곽선
이 이미지는 반환되는 세 가지 화합물 유형을 모두 보여줍니다.

이 이미지는 공항 내부의 여러 위치를 보여주며 공항 내 건물 경계와 공항 및 관련 부지 전체의 경계를 보여줍니다.
