<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 搜索约车点之前,请按照此处的说明与客户端库集成。
位置选择服务提供了三个用于选择上车点和下车点的 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 调用会返回满足输入条件的地点响应的排序列表,并按距离和知名度的组合进行排序。您可以让乘客选择地点,或使用第一个结果并继续选择上车点。每个地点响应都有一个唯一的 place_id
,可用于在 FindPickupPointsForPlaceRequest
中提取上车点。如需了解详情,请参阅按地点 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 自动补全服务来获取 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);
显示建筑物的轮廓、入口和出口
在地点 proto 响应消息中, associatedCompounds
字段用于标识与地点关联的化合物。复合消息包含三种类型的信息:
- 复合类型 - 四个选项之一
compoundBuilding
- 单个独立建筑物,例如购物中心或超市。compoundSection
- 较大化合物(例如购物中心中的一家商店)内的复合结构。compoundGrounds
- 与compoundBuilding
关联的所有内容,例如购物中心、停车场以及该停车场内的任何其他建筑物。unrecognized
- 默认值
- 几何图形 - 轮廓多边形的坐标,存储在 GeoJSON 结构中的
displayBoundary
字段中。这些坐标用于构造路段、建筑物或场地的轮廓。 - 入口 - 所有相应入口和出口的经纬度坐标。
位置选择会返回与搜索位置关联的任何复合类型。如果搜索位置是某个购物中心内的特定商店,则“位置选择”会返回: * 特定商店、出入口以及该商店的轮廓 * 复合建筑(购物中心)、出入口和购物中心的轮廓 * 复合场地(购物中心 + 停车场)、出入口和整个场地的轮廓
此图片显示了要返回的所有三种化合物类型。
此图片显示了机场内的多个位置,包括机场内建筑物的边界以及机场及其所有相关场地的边界。