diff --git a/dist/index.html b/dist/index.html index 9a47c295..27835b3c 100644 --- a/dist/index.html +++ b/dist/index.html @@ -64,6 +64,7 @@

Maps JSAPI Samples

  • react-ui-kit-place-details-latlng-compact
  • react-ui-kit-search-nearby
  • react-ui-kit-search-text
  • +
  • routes-compute-routes
  • routes-get-alternatives
  • routes-get-directions
  • routes-get-directions-panel
  • diff --git a/dist/samples/routes-compute-routes/app/.eslintsrc.json b/dist/samples/routes-compute-routes/app/.eslintsrc.json new file mode 100644 index 00000000..4c44dab0 --- /dev/null +++ b/dist/samples/routes-compute-routes/app/.eslintsrc.json @@ -0,0 +1,13 @@ +{ + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/no-this-alias": 1, + "@typescript-eslint/no-empty-function": 1, + "@typescript-eslint/explicit-module-boundary-types": 1, + "@typescript-eslint/no-unused-vars": 1 + } +} diff --git a/dist/samples/routes-compute-routes/app/README.md b/dist/samples/routes-compute-routes/app/README.md new file mode 100644 index 00000000..e8859a05 --- /dev/null +++ b/dist/samples/routes-compute-routes/app/README.md @@ -0,0 +1,34 @@ + +# Google Maps JavaScript Sample + +This sample is generated from @googlemaps/js-samples located at +https://github.com/googlemaps-samples/js-api-samples. + +## Setup + +### Before starting run: + +`$npm i` + +### Run an example on a local web server + +First `cd` to the folder for the sample to run, then: + +`$npm start` + +### Build an individual example + +From `samples/`: + +`$npm run build --workspace=sample-name/` + +### Build all of the examples. + +From `samples/`: +`$npm run build-all` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). + diff --git a/dist/samples/routes-compute-routes/app/index.html b/dist/samples/routes-compute-routes/app/index.html new file mode 100644 index 00000000..b3675d7f --- /dev/null +++ b/dist/samples/routes-compute-routes/app/index.html @@ -0,0 +1,309 @@ + + + + + + Get routes + + + + + + +
    +
    +

    error

    + +
    +
    +
    +
    +

    Input locations

    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +

    Travel Mode

    +
    +
    + +
    +
    + + +
    +
    +

    Departure Time (Your local time)

    +

    + Choose your local time. The selected time will be converted to + UTC format time. +

    +

    + 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. +

    +
    +
    + +

    +
    +
    +
    +
    +

    Route Options

    +
    +
    +
    +

    Polyline Quality

    + +
    +
    +

    Traffic Awareness

    + +
    + +
    +

    Traffic Aware Polyline

    +
    + + +
    +
    +
    +
    +

    Route Modifiers

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +

    Reference routes

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +

    Emission Type

    + +
    +
    +
    + +
    +

    Fields

    +
    +
    +

    Fields

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/dist/samples/routes-compute-routes/app/index.ts b/dist/samples/routes-compute-routes/app/index.ts new file mode 100644 index 00000000..7c475856 --- /dev/null +++ b/dist/samples/routes-compute-routes/app/index.ts @@ -0,0 +1,533 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_routes_compute_routes] +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.importLibrary('marker') as Promise, + google.maps.importLibrary('places') as Promise, + google.maps.importLibrary('routes') as Promise, + ]); + + 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); +// [END maps_routes_compute_routes] diff --git a/dist/samples/routes-compute-routes/app/package.json b/dist/samples/routes-compute-routes/app/package.json new file mode 100644 index 00000000..2afedb69 --- /dev/null +++ b/dist/samples/routes-compute-routes/app/package.json @@ -0,0 +1,14 @@ +{ + "name": "@js-api-samples/routes-compute-routes", + "version": "1.0.0", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh routes-compute-routes && bash ../app.sh routes-compute-routes && bash ../docs.sh routes-compute-routes && npm run build:vite --workspace=. && bash ../dist.sh routes-compute-routes", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + }, + "dependencies": { + + } +} diff --git a/dist/samples/routes-compute-routes/app/style.css b/dist/samples/routes-compute-routes/app/style.css new file mode 100644 index 00000000..38caf5fc --- /dev/null +++ b/dist/samples/routes-compute-routes/app/style.css @@ -0,0 +1,408 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_routes_compute_routes] */ +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%; + } +} +/* [END maps_routes_compute_routes] */ diff --git a/dist/samples/routes-compute-routes/app/tsconfig.json b/dist/samples/routes-compute-routes/app/tsconfig.json new file mode 100644 index 00000000..09179087 --- /dev/null +++ b/dist/samples/routes-compute-routes/app/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "strict": true, + "noImplicitAny": false, + "lib": [ + "es2015", + "esnext", + "es6", + "dom", + "dom.iterable" + ], + "moduleResolution": "Node", + "jsx": "preserve" + } + } \ No newline at end of file diff --git a/dist/samples/routes-compute-routes/dist/assets/index-C5QbZz0i.js b/dist/samples/routes-compute-routes/dist/assets/index-C5QbZz0i.js new file mode 100644 index 00000000..ae0e1275 --- /dev/null +++ b/dist/samples/routes-compute-routes/dist/assets/index-C5QbZz0i.js @@ -0,0 +1,5 @@ +(function(){const u=document.createElement("link").relList;if(u&&u.supports&&u.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))p(l);new MutationObserver(l=>{for(const c of l)if(c.type==="childList")for(const d of c.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&p(d)}).observe(document,{childList:!0,subtree:!0});function m(l){const c={};return l.integrity&&(c.integrity=l.integrity),l.referrerPolicy&&(c.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?c.credentials="include":l.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function p(l){if(l.ep)return;l.ep=!0;const c=m(l);fetch(l.href,c)}})();/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */let g=[],f=[];const L={predictionText:null,location:null},v={predictionText:null,location:null};async function P(){const[{InfoWindow:h},{AdvancedMarkerElement:u},{PlaceAutocompleteElement:m},{ComputeRoutesExtraComputation:p,ReferenceRoute:l,Route:c,RouteLabel:d}]=await Promise.all([google.maps.importLibrary("maps"),google.maps.importLibrary("marker"),google.maps.importLibrary("places"),google.maps.importLibrary("routes")]),a=document.getElementById("map");T(),M(),b(),N(),x(),F();function T(){const e=document.getElementById("compute-routes-form");e.addEventListener("submit",t=>{t.preventDefault(),_(new FormData(e))})}async function _(e){R();try{const{routes:t}=await c.computeRoutes(w(e));if(!t){console.log("No routes returned.");return}console.log("Routes:"),t.forEach(n=>{console.log(n.toJSON())}),await Promise.all(t.map(n=>A(n,!!n.routeLabels?.includes(d.DEFAULT_ROUTE))))}catch(t){console.error(t),I(t.message||"Unknown error.")}}function w(e){const t=e.get("travel_mode")===""?void 0:e.get("travel_mode"),n=[],i=[],s={},o={origin:{location:E(L,e.get("origin_location"),e.get("heading_org"),t),vehicleStopover:e.get("origin_stopover")==="on",sideOfRoad:e.get("origin_side_of_road")==="on"},destination:{location:E(v,e.get("destination_location"),e.get("heading_dest"),t),vehicleStopover:e.get("destination_stopover")==="on",sideOfRoad:e.get("destination_side_of_road")==="on"},fields:Array.from(document.querySelectorAll('ul#fields li input[type="checkbox"]:checked'),r=>r.value),travelMode:t,routingPreference:e.get("routing_preference")===""?void 0:e.get("routing_preference"),polylineQuality:e.get("polyline_quality")===""?void 0:e.get("polyline_quality"),computeAlternativeRoutes:e.get("compute_alternative_routes")==="on",routeModifiers:{avoidTolls:e.get("avoid_tolls")==="on",avoidHighways:e.get("avoid_highways")==="on",avoidFerries:e.get("avoid_ferries")==="on",avoidIndoor:e.get("avoid_indoor")==="on"},departureTime:e.get("departure_time")===""?void 0:new Date(e.get("departure_time")),extraComputations:n,requestedReferenceRoutes:i,transitPreference:s};if(e.get("traffic_aware_polyline")==="on"&&n.push(p.TRAFFIC_ON_POLYLINE),e.get("shorter_distance")==="on"&&i.push(l.SHORTER_DISTANCE),e.get("eco_routes")==="on"&&(i.push(l.FUEL_EFFICIENT),n.push(p.FUEL_CONSUMPTION),o.routeModifiers.vehicleInfo={emissionType:e.get("emission_type")}),t===google.maps.TravelMode.TRANSIT){const r=document.querySelectorAll('ul#transitModes li input[type="checkbox"]:checked');s.allowedTransitModes=Array.from(r,y=>y.value),s.routingPreference=e.get("transit_preference")===""?void 0:e.get("transit_preference")}return o}function E(e,t,n,i){if(!t)throw new Error("Location is required.");const s=/^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/,o=t,r=n&&i!=="TRANSIT"?Number(n):void 0;return e.predictionText===o&&e.location?{lat:e.location.lat(),lng:e.location.lng(),altitude:0,heading:r}:s.test(o)?{lat:Number(o.split(",")[0]),lng:Number(o.split(",")[1]),altitude:0,heading:r}:o}function I(e){const t=document.getElementById("alert");t.querySelector("p").textContent=e,t.style.display="flex"}async function A(e,t){f=f.concat(e.createPolylines({polylineOptions:t?{map:a.innerMap,zIndex:1}:{map:a.innerMap,strokeColor:"#669DF6",strokeOpacity:.5,strokeWeight:8},colorScheme:a.innerMap.get("colorScheme")})),t&&(g=g.concat(await e.createWaypointAdvancedMarkers({map:a.innerMap,zIndex:1})),e.viewport&&a.innerMap.fitBounds(e.viewport)),C(e,Math.floor(e.path.length/2))}function C(e,t){const n=document.createElement("div");if(n.className="route-tag",e.routeLabels&&e.routeLabels.length>0){const o=document.createElement("p");e.routeLabels.forEach((r,y)=>{r.includes(d.FUEL_EFFICIENT)&&n.classList.add("eco"),r.includes(d.DEFAULT_ROUTE_ALTERNATE)&&n.classList.add("alternate"),r.includes(d.SHORTER_DISTANCE)&&n.classList.add("shorter-distance"),o.appendChild(document.createTextNode(r)),y{e.map=null}),g.length=0,f.forEach(e=>{e.setMap(null)}),f.length=0}function b(){if(!a||!a.innerMap)return;let e=document.getElementById("infowindow-alert");e||(e=document.createElement("div"),e.id=e.className="infowindow-alert",e.textContent="Lat/Lng are copied to clipboard");const t=new h;let n;a.innerMap.addListener("click",async i=>{i.latLng&&(t.close(),n&&clearTimeout(n),t.setContent(e),t.setPosition({lat:i.latLng.lat(),lng:i.latLng.lng()}),await navigator.clipboard.writeText(`${i.latLng.lat()},${i.latLng.lng()}`),t.open(a.innerMap),n=window.setTimeout(()=>{t.close()},2e3))})}function N(){const e=document.getElementById("travel-mode"),t=document.getElementById("routing-preference"),n=document.getElementById("traffic-aware-polyline"),i=document.getElementById("eco-routes"),s=document.getElementById("emission-type");e.addEventListener("change",()=>{e.value==="WALKING"||e.value==="BICYCLING"||e.value==="TRANSIT"?(t.disabled=!0,t.value=""):(t.disabled=!1,t.value=t.value||"TRAFFIC_UNAWARE"),o(),document.getElementById("transit-options").style.display=e.value==="TRANSIT"?"flex":"none"}),t.addEventListener("change",()=>{o()}),i.addEventListener("change",()=>{i.checked?s.disabled=!1:s.disabled=!0});function o(){!t.value||t.value==="TRAFFIC_UNAWARE"?(n.checked=!1,n.disabled=!0):n.disabled=!1}}function x(){const e=document.getElementById("alert");e.querySelector(".close").addEventListener("click",()=>{e.style.display!=="none"&&(e.style.display="none")})}function M(){const e=new m({name:"origin_location"}),t=new m({name:"destination_location"});[[e,L],[t,v]].forEach(([n,i])=>{n.addEventListener("gmp-select",async s=>{i.predictionText=s.placePrediction.text.text;const o=s.placePrediction.toPlace();await o.fetchFields({fields:["location"]}),i.location=o.location})}),document.getElementById("origin-input")?.appendChild(e),document.getElementById("destination-input")?.appendChild(t)}function F(){const e=document.getElementById("departure-time"),t=document.getElementById("utc-output");e.addEventListener("change",()=>{t.textContent=`UTC time: ${new Date(e.value).toUTCString()}`})}}window.addEventListener("load",P); diff --git a/dist/samples/routes-compute-routes/dist/assets/index-DeGRB6LY.css b/dist/samples/routes-compute-routes/dist/assets/index-DeGRB6LY.css new file mode 100644 index 00000000..627e3513 --- /dev/null +++ b/dist/samples/routes-compute-routes/dist/assets/index-DeGRB6LY.css @@ -0,0 +1,5 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */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:400;line-height:1.4;margin-bottom:.5rem;margin-top:.2rem}h2{font-weight:700;font-size:1rem}h3{font-size:.8rem}p{font-size:.8rem;margin:0 0 .6rem}label{color:#4d4d4d;display:inline-block;margin:0;position:relative;z-index:2;font-size:.875rem}input[type=text]{height:50px;width:100%;padding:.5rem;border-radius:4px;border:1px solid #ccc}ul{list-style:none;padding-inline-start:.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}select:disabled{background-color:#ddd;cursor:default}select[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:.3rem;font-family:inherit;font-size:.8rem}button{min-height:3rem;min-width:3rem;cursor:pointer;font-family:inherit;font-weight:400;font-size:.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 .2s,border .2s}button.button-primary{background-color:#1a73e8;color:#fff;border:1px solid #dadce0}button.button-primary:hover{background-color:#e8f0fe;border-color:#d2e3fc;color:#1a73e8}button.button-secondary{background-color:#fff;color:#1a73e8;border:none}button.button-secondary:hover{background-color:#1a73e8;color:#fff}button.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:.5rem}section: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}.row:not(:last-child){margin-bottom:.5rem}gmp-place-autocomplete{border:1px solid #ccc;border-radius:4px}gmp-advanced-marker:hover{z-index:1}.infowindow-alert{font-size:.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 #e4e4e4;flex-direction:row;justify-content:space-between}.alert 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 #0000004d;width:auto;height:auto;transition:.3s;color:#fff}.route-tag .details{display:none}.route-tag .details p{font-size:.7em;margin:0 5px;color:#fff}.route-tag:after{content:"";position:absolute;left:50%;top:100%;transform:translate(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #4285f4}.route-tag:hover p{font-size:.9em}.route-tag:hover .details{display:block}.route-tag.eco{background-color:#188038}.route-tag.eco:after{border-top-color:#188038}.route-tag.alternate{background-color:#fff;color:#000}.route-tag.alternate .details p{color:#000}.route-tag.alternate:after{border-top-color:#fff}.route-tag.shorter-distance{background-color:purple}.route-tag.shorter-distance: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% - .5rem)}.location-input-wrapper,.route-option-input{width:calc(75% - .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% - .5rem)}.departure-time-wrapper,.travel-mode-wrapper{width:calc(33.33333% - .5rem)}.eco-friendly-options-wrapper,.transit-modes-wrapper,.transit-routing-preference-wrapper,.route-options-wrapper{width:calc(50% - .5rem)}.location-input-wrapper,.route-option-input{width:calc(75% - .5rem)}.location-options-wrapper{width:100%}} diff --git a/dist/samples/routes-compute-routes/dist/index.html b/dist/samples/routes-compute-routes/dist/index.html new file mode 100644 index 00000000..f23d536f --- /dev/null +++ b/dist/samples/routes-compute-routes/dist/index.html @@ -0,0 +1,309 @@ + + + + + + Get routes + + + + + + +
    +
    +

    error

    + +
    +
    +
    +
    +

    Input locations

    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +

    Travel Mode

    +
    +
    + +
    +
    + + +
    +
    +

    Departure Time (Your local time)

    +

    + Choose your local time. The selected time will be converted to + UTC format time. +

    +

    + 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. +

    +
    +
    + +

    +
    +
    +
    +
    +

    Route Options

    +
    +
    +
    +

    Polyline Quality

    + +
    +
    +

    Traffic Awareness

    + +
    + +
    +

    Traffic Aware Polyline

    +
    + + +
    +
    +
    +
    +

    Route Modifiers

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +

    Reference routes

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +

    Emission Type

    + +
    +
    +
    + +
    +

    Fields

    +
    +
    +

    Fields

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/dist/samples/routes-compute-routes/docs/index.html b/dist/samples/routes-compute-routes/docs/index.html new file mode 100644 index 00000000..b3675d7f --- /dev/null +++ b/dist/samples/routes-compute-routes/docs/index.html @@ -0,0 +1,309 @@ + + + + + + Get routes + + + + + + +
    +
    +

    error

    + +
    +
    +
    +
    +

    Input locations

    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +

    Travel Mode

    +
    +
    + +
    +
    + + +
    +
    +

    Departure Time (Your local time)

    +

    + Choose your local time. The selected time will be converted to + UTC format time. +

    +

    + 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. +

    +
    +
    + +

    +
    +
    +
    +
    +

    Route Options

    +
    +
    +
    +

    Polyline Quality

    + +
    +
    +

    Traffic Awareness

    + +
    + +
    +

    Traffic Aware Polyline

    +
    + + +
    +
    +
    +
    +

    Route Modifiers

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +

    Reference routes

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +

    Emission Type

    + +
    +
    +
    + +
    +

    Fields

    +
    +
    +

    Fields

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/dist/samples/routes-compute-routes/docs/index.js b/dist/samples/routes-compute-routes/docs/index.js new file mode 100644 index 00000000..69c35ad8 --- /dev/null +++ b/dist/samples/routes-compute-routes/docs/index.js @@ -0,0 +1,374 @@ +"use strict"; +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_routes_compute_routes] +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); +// [END maps_routes_compute_routes] diff --git a/dist/samples/routes-compute-routes/docs/index.ts b/dist/samples/routes-compute-routes/docs/index.ts new file mode 100644 index 00000000..7c475856 --- /dev/null +++ b/dist/samples/routes-compute-routes/docs/index.ts @@ -0,0 +1,533 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_routes_compute_routes] +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.importLibrary('marker') as Promise, + google.maps.importLibrary('places') as Promise, + google.maps.importLibrary('routes') as Promise, + ]); + + 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); +// [END maps_routes_compute_routes] diff --git a/dist/samples/routes-compute-routes/docs/style.css b/dist/samples/routes-compute-routes/docs/style.css new file mode 100644 index 00000000..38caf5fc --- /dev/null +++ b/dist/samples/routes-compute-routes/docs/style.css @@ -0,0 +1,408 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_routes_compute_routes] */ +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%; + } +} +/* [END maps_routes_compute_routes] */ diff --git a/dist/samples/routes-compute-routes/jsfiddle/demo.css b/dist/samples/routes-compute-routes/jsfiddle/demo.css new file mode 100644 index 00000000..6c78e3d0 --- /dev/null +++ b/dist/samples/routes-compute-routes/jsfiddle/demo.css @@ -0,0 +1,408 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +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%; + } +} + diff --git a/dist/samples/routes-compute-routes/jsfiddle/demo.details b/dist/samples/routes-compute-routes/jsfiddle/demo.details new file mode 100644 index 00000000..48f9e5cc --- /dev/null +++ b/dist/samples/routes-compute-routes/jsfiddle/demo.details @@ -0,0 +1,7 @@ +name: routes-compute-routes +authors: + - Geo Developer IX Documentation Team +tags: + - google maps +load_type: h +description: Sample code supporting Google Maps Platform JavaScript API documentation. diff --git a/dist/samples/routes-compute-routes/jsfiddle/demo.html b/dist/samples/routes-compute-routes/jsfiddle/demo.html new file mode 100644 index 00000000..b3675d7f --- /dev/null +++ b/dist/samples/routes-compute-routes/jsfiddle/demo.html @@ -0,0 +1,309 @@ + + + + + + Get routes + + + + + + +
    +
    +

    error

    + +
    +
    +
    +
    +

    Input locations

    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +

    Travel Mode

    +
    +
    + +
    +
    + + +
    +
    +

    Departure Time (Your local time)

    +

    + Choose your local time. The selected time will be converted to + UTC format time. +

    +

    + 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. +

    +
    +
    + +

    +
    +
    +
    +
    +

    Route Options

    +
    +
    +
    +

    Polyline Quality

    + +
    +
    +

    Traffic Awareness

    + +
    + +
    +

    Traffic Aware Polyline

    +
    + + +
    +
    +
    +
    +

    Route Modifiers

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +

    Reference routes

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +

    Emission Type

    + +
    +
    +
    + +
    +

    Fields

    +
    +
    +

    Fields

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/dist/samples/routes-compute-routes/jsfiddle/demo.js b/dist/samples/routes-compute-routes/jsfiddle/demo.js new file mode 100644 index 00000000..2c69ff25 --- /dev/null +++ b/dist/samples/routes-compute-routes/jsfiddle/demo.js @@ -0,0 +1,374 @@ +"use strict"; +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +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); + diff --git a/index.html b/index.html index 9a47c295..27835b3c 100644 --- a/index.html +++ b/index.html @@ -64,6 +64,7 @@

    Maps JSAPI Samples

  • react-ui-kit-place-details-latlng-compact
  • react-ui-kit-search-nearby
  • react-ui-kit-search-text
  • +
  • routes-compute-routes
  • routes-get-alternatives
  • routes-get-directions
  • routes-get-directions-panel
  • diff --git a/package-lock.json b/package-lock.json index c5fbaac2..35ff3c2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1411,6 +1411,10 @@ "resolved": "samples/react-ui-kit-place-details-compact", "link": true }, + "node_modules/@js-api-samples/routes-compute-routes": { + "resolved": "samples/routes-compute-routes", + "link": true + }, "node_modules/@js-api-samples/routes-get-alternatives": { "resolved": "samples/routes-get-alternatives", "link": true @@ -9121,6 +9125,10 @@ } } }, + "samples/routes-compute-routes": { + "name": "@js-api-samples/routes-compute-routes", + "version": "1.0.0" + }, "samples/routes-get-alternatives": { "name": "@js-api-samples/routes-get-alternatives", "version": "1.0.0"