Démonstration d'itinéraire

Développeurs de l'Espace économique européen (EEE)

La démo d'itinéraire vous permet de spécifier un point de départ et une destination sous forme de paire de coordonnées de latitude/longitude ou d'ID de lieu. Pour copier les coordonnées de latitude/longitude, recherchez un lieu sur la carte et cliquez dessus, puis collez-le dans le formulaire.

Après avoir sélectionné Obtenir l'itinéraire, la démo affiche la réponse de la méthode computeRoutes sous la forme d'un itinéraire sur la carte.

Afficher l'exemple de code source complet

La démo suivante vous permet de créer différents types d'itinéraires. Cliquez sur la carte pour copier les coordonnées de latitude et de longitude d'un lieu. Collez les coordonnées dans le formulaire pour obtenir un itinéraire.

TypeScript

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>

Essayer l'exemple