<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
。例如,假設要求位置靠近 Place P1、P2 和 P3。如果最佳上車地點 T1 與 Place 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 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);
顯示建築物輪廓、入口和出口
在地點 proto 回應訊息中, associatedCompounds
欄位會指出與地點相關的化合物。複合訊息包含三種類型的資訊:
- 複合類型 - 四個選項之一
compoundBuilding
- 單一獨立建築物,例如購物中心或超市。compoundSection
- 大型複合型建築中的複合材質,例如購物中心中的單一商店。compoundGrounds
- 與compoundBuilding
相關聯的所有項目,例如購物中心、停車場以及該停車場內的任何其他建築物。unrecognized
:預設值
- 幾何圖形 - 描繪多邊形的座標,儲存在
displayBoundary
欄位中的 GeoJSON 結構中。這些座標可用來建構區塊、建築物或區域的輪廓。 - 入口 - 所有確定入口和出口的經緯度座標。
選取位置後,系統會傳回與搜尋位置相關聯的任何複合類型。如果搜尋地點位於購物中心內的特定商店,「地點選擇」就會傳回: * 特定商店、入口和出口,以及商店外框 * 複合式建築物 (購物中心)、入口和出口的輪廓 * 建築場 (購物中心 + 停車場)、入口和出口,以及整個地面的外框
這張圖片顯示傳回全部三種複合類型。
這張圖片顯示機場內的多個地點,同時是機場內一棟建築物的邊界,以及機場及其所有相關地面的邊界。