Демонстрация маршрута

Разработчики Европейской экономической зоны (ЕЭЗ)

Демонстрация маршрута позволяет указать начальную и конечную точку в виде пары координат широты и долготы или идентификатора места. Чтобы скопировать координаты широты и долготы, найдите и щёлкните нужное место на карте, а затем вставьте его в форму.

После выбора пункта Получить маршрут демо-версия отображает ответ метода computeRoutes в виде маршрута на карте.

Посмотреть полный исходный код примера

Следующая демонстрация позволит вам поэкспериментировать с созданием множества различных маршрутов. Щёлкните по карте, чтобы скопировать координаты широты и долготы нужного места. Вставьте координаты в форму, чтобы получить маршрут.

Машинопись

let markers: google.maps.marker.AdvancedMarkerElement[] = [];
let polylines: google.maps.Polyline[] = [];
let waypointInfoWindow: google.maps.InfoWindow | null = null;

interface PlaceAutocompleteSelection {
  predictionText: string | null;
  location: google.maps.LatLng | null;
}

const originAutocompleteSelection: PlaceAutocompleteSelection = {
  predictionText: null,
  location: null,
};
const destinationAutocompleteSelection: PlaceAutocompleteSelection = {
  predictionText: null,
  location: null,
};

async function init() {
  const [
    { InfoWindow },
    { AdvancedMarkerElement },
    //@ts-ignore
    { PlaceAutocompleteElement },
    //@ts-ignore
    { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel },
  ] = await Promise.all([
    google.maps.importLibrary('maps') as Promise<google.maps.MapsLibrary>,
    google.maps.importLibrary('marker') as Promise<google.maps.MarkerLibrary>,
    google.maps.importLibrary('places') as Promise<google.maps.PlacesLibrary>,
    google.maps.importLibrary('routes') as Promise<google.maps.RoutesLibrary>,
  ]);

  const map = document.getElementById('map') as google.maps.MapElement;

  attachSubmitListener();
  initializeLocationInputs();
  attachMapClickListener();
  attachTravelModeListener();
  attachAlertWindowListener();
  attachDepartureTimeListener();

  function attachSubmitListener() {
    const computeRoutesForm = document.getElementById(
      'compute-routes-form',
    ) as HTMLFormElement;

    computeRoutesForm.addEventListener('submit', (event) => {
      event.preventDefault();
      sendRequest(new FormData(computeRoutesForm));
    });
  }

  async function sendRequest(formData: FormData) {
    clearMap();

    try {
      const { routes } = await Route.computeRoutes(
        buildComputeRoutesJsRequest(formData),
      );

      if (!routes) {
        console.log('No routes returned.');
        return;
      }

      console.log('Routes:');
      routes.forEach((route) => {
        console.log(route.toJSON());
      });

      await Promise.all(
        routes.map((route) =>
          drawRoute(
            route,
            !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE),
          ),
        ),
      );
    } catch (error: unknown) {
      console.error(error);
      setErrorMessage((error as Error).message || 'Unknown error.');
    }
  }

  function buildComputeRoutesJsRequest(
    formData: FormData,
    //@ts-ignore
  ): google.maps.routes.ComputeRoutesRequest {
    const travelMode =
      (formData.get('travel_mode') as string) === ''
        ? undefined
        : (formData.get('travel_mode') as google.maps.TravelMode);
    //@ts-ignore
    const extraComputations: google.maps.routes.ComputeRoutesExtraComputation[] =
      [];
    //@ts-ignore
    const requestedReferenceRoutes: google.maps.routes.ReferenceRoute[] = [];
    //@ts-ignore
    const transitPreference: google.maps.routes.TransitPreference = {};

    const request = {
      origin: {
        location: buildComputeRoutesLocation(
          originAutocompleteSelection,
          formData.get('origin_location'),
          formData.get('heading_org'),
          travelMode,
        ),
        vehicleStopover: formData.get('origin_stopover') === 'on',
        sideOfRoad: formData.get('origin_side_of_road') === 'on',
      },
      destination: {
        location: buildComputeRoutesLocation(
          destinationAutocompleteSelection,
          formData.get('destination_location'),
          formData.get('heading_dest'),
          travelMode,
        ),
        vehicleStopover: formData.get('destination_stopover') === 'on',
        sideOfRoad: formData.get('destination_side_of_road') === 'on',
      },
      fields: Array.from(
        document.querySelectorAll(
          'ul#fields li input[type="checkbox"]:checked',
        ),
        (input) => (input as HTMLInputElement).value,
      ),
      travelMode: travelMode as google.maps.TravelMode,
      routingPreference:
        formData.get('routing_preference') === ''
          ? undefined
          : (formData.get(
            'routing_preference',
            //@ts-ignore
          ) as google.maps.routes.RoutingPreference),
      polylineQuality:
        formData.get('polyline_quality') === ''
          ? undefined
          : (formData.get(
            'polyline_quality',
            //@ts-ignore
          ) as google.maps.routes.PolylineQuality),
      computeAlternativeRoutes:
        formData.get('compute_alternative_routes') === 'on',
      routeModifiers: {
        avoidTolls: formData.get('avoid_tolls') === 'on',
        avoidHighways: formData.get('avoid_highways') === 'on',
        avoidFerries: formData.get('avoid_ferries') === 'on',
        avoidIndoor: formData.get('avoid_indoor') === 'on',
      },
      departureTime:
        (formData.get('departure_time') as string) === ''
          ? undefined
          : new Date(formData.get('departure_time') as string),
      extraComputations,
      requestedReferenceRoutes,
      transitPreference,
    };

    if (formData.get('traffic_aware_polyline') === 'on') {
      extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE);
    }

    if (formData.get('shorter_distance') === 'on') {
      requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE);
    }

    if (formData.get('eco_routes') === 'on') {
      requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT);
      extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION);
      (
        //@ts-ignore
        request.routeModifiers as google.maps.routes.RouteModifiers
      ).vehicleInfo = {
        emissionType: formData.get(
          'emission_type',
          //@ts-ignore
        ) as google.maps.routes.VehicleEmissionType,
      };
    }

    if (travelMode === google.maps.TravelMode.TRANSIT) {
      const selectedTransitModes = document.querySelectorAll(
        'ul#transitModes li input[type="checkbox"]:checked',
      );
      transitPreference.allowedTransitModes = Array.from(
        selectedTransitModes,
        (input) => (input as HTMLInputElement).value as google.maps.TransitMode,
      );
      transitPreference.routingPreference =
        formData.get('transit_preference') === ''
          ? undefined
          : (formData.get(
            'transit_preference',
          ) as google.maps.TransitRoutePreference);
    }

    return request;
  }

  function buildComputeRoutesLocation(
    autocompleteSelection: PlaceAutocompleteSelection,
    locationInput?: FormDataEntryValue | null,
    headingInput?: FormDataEntryValue | null,
    travelModeInput?: FormDataEntryValue | null,
    // @ts-ignore
  ): string | google.maps.routes.DirectionalLocationLiteral {
    if (!locationInput) {
      throw new Error('Location is required.');
    }

    const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/;
    const location = locationInput as string;
    const heading =
      headingInput && travelModeInput !== 'TRANSIT'
        ? Number(headingInput as string)
        : undefined;

    if (
      autocompleteSelection.predictionText === location &&
      autocompleteSelection.location
    ) {
      // Use the lat/lng from the autocomplete selection if the current input
      // matches the autocomplete prediction text
      return {
        lat: autocompleteSelection.location.lat(),
        lng: autocompleteSelection.location.lng(),
        altitude: 0,
        heading,
      };
    } else if (latLngRegex.test(location)) {
      // If the current input looks like a lat/lng, format it as a
      // google.maps.routes.DirectionalLocationLiteral
      return {
        lat: Number(location.split(',')[0]),
        lng: Number(location.split(',')[1]),
        altitude: 0,
        heading,
      };
    }

    // Otherwise return the input location string
    return location;
  }

  function setErrorMessage(error: string) {
    const alertBox = document.getElementById('alert') as HTMLDivElement;
    alertBox.querySelector('p')!.textContent = error;
    alertBox.style.display = 'flex';
  }

  async function drawRoute(
    //@ts-ignore
    route: google.maps.routes.Route,
    isPrimaryRoute: boolean,
  ) {
    polylines = polylines.concat(
      route.createPolylines({
        polylineOptions: isPrimaryRoute
          ? { map: map.innerMap, zIndex: 1 }
          : {
            map: map.innerMap,
            strokeColor: '#669DF6',
            strokeOpacity: 0.5,
            strokeWeight: 8,
          },
        colorScheme: map.innerMap.get('colorScheme'),
      }),
    );

    if (isPrimaryRoute) {
      markers = markers.concat(
        await route.createWaypointAdvancedMarkers({
          map: map.innerMap,
          zIndex: 1,
        }),
      );

      if (route.viewport) {
        map.innerMap.fitBounds(route.viewport);
      }
    }

    addRouteLabel(route, Math.floor(route.path!.length / 2));
  }

  //@ts-ignore
  function addRouteLabel(route: google.maps.routes.Route, index: number) {
    const routeTag = document.createElement('div');
    routeTag.className = 'route-tag';

    if (route.routeLabels && route.routeLabels.length > 0) {
      const p = document.createElement('p');
      route.routeLabels.forEach((label, i) => {
        if (label.includes(RouteLabel.FUEL_EFFICIENT)) {
          routeTag.classList.add('eco');
        }
        if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) {
          routeTag.classList.add('alternate');
        }
        if (label.includes(RouteLabel.SHORTER_DISTANCE)) {
          routeTag.classList.add('shorter-distance');
        }

        p.appendChild(document.createTextNode(label));
        if (i < route.routeLabels!.length - 1) {
          p.appendChild(document.createElement('br'));
        }
      });
      routeTag.appendChild(p);
    }

    const detailsDiv = document.createElement('div');
    detailsDiv.className = 'details';

    if (route.localizedValues) {
      const distanceP = document.createElement('p');
      distanceP.textContent = `Distance: ${route.localizedValues.distance!}`;
      detailsDiv.appendChild(distanceP);

      const durationP = document.createElement('p');
      durationP.textContent = `Duration: ${route.localizedValues.duration}`!;
      detailsDiv.appendChild(durationP);
    }

    if (route.travelAdvisory?.fuelConsumptionMicroliters) {
      const fuelP = document.createElement('p');
      fuelP.textContent = `Fuel consumption: ${(
        route.travelAdvisory.fuelConsumptionMicroliters / 1e6
      ).toFixed(2)} L`;
      detailsDiv.appendChild(fuelP);
    }

    routeTag.appendChild(detailsDiv);

    const marker = new AdvancedMarkerElement({
      map: map.innerMap,
      position: route.path![index],
      content: routeTag,
      zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE)
        ? 1
        : undefined,
    });
    markers.push(marker);
  }

  function clearMap() {
    markers.forEach((marker) => {
      marker.map = null;
    });
    markers.length = 0;

    polylines.forEach((polyline) => {
      polyline.setMap(null);
    });
    polylines.length = 0;
  }

  function attachMapClickListener() {
    if (!map || !map.innerMap) {
      return;
    }

    let infoWindowAlert = document.getElementById('infowindow-alert');
    if (!infoWindowAlert) {
      infoWindowAlert = document.createElement('div');
      infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert';
      infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard';
    }

    const infoWindow = new InfoWindow();
    let closeWindowTimeout: number;

    map.innerMap.addListener(
      'click',
      async (mapsMouseEvent: google.maps.MapMouseEvent) => {
        if (!mapsMouseEvent.latLng) {
          return;
        }

        infoWindow.close();
        if (closeWindowTimeout) {
          clearTimeout(closeWindowTimeout);
        }

        infoWindow.setContent(infoWindowAlert);
        infoWindow.setPosition({
          lat: mapsMouseEvent.latLng.lat(),
          lng: mapsMouseEvent.latLng.lng(),
        });

        await navigator.clipboard.writeText(
          `${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`,
        );

        infoWindow.open(map.innerMap);
        closeWindowTimeout = window.setTimeout(() => {
          infoWindow.close();
        }, 2000);
      },
    );
  }

  function attachTravelModeListener() {
    const travelMode = document.getElementById(
      'travel-mode',
    ) as HTMLSelectElement;
    const routingPreference = document.getElementById(
      'routing-preference',
    ) as HTMLSelectElement;
    const trafficAwarePolyline = document.getElementById(
      'traffic-aware-polyline',
    ) as HTMLInputElement;
    const ecoRoutes = document.getElementById('eco-routes') as HTMLInputElement;
    const emissionType = document.getElementById(
      'emission-type',
    ) as HTMLSelectElement;

    travelMode.addEventListener('change', () => {
      // Toggle the Routing Preference selection and Traffic Aware Polyline
      // selection for WALKING, BICYCLING, and TRANSIT modes.
      if (
        travelMode.value === 'WALKING' ||
        travelMode.value === 'BICYCLING' ||
        travelMode.value === 'TRANSIT'
      ) {
        routingPreference.disabled = true;
        routingPreference.value = '';
      } else {
        routingPreference.disabled = false;
        routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE';
      }

      toggleTrafficAwarePolyline();

      // Toggle transit options for Transit mode
      (
        document.getElementById('transit-options') as HTMLElement
      ).style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none';
    });

    routingPreference.addEventListener('change', () => {
      toggleTrafficAwarePolyline();
    });

    ecoRoutes.addEventListener('change', () => {
      if (ecoRoutes.checked) {
        emissionType.disabled = false;
      } else {
        emissionType.disabled = true;
      }
    });

    function toggleTrafficAwarePolyline() {
      if (
        !routingPreference.value ||
        routingPreference.value === 'TRAFFIC_UNAWARE'
      ) {
        trafficAwarePolyline.checked = false;
        trafficAwarePolyline.disabled = true;
      } else {
        trafficAwarePolyline.disabled = false;
      }
    }
  }

  function attachAlertWindowListener() {
    const alertBox = document.getElementById('alert') as HTMLDivElement;
    const closeBtn = alertBox.querySelector('.close') as HTMLButtonElement;
    closeBtn.addEventListener('click', () => {
      if (alertBox.style.display !== 'none') {
        alertBox.style.display = 'none';
      }
    });
  }

  function initializeLocationInputs() {
    const originAutocomplete = new PlaceAutocompleteElement({
      name: 'origin_location',
    });
    const destinationAutocomplete = new PlaceAutocompleteElement({
      name: 'destination_location',
    });

    [
      [originAutocomplete, originAutocompleteSelection],
      [destinationAutocomplete, destinationAutocompleteSelection],
    ].forEach(([autocomplete, autocompleteData]) => {
      autocomplete.addEventListener(
        'gmp-select',
        //@ts-ignore
        async (event: google.maps.places.PlacePredictionSelectEvent) => {
          autocompleteData.predictionText = event.placePrediction.text.text;

          const place = event.placePrediction.toPlace();
          await place.fetchFields({
            fields: ['location'],
          });
          autocompleteData.location = place.location;
        },
      );
    });

    document.getElementById('origin-input')?.appendChild(originAutocomplete);
    document
      .getElementById('destination-input')
      ?.appendChild(destinationAutocomplete);
  }

  function attachDepartureTimeListener() {
    const departureTime = document.getElementById(
      'departure-time',
    ) as HTMLInputElement;
    const utcOutput = document.getElementById(
      'utc-output',
    ) as HTMLParagraphElement;
    departureTime.addEventListener('change', () => {
      utcOutput.textContent = `UTC time: ${new Date(
        departureTime.value,
      ).toUTCString()}`;
    });
  }
}

window.addEventListener('load', init);

JavaScript

let markers = [];
let polylines = [];
let waypointInfoWindow = null;
const originAutocompleteSelection = {
    predictionText: null,
    location: null,
};
const destinationAutocompleteSelection = {
    predictionText: null,
    location: null,
};
async function init() {
    const [{ InfoWindow }, { AdvancedMarkerElement }, 
    //@ts-ignore
    { PlaceAutocompleteElement }, 
    //@ts-ignore
    { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel },] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('marker'),
        google.maps.importLibrary('places'),
        google.maps.importLibrary('routes'),
    ]);
    const map = document.getElementById('map');
    attachSubmitListener();
    initializeLocationInputs();
    attachMapClickListener();
    attachTravelModeListener();
    attachAlertWindowListener();
    attachDepartureTimeListener();
    function attachSubmitListener() {
        const computeRoutesForm = document.getElementById('compute-routes-form');
        computeRoutesForm.addEventListener('submit', (event) => {
            event.preventDefault();
            sendRequest(new FormData(computeRoutesForm));
        });
    }
    async function sendRequest(formData) {
        clearMap();
        try {
            const { routes } = await Route.computeRoutes(buildComputeRoutesJsRequest(formData));
            if (!routes) {
                console.log('No routes returned.');
                return;
            }
            console.log('Routes:');
            routes.forEach((route) => {
                console.log(route.toJSON());
            });
            await Promise.all(routes.map((route) => drawRoute(route, !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE))));
        }
        catch (error) {
            console.error(error);
            setErrorMessage(error.message || 'Unknown error.');
        }
    }
    function buildComputeRoutesJsRequest(formData) {
        const travelMode = formData.get('travel_mode') === ''
            ? undefined
            : formData.get('travel_mode');
        //@ts-ignore
        const extraComputations = [];
        //@ts-ignore
        const requestedReferenceRoutes = [];
        //@ts-ignore
        const transitPreference = {};
        const request = {
            origin: {
                location: buildComputeRoutesLocation(originAutocompleteSelection, formData.get('origin_location'), formData.get('heading_org'), travelMode),
                vehicleStopover: formData.get('origin_stopover') === 'on',
                sideOfRoad: formData.get('origin_side_of_road') === 'on',
            },
            destination: {
                location: buildComputeRoutesLocation(destinationAutocompleteSelection, formData.get('destination_location'), formData.get('heading_dest'), travelMode),
                vehicleStopover: formData.get('destination_stopover') === 'on',
                sideOfRoad: formData.get('destination_side_of_road') === 'on',
            },
            fields: Array.from(document.querySelectorAll('ul#fields li input[type="checkbox"]:checked'), (input) => input.value),
            travelMode: travelMode,
            routingPreference: formData.get('routing_preference') === ''
                ? undefined
                : formData.get('routing_preference'),
            polylineQuality: formData.get('polyline_quality') === ''
                ? undefined
                : formData.get('polyline_quality'),
            computeAlternativeRoutes: formData.get('compute_alternative_routes') === 'on',
            routeModifiers: {
                avoidTolls: formData.get('avoid_tolls') === 'on',
                avoidHighways: formData.get('avoid_highways') === 'on',
                avoidFerries: formData.get('avoid_ferries') === 'on',
                avoidIndoor: formData.get('avoid_indoor') === 'on',
            },
            departureTime: formData.get('departure_time') === ''
                ? undefined
                : new Date(formData.get('departure_time')),
            extraComputations,
            requestedReferenceRoutes,
            transitPreference,
        };
        if (formData.get('traffic_aware_polyline') === 'on') {
            extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE);
        }
        if (formData.get('shorter_distance') === 'on') {
            requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE);
        }
        if (formData.get('eco_routes') === 'on') {
            requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT);
            extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION);
            //@ts-ignore
            request.routeModifiers.vehicleInfo = {
                emissionType: formData.get('emission_type'),
            };
        }
        if (travelMode === google.maps.TravelMode.TRANSIT) {
            const selectedTransitModes = document.querySelectorAll('ul#transitModes li input[type="checkbox"]:checked');
            transitPreference.allowedTransitModes = Array.from(selectedTransitModes, (input) => input.value);
            transitPreference.routingPreference =
                formData.get('transit_preference') === ''
                    ? undefined
                    : formData.get('transit_preference');
        }
        return request;
    }
    function buildComputeRoutesLocation(autocompleteSelection, locationInput, headingInput, travelModeInput) {
        if (!locationInput) {
            throw new Error('Location is required.');
        }
        const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/;
        const location = locationInput;
        const heading = headingInput && travelModeInput !== 'TRANSIT'
            ? Number(headingInput)
            : undefined;
        if (autocompleteSelection.predictionText === location &&
            autocompleteSelection.location) {
            // Use the lat/lng from the autocomplete selection if the current input
            // matches the autocomplete prediction text
            return {
                lat: autocompleteSelection.location.lat(),
                lng: autocompleteSelection.location.lng(),
                altitude: 0,
                heading,
            };
        }
        else if (latLngRegex.test(location)) {
            // If the current input looks like a lat/lng, format it as a
            // google.maps.routes.DirectionalLocationLiteral
            return {
                lat: Number(location.split(',')[0]),
                lng: Number(location.split(',')[1]),
                altitude: 0,
                heading,
            };
        }
        // Otherwise return the input location string
        return location;
    }
    function setErrorMessage(error) {
        const alertBox = document.getElementById('alert');
        alertBox.querySelector('p').textContent = error;
        alertBox.style.display = 'flex';
    }
    async function drawRoute(
    //@ts-ignore
    route, isPrimaryRoute) {
        polylines = polylines.concat(route.createPolylines({
            polylineOptions: isPrimaryRoute
                ? { map: map.innerMap, zIndex: 1 }
                : {
                    map: map.innerMap,
                    strokeColor: '#669DF6',
                    strokeOpacity: 0.5,
                    strokeWeight: 8,
                },
            colorScheme: map.innerMap.get('colorScheme'),
        }));
        if (isPrimaryRoute) {
            markers = markers.concat(await route.createWaypointAdvancedMarkers({
                map: map.innerMap,
                zIndex: 1,
            }));
            if (route.viewport) {
                map.innerMap.fitBounds(route.viewport);
            }
        }
        addRouteLabel(route, Math.floor(route.path.length / 2));
    }
    //@ts-ignore
    function addRouteLabel(route, index) {
        const routeTag = document.createElement('div');
        routeTag.className = 'route-tag';
        if (route.routeLabels && route.routeLabels.length > 0) {
            const p = document.createElement('p');
            route.routeLabels.forEach((label, i) => {
                if (label.includes(RouteLabel.FUEL_EFFICIENT)) {
                    routeTag.classList.add('eco');
                }
                if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) {
                    routeTag.classList.add('alternate');
                }
                if (label.includes(RouteLabel.SHORTER_DISTANCE)) {
                    routeTag.classList.add('shorter-distance');
                }
                p.appendChild(document.createTextNode(label));
                if (i < route.routeLabels.length - 1) {
                    p.appendChild(document.createElement('br'));
                }
            });
            routeTag.appendChild(p);
        }
        const detailsDiv = document.createElement('div');
        detailsDiv.className = 'details';
        if (route.localizedValues) {
            const distanceP = document.createElement('p');
            distanceP.textContent = `Distance: ${route.localizedValues.distance}`;
            detailsDiv.appendChild(distanceP);
            const durationP = document.createElement('p');
            durationP.textContent = `Duration: ${route.localizedValues.duration}`;
            detailsDiv.appendChild(durationP);
        }
        if (route.travelAdvisory?.fuelConsumptionMicroliters) {
            const fuelP = document.createElement('p');
            fuelP.textContent = `Fuel consumption: ${(route.travelAdvisory.fuelConsumptionMicroliters / 1e6).toFixed(2)} L`;
            detailsDiv.appendChild(fuelP);
        }
        routeTag.appendChild(detailsDiv);
        const marker = new AdvancedMarkerElement({
            map: map.innerMap,
            position: route.path[index],
            content: routeTag,
            zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE)
                ? 1
                : undefined,
        });
        markers.push(marker);
    }
    function clearMap() {
        markers.forEach((marker) => {
            marker.map = null;
        });
        markers.length = 0;
        polylines.forEach((polyline) => {
            polyline.setMap(null);
        });
        polylines.length = 0;
    }
    function attachMapClickListener() {
        if (!map || !map.innerMap) {
            return;
        }
        let infoWindowAlert = document.getElementById('infowindow-alert');
        if (!infoWindowAlert) {
            infoWindowAlert = document.createElement('div');
            infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert';
            infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard';
        }
        const infoWindow = new InfoWindow();
        let closeWindowTimeout;
        map.innerMap.addListener('click', async (mapsMouseEvent) => {
            if (!mapsMouseEvent.latLng) {
                return;
            }
            infoWindow.close();
            if (closeWindowTimeout) {
                clearTimeout(closeWindowTimeout);
            }
            infoWindow.setContent(infoWindowAlert);
            infoWindow.setPosition({
                lat: mapsMouseEvent.latLng.lat(),
                lng: mapsMouseEvent.latLng.lng(),
            });
            await navigator.clipboard.writeText(`${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`);
            infoWindow.open(map.innerMap);
            closeWindowTimeout = window.setTimeout(() => {
                infoWindow.close();
            }, 2000);
        });
    }
    function attachTravelModeListener() {
        const travelMode = document.getElementById('travel-mode');
        const routingPreference = document.getElementById('routing-preference');
        const trafficAwarePolyline = document.getElementById('traffic-aware-polyline');
        const ecoRoutes = document.getElementById('eco-routes');
        const emissionType = document.getElementById('emission-type');
        travelMode.addEventListener('change', () => {
            // Toggle the Routing Preference selection and Traffic Aware Polyline
            // selection for WALKING, BICYCLING, and TRANSIT modes.
            if (travelMode.value === 'WALKING' ||
                travelMode.value === 'BICYCLING' ||
                travelMode.value === 'TRANSIT') {
                routingPreference.disabled = true;
                routingPreference.value = '';
            }
            else {
                routingPreference.disabled = false;
                routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE';
            }
            toggleTrafficAwarePolyline();
            // Toggle transit options for Transit mode
            document.getElementById('transit-options').style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none';
        });
        routingPreference.addEventListener('change', () => {
            toggleTrafficAwarePolyline();
        });
        ecoRoutes.addEventListener('change', () => {
            if (ecoRoutes.checked) {
                emissionType.disabled = false;
            }
            else {
                emissionType.disabled = true;
            }
        });
        function toggleTrafficAwarePolyline() {
            if (!routingPreference.value ||
                routingPreference.value === 'TRAFFIC_UNAWARE') {
                trafficAwarePolyline.checked = false;
                trafficAwarePolyline.disabled = true;
            }
            else {
                trafficAwarePolyline.disabled = false;
            }
        }
    }
    function attachAlertWindowListener() {
        const alertBox = document.getElementById('alert');
        const closeBtn = alertBox.querySelector('.close');
        closeBtn.addEventListener('click', () => {
            if (alertBox.style.display !== 'none') {
                alertBox.style.display = 'none';
            }
        });
    }
    function initializeLocationInputs() {
        const originAutocomplete = new PlaceAutocompleteElement({
            name: 'origin_location',
        });
        const destinationAutocomplete = new PlaceAutocompleteElement({
            name: 'destination_location',
        });
        [
            [originAutocomplete, originAutocompleteSelection],
            [destinationAutocomplete, destinationAutocompleteSelection],
        ].forEach(([autocomplete, autocompleteData]) => {
            autocomplete.addEventListener('gmp-select', 
            //@ts-ignore
            async (event) => {
                autocompleteData.predictionText = event.placePrediction.text.text;
                const place = event.placePrediction.toPlace();
                await place.fetchFields({
                    fields: ['location'],
                });
                autocompleteData.location = place.location;
            });
        });
        document.getElementById('origin-input')?.appendChild(originAutocomplete);
        document
            .getElementById('destination-input')
            ?.appendChild(destinationAutocomplete);
    }
    function attachDepartureTimeListener() {
        const departureTime = document.getElementById('departure-time');
        const utcOutput = document.getElementById('utc-output');
        departureTime.addEventListener('change', () => {
            utcOutput.textContent = `UTC time: ${new Date(departureTime.value).toUTCString()}`;
        });
    }
}
window.addEventListener('load', init);

CSS

html,
body {
  height: 100%;
  font-size: 100%;
  font-family: 'Google Sans', sans-serif;
  margin: 0;
  background-color: #fff;
}

* {
  box-sizing: border-box;
}

h2,
h3 {
  color: #222;
  font-style: normal;
  font-weight: normal;
  line-height: 1.4;
  margin-bottom: 0.5rem;
  margin-top: 0.2rem;
}

h2 {
  font-weight: bold;
  font-size: 1rem;
}

h3 {
  font-size: 0.8rem;
}

p {
  font-size: 0.8rem;
  margin: 0 0 0.6rem 0;
}

label {
  color: #4d4d4d;
  display: inline-block;
  margin: 0;
  position: relative;
  z-index: 2;
  font-size: 0.875rem;
}

input[type='text'] {
  height: 50px;
  width: 100%;
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid #ccc;
}

ul {
  list-style: none;
  padding-inline-start: 0.25rem;
}

select {
  appearance: none;
  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+);
  background-position: 100% center;
  background-repeat: no-repeat;
  padding-right: 1.5rem;

  &:disabled {
    background-color: #ddd;
    cursor: default;
  }

  &[multiple] {
    height: auto;
  }
}

select,
input[type='datetime-local'] {
  height: 2.3125rem;
  width: 100%;
  border-style: solid;
  border-width: 1px;
  border-color: #ccc;
  border-radius: 4px;
  padding: 0.3rem;
  font-family: inherit;
  font-size: 0.8rem;
}

button {
  min-height: 3rem;
  min-width: 3rem;
  cursor: pointer;
  font-family: inherit;
  font-weight: normal;
  font-size: 0.875rem;
  line-height: normal;
  padding: 0 1.5rem;
  position: relative;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  border-radius: 4px;
  transition:
    background-color 0.2s,
    border 0.2s;

  &.button-primary {
    background-color: #1a73e8;
    color: #fff;
    border: 1px solid #dadce0;

    &:hover {
      background-color: #e8f0fe;
      border-color: #d2e3fc;
      color: #1a73e8;
    }
  }

  &.button-secondary {
    background-color: #fff;
    color: #1a73e8;
    border: none;

    &:hover {
      background-color: #1a73e8;
      color: #fff;
    }
  }

  &.close {
    font-size: 2rem;
  }
}

hr {
  border: 1px solid #f4f0f0;
  margin-inline: 0;
}

section {
  display: flex;
  flex-direction: column;
  padding: 1.25rem 1rem;
  border-bottom: 1px solid #ddd;
  gap: 0.5rem;

  &:last-child {
    border-bottom: none;
  }
}

.main-content {
  width: 100%;
  border: 1px solid #e4e4e4;
  border-radius: 25px 25px 0 0;
}

.control-panel {
  padding-top: 20px;
  overflow: scroll;
}

.map-container {
  height: 100%;
  padding: 0;
}

.map {
  height: 100%;
}

.row {
  display: flex;
  flex-flow: row wrap;
  align-items: flex-start;
  gap: 1rem;

  &:not(:last-child) {
    margin-bottom: 0.5rem;
  }
}

gmp-place-autocomplete {
  border: 1px solid #ccc;
  border-radius: 4px;
}

gmp-advanced-marker:hover {
  z-index: 1;
}

.infowindow-alert {
  font-size: 0.8rem;
  margin: 0;
  color: #fff;
}

.alert {
  display: none;
  position: fixed;
  padding: 1rem;
  width: 100%;
  z-index: 10;
  background-color: #fff;
  border-radius: 25px 25px 0 0;
  box-shadow: 0 1px 8px 0px #e4e4e4;
  flex-direction: row;
  justify-content: space-between;

  p {
    padding: 0 3rem 0 1rem;
    color: #f04124;
  }
}

.route-tag {
  background-color: #4285f4;
  border-radius: 8px;
  font-size: 14px;
  padding: 6px 10px;
  position: relative;
  box-shadow: 10px 10px 24px 0 rgba(0, 0, 0, 0.3);
  width: auto;
  height: auto;
  transition: 0.3s;
  color: #fff;

  .details {
    display: none;

    p {
      font-size: 0.7em;
      margin: 0 5px;
      color: #fff;
    }
  }

  &::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translate(-50%, 0);
    width: 0;
    height: 0;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-top: 8px solid #4285f4;
  }

  &:hover {
    p {
      font-size: 0.9em;
    }

    .details {
      display: block;
    }
  }

  &.eco {
    background-color: #188038;

    &::after {
      border-top-color: #188038;
    }
  }

  &.alternate {
    background-color: white;
    color: black;

    .details p {
      color: black;
    }

    &::after {
      border-top-color: white;
    }
  }

  &.shorter-distance {
    background-color: purple;

    &::after {
      border-top-color: purple;
    }
  }
}

@media only screen and (max-width: 40em) {
  .control-panel {
    width: 100%;
    height: 500px;
    overflow: scroll;
  }

  .map-container {
    width: 100%;
    height: 500px;
  }
}

@media only screen and (min-width: 40.0625em) and (max-width: 64em) {
  .control-panel {
    width: 100%;
    overflow: auto;
  }

  .map-container {
    width: 100%;
    height: 800px;
  }
}

@media only screen and (min-width: 64.0625em) and (max-width: 100em) {
  .main-content {
    display: flex;
    height: 100%;
  }

  .control-panel {
    width: 50%;
    height: 100%;
  }

  .map-container {
    width: 50%;
    height: 100%;
    padding: 1rem;
  }
}

@media only screen and (min-width: 100.0625em) {
  .main-content {
    display: flex;
    height: 100%;
  }

  .control-panel {
    width: 33.33333%;
    height: 100%;
  }

  .map-container {
    width: 66.66667%;
    height: 100%;
    padding: 1rem;
  }
}

@media only screen {
  .heading-wrapper,
  .route-option-name-wrapper {
    width: calc(25% - 0.5rem);
  }

  .location-input-wrapper,
  .route-option-input {
    width: calc(75% - 0.5rem);
  }

  .departure-time-wrapper,
  .eco-friendly-options-wrapper,
  .location-options-wrapper,
  .route-options-wrapper,
  .transit-modes-wrapper,
  .transit-routing-preference-wrapper,
  .travel-mode-wrapper {
    width: 100%;
  }
}

@media only screen and (min-width: 40.0625em) {
  .heading-wrapper,
  .route-option-name-wrapper {
    width: calc(25% - 0.5rem);
  }

  .departure-time-wrapper,
  .travel-mode-wrapper {
    width: calc(33.33333% - 0.5rem);
  }

  .eco-friendly-options-wrapper,
  .transit-modes-wrapper,
  .transit-routing-preference-wrapper,
  .route-options-wrapper {
    width: calc(50% - 0.5rem);
  }

  .location-input-wrapper,
  .route-option-input {
    width: calc(75% - 0.5rem);
  }

  .location-options-wrapper {
    width: 100%;
  }
}

HTML

<html>

  <head>
    <title>Get routes</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>

  <body>
    <div class="main-content">
      <div class="alert" id="alert">
        <p>error</p>
        <button class="button-secondary close">&times;</button>
      </div>
      <div class="control-panel">
        <form id="compute-routes-form">
          <section>
            <h2>Input locations</h2>
            <div class="row">
              <div class="location-input-wrapper">
                <label class="text">Origin*</label>
                <div id="origin-input"></div>
              </div>
              <div class="heading-wrapper">
                <label for="heading_org" class="text">Heading</label>
                <input type="text" id="heading_org" name="heading_org" value="" />
              </div>
            </div>
            <div class="row">
              <div class="location-options-wrapper">
                <input type="checkbox" id="origin_stopover" name="origin_stopover" />
                <label for="origin_stopover">Stopover</label>
                <input type="checkbox" id="side_of_road_org" name="origin_side_of_road" />
                <label for="side_of_road_org">Side of Road</label>
              </div>
            </div>
            <hr />
            <div class="row">
              <div class="location-input-wrapper">
                <label class="text">Destination*</label>
                <div id="destination-input"></div>
              </div>
              <div class="heading-wrapper">
                <label for="heading_des" class="text">Heading</label>
                <input type="text" id="heading_des" name="heading_des" value="" />
              </div>
            </div>
            <div class="row">
              <div class="location-options-wrapper">
                <input type="checkbox" id="destination_stopover" name="destination_stopover" />
                <label for="destination_stopover">Stopover</label>
                <input type="checkbox" id="side_of_road_des" name="destination_side_of_road" />
                <label for="side_of_road_des">Side of Road</label>
              </div>
            </div>
          </section>
          <section>
            <h2>Travel Mode</h2>
            <div class="row">
              <div class="travel-mode-wrapper">
                <select name="travel_mode" id="travel-mode">
                  <option value="DRIVING">Driving</option>
                  <option value="WALKING">Walking</option>
                  <option value="BICYCLING">Bicycling</option>
                  <option value="TWO_WHEELER">Two Wheeler (two-wheeled motorized vehicle)</option>
                  <option value="TRANSIT">Transit</option>
                </select>
              </div>
            </div>

            <div class="row" id="transit-options" style="display: none">
              <div class="transit-modes-wrapper">
                <h3>Transit Modes</h3>
                <ul id="transitModes">
                  <li>
                    <input type="checkbox" name="bus" value="BUS" id="bus" checked />
                    <label for="bus">Bus</label>
                  </li>
                  <li>
                    <input type="checkbox" name="subway" value="SUBWAY" id="subway" checked />
                    <label for="subway">Subway</label>
                  </li>
                  <li>
                    <input type="checkbox" name="train" value="TRAIN" id="train" checked />
                    <label for="train">Train</label>
                  </li>
                  <li>
                    <input type="checkbox" name="light_rail" value="LIGHT_RAIL" id="light_rail" checked />
                    <label for="light_rail">Light rail</label>
                  </li>
                </ul>
              </div>
              <div class="transit-routing-preference-wrapper">
                <h3>Transit Routing Preference</h3>
                <select name="transit_preference" id="transitPreference">
                  <option value=""></option>
                  <option value="LESS_WALKING">Less walking</option>
                  <option value="FEWER_TRANSFERS">Fewer transfers</option>
                </select>
              </div>
            </div>
          </section>
          <section>
            <h2>Departure Time (Your local time)</h2>
            <p>
              Choose your <b>local time</b>. The selected time will be converted to
              <b>UTC format time</b>.
            </p>
            <p>
              If you set the departure time, the routing preference has to be either TRAFFIC_AWARE
              or TRAFFIC_AWARE_OPTIMAL. TRAFFIC_AWARE_OPTIMAL calculates best routes by factoring in
              real-time road conditions, including closures.
            </p>
            <div class="row">
              <div class="departure-time-wrapper">
                <input type="datetime-local" id="departure-time" name="departure_time" />
                <p id="utc-output"></p>
              </div>
            </div>
          </section>
          <section>
            <h2>Route Options</h2>
            <div class="row">
              <div class="route-options-wrapper">
                <div class="row">
                  <h3 class="route-option-name-wrapper">Polyline Quality</h3>
                  <select class="route-option-input" name="polyline_quality" id="polyline_quality">
                    <option value=""></option>
                    <option value="HIGH_QUALITY">High quality</option>
                    <option value="OVERVIEW">Overview</option>
                  </select>
                </div>
                <div class="row">
                  <h3 class="route-option-name-wrapper">Traffic Awareness</h3>
                  <select class="route-option-input" name="routing_preference" id="routing-preference">
                    <option value=""></option>
                    <option value="TRAFFIC_UNAWARE">Traffic unaware</option>
                    <option value="TRAFFIC_AWARE">Traffic aware</option>
                    <option value="TRAFFIC_AWARE_OPTIMAL">
                      Traffic aware optimal (best routes with accurate ETA)
                    </option>
                  </select>
                </div>

                <div class="row">
                  <h3 class="route-option-name-wrapper">Traffic Aware Polyline</h3>
                  <div class="route-option-input">
                    <input type="checkbox" name="traffic_aware_polyline" id="traffic-aware-polyline" disabled />
                    <label for="traffic-aware-polyline"></label>
                  </div>
                </div>
              </div>
              <div class="route-options-wrapper">
                <h3>Route Modifiers</h3>
                <ul>
                  <li>
                    <input type="checkbox" name="avoid_tolls" value="avoid_tolls" id="avoid_tolls" />
                    <label for="avoid_tolls">Avoid tolls</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_highways" value="avoid_highways" id="avoid_highways" />
                    <label for="avoid_highways">Avoid highways</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_ferries" value="avoid_ferries" id="avoid_ferries" />
                    <label for="avoid_ferries">Avoid ferries</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_indoor" value="avoid_indoor" id="avoid_indoor" />
                    <label for="avoid_indoor">Avoid indoor</label>
                  </li>
                </ul>
              </div>
            </div>
          </section>

          <section>
            <h2>Reference routes</h2>
            <div class="row">
              <div>
                <input type="checkbox" name="compute_alternative_routes" id="compute_alternative_routes" />
                <label for="compute_alternative_routes">Alternative Routes</label>
              </div>
            </div>
            <div class="row">
              <div>
                <input type="checkbox" name="shorter_distance" id="shorter_distance" />
                <label for="shorter_distance">Shorter Distance Routes</label>
              </div>
            </div>

            <hr />

            <div class="row">
              <div class="eco-friendly-options-wrapper">
                <div>
                  <input type="checkbox" name="eco_routes" id="eco-routes" />
                  <label for="eco-routes">Eco-friendly Routes</label>
                </div>
              </div>
              <div class="eco-friendly-options-wrapper" id="enginetype">
                <h3>Emission Type</h3>
                <select name="emission_type" id="emission-type" disabled>
                  <option value="GASOLINE">Gasoline</option>
                  <option value="ELECTRIC">Electric</option>
                  <option value="HYBRID">Hybrid</option>
                  <option value="DIESEL">Diesel</option>
                </select>
              </div>
            </div>
          </section>

          <section>
            <h2>Fields</h2>
            <div class="row" id="field-mask">
              <div>
                <h3>Fields</h3>
                <ul id="fields">
                  <li>
                    <input type="checkbox" name="route_labels" value="routeLabels" id="route_labels" checked disabled />
                    <label for="route_labels">routeLabels</label>
                  </li>
                  <li>
                    <input type="checkbox" name="legs" value="legs" id="legs" checked />
                    <label for="legs">legs</label>
                  </li>
                  <li>
                    <input type="checkbox" name="distance_meters" value="distanceMeters" id="distance_meters" />
                    <label for="distance_meters">distanceMeters</label>
                  </li>
                  <li>
                    <input type="checkbox" name="duration_millis" value="durationMillis" id="duration_millis" />
                    <label for="duration_millis">durationMillis</label>
                  </li>
                  <li>
                    <input type="checkbox" name="static_duration_millis" value="staticDurationMillis"
                      id="static_duration_millis" />
                    <label for="static_duration_millis">staticDurationMillis</label>
                  </li>
                  <li>
                    <input type="checkbox" name="path" value="path" id="path" checked disabled />
                    <label for="path">path</label>
                  </li>
                  <li>
                    <input type="checkbox" name="polyline_details" value="polylineDetails" id="polyline_details" />
                    <label for="polyline_details">polylineDetails</label>
                  </li>
                  <li>
                    <input type="checkbox" name="description" value="description" id="description" />
                    <label for="description">description</label>
                  </li>
                  <li>
                    <input type="checkbox" name="warnings" value="warnings" id="warnings" />
                    <label for="warnings">warnings</label>
                  </li>
                  <li>
                    <input type="checkbox" name="viewport" value="viewport" id="viewport" checked disabled />
                    <label for="viewport">viewport</label>
                  </li>
                  <li>
                    <input type="checkbox" name="travel_advisory" value="travelAdvisory" id="travel_advisory" />
                    <label for="travel_advisory">travelAdvisory</label>
                  </li>
                  <li>
                    <input type="checkbox" name="optimized_intermediate_waypoint_indices"
                      value="optimizedIntermediateWaypointIndices" id="optimized_intermediate_waypoint_indices" />
                    <label for="optimized_intermediate_waypoint_indices">optimizedIntermediateWaypointIndices</label>
                  </li>
                  <li>
                    <input type="checkbox" name="localized_values" value="localizedValues" id="localized_values" checked
                      disabled />
                    <label for="localized_values">localizedValues</label>
                  </li>
                  <li>
                    <input type="checkbox" name="route_token" value="routeToken" id="route_token" />
                    <label for="route_token">routeToken</label>
                  </li>
                  <li>
                    <input type="checkbox" name="speed_paths" value="speedPaths" id="speed_paths" />
                    <label for="speed_paths">speedPaths</label>
                  </li>
                </ul>
              </div>
            </div>
          </section>

          <section>
            <div class="row">
              <button class="button-primary" type="submit">Get routes</button>
            </div>
          </section>
        </form>
      </div>
      <div class="map-container">
        <gmp-map id="map" class="map" center="-34.397, 150.644" zoom="4" map-id="DEMO_MAP_ID"></gmp-map>
      </div>
    </div>
    <!-- prettier-ignore -->
    <script>(g => { var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__", m = document, b = window; b = b[c] || (b[c] = {}); var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams, u = () => h || (h = new Promise(async (f, n) => { await (a = m.createElement("script")); e.set("libraries", [...r] + ""); for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]); e.set("callback", c + ".maps." + q); a.src = `https://maps.${c}apis.com/maps/api/js?` + e; d[q] = f; a.onerror = () => h = n(Error(p + " could not load.")); a.nonce = m.querySelector("script[nonce]")?.nonce || ""; m.head.append(a) })); d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)) })
        ({ key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "beta" });</script>
  </body>
</html>

Попробуйте образец

,
Разработчики Европейской экономической зоны (ЕЭЗ)

Демонстрация маршрута позволяет указать начальную и конечную точку в виде пары координат широты и долготы или идентификатора места. Чтобы скопировать координаты широты и долготы, найдите и щёлкните нужное место на карте, а затем вставьте его в форму.

После выбора пункта Получить маршрут демо-версия отображает ответ метода computeRoutes в виде маршрута на карте.

Посмотреть полный исходный код примера

Следующая демонстрация позволит вам поэкспериментировать с созданием множества различных маршрутов. Щёлкните по карте, чтобы скопировать координаты широты и долготы нужного места. Вставьте координаты в форму, чтобы получить маршрут.

Машинопись

let markers: google.maps.marker.AdvancedMarkerElement[] = [];
let polylines: google.maps.Polyline[] = [];
let waypointInfoWindow: google.maps.InfoWindow | null = null;

interface PlaceAutocompleteSelection {
  predictionText: string | null;
  location: google.maps.LatLng | null;
}

const originAutocompleteSelection: PlaceAutocompleteSelection = {
  predictionText: null,
  location: null,
};
const destinationAutocompleteSelection: PlaceAutocompleteSelection = {
  predictionText: null,
  location: null,
};

async function init() {
  const [
    { InfoWindow },
    { AdvancedMarkerElement },
    //@ts-ignore
    { PlaceAutocompleteElement },
    //@ts-ignore
    { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel },
  ] = await Promise.all([
    google.maps.importLibrary('maps') as Promise<google.maps.MapsLibrary>,
    google.maps.importLibrary('marker') as Promise<google.maps.MarkerLibrary>,
    google.maps.importLibrary('places') as Promise<google.maps.PlacesLibrary>,
    google.maps.importLibrary('routes') as Promise<google.maps.RoutesLibrary>,
  ]);

  const map = document.getElementById('map') as google.maps.MapElement;

  attachSubmitListener();
  initializeLocationInputs();
  attachMapClickListener();
  attachTravelModeListener();
  attachAlertWindowListener();
  attachDepartureTimeListener();

  function attachSubmitListener() {
    const computeRoutesForm = document.getElementById(
      'compute-routes-form',
    ) as HTMLFormElement;

    computeRoutesForm.addEventListener('submit', (event) => {
      event.preventDefault();
      sendRequest(new FormData(computeRoutesForm));
    });
  }

  async function sendRequest(formData: FormData) {
    clearMap();

    try {
      const { routes } = await Route.computeRoutes(
        buildComputeRoutesJsRequest(formData),
      );

      if (!routes) {
        console.log('No routes returned.');
        return;
      }

      console.log('Routes:');
      routes.forEach((route) => {
        console.log(route.toJSON());
      });

      await Promise.all(
        routes.map((route) =>
          drawRoute(
            route,
            !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE),
          ),
        ),
      );
    } catch (error: unknown) {
      console.error(error);
      setErrorMessage((error as Error).message || 'Unknown error.');
    }
  }

  function buildComputeRoutesJsRequest(
    formData: FormData,
    //@ts-ignore
  ): google.maps.routes.ComputeRoutesRequest {
    const travelMode =
      (formData.get('travel_mode') as string) === ''
        ? undefined
        : (formData.get('travel_mode') as google.maps.TravelMode);
    //@ts-ignore
    const extraComputations: google.maps.routes.ComputeRoutesExtraComputation[] =
      [];
    //@ts-ignore
    const requestedReferenceRoutes: google.maps.routes.ReferenceRoute[] = [];
    //@ts-ignore
    const transitPreference: google.maps.routes.TransitPreference = {};

    const request = {
      origin: {
        location: buildComputeRoutesLocation(
          originAutocompleteSelection,
          formData.get('origin_location'),
          formData.get('heading_org'),
          travelMode,
        ),
        vehicleStopover: formData.get('origin_stopover') === 'on',
        sideOfRoad: formData.get('origin_side_of_road') === 'on',
      },
      destination: {
        location: buildComputeRoutesLocation(
          destinationAutocompleteSelection,
          formData.get('destination_location'),
          formData.get('heading_dest'),
          travelMode,
        ),
        vehicleStopover: formData.get('destination_stopover') === 'on',
        sideOfRoad: formData.get('destination_side_of_road') === 'on',
      },
      fields: Array.from(
        document.querySelectorAll(
          'ul#fields li input[type="checkbox"]:checked',
        ),
        (input) => (input as HTMLInputElement).value,
      ),
      travelMode: travelMode as google.maps.TravelMode,
      routingPreference:
        formData.get('routing_preference') === ''
          ? undefined
          : (formData.get(
            'routing_preference',
            //@ts-ignore
          ) as google.maps.routes.RoutingPreference),
      polylineQuality:
        formData.get('polyline_quality') === ''
          ? undefined
          : (formData.get(
            'polyline_quality',
            //@ts-ignore
          ) as google.maps.routes.PolylineQuality),
      computeAlternativeRoutes:
        formData.get('compute_alternative_routes') === 'on',
      routeModifiers: {
        avoidTolls: formData.get('avoid_tolls') === 'on',
        avoidHighways: formData.get('avoid_highways') === 'on',
        avoidFerries: formData.get('avoid_ferries') === 'on',
        avoidIndoor: formData.get('avoid_indoor') === 'on',
      },
      departureTime:
        (formData.get('departure_time') as string) === ''
          ? undefined
          : new Date(formData.get('departure_time') as string),
      extraComputations,
      requestedReferenceRoutes,
      transitPreference,
    };

    if (formData.get('traffic_aware_polyline') === 'on') {
      extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE);
    }

    if (formData.get('shorter_distance') === 'on') {
      requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE);
    }

    if (formData.get('eco_routes') === 'on') {
      requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT);
      extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION);
      (
        //@ts-ignore
        request.routeModifiers as google.maps.routes.RouteModifiers
      ).vehicleInfo = {
        emissionType: formData.get(
          'emission_type',
          //@ts-ignore
        ) as google.maps.routes.VehicleEmissionType,
      };
    }

    if (travelMode === google.maps.TravelMode.TRANSIT) {
      const selectedTransitModes = document.querySelectorAll(
        'ul#transitModes li input[type="checkbox"]:checked',
      );
      transitPreference.allowedTransitModes = Array.from(
        selectedTransitModes,
        (input) => (input as HTMLInputElement).value as google.maps.TransitMode,
      );
      transitPreference.routingPreference =
        formData.get('transit_preference') === ''
          ? undefined
          : (formData.get(
            'transit_preference',
          ) as google.maps.TransitRoutePreference);
    }

    return request;
  }

  function buildComputeRoutesLocation(
    autocompleteSelection: PlaceAutocompleteSelection,
    locationInput?: FormDataEntryValue | null,
    headingInput?: FormDataEntryValue | null,
    travelModeInput?: FormDataEntryValue | null,
    // @ts-ignore
  ): string | google.maps.routes.DirectionalLocationLiteral {
    if (!locationInput) {
      throw new Error('Location is required.');
    }

    const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/;
    const location = locationInput as string;
    const heading =
      headingInput && travelModeInput !== 'TRANSIT'
        ? Number(headingInput as string)
        : undefined;

    if (
      autocompleteSelection.predictionText === location &&
      autocompleteSelection.location
    ) {
      // Use the lat/lng from the autocomplete selection if the current input
      // matches the autocomplete prediction text
      return {
        lat: autocompleteSelection.location.lat(),
        lng: autocompleteSelection.location.lng(),
        altitude: 0,
        heading,
      };
    } else if (latLngRegex.test(location)) {
      // If the current input looks like a lat/lng, format it as a
      // google.maps.routes.DirectionalLocationLiteral
      return {
        lat: Number(location.split(',')[0]),
        lng: Number(location.split(',')[1]),
        altitude: 0,
        heading,
      };
    }

    // Otherwise return the input location string
    return location;
  }

  function setErrorMessage(error: string) {
    const alertBox = document.getElementById('alert') as HTMLDivElement;
    alertBox.querySelector('p')!.textContent = error;
    alertBox.style.display = 'flex';
  }

  async function drawRoute(
    //@ts-ignore
    route: google.maps.routes.Route,
    isPrimaryRoute: boolean,
  ) {
    polylines = polylines.concat(
      route.createPolylines({
        polylineOptions: isPrimaryRoute
          ? { map: map.innerMap, zIndex: 1 }
          : {
            map: map.innerMap,
            strokeColor: '#669DF6',
            strokeOpacity: 0.5,
            strokeWeight: 8,
          },
        colorScheme: map.innerMap.get('colorScheme'),
      }),
    );

    if (isPrimaryRoute) {
      markers = markers.concat(
        await route.createWaypointAdvancedMarkers({
          map: map.innerMap,
          zIndex: 1,
        }),
      );

      if (route.viewport) {
        map.innerMap.fitBounds(route.viewport);
      }
    }

    addRouteLabel(route, Math.floor(route.path!.length / 2));
  }

  //@ts-ignore
  function addRouteLabel(route: google.maps.routes.Route, index: number) {
    const routeTag = document.createElement('div');
    routeTag.className = 'route-tag';

    if (route.routeLabels && route.routeLabels.length > 0) {
      const p = document.createElement('p');
      route.routeLabels.forEach((label, i) => {
        if (label.includes(RouteLabel.FUEL_EFFICIENT)) {
          routeTag.classList.add('eco');
        }
        if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) {
          routeTag.classList.add('alternate');
        }
        if (label.includes(RouteLabel.SHORTER_DISTANCE)) {
          routeTag.classList.add('shorter-distance');
        }

        p.appendChild(document.createTextNode(label));
        if (i < route.routeLabels!.length - 1) {
          p.appendChild(document.createElement('br'));
        }
      });
      routeTag.appendChild(p);
    }

    const detailsDiv = document.createElement('div');
    detailsDiv.className = 'details';

    if (route.localizedValues) {
      const distanceP = document.createElement('p');
      distanceP.textContent = `Distance: ${route.localizedValues.distance!}`;
      detailsDiv.appendChild(distanceP);

      const durationP = document.createElement('p');
      durationP.textContent = `Duration: ${route.localizedValues.duration}`!;
      detailsDiv.appendChild(durationP);
    }

    if (route.travelAdvisory?.fuelConsumptionMicroliters) {
      const fuelP = document.createElement('p');
      fuelP.textContent = `Fuel consumption: ${(
        route.travelAdvisory.fuelConsumptionMicroliters / 1e6
      ).toFixed(2)} L`;
      detailsDiv.appendChild(fuelP);
    }

    routeTag.appendChild(detailsDiv);

    const marker = new AdvancedMarkerElement({
      map: map.innerMap,
      position: route.path![index],
      content: routeTag,
      zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE)
        ? 1
        : undefined,
    });
    markers.push(marker);
  }

  function clearMap() {
    markers.forEach((marker) => {
      marker.map = null;
    });
    markers.length = 0;

    polylines.forEach((polyline) => {
      polyline.setMap(null);
    });
    polylines.length = 0;
  }

  function attachMapClickListener() {
    if (!map || !map.innerMap) {
      return;
    }

    let infoWindowAlert = document.getElementById('infowindow-alert');
    if (!infoWindowAlert) {
      infoWindowAlert = document.createElement('div');
      infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert';
      infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard';
    }

    const infoWindow = new InfoWindow();
    let closeWindowTimeout: number;

    map.innerMap.addListener(
      'click',
      async (mapsMouseEvent: google.maps.MapMouseEvent) => {
        if (!mapsMouseEvent.latLng) {
          return;
        }

        infoWindow.close();
        if (closeWindowTimeout) {
          clearTimeout(closeWindowTimeout);
        }

        infoWindow.setContent(infoWindowAlert);
        infoWindow.setPosition({
          lat: mapsMouseEvent.latLng.lat(),
          lng: mapsMouseEvent.latLng.lng(),
        });

        await navigator.clipboard.writeText(
          `${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`,
        );

        infoWindow.open(map.innerMap);
        closeWindowTimeout = window.setTimeout(() => {
          infoWindow.close();
        }, 2000);
      },
    );
  }

  function attachTravelModeListener() {
    const travelMode = document.getElementById(
      'travel-mode',
    ) as HTMLSelectElement;
    const routingPreference = document.getElementById(
      'routing-preference',
    ) as HTMLSelectElement;
    const trafficAwarePolyline = document.getElementById(
      'traffic-aware-polyline',
    ) as HTMLInputElement;
    const ecoRoutes = document.getElementById('eco-routes') as HTMLInputElement;
    const emissionType = document.getElementById(
      'emission-type',
    ) as HTMLSelectElement;

    travelMode.addEventListener('change', () => {
      // Toggle the Routing Preference selection and Traffic Aware Polyline
      // selection for WALKING, BICYCLING, and TRANSIT modes.
      if (
        travelMode.value === 'WALKING' ||
        travelMode.value === 'BICYCLING' ||
        travelMode.value === 'TRANSIT'
      ) {
        routingPreference.disabled = true;
        routingPreference.value = '';
      } else {
        routingPreference.disabled = false;
        routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE';
      }

      toggleTrafficAwarePolyline();

      // Toggle transit options for Transit mode
      (
        document.getElementById('transit-options') as HTMLElement
      ).style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none';
    });

    routingPreference.addEventListener('change', () => {
      toggleTrafficAwarePolyline();
    });

    ecoRoutes.addEventListener('change', () => {
      if (ecoRoutes.checked) {
        emissionType.disabled = false;
      } else {
        emissionType.disabled = true;
      }
    });

    function toggleTrafficAwarePolyline() {
      if (
        !routingPreference.value ||
        routingPreference.value === 'TRAFFIC_UNAWARE'
      ) {
        trafficAwarePolyline.checked = false;
        trafficAwarePolyline.disabled = true;
      } else {
        trafficAwarePolyline.disabled = false;
      }
    }
  }

  function attachAlertWindowListener() {
    const alertBox = document.getElementById('alert') as HTMLDivElement;
    const closeBtn = alertBox.querySelector('.close') as HTMLButtonElement;
    closeBtn.addEventListener('click', () => {
      if (alertBox.style.display !== 'none') {
        alertBox.style.display = 'none';
      }
    });
  }

  function initializeLocationInputs() {
    const originAutocomplete = new PlaceAutocompleteElement({
      name: 'origin_location',
    });
    const destinationAutocomplete = new PlaceAutocompleteElement({
      name: 'destination_location',
    });

    [
      [originAutocomplete, originAutocompleteSelection],
      [destinationAutocomplete, destinationAutocompleteSelection],
    ].forEach(([autocomplete, autocompleteData]) => {
      autocomplete.addEventListener(
        'gmp-select',
        //@ts-ignore
        async (event: google.maps.places.PlacePredictionSelectEvent) => {
          autocompleteData.predictionText = event.placePrediction.text.text;

          const place = event.placePrediction.toPlace();
          await place.fetchFields({
            fields: ['location'],
          });
          autocompleteData.location = place.location;
        },
      );
    });

    document.getElementById('origin-input')?.appendChild(originAutocomplete);
    document
      .getElementById('destination-input')
      ?.appendChild(destinationAutocomplete);
  }

  function attachDepartureTimeListener() {
    const departureTime = document.getElementById(
      'departure-time',
    ) as HTMLInputElement;
    const utcOutput = document.getElementById(
      'utc-output',
    ) as HTMLParagraphElement;
    departureTime.addEventListener('change', () => {
      utcOutput.textContent = `UTC time: ${new Date(
        departureTime.value,
      ).toUTCString()}`;
    });
  }
}

window.addEventListener('load', init);

JavaScript

let markers = [];
let polylines = [];
let waypointInfoWindow = null;
const originAutocompleteSelection = {
    predictionText: null,
    location: null,
};
const destinationAutocompleteSelection = {
    predictionText: null,
    location: null,
};
async function init() {
    const [{ InfoWindow }, { AdvancedMarkerElement }, 
    //@ts-ignore
    { PlaceAutocompleteElement }, 
    //@ts-ignore
    { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel },] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('marker'),
        google.maps.importLibrary('places'),
        google.maps.importLibrary('routes'),
    ]);
    const map = document.getElementById('map');
    attachSubmitListener();
    initializeLocationInputs();
    attachMapClickListener();
    attachTravelModeListener();
    attachAlertWindowListener();
    attachDepartureTimeListener();
    function attachSubmitListener() {
        const computeRoutesForm = document.getElementById('compute-routes-form');
        computeRoutesForm.addEventListener('submit', (event) => {
            event.preventDefault();
            sendRequest(new FormData(computeRoutesForm));
        });
    }
    async function sendRequest(formData) {
        clearMap();
        try {
            const { routes } = await Route.computeRoutes(buildComputeRoutesJsRequest(formData));
            if (!routes) {
                console.log('No routes returned.');
                return;
            }
            console.log('Routes:');
            routes.forEach((route) => {
                console.log(route.toJSON());
            });
            await Promise.all(routes.map((route) => drawRoute(route, !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE))));
        }
        catch (error) {
            console.error(error);
            setErrorMessage(error.message || 'Unknown error.');
        }
    }
    function buildComputeRoutesJsRequest(formData) {
        const travelMode = formData.get('travel_mode') === ''
            ? undefined
            : formData.get('travel_mode');
        //@ts-ignore
        const extraComputations = [];
        //@ts-ignore
        const requestedReferenceRoutes = [];
        //@ts-ignore
        const transitPreference = {};
        const request = {
            origin: {
                location: buildComputeRoutesLocation(originAutocompleteSelection, formData.get('origin_location'), formData.get('heading_org'), travelMode),
                vehicleStopover: formData.get('origin_stopover') === 'on',
                sideOfRoad: formData.get('origin_side_of_road') === 'on',
            },
            destination: {
                location: buildComputeRoutesLocation(destinationAutocompleteSelection, formData.get('destination_location'), formData.get('heading_dest'), travelMode),
                vehicleStopover: formData.get('destination_stopover') === 'on',
                sideOfRoad: formData.get('destination_side_of_road') === 'on',
            },
            fields: Array.from(document.querySelectorAll('ul#fields li input[type="checkbox"]:checked'), (input) => input.value),
            travelMode: travelMode,
            routingPreference: formData.get('routing_preference') === ''
                ? undefined
                : formData.get('routing_preference'),
            polylineQuality: formData.get('polyline_quality') === ''
                ? undefined
                : formData.get('polyline_quality'),
            computeAlternativeRoutes: formData.get('compute_alternative_routes') === 'on',
            routeModifiers: {
                avoidTolls: formData.get('avoid_tolls') === 'on',
                avoidHighways: formData.get('avoid_highways') === 'on',
                avoidFerries: formData.get('avoid_ferries') === 'on',
                avoidIndoor: formData.get('avoid_indoor') === 'on',
            },
            departureTime: formData.get('departure_time') === ''
                ? undefined
                : new Date(formData.get('departure_time')),
            extraComputations,
            requestedReferenceRoutes,
            transitPreference,
        };
        if (formData.get('traffic_aware_polyline') === 'on') {
            extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE);
        }
        if (formData.get('shorter_distance') === 'on') {
            requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE);
        }
        if (formData.get('eco_routes') === 'on') {
            requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT);
            extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION);
            //@ts-ignore
            request.routeModifiers.vehicleInfo = {
                emissionType: formData.get('emission_type'),
            };
        }
        if (travelMode === google.maps.TravelMode.TRANSIT) {
            const selectedTransitModes = document.querySelectorAll('ul#transitModes li input[type="checkbox"]:checked');
            transitPreference.allowedTransitModes = Array.from(selectedTransitModes, (input) => input.value);
            transitPreference.routingPreference =
                formData.get('transit_preference') === ''
                    ? undefined
                    : formData.get('transit_preference');
        }
        return request;
    }
    function buildComputeRoutesLocation(autocompleteSelection, locationInput, headingInput, travelModeInput) {
        if (!locationInput) {
            throw new Error('Location is required.');
        }
        const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/;
        const location = locationInput;
        const heading = headingInput && travelModeInput !== 'TRANSIT'
            ? Number(headingInput)
            : undefined;
        if (autocompleteSelection.predictionText === location &&
            autocompleteSelection.location) {
            // Use the lat/lng from the autocomplete selection if the current input
            // matches the autocomplete prediction text
            return {
                lat: autocompleteSelection.location.lat(),
                lng: autocompleteSelection.location.lng(),
                altitude: 0,
                heading,
            };
        }
        else if (latLngRegex.test(location)) {
            // If the current input looks like a lat/lng, format it as a
            // google.maps.routes.DirectionalLocationLiteral
            return {
                lat: Number(location.split(',')[0]),
                lng: Number(location.split(',')[1]),
                altitude: 0,
                heading,
            };
        }
        // Otherwise return the input location string
        return location;
    }
    function setErrorMessage(error) {
        const alertBox = document.getElementById('alert');
        alertBox.querySelector('p').textContent = error;
        alertBox.style.display = 'flex';
    }
    async function drawRoute(
    //@ts-ignore
    route, isPrimaryRoute) {
        polylines = polylines.concat(route.createPolylines({
            polylineOptions: isPrimaryRoute
                ? { map: map.innerMap, zIndex: 1 }
                : {
                    map: map.innerMap,
                    strokeColor: '#669DF6',
                    strokeOpacity: 0.5,
                    strokeWeight: 8,
                },
            colorScheme: map.innerMap.get('colorScheme'),
        }));
        if (isPrimaryRoute) {
            markers = markers.concat(await route.createWaypointAdvancedMarkers({
                map: map.innerMap,
                zIndex: 1,
            }));
            if (route.viewport) {
                map.innerMap.fitBounds(route.viewport);
            }
        }
        addRouteLabel(route, Math.floor(route.path.length / 2));
    }
    //@ts-ignore
    function addRouteLabel(route, index) {
        const routeTag = document.createElement('div');
        routeTag.className = 'route-tag';
        if (route.routeLabels && route.routeLabels.length > 0) {
            const p = document.createElement('p');
            route.routeLabels.forEach((label, i) => {
                if (label.includes(RouteLabel.FUEL_EFFICIENT)) {
                    routeTag.classList.add('eco');
                }
                if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) {
                    routeTag.classList.add('alternate');
                }
                if (label.includes(RouteLabel.SHORTER_DISTANCE)) {
                    routeTag.classList.add('shorter-distance');
                }
                p.appendChild(document.createTextNode(label));
                if (i < route.routeLabels.length - 1) {
                    p.appendChild(document.createElement('br'));
                }
            });
            routeTag.appendChild(p);
        }
        const detailsDiv = document.createElement('div');
        detailsDiv.className = 'details';
        if (route.localizedValues) {
            const distanceP = document.createElement('p');
            distanceP.textContent = `Distance: ${route.localizedValues.distance}`;
            detailsDiv.appendChild(distanceP);
            const durationP = document.createElement('p');
            durationP.textContent = `Duration: ${route.localizedValues.duration}`;
            detailsDiv.appendChild(durationP);
        }
        if (route.travelAdvisory?.fuelConsumptionMicroliters) {
            const fuelP = document.createElement('p');
            fuelP.textContent = `Fuel consumption: ${(route.travelAdvisory.fuelConsumptionMicroliters / 1e6).toFixed(2)} L`;
            detailsDiv.appendChild(fuelP);
        }
        routeTag.appendChild(detailsDiv);
        const marker = new AdvancedMarkerElement({
            map: map.innerMap,
            position: route.path[index],
            content: routeTag,
            zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE)
                ? 1
                : undefined,
        });
        markers.push(marker);
    }
    function clearMap() {
        markers.forEach((marker) => {
            marker.map = null;
        });
        markers.length = 0;
        polylines.forEach((polyline) => {
            polyline.setMap(null);
        });
        polylines.length = 0;
    }
    function attachMapClickListener() {
        if (!map || !map.innerMap) {
            return;
        }
        let infoWindowAlert = document.getElementById('infowindow-alert');
        if (!infoWindowAlert) {
            infoWindowAlert = document.createElement('div');
            infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert';
            infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard';
        }
        const infoWindow = new InfoWindow();
        let closeWindowTimeout;
        map.innerMap.addListener('click', async (mapsMouseEvent) => {
            if (!mapsMouseEvent.latLng) {
                return;
            }
            infoWindow.close();
            if (closeWindowTimeout) {
                clearTimeout(closeWindowTimeout);
            }
            infoWindow.setContent(infoWindowAlert);
            infoWindow.setPosition({
                lat: mapsMouseEvent.latLng.lat(),
                lng: mapsMouseEvent.latLng.lng(),
            });
            await navigator.clipboard.writeText(`${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`);
            infoWindow.open(map.innerMap);
            closeWindowTimeout = window.setTimeout(() => {
                infoWindow.close();
            }, 2000);
        });
    }
    function attachTravelModeListener() {
        const travelMode = document.getElementById('travel-mode');
        const routingPreference = document.getElementById('routing-preference');
        const trafficAwarePolyline = document.getElementById('traffic-aware-polyline');
        const ecoRoutes = document.getElementById('eco-routes');
        const emissionType = document.getElementById('emission-type');
        travelMode.addEventListener('change', () => {
            // Toggle the Routing Preference selection and Traffic Aware Polyline
            // selection for WALKING, BICYCLING, and TRANSIT modes.
            if (travelMode.value === 'WALKING' ||
                travelMode.value === 'BICYCLING' ||
                travelMode.value === 'TRANSIT') {
                routingPreference.disabled = true;
                routingPreference.value = '';
            }
            else {
                routingPreference.disabled = false;
                routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE';
            }
            toggleTrafficAwarePolyline();
            // Toggle transit options for Transit mode
            document.getElementById('transit-options').style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none';
        });
        routingPreference.addEventListener('change', () => {
            toggleTrafficAwarePolyline();
        });
        ecoRoutes.addEventListener('change', () => {
            if (ecoRoutes.checked) {
                emissionType.disabled = false;
            }
            else {
                emissionType.disabled = true;
            }
        });
        function toggleTrafficAwarePolyline() {
            if (!routingPreference.value ||
                routingPreference.value === 'TRAFFIC_UNAWARE') {
                trafficAwarePolyline.checked = false;
                trafficAwarePolyline.disabled = true;
            }
            else {
                trafficAwarePolyline.disabled = false;
            }
        }
    }
    function attachAlertWindowListener() {
        const alertBox = document.getElementById('alert');
        const closeBtn = alertBox.querySelector('.close');
        closeBtn.addEventListener('click', () => {
            if (alertBox.style.display !== 'none') {
                alertBox.style.display = 'none';
            }
        });
    }
    function initializeLocationInputs() {
        const originAutocomplete = new PlaceAutocompleteElement({
            name: 'origin_location',
        });
        const destinationAutocomplete = new PlaceAutocompleteElement({
            name: 'destination_location',
        });
        [
            [originAutocomplete, originAutocompleteSelection],
            [destinationAutocomplete, destinationAutocompleteSelection],
        ].forEach(([autocomplete, autocompleteData]) => {
            autocomplete.addEventListener('gmp-select', 
            //@ts-ignore
            async (event) => {
                autocompleteData.predictionText = event.placePrediction.text.text;
                const place = event.placePrediction.toPlace();
                await place.fetchFields({
                    fields: ['location'],
                });
                autocompleteData.location = place.location;
            });
        });
        document.getElementById('origin-input')?.appendChild(originAutocomplete);
        document
            .getElementById('destination-input')
            ?.appendChild(destinationAutocomplete);
    }
    function attachDepartureTimeListener() {
        const departureTime = document.getElementById('departure-time');
        const utcOutput = document.getElementById('utc-output');
        departureTime.addEventListener('change', () => {
            utcOutput.textContent = `UTC time: ${new Date(departureTime.value).toUTCString()}`;
        });
    }
}
window.addEventListener('load', init);

CSS

html,
body {
  height: 100%;
  font-size: 100%;
  font-family: 'Google Sans', sans-serif;
  margin: 0;
  background-color: #fff;
}

* {
  box-sizing: border-box;
}

h2,
h3 {
  color: #222;
  font-style: normal;
  font-weight: normal;
  line-height: 1.4;
  margin-bottom: 0.5rem;
  margin-top: 0.2rem;
}

h2 {
  font-weight: bold;
  font-size: 1rem;
}

h3 {
  font-size: 0.8rem;
}

p {
  font-size: 0.8rem;
  margin: 0 0 0.6rem 0;
}

label {
  color: #4d4d4d;
  display: inline-block;
  margin: 0;
  position: relative;
  z-index: 2;
  font-size: 0.875rem;
}

input[type='text'] {
  height: 50px;
  width: 100%;
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid #ccc;
}

ul {
  list-style: none;
  padding-inline-start: 0.25rem;
}

select {
  appearance: none;
  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+);
  background-position: 100% center;
  background-repeat: no-repeat;
  padding-right: 1.5rem;

  &:disabled {
    background-color: #ddd;
    cursor: default;
  }

  &[multiple] {
    height: auto;
  }
}

select,
input[type='datetime-local'] {
  height: 2.3125rem;
  width: 100%;
  border-style: solid;
  border-width: 1px;
  border-color: #ccc;
  border-radius: 4px;
  padding: 0.3rem;
  font-family: inherit;
  font-size: 0.8rem;
}

button {
  min-height: 3rem;
  min-width: 3rem;
  cursor: pointer;
  font-family: inherit;
  font-weight: normal;
  font-size: 0.875rem;
  line-height: normal;
  padding: 0 1.5rem;
  position: relative;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  border-radius: 4px;
  transition:
    background-color 0.2s,
    border 0.2s;

  &.button-primary {
    background-color: #1a73e8;
    color: #fff;
    border: 1px solid #dadce0;

    &:hover {
      background-color: #e8f0fe;
      border-color: #d2e3fc;
      color: #1a73e8;
    }
  }

  &.button-secondary {
    background-color: #fff;
    color: #1a73e8;
    border: none;

    &:hover {
      background-color: #1a73e8;
      color: #fff;
    }
  }

  &.close {
    font-size: 2rem;
  }
}

hr {
  border: 1px solid #f4f0f0;
  margin-inline: 0;
}

section {
  display: flex;
  flex-direction: column;
  padding: 1.25rem 1rem;
  border-bottom: 1px solid #ddd;
  gap: 0.5rem;

  &:last-child {
    border-bottom: none;
  }
}

.main-content {
  width: 100%;
  border: 1px solid #e4e4e4;
  border-radius: 25px 25px 0 0;
}

.control-panel {
  padding-top: 20px;
  overflow: scroll;
}

.map-container {
  height: 100%;
  padding: 0;
}

.map {
  height: 100%;
}

.row {
  display: flex;
  flex-flow: row wrap;
  align-items: flex-start;
  gap: 1rem;

  &:not(:last-child) {
    margin-bottom: 0.5rem;
  }
}

gmp-place-autocomplete {
  border: 1px solid #ccc;
  border-radius: 4px;
}

gmp-advanced-marker:hover {
  z-index: 1;
}

.infowindow-alert {
  font-size: 0.8rem;
  margin: 0;
  color: #fff;
}

.alert {
  display: none;
  position: fixed;
  padding: 1rem;
  width: 100%;
  z-index: 10;
  background-color: #fff;
  border-radius: 25px 25px 0 0;
  box-shadow: 0 1px 8px 0px #e4e4e4;
  flex-direction: row;
  justify-content: space-between;

  p {
    padding: 0 3rem 0 1rem;
    color: #f04124;
  }
}

.route-tag {
  background-color: #4285f4;
  border-radius: 8px;
  font-size: 14px;
  padding: 6px 10px;
  position: relative;
  box-shadow: 10px 10px 24px 0 rgba(0, 0, 0, 0.3);
  width: auto;
  height: auto;
  transition: 0.3s;
  color: #fff;

  .details {
    display: none;

    p {
      font-size: 0.7em;
      margin: 0 5px;
      color: #fff;
    }
  }

  &::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translate(-50%, 0);
    width: 0;
    height: 0;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-top: 8px solid #4285f4;
  }

  &:hover {
    p {
      font-size: 0.9em;
    }

    .details {
      display: block;
    }
  }

  &.eco {
    background-color: #188038;

    &::after {
      border-top-color: #188038;
    }
  }

  &.alternate {
    background-color: white;
    color: black;

    .details p {
      color: black;
    }

    &::after {
      border-top-color: white;
    }
  }

  &.shorter-distance {
    background-color: purple;

    &::after {
      border-top-color: purple;
    }
  }
}

@media only screen and (max-width: 40em) {
  .control-panel {
    width: 100%;
    height: 500px;
    overflow: scroll;
  }

  .map-container {
    width: 100%;
    height: 500px;
  }
}

@media only screen and (min-width: 40.0625em) and (max-width: 64em) {
  .control-panel {
    width: 100%;
    overflow: auto;
  }

  .map-container {
    width: 100%;
    height: 800px;
  }
}

@media only screen and (min-width: 64.0625em) and (max-width: 100em) {
  .main-content {
    display: flex;
    height: 100%;
  }

  .control-panel {
    width: 50%;
    height: 100%;
  }

  .map-container {
    width: 50%;
    height: 100%;
    padding: 1rem;
  }
}

@media only screen and (min-width: 100.0625em) {
  .main-content {
    display: flex;
    height: 100%;
  }

  .control-panel {
    width: 33.33333%;
    height: 100%;
  }

  .map-container {
    width: 66.66667%;
    height: 100%;
    padding: 1rem;
  }
}

@media only screen {
  .heading-wrapper,
  .route-option-name-wrapper {
    width: calc(25% - 0.5rem);
  }

  .location-input-wrapper,
  .route-option-input {
    width: calc(75% - 0.5rem);
  }

  .departure-time-wrapper,
  .eco-friendly-options-wrapper,
  .location-options-wrapper,
  .route-options-wrapper,
  .transit-modes-wrapper,
  .transit-routing-preference-wrapper,
  .travel-mode-wrapper {
    width: 100%;
  }
}

@media only screen and (min-width: 40.0625em) {
  .heading-wrapper,
  .route-option-name-wrapper {
    width: calc(25% - 0.5rem);
  }

  .departure-time-wrapper,
  .travel-mode-wrapper {
    width: calc(33.33333% - 0.5rem);
  }

  .eco-friendly-options-wrapper,
  .transit-modes-wrapper,
  .transit-routing-preference-wrapper,
  .route-options-wrapper {
    width: calc(50% - 0.5rem);
  }

  .location-input-wrapper,
  .route-option-input {
    width: calc(75% - 0.5rem);
  }

  .location-options-wrapper {
    width: 100%;
  }
}

HTML

<html>

  <head>
    <title>Get routes</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>

  <body>
    <div class="main-content">
      <div class="alert" id="alert">
        <p>error</p>
        <button class="button-secondary close">&times;</button>
      </div>
      <div class="control-panel">
        <form id="compute-routes-form">
          <section>
            <h2>Input locations</h2>
            <div class="row">
              <div class="location-input-wrapper">
                <label class="text">Origin*</label>
                <div id="origin-input"></div>
              </div>
              <div class="heading-wrapper">
                <label for="heading_org" class="text">Heading</label>
                <input type="text" id="heading_org" name="heading_org" value="" />
              </div>
            </div>
            <div class="row">
              <div class="location-options-wrapper">
                <input type="checkbox" id="origin_stopover" name="origin_stopover" />
                <label for="origin_stopover">Stopover</label>
                <input type="checkbox" id="side_of_road_org" name="origin_side_of_road" />
                <label for="side_of_road_org">Side of Road</label>
              </div>
            </div>
            <hr />
            <div class="row">
              <div class="location-input-wrapper">
                <label class="text">Destination*</label>
                <div id="destination-input"></div>
              </div>
              <div class="heading-wrapper">
                <label for="heading_des" class="text">Heading</label>
                <input type="text" id="heading_des" name="heading_des" value="" />
              </div>
            </div>
            <div class="row">
              <div class="location-options-wrapper">
                <input type="checkbox" id="destination_stopover" name="destination_stopover" />
                <label for="destination_stopover">Stopover</label>
                <input type="checkbox" id="side_of_road_des" name="destination_side_of_road" />
                <label for="side_of_road_des">Side of Road</label>
              </div>
            </div>
          </section>
          <section>
            <h2>Travel Mode</h2>
            <div class="row">
              <div class="travel-mode-wrapper">
                <select name="travel_mode" id="travel-mode">
                  <option value="DRIVING">Driving</option>
                  <option value="WALKING">Walking</option>
                  <option value="BICYCLING">Bicycling</option>
                  <option value="TWO_WHEELER">Two Wheeler (two-wheeled motorized vehicle)</option>
                  <option value="TRANSIT">Transit</option>
                </select>
              </div>
            </div>

            <div class="row" id="transit-options" style="display: none">
              <div class="transit-modes-wrapper">
                <h3>Transit Modes</h3>
                <ul id="transitModes">
                  <li>
                    <input type="checkbox" name="bus" value="BUS" id="bus" checked />
                    <label for="bus">Bus</label>
                  </li>
                  <li>
                    <input type="checkbox" name="subway" value="SUBWAY" id="subway" checked />
                    <label for="subway">Subway</label>
                  </li>
                  <li>
                    <input type="checkbox" name="train" value="TRAIN" id="train" checked />
                    <label for="train">Train</label>
                  </li>
                  <li>
                    <input type="checkbox" name="light_rail" value="LIGHT_RAIL" id="light_rail" checked />
                    <label for="light_rail">Light rail</label>
                  </li>
                </ul>
              </div>
              <div class="transit-routing-preference-wrapper">
                <h3>Transit Routing Preference</h3>
                <select name="transit_preference" id="transitPreference">
                  <option value=""></option>
                  <option value="LESS_WALKING">Less walking</option>
                  <option value="FEWER_TRANSFERS">Fewer transfers</option>
                </select>
              </div>
            </div>
          </section>
          <section>
            <h2>Departure Time (Your local time)</h2>
            <p>
              Choose your <b>local time</b>. The selected time will be converted to
              <b>UTC format time</b>.
            </p>
            <p>
              If you set the departure time, the routing preference has to be either TRAFFIC_AWARE
              or TRAFFIC_AWARE_OPTIMAL. TRAFFIC_AWARE_OPTIMAL calculates best routes by factoring in
              real-time road conditions, including closures.
            </p>
            <div class="row">
              <div class="departure-time-wrapper">
                <input type="datetime-local" id="departure-time" name="departure_time" />
                <p id="utc-output"></p>
              </div>
            </div>
          </section>
          <section>
            <h2>Route Options</h2>
            <div class="row">
              <div class="route-options-wrapper">
                <div class="row">
                  <h3 class="route-option-name-wrapper">Polyline Quality</h3>
                  <select class="route-option-input" name="polyline_quality" id="polyline_quality">
                    <option value=""></option>
                    <option value="HIGH_QUALITY">High quality</option>
                    <option value="OVERVIEW">Overview</option>
                  </select>
                </div>
                <div class="row">
                  <h3 class="route-option-name-wrapper">Traffic Awareness</h3>
                  <select class="route-option-input" name="routing_preference" id="routing-preference">
                    <option value=""></option>
                    <option value="TRAFFIC_UNAWARE">Traffic unaware</option>
                    <option value="TRAFFIC_AWARE">Traffic aware</option>
                    <option value="TRAFFIC_AWARE_OPTIMAL">
                      Traffic aware optimal (best routes with accurate ETA)
                    </option>
                  </select>
                </div>

                <div class="row">
                  <h3 class="route-option-name-wrapper">Traffic Aware Polyline</h3>
                  <div class="route-option-input">
                    <input type="checkbox" name="traffic_aware_polyline" id="traffic-aware-polyline" disabled />
                    <label for="traffic-aware-polyline"></label>
                  </div>
                </div>
              </div>
              <div class="route-options-wrapper">
                <h3>Route Modifiers</h3>
                <ul>
                  <li>
                    <input type="checkbox" name="avoid_tolls" value="avoid_tolls" id="avoid_tolls" />
                    <label for="avoid_tolls">Avoid tolls</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_highways" value="avoid_highways" id="avoid_highways" />
                    <label for="avoid_highways">Avoid highways</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_ferries" value="avoid_ferries" id="avoid_ferries" />
                    <label for="avoid_ferries">Avoid ferries</label>
                  </li>
                  <li>
                    <input type="checkbox" name="avoid_indoor" value="avoid_indoor" id="avoid_indoor" />
                    <label for="avoid_indoor">Avoid indoor</label>
                  </li>
                </ul>
              </div>
            </div>
          </section>

          <section>
            <h2>Reference routes</h2>
            <div class="row">
              <div>
                <input type="checkbox" name="compute_alternative_routes" id="compute_alternative_routes" />
                <label for="compute_alternative_routes">Alternative Routes</label>
              </div>
            </div>
            <div class="row">
              <div>
                <input type="checkbox" name="shorter_distance" id="shorter_distance" />
                <label for="shorter_distance">Shorter Distance Routes</label>
              </div>
            </div>

            <hr />

            <div class="row">
              <div class="eco-friendly-options-wrapper">
                <div>
                  <input type="checkbox" name="eco_routes" id="eco-routes" />
                  <label for="eco-routes">Eco-friendly Routes</label>
                </div>
              </div>
              <div class="eco-friendly-options-wrapper" id="enginetype">
                <h3>Emission Type</h3>
                <select name="emission_type" id="emission-type" disabled>
                  <option value="GASOLINE">Gasoline</option>
                  <option value="ELECTRIC">Electric</option>
                  <option value="HYBRID">Hybrid</option>
                  <option value="DIESEL">Diesel</option>
                </select>
              </div>
            </div>
          </section>

          <section>
            <h2>Fields</h2>
            <div class="row" id="field-mask">
              <div>
                <h3>Fields</h3>
                <ul id="fields">
                  <li>
                    <input type="checkbox" name="route_labels" value="routeLabels" id="route_labels" checked disabled />
                    <label for="route_labels">routeLabels</label>
                  </li>
                  <li>
                    <input type="checkbox" name="legs" value="legs" id="legs" checked />
                    <label for="legs">legs</label>
                  </li>
                  <li>
                    <input type="checkbox" name="distance_meters" value="distanceMeters" id="distance_meters" />
                    <label for="distance_meters">distanceMeters</label>
                  </li>
                  <li>
                    <input type="checkbox" name="duration_millis" value="durationMillis" id="duration_millis" />
                    <label for="duration_millis">durationMillis</label>
                  </li>
                  <li>
                    <input type="checkbox" name="static_duration_millis" value="staticDurationMillis"
                      id="static_duration_millis" />
                    <label for="static_duration_millis">staticDurationMillis</label>
                  </li>
                  <li>
                    <input type="checkbox" name="path" value="path" id="path" checked disabled />
                    <label for="path">path</label>
                  </li>
                  <li>
                    <input type="checkbox" name="polyline_details" value="polylineDetails" id="polyline_details" />
                    <label for="polyline_details">polylineDetails</label>
                  </li>
                  <li>
                    <input type="checkbox" name="description" value="description" id="description" />
                    <label for="description">description</label>
                  </li>
                  <li>
                    <input type="checkbox" name="warnings" value="warnings" id="warnings" />
                    <label for="warnings">warnings</label>
                  </li>
                  <li>
                    <input type="checkbox" name="viewport" value="viewport" id="viewport" checked disabled />
                    <label for="viewport">viewport</label>
                  </li>
                  <li>
                    <input type="checkbox" name="travel_advisory" value="travelAdvisory" id="travel_advisory" />
                    <label for="travel_advisory">travelAdvisory</label>
                  </li>
                  <li>
                    <input type="checkbox" name="optimized_intermediate_waypoint_indices"
                      value="optimizedIntermediateWaypointIndices" id="optimized_intermediate_waypoint_indices" />
                    <label for="optimized_intermediate_waypoint_indices">optimizedIntermediateWaypointIndices</label>
                  </li>
                  <li>
                    <input type="checkbox" name="localized_values" value="localizedValues" id="localized_values" checked
                      disabled />
                    <label for="localized_values">localizedValues</label>
                  </li>
                  <li>
                    <input type="checkbox" name="route_token" value="routeToken" id="route_token" />
                    <label for="route_token">routeToken</label>
                  </li>
                  <li>
                    <input type="checkbox" name="speed_paths" value="speedPaths" id="speed_paths" />
                    <label for="speed_paths">speedPaths</label>
                  </li>
                </ul>
              </div>
            </div>
          </section>

          <section>
            <div class="row">
              <button class="button-primary" type="submit">Get routes</button>
            </div>
          </section>
        </form>
      </div>
      <div class="map-container">
        <gmp-map id="map" class="map" center="-34.397, 150.644" zoom="4" map-id="DEMO_MAP_ID"></gmp-map>
      </div>
    </div>
    <!-- prettier-ignore -->
    <script>(g => { var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__", m = document, b = window; b = b[c] || (b[c] = {}); var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams, u = () => h || (h = new Promise(async (f, n) => { await (a = m.createElement("script")); e.set("libraries", [...r] + ""); for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]); e.set("callback", c + ".maps." + q); a.src = `https://maps.${c}apis.com/maps/api/js?` + e; d[q] = f; a.onerror = () => h = n(Error(p + " could not load.")); a.nonce = m.querySelector("script[nonce]")?.nonce || ""; m.head.append(a) })); d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)) })
        ({ key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "beta" });</script>
  </body>
</html>

Попробуйте образец