Поиск мест посадки с помощью выбора местоположения (бета-версия)

        <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();


    

Прежде чем использовать 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 для получения точек выдачи. Дополнительную информацию см. в разделе Поиск по идентификатору места .

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 и может использоваться вместо объединения двух других вызовов.

Поиск по идентификатору места

Вы можете получить place_id с помощью FindNearbyPlaces или с помощью службы автозаполнения Places API. Затем используйте следующий вызов RPC, чтобы указать оптимальные точки посадки для указанного места:

FindPickupPointsForPlaceRequest findPickupPointsForPlaceRequest =
    FindPickupPointsForPlaceRequest.newBuilder()
        // Language used for localizing text such as name and address.
        .setLocalizationPreferences(LocalizationPreferences.newBuilder().setRegionCode("US").setLanguageCode("en"))
        // Place ID of the place for which pickup points are being fetched;
        // for example, Hilton Hotel, Downtown San Jose.
        .setPlaceId("ChIJwTUa-q_Mj4ARff4yludGH-M")
        // List of travel modes. At least one of the travel modes must be supported by the pickup points.
        .addTravelModes(TravelMode.DRIVING)
        // Rider's location or location of dragged pin.
        // It is recommended to use the same location that was used in `FindNearbyPlaces` for better quality.
        .setSearchLocation(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
        .setOrderBy(PickupPointOrder.DISTANCE_FROM_SEARCH_LOCATION)
        .setMaxResults(5)
         .build();

FindPickupPointsForPlaceResponse findPickupPointsForPlaceResponse =
    locationSelectionBetaClient.findPickupPointsForPlace(findPickupPointsForPlaceRequest);

FindPickupPointsForPlace возвращает PickupPointResponses с широтой и долготой для точек, где можно подобрать пассажира.

Оптимизация пикапов для нескольких поездок

Для совместных поездок или поездок подряд FindPickupPointsForPlace поддерживает упорядочивание точек посадки по DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION . Это позволяет вам вернуть точку посадки для следующей поездки, оптимизированную с учетом текущего маршрута поездки водителя.

Пример

FindPickupPointsForPlaceRequest findPickupPointsForPlaceRequest =
    FindPickupPointsForPlaceRequest.newBuilder()
        // Language used for localizing text such as name and address.
        .setLocalizationPreferences(LocalizationPreferences.newBuilder().setRegionCode("US").setLanguageCode("en"))
        // Place ID of the place for which pickup points are being fetched;
        // for example, Hilton Hotel, Downtown San Jose.
        .setPlaceId("ChIJwTUa-q_Mj4ARff4yludGH-M")
        // List of travel modes. At least one of the travel modes must be supported by the pickup points.
        .addTravelModes(TravelMode.DRIVING)
        // Second rider's location or location of dragged pin.
        .setSearchLocation(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
        // Location of the driver's next drop off after picking up the second
        // rider. Note, it is not necessarily the second rider's destination.
        .setDestination(LatLng.newBuilder().setLatitude(37.329472).setLongitude(-121.890449))
        .setOrderBy(PickupPointOrder.DRIVING_ETA_FROM_PICKUP_POINT_TO_DESTINATION)
        .setComputeDrivingEta(true)
        .setMaxResults(5)
        .build();

FindPickupPointsForPlaceResponse findPickupPointsForPlaceResponse =
    locationSelectionBetaClient.findPickupPointsForPlace(findPickupPointsForPlaceRequest);

Отображение контуров зданий, входов и выходов

В ответном сообщении прототипа места поле associatedCompounds идентифицирует соединения, связанные с этим местом. Составное сообщение содержит три типа информации:

  • Сложный тип — один из четырех вариантов
    • compoundBuilding — отдельно стоящее здание, например торговый центр или супермаркет.
    • compoundSection — комплекс внутри более крупного комплекса, например отдельного магазина в торговом центре.
    • compoundGrounds — все, что связано с compoundBuilding , например торговый центр, его парковка и любые другие здания на этой парковке.
    • unrecognized — значение по умолчанию
  • Геометрия — координаты очерченного многоугольника, хранящиеся в структуре GeoJSON в поле displayBoundary . Эти координаты используются для построения контура участка, здания или территории.
  • Входы – координаты широты и долготы всех расположенных входов и выходов.

Выбор местоположения возвращает любой составной тип, связанный с местоположением поиска. Если место поиска находится в определенном магазине в торговом центре, функция выбора местоположения возвращает: * конкретный магазин, его входы и выходы, а также контур магазина; * составное здание (торговый центр), его входы и выходы, а также контур. территории торгового центра * территории комплекса (торговый центр + парковка), их входы и выходы, а также контур всей территории

На этом изображении показаны возвращаемые все три составных типа.

На этом изображении показаны несколько мест внутри аэропорта: граница здания внутри аэропорта, а также граница аэропорта и всех связанных с ним территорий.