在“路线”演示中,您可以将出发地和目的地指定为纬度/经度坐标对或地点 ID。如需复制纬度/经度坐标,请在地图上找到并点击某个位置,然后将该位置粘贴到表单中。
选择获取路线后,演示会显示 computeRoutes 方法的响应,即地图上的路线。
查看完整的示例源代码
通过以下演示,您可以尝试创建各种不同的路线。 点击地图即可复制某个位置的纬度/经度坐标。将坐标粘贴到表单中以获取路线。
TypeScript
let markers: google.maps.marker.AdvancedMarkerElement[] = []; let polylines: google.maps.Polyline[] = []; let waypointInfoWindow: google.maps.InfoWindow | null = null; interface PlaceAutocompleteSelection { predictionText: string | null; location: google.maps.LatLng | null; } const originAutocompleteSelection: PlaceAutocompleteSelection = { predictionText: null, location: null, }; const destinationAutocompleteSelection: PlaceAutocompleteSelection = { predictionText: null, location: null, }; async function init() { const [ { InfoWindow }, { AdvancedMarkerElement }, //@ts-ignore { PlaceAutocompleteElement }, //@ts-ignore { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel }, ] = await Promise.all([ google.maps.importLibrary('maps') as Promise<google.maps.MapsLibrary>, google.maps.importLibrary('marker') as Promise<google.maps.MarkerLibrary>, google.maps.importLibrary('places') as Promise<google.maps.PlacesLibrary>, google.maps.importLibrary('routes') as Promise<google.maps.RoutesLibrary>, ]); const map = document.getElementById('map') as google.maps.MapElement; attachSubmitListener(); initializeLocationInputs(); attachMapClickListener(); attachTravelModeListener(); attachAlertWindowListener(); attachDepartureTimeListener(); function attachSubmitListener() { const computeRoutesForm = document.getElementById( 'compute-routes-form', ) as HTMLFormElement; computeRoutesForm.addEventListener('submit', (event) => { event.preventDefault(); sendRequest(new FormData(computeRoutesForm)); }); } async function sendRequest(formData: FormData) { clearMap(); try { const { routes } = await Route.computeRoutes( buildComputeRoutesJsRequest(formData), ); if (!routes) { console.log('No routes returned.'); return; } console.log('Routes:'); routes.forEach((route) => { console.log(route.toJSON()); }); await Promise.all( routes.map((route) => drawRoute( route, !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE), ), ), ); } catch (error: unknown) { console.error(error); setErrorMessage((error as Error).message || 'Unknown error.'); } } function buildComputeRoutesJsRequest( formData: FormData, //@ts-ignore ): google.maps.routes.ComputeRoutesRequest { const travelMode = (formData.get('travel_mode') as string) === '' ? undefined : (formData.get('travel_mode') as google.maps.TravelMode); //@ts-ignore const extraComputations: google.maps.routes.ComputeRoutesExtraComputation[] = []; //@ts-ignore const requestedReferenceRoutes: google.maps.routes.ReferenceRoute[] = []; //@ts-ignore const transitPreference: google.maps.routes.TransitPreference = {}; const request = { origin: { location: buildComputeRoutesLocation( originAutocompleteSelection, formData.get('origin_location'), formData.get('heading_org'), travelMode, ), vehicleStopover: formData.get('origin_stopover') === 'on', sideOfRoad: formData.get('origin_side_of_road') === 'on', }, destination: { location: buildComputeRoutesLocation( destinationAutocompleteSelection, formData.get('destination_location'), formData.get('heading_dest'), travelMode, ), vehicleStopover: formData.get('destination_stopover') === 'on', sideOfRoad: formData.get('destination_side_of_road') === 'on', }, fields: Array.from( document.querySelectorAll( 'ul#fields li input[type="checkbox"]:checked', ), (input) => (input as HTMLInputElement).value, ), travelMode: travelMode as google.maps.TravelMode, routingPreference: formData.get('routing_preference') === '' ? undefined : (formData.get( 'routing_preference', //@ts-ignore ) as google.maps.routes.RoutingPreference), polylineQuality: formData.get('polyline_quality') === '' ? undefined : (formData.get( 'polyline_quality', //@ts-ignore ) as google.maps.routes.PolylineQuality), computeAlternativeRoutes: formData.get('compute_alternative_routes') === 'on', routeModifiers: { avoidTolls: formData.get('avoid_tolls') === 'on', avoidHighways: formData.get('avoid_highways') === 'on', avoidFerries: formData.get('avoid_ferries') === 'on', avoidIndoor: formData.get('avoid_indoor') === 'on', }, departureTime: (formData.get('departure_time') as string) === '' ? undefined : new Date(formData.get('departure_time') as string), extraComputations, requestedReferenceRoutes, transitPreference, }; if (formData.get('traffic_aware_polyline') === 'on') { extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE); } if (formData.get('shorter_distance') === 'on') { requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE); } if (formData.get('eco_routes') === 'on') { requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT); extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION); ( //@ts-ignore request.routeModifiers as google.maps.routes.RouteModifiers ).vehicleInfo = { emissionType: formData.get( 'emission_type', //@ts-ignore ) as google.maps.routes.VehicleEmissionType, }; } if (travelMode === google.maps.TravelMode.TRANSIT) { const selectedTransitModes = document.querySelectorAll( 'ul#transitModes li input[type="checkbox"]:checked', ); transitPreference.allowedTransitModes = Array.from( selectedTransitModes, (input) => (input as HTMLInputElement).value as google.maps.TransitMode, ); transitPreference.routingPreference = formData.get('transit_preference') === '' ? undefined : (formData.get( 'transit_preference', ) as google.maps.TransitRoutePreference); } return request; } function buildComputeRoutesLocation( autocompleteSelection: PlaceAutocompleteSelection, locationInput?: FormDataEntryValue | null, headingInput?: FormDataEntryValue | null, travelModeInput?: FormDataEntryValue | null, // @ts-ignore ): string | google.maps.routes.DirectionalLocationLiteral { if (!locationInput) { throw new Error('Location is required.'); } const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/; const location = locationInput as string; const heading = headingInput && travelModeInput !== 'TRANSIT' ? Number(headingInput as string) : undefined; if ( autocompleteSelection.predictionText === location && autocompleteSelection.location ) { // Use the lat/lng from the autocomplete selection if the current input // matches the autocomplete prediction text return { lat: autocompleteSelection.location.lat(), lng: autocompleteSelection.location.lng(), altitude: 0, heading, }; } else if (latLngRegex.test(location)) { // If the current input looks like a lat/lng, format it as a // google.maps.routes.DirectionalLocationLiteral return { lat: Number(location.split(',')[0]), lng: Number(location.split(',')[1]), altitude: 0, heading, }; } // Otherwise return the input location string return location; } function setErrorMessage(error: string) { const alertBox = document.getElementById('alert') as HTMLDivElement; alertBox.querySelector('p')!.textContent = error; alertBox.style.display = 'flex'; } async function drawRoute( //@ts-ignore route: google.maps.routes.Route, isPrimaryRoute: boolean, ) { polylines = polylines.concat( route.createPolylines({ polylineOptions: isPrimaryRoute ? { map: map.innerMap, zIndex: 1 } : { map: map.innerMap, strokeColor: '#669DF6', strokeOpacity: 0.5, strokeWeight: 8, }, colorScheme: map.innerMap.get('colorScheme'), }), ); if (isPrimaryRoute) { markers = markers.concat( await route.createWaypointAdvancedMarkers({ map: map.innerMap, zIndex: 1, }), ); if (route.viewport) { map.innerMap.fitBounds(route.viewport); } } addRouteLabel(route, Math.floor(route.path!.length / 2)); } //@ts-ignore function addRouteLabel(route: google.maps.routes.Route, index: number) { const routeTag = document.createElement('div'); routeTag.className = 'route-tag'; if (route.routeLabels && route.routeLabels.length > 0) { const p = document.createElement('p'); route.routeLabels.forEach((label, i) => { if (label.includes(RouteLabel.FUEL_EFFICIENT)) { routeTag.classList.add('eco'); } if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) { routeTag.classList.add('alternate'); } if (label.includes(RouteLabel.SHORTER_DISTANCE)) { routeTag.classList.add('shorter-distance'); } p.appendChild(document.createTextNode(label)); if (i < route.routeLabels!.length - 1) { p.appendChild(document.createElement('br')); } }); routeTag.appendChild(p); } const detailsDiv = document.createElement('div'); detailsDiv.className = 'details'; if (route.localizedValues) { const distanceP = document.createElement('p'); distanceP.textContent = `Distance: ${route.localizedValues.distance!}`; detailsDiv.appendChild(distanceP); const durationP = document.createElement('p'); durationP.textContent = `Duration: ${route.localizedValues.duration}`!; detailsDiv.appendChild(durationP); } if (route.travelAdvisory?.fuelConsumptionMicroliters) { const fuelP = document.createElement('p'); fuelP.textContent = `Fuel consumption: ${( route.travelAdvisory.fuelConsumptionMicroliters / 1e6 ).toFixed(2)} L`; detailsDiv.appendChild(fuelP); } routeTag.appendChild(detailsDiv); const marker = new AdvancedMarkerElement({ map: map.innerMap, position: route.path![index], content: routeTag, zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE) ? 1 : undefined, }); markers.push(marker); } function clearMap() { markers.forEach((marker) => { marker.map = null; }); markers.length = 0; polylines.forEach((polyline) => { polyline.setMap(null); }); polylines.length = 0; } function attachMapClickListener() { if (!map || !map.innerMap) { return; } let infoWindowAlert = document.getElementById('infowindow-alert'); if (!infoWindowAlert) { infoWindowAlert = document.createElement('div'); infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert'; infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard'; } const infoWindow = new InfoWindow(); let closeWindowTimeout: number; map.innerMap.addListener( 'click', async (mapsMouseEvent: google.maps.MapMouseEvent) => { if (!mapsMouseEvent.latLng) { return; } infoWindow.close(); if (closeWindowTimeout) { clearTimeout(closeWindowTimeout); } infoWindow.setContent(infoWindowAlert); infoWindow.setPosition({ lat: mapsMouseEvent.latLng.lat(), lng: mapsMouseEvent.latLng.lng(), }); await navigator.clipboard.writeText( `${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`, ); infoWindow.open(map.innerMap); closeWindowTimeout = window.setTimeout(() => { infoWindow.close(); }, 2000); }, ); } function attachTravelModeListener() { const travelMode = document.getElementById( 'travel-mode', ) as HTMLSelectElement; const routingPreference = document.getElementById( 'routing-preference', ) as HTMLSelectElement; const trafficAwarePolyline = document.getElementById( 'traffic-aware-polyline', ) as HTMLInputElement; const ecoRoutes = document.getElementById('eco-routes') as HTMLInputElement; const emissionType = document.getElementById( 'emission-type', ) as HTMLSelectElement; travelMode.addEventListener('change', () => { // Toggle the Routing Preference selection and Traffic Aware Polyline // selection for WALKING, BICYCLING, and TRANSIT modes. if ( travelMode.value === 'WALKING' || travelMode.value === 'BICYCLING' || travelMode.value === 'TRANSIT' ) { routingPreference.disabled = true; routingPreference.value = ''; } else { routingPreference.disabled = false; routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE'; } toggleTrafficAwarePolyline(); // Toggle transit options for Transit mode ( document.getElementById('transit-options') as HTMLElement ).style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none'; }); routingPreference.addEventListener('change', () => { toggleTrafficAwarePolyline(); }); ecoRoutes.addEventListener('change', () => { if (ecoRoutes.checked) { emissionType.disabled = false; } else { emissionType.disabled = true; } }); function toggleTrafficAwarePolyline() { if ( !routingPreference.value || routingPreference.value === 'TRAFFIC_UNAWARE' ) { trafficAwarePolyline.checked = false; trafficAwarePolyline.disabled = true; } else { trafficAwarePolyline.disabled = false; } } } function attachAlertWindowListener() { const alertBox = document.getElementById('alert') as HTMLDivElement; const closeBtn = alertBox.querySelector('.close') as HTMLButtonElement; closeBtn.addEventListener('click', () => { if (alertBox.style.display !== 'none') { alertBox.style.display = 'none'; } }); } function initializeLocationInputs() { const originAutocomplete = new PlaceAutocompleteElement({ name: 'origin_location', }); const destinationAutocomplete = new PlaceAutocompleteElement({ name: 'destination_location', }); [ [originAutocomplete, originAutocompleteSelection], [destinationAutocomplete, destinationAutocompleteSelection], ].forEach(([autocomplete, autocompleteData]) => { autocomplete.addEventListener( 'gmp-select', //@ts-ignore async (event: google.maps.places.PlacePredictionSelectEvent) => { autocompleteData.predictionText = event.placePrediction.text.text; const place = event.placePrediction.toPlace(); await place.fetchFields({ fields: ['location'], }); autocompleteData.location = place.location; }, ); }); document.getElementById('origin-input')?.appendChild(originAutocomplete); document .getElementById('destination-input') ?.appendChild(destinationAutocomplete); } function attachDepartureTimeListener() { const departureTime = document.getElementById( 'departure-time', ) as HTMLInputElement; const utcOutput = document.getElementById( 'utc-output', ) as HTMLParagraphElement; departureTime.addEventListener('change', () => { utcOutput.textContent = `UTC time: ${new Date( departureTime.value, ).toUTCString()}`; }); } } window.addEventListener('load', init);
JavaScript
let markers = []; let polylines = []; let waypointInfoWindow = null; const originAutocompleteSelection = { predictionText: null, location: null, }; const destinationAutocompleteSelection = { predictionText: null, location: null, }; async function init() { const [{ InfoWindow }, { AdvancedMarkerElement }, //@ts-ignore { PlaceAutocompleteElement }, //@ts-ignore { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel },] = await Promise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), google.maps.importLibrary('places'), google.maps.importLibrary('routes'), ]); const map = document.getElementById('map'); attachSubmitListener(); initializeLocationInputs(); attachMapClickListener(); attachTravelModeListener(); attachAlertWindowListener(); attachDepartureTimeListener(); function attachSubmitListener() { const computeRoutesForm = document.getElementById('compute-routes-form'); computeRoutesForm.addEventListener('submit', (event) => { event.preventDefault(); sendRequest(new FormData(computeRoutesForm)); }); } async function sendRequest(formData) { clearMap(); try { const { routes } = await Route.computeRoutes(buildComputeRoutesJsRequest(formData)); if (!routes) { console.log('No routes returned.'); return; } console.log('Routes:'); routes.forEach((route) => { console.log(route.toJSON()); }); await Promise.all(routes.map((route) => drawRoute(route, !!route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE)))); } catch (error) { console.error(error); setErrorMessage(error.message || 'Unknown error.'); } } function buildComputeRoutesJsRequest(formData) { const travelMode = formData.get('travel_mode') === '' ? undefined : formData.get('travel_mode'); //@ts-ignore const extraComputations = []; //@ts-ignore const requestedReferenceRoutes = []; //@ts-ignore const transitPreference = {}; const request = { origin: { location: buildComputeRoutesLocation(originAutocompleteSelection, formData.get('origin_location'), formData.get('heading_org'), travelMode), vehicleStopover: formData.get('origin_stopover') === 'on', sideOfRoad: formData.get('origin_side_of_road') === 'on', }, destination: { location: buildComputeRoutesLocation(destinationAutocompleteSelection, formData.get('destination_location'), formData.get('heading_dest'), travelMode), vehicleStopover: formData.get('destination_stopover') === 'on', sideOfRoad: formData.get('destination_side_of_road') === 'on', }, fields: Array.from(document.querySelectorAll('ul#fields li input[type="checkbox"]:checked'), (input) => input.value), travelMode: travelMode, routingPreference: formData.get('routing_preference') === '' ? undefined : formData.get('routing_preference'), polylineQuality: formData.get('polyline_quality') === '' ? undefined : formData.get('polyline_quality'), computeAlternativeRoutes: formData.get('compute_alternative_routes') === 'on', routeModifiers: { avoidTolls: formData.get('avoid_tolls') === 'on', avoidHighways: formData.get('avoid_highways') === 'on', avoidFerries: formData.get('avoid_ferries') === 'on', avoidIndoor: formData.get('avoid_indoor') === 'on', }, departureTime: formData.get('departure_time') === '' ? undefined : new Date(formData.get('departure_time')), extraComputations, requestedReferenceRoutes, transitPreference, }; if (formData.get('traffic_aware_polyline') === 'on') { extraComputations.push(ComputeRoutesExtraComputation.TRAFFIC_ON_POLYLINE); } if (formData.get('shorter_distance') === 'on') { requestedReferenceRoutes.push(ReferenceRoute.SHORTER_DISTANCE); } if (formData.get('eco_routes') === 'on') { requestedReferenceRoutes.push(ReferenceRoute.FUEL_EFFICIENT); extraComputations.push(ComputeRoutesExtraComputation.FUEL_CONSUMPTION); //@ts-ignore request.routeModifiers.vehicleInfo = { emissionType: formData.get('emission_type'), }; } if (travelMode === google.maps.TravelMode.TRANSIT) { const selectedTransitModes = document.querySelectorAll('ul#transitModes li input[type="checkbox"]:checked'); transitPreference.allowedTransitModes = Array.from(selectedTransitModes, (input) => input.value); transitPreference.routingPreference = formData.get('transit_preference') === '' ? undefined : formData.get('transit_preference'); } return request; } function buildComputeRoutesLocation(autocompleteSelection, locationInput, headingInput, travelModeInput) { if (!locationInput) { throw new Error('Location is required.'); } const latLngRegex = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/; const location = locationInput; const heading = headingInput && travelModeInput !== 'TRANSIT' ? Number(headingInput) : undefined; if (autocompleteSelection.predictionText === location && autocompleteSelection.location) { // Use the lat/lng from the autocomplete selection if the current input // matches the autocomplete prediction text return { lat: autocompleteSelection.location.lat(), lng: autocompleteSelection.location.lng(), altitude: 0, heading, }; } else if (latLngRegex.test(location)) { // If the current input looks like a lat/lng, format it as a // google.maps.routes.DirectionalLocationLiteral return { lat: Number(location.split(',')[0]), lng: Number(location.split(',')[1]), altitude: 0, heading, }; } // Otherwise return the input location string return location; } function setErrorMessage(error) { const alertBox = document.getElementById('alert'); alertBox.querySelector('p').textContent = error; alertBox.style.display = 'flex'; } async function drawRoute( //@ts-ignore route, isPrimaryRoute) { polylines = polylines.concat(route.createPolylines({ polylineOptions: isPrimaryRoute ? { map: map.innerMap, zIndex: 1 } : { map: map.innerMap, strokeColor: '#669DF6', strokeOpacity: 0.5, strokeWeight: 8, }, colorScheme: map.innerMap.get('colorScheme'), })); if (isPrimaryRoute) { markers = markers.concat(await route.createWaypointAdvancedMarkers({ map: map.innerMap, zIndex: 1, })); if (route.viewport) { map.innerMap.fitBounds(route.viewport); } } addRouteLabel(route, Math.floor(route.path.length / 2)); } //@ts-ignore function addRouteLabel(route, index) { const routeTag = document.createElement('div'); routeTag.className = 'route-tag'; if (route.routeLabels && route.routeLabels.length > 0) { const p = document.createElement('p'); route.routeLabels.forEach((label, i) => { if (label.includes(RouteLabel.FUEL_EFFICIENT)) { routeTag.classList.add('eco'); } if (label.includes(RouteLabel.DEFAULT_ROUTE_ALTERNATE)) { routeTag.classList.add('alternate'); } if (label.includes(RouteLabel.SHORTER_DISTANCE)) { routeTag.classList.add('shorter-distance'); } p.appendChild(document.createTextNode(label)); if (i < route.routeLabels.length - 1) { p.appendChild(document.createElement('br')); } }); routeTag.appendChild(p); } const detailsDiv = document.createElement('div'); detailsDiv.className = 'details'; if (route.localizedValues) { const distanceP = document.createElement('p'); distanceP.textContent = `Distance: ${route.localizedValues.distance}`; detailsDiv.appendChild(distanceP); const durationP = document.createElement('p'); durationP.textContent = `Duration: ${route.localizedValues.duration}`; detailsDiv.appendChild(durationP); } if (route.travelAdvisory?.fuelConsumptionMicroliters) { const fuelP = document.createElement('p'); fuelP.textContent = `Fuel consumption: ${(route.travelAdvisory.fuelConsumptionMicroliters / 1e6).toFixed(2)} L`; detailsDiv.appendChild(fuelP); } routeTag.appendChild(detailsDiv); const marker = new AdvancedMarkerElement({ map: map.innerMap, position: route.path[index], content: routeTag, zIndex: route.routeLabels?.includes(RouteLabel.DEFAULT_ROUTE) ? 1 : undefined, }); markers.push(marker); } function clearMap() { markers.forEach((marker) => { marker.map = null; }); markers.length = 0; polylines.forEach((polyline) => { polyline.setMap(null); }); polylines.length = 0; } function attachMapClickListener() { if (!map || !map.innerMap) { return; } let infoWindowAlert = document.getElementById('infowindow-alert'); if (!infoWindowAlert) { infoWindowAlert = document.createElement('div'); infoWindowAlert.id = infoWindowAlert.className = 'infowindow-alert'; infoWindowAlert.textContent = 'Lat/Lng are copied to clipboard'; } const infoWindow = new InfoWindow(); let closeWindowTimeout; map.innerMap.addListener('click', async (mapsMouseEvent) => { if (!mapsMouseEvent.latLng) { return; } infoWindow.close(); if (closeWindowTimeout) { clearTimeout(closeWindowTimeout); } infoWindow.setContent(infoWindowAlert); infoWindow.setPosition({ lat: mapsMouseEvent.latLng.lat(), lng: mapsMouseEvent.latLng.lng(), }); await navigator.clipboard.writeText(`${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`); infoWindow.open(map.innerMap); closeWindowTimeout = window.setTimeout(() => { infoWindow.close(); }, 2000); }); } function attachTravelModeListener() { const travelMode = document.getElementById('travel-mode'); const routingPreference = document.getElementById('routing-preference'); const trafficAwarePolyline = document.getElementById('traffic-aware-polyline'); const ecoRoutes = document.getElementById('eco-routes'); const emissionType = document.getElementById('emission-type'); travelMode.addEventListener('change', () => { // Toggle the Routing Preference selection and Traffic Aware Polyline // selection for WALKING, BICYCLING, and TRANSIT modes. if (travelMode.value === 'WALKING' || travelMode.value === 'BICYCLING' || travelMode.value === 'TRANSIT') { routingPreference.disabled = true; routingPreference.value = ''; } else { routingPreference.disabled = false; routingPreference.value = routingPreference.value || 'TRAFFIC_UNAWARE'; } toggleTrafficAwarePolyline(); // Toggle transit options for Transit mode document.getElementById('transit-options').style.display = travelMode.value === 'TRANSIT' ? 'flex' : 'none'; }); routingPreference.addEventListener('change', () => { toggleTrafficAwarePolyline(); }); ecoRoutes.addEventListener('change', () => { if (ecoRoutes.checked) { emissionType.disabled = false; } else { emissionType.disabled = true; } }); function toggleTrafficAwarePolyline() { if (!routingPreference.value || routingPreference.value === 'TRAFFIC_UNAWARE') { trafficAwarePolyline.checked = false; trafficAwarePolyline.disabled = true; } else { trafficAwarePolyline.disabled = false; } } } function attachAlertWindowListener() { const alertBox = document.getElementById('alert'); const closeBtn = alertBox.querySelector('.close'); closeBtn.addEventListener('click', () => { if (alertBox.style.display !== 'none') { alertBox.style.display = 'none'; } }); } function initializeLocationInputs() { const originAutocomplete = new PlaceAutocompleteElement({ name: 'origin_location', }); const destinationAutocomplete = new PlaceAutocompleteElement({ name: 'destination_location', }); [ [originAutocomplete, originAutocompleteSelection], [destinationAutocomplete, destinationAutocompleteSelection], ].forEach(([autocomplete, autocompleteData]) => { autocomplete.addEventListener('gmp-select', //@ts-ignore async (event) => { autocompleteData.predictionText = event.placePrediction.text.text; const place = event.placePrediction.toPlace(); await place.fetchFields({ fields: ['location'], }); autocompleteData.location = place.location; }); }); document.getElementById('origin-input')?.appendChild(originAutocomplete); document .getElementById('destination-input') ?.appendChild(destinationAutocomplete); } function attachDepartureTimeListener() { const departureTime = document.getElementById('departure-time'); const utcOutput = document.getElementById('utc-output'); departureTime.addEventListener('change', () => { utcOutput.textContent = `UTC time: ${new Date(departureTime.value).toUTCString()}`; }); } } window.addEventListener('load', init);
CSS
html, body { height: 100%; font-size: 100%; font-family: 'Google Sans', sans-serif; margin: 0; background-color: #fff; } * { box-sizing: border-box; } h2, h3 { color: #222; font-style: normal; font-weight: normal; line-height: 1.4; margin-bottom: 0.5rem; margin-top: 0.2rem; } h2 { font-weight: bold; font-size: 1rem; } h3 { font-size: 0.8rem; } p { font-size: 0.8rem; margin: 0 0 0.6rem 0; } label { color: #4d4d4d; display: inline-block; margin: 0; position: relative; z-index: 2; font-size: 0.875rem; } input[type='text'] { height: 50px; width: 100%; padding: 0.5rem; border-radius: 4px; border: 1px solid #ccc; } ul { list-style: none; padding-inline-start: 0.25rem; } select { appearance: none; background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+); background-position: 100% center; background-repeat: no-repeat; padding-right: 1.5rem; &:disabled { background-color: #ddd; cursor: default; } &[multiple] { height: auto; } } select, input[type='datetime-local'] { height: 2.3125rem; width: 100%; border-style: solid; border-width: 1px; border-color: #ccc; border-radius: 4px; padding: 0.3rem; font-family: inherit; font-size: 0.8rem; } button { min-height: 3rem; min-width: 3rem; cursor: pointer; font-family: inherit; font-weight: normal; font-size: 0.875rem; line-height: normal; padding: 0 1.5rem; position: relative; text-align: center; text-decoration: none; display: inline-block; border-radius: 4px; transition: background-color 0.2s, border 0.2s; &.button-primary { background-color: #1a73e8; color: #fff; border: 1px solid #dadce0; &:hover { background-color: #e8f0fe; border-color: #d2e3fc; color: #1a73e8; } } &.button-secondary { background-color: #fff; color: #1a73e8; border: none; &:hover { background-color: #1a73e8; color: #fff; } } &.close { font-size: 2rem; } } hr { border: 1px solid #f4f0f0; margin-inline: 0; } section { display: flex; flex-direction: column; padding: 1.25rem 1rem; border-bottom: 1px solid #ddd; gap: 0.5rem; &:last-child { border-bottom: none; } } .main-content { width: 100%; border: 1px solid #e4e4e4; border-radius: 25px 25px 0 0; } .control-panel { padding-top: 20px; overflow: scroll; } .map-container { height: 100%; padding: 0; } .map { height: 100%; } .row { display: flex; flex-flow: row wrap; align-items: flex-start; gap: 1rem; &:not(:last-child) { margin-bottom: 0.5rem; } } gmp-place-autocomplete { border: 1px solid #ccc; border-radius: 4px; } gmp-advanced-marker:hover { z-index: 1; } .infowindow-alert { font-size: 0.8rem; margin: 0; color: #fff; } .alert { display: none; position: fixed; padding: 1rem; width: 100%; z-index: 10; background-color: #fff; border-radius: 25px 25px 0 0; box-shadow: 0 1px 8px 0px #e4e4e4; flex-direction: row; justify-content: space-between; p { padding: 0 3rem 0 1rem; color: #f04124; } } .route-tag { background-color: #4285f4; border-radius: 8px; font-size: 14px; padding: 6px 10px; position: relative; box-shadow: 10px 10px 24px 0 rgba(0, 0, 0, 0.3); width: auto; height: auto; transition: 0.3s; color: #fff; .details { display: none; p { font-size: 0.7em; margin: 0 5px; color: #fff; } } &::after { content: ''; position: absolute; left: 50%; top: 100%; transform: translate(-50%, 0); width: 0; height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 8px solid #4285f4; } &:hover { p { font-size: 0.9em; } .details { display: block; } } &.eco { background-color: #188038; &::after { border-top-color: #188038; } } &.alternate { background-color: white; color: black; .details p { color: black; } &::after { border-top-color: white; } } &.shorter-distance { background-color: purple; &::after { border-top-color: purple; } } } @media only screen and (max-width: 40em) { .control-panel { width: 100%; height: 500px; overflow: scroll; } .map-container { width: 100%; height: 500px; } } @media only screen and (min-width: 40.0625em) and (max-width: 64em) { .control-panel { width: 100%; overflow: auto; } .map-container { width: 100%; height: 800px; } } @media only screen and (min-width: 64.0625em) and (max-width: 100em) { .main-content { display: flex; height: 100%; } .control-panel { width: 50%; height: 100%; } .map-container { width: 50%; height: 100%; padding: 1rem; } } @media only screen and (min-width: 100.0625em) { .main-content { display: flex; height: 100%; } .control-panel { width: 33.33333%; height: 100%; } .map-container { width: 66.66667%; height: 100%; padding: 1rem; } } @media only screen { .heading-wrapper, .route-option-name-wrapper { width: calc(25% - 0.5rem); } .location-input-wrapper, .route-option-input { width: calc(75% - 0.5rem); } .departure-time-wrapper, .eco-friendly-options-wrapper, .location-options-wrapper, .route-options-wrapper, .transit-modes-wrapper, .transit-routing-preference-wrapper, .travel-mode-wrapper { width: 100%; } } @media only screen and (min-width: 40.0625em) { .heading-wrapper, .route-option-name-wrapper { width: calc(25% - 0.5rem); } .departure-time-wrapper, .travel-mode-wrapper { width: calc(33.33333% - 0.5rem); } .eco-friendly-options-wrapper, .transit-modes-wrapper, .transit-routing-preference-wrapper, .route-options-wrapper { width: calc(50% - 0.5rem); } .location-input-wrapper, .route-option-input { width: calc(75% - 0.5rem); } .location-options-wrapper { width: 100%; } }
HTML
<html> <head> <title>Get routes</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div class="main-content"> <div class="alert" id="alert"> <p>error</p> <button class="button-secondary close">×</button> </div> <div class="control-panel"> <form id="compute-routes-form"> <section> <h2>Input locations</h2> <div class="row"> <div class="location-input-wrapper"> <label class="text">Origin*</label> <div id="origin-input"></div> </div> <div class="heading-wrapper"> <label for="heading_org" class="text">Heading</label> <input type="text" id="heading_org" name="heading_org" value="" /> </div> </div> <div class="row"> <div class="location-options-wrapper"> <input type="checkbox" id="origin_stopover" name="origin_stopover" /> <label for="origin_stopover">Stopover</label> <input type="checkbox" id="side_of_road_org" name="origin_side_of_road" /> <label for="side_of_road_org">Side of Road</label> </div> </div> <hr /> <div class="row"> <div class="location-input-wrapper"> <label class="text">Destination*</label> <div id="destination-input"></div> </div> <div class="heading-wrapper"> <label for="heading_des" class="text">Heading</label> <input type="text" id="heading_des" name="heading_des" value="" /> </div> </div> <div class="row"> <div class="location-options-wrapper"> <input type="checkbox" id="destination_stopover" name="destination_stopover" /> <label for="destination_stopover">Stopover</label> <input type="checkbox" id="side_of_road_des" name="destination_side_of_road" /> <label for="side_of_road_des">Side of Road</label> </div> </div> </section> <section> <h2>Travel Mode</h2> <div class="row"> <div class="travel-mode-wrapper"> <select name="travel_mode" id="travel-mode"> <option value="DRIVING">Driving</option> <option value="WALKING">Walking</option> <option value="BICYCLING">Bicycling</option> <option value="TWO_WHEELER">Two Wheeler (two-wheeled motorized vehicle)</option> <option value="TRANSIT">Transit</option> </select> </div> </div> <div class="row" id="transit-options" style="display: none"> <div class="transit-modes-wrapper"> <h3>Transit Modes</h3> <ul id="transitModes"> <li> <input type="checkbox" name="bus" value="BUS" id="bus" checked /> <label for="bus">Bus</label> </li> <li> <input type="checkbox" name="subway" value="SUBWAY" id="subway" checked /> <label for="subway">Subway</label> </li> <li> <input type="checkbox" name="train" value="TRAIN" id="train" checked /> <label for="train">Train</label> </li> <li> <input type="checkbox" name="light_rail" value="LIGHT_RAIL" id="light_rail" checked /> <label for="light_rail">Light rail</label> </li> </ul> </div> <div class="transit-routing-preference-wrapper"> <h3>Transit Routing Preference</h3> <select name="transit_preference" id="transitPreference"> <option value=""></option> <option value="LESS_WALKING">Less walking</option> <option value="FEWER_TRANSFERS">Fewer transfers</option> </select> </div> </div> </section> <section> <h2>Departure Time (Your local time)</h2> <p> Choose your <b>local time</b>. The selected time will be converted to <b>UTC format time</b>. </p> <p> If you set the departure time, the routing preference has to be either TRAFFIC_AWARE or TRAFFIC_AWARE_OPTIMAL. TRAFFIC_AWARE_OPTIMAL calculates best routes by factoring in real-time road conditions, including closures. </p> <div class="row"> <div class="departure-time-wrapper"> <input type="datetime-local" id="departure-time" name="departure_time" /> <p id="utc-output"></p> </div> </div> </section> <section> <h2>Route Options</h2> <div class="row"> <div class="route-options-wrapper"> <div class="row"> <h3 class="route-option-name-wrapper">Polyline Quality</h3> <select class="route-option-input" name="polyline_quality" id="polyline_quality"> <option value=""></option> <option value="HIGH_QUALITY">High quality</option> <option value="OVERVIEW">Overview</option> </select> </div> <div class="row"> <h3 class="route-option-name-wrapper">Traffic Awareness</h3> <select class="route-option-input" name="routing_preference" id="routing-preference"> <option value=""></option> <option value="TRAFFIC_UNAWARE">Traffic unaware</option> <option value="TRAFFIC_AWARE">Traffic aware</option> <option value="TRAFFIC_AWARE_OPTIMAL"> Traffic aware optimal (best routes with accurate ETA) </option> </select> </div> <div class="row"> <h3 class="route-option-name-wrapper">Traffic Aware Polyline</h3> <div class="route-option-input"> <input type="checkbox" name="traffic_aware_polyline" id="traffic-aware-polyline" disabled /> <label for="traffic-aware-polyline"></label> </div> </div> </div> <div class="route-options-wrapper"> <h3>Route Modifiers</h3> <ul> <li> <input type="checkbox" name="avoid_tolls" value="avoid_tolls" id="avoid_tolls" /> <label for="avoid_tolls">Avoid tolls</label> </li> <li> <input type="checkbox" name="avoid_highways" value="avoid_highways" id="avoid_highways" /> <label for="avoid_highways">Avoid highways</label> </li> <li> <input type="checkbox" name="avoid_ferries" value="avoid_ferries" id="avoid_ferries" /> <label for="avoid_ferries">Avoid ferries</label> </li> <li> <input type="checkbox" name="avoid_indoor" value="avoid_indoor" id="avoid_indoor" /> <label for="avoid_indoor">Avoid indoor</label> </li> </ul> </div> </div> </section> <section> <h2>Reference routes</h2> <div class="row"> <div> <input type="checkbox" name="compute_alternative_routes" id="compute_alternative_routes" /> <label for="compute_alternative_routes">Alternative Routes</label> </div> </div> <div class="row"> <div> <input type="checkbox" name="shorter_distance" id="shorter_distance" /> <label for="shorter_distance">Shorter Distance Routes</label> </div> </div> <hr /> <div class="row"> <div class="eco-friendly-options-wrapper"> <div> <input type="checkbox" name="eco_routes" id="eco-routes" /> <label for="eco-routes">Eco-friendly Routes</label> </div> </div> <div class="eco-friendly-options-wrapper" id="enginetype"> <h3>Emission Type</h3> <select name="emission_type" id="emission-type" disabled> <option value="GASOLINE">Gasoline</option> <option value="ELECTRIC">Electric</option> <option value="HYBRID">Hybrid</option> <option value="DIESEL">Diesel</option> </select> </div> </div> </section> <section> <h2>Fields</h2> <div class="row" id="field-mask"> <div> <h3>Fields</h3> <ul id="fields"> <li> <input type="checkbox" name="route_labels" value="routeLabels" id="route_labels" checked disabled /> <label for="route_labels">routeLabels</label> </li> <li> <input type="checkbox" name="legs" value="legs" id="legs" checked /> <label for="legs">legs</label> </li> <li> <input type="checkbox" name="distance_meters" value="distanceMeters" id="distance_meters" /> <label for="distance_meters">distanceMeters</label> </li> <li> <input type="checkbox" name="duration_millis" value="durationMillis" id="duration_millis" /> <label for="duration_millis">durationMillis</label> </li> <li> <input type="checkbox" name="static_duration_millis" value="staticDurationMillis" id="static_duration_millis" /> <label for="static_duration_millis">staticDurationMillis</label> </li> <li> <input type="checkbox" name="path" value="path" id="path" checked disabled /> <label for="path">path</label> </li> <li> <input type="checkbox" name="polyline_details" value="polylineDetails" id="polyline_details" /> <label for="polyline_details">polylineDetails</label> </li> <li> <input type="checkbox" name="description" value="description" id="description" /> <label for="description">description</label> </li> <li> <input type="checkbox" name="warnings" value="warnings" id="warnings" /> <label for="warnings">warnings</label> </li> <li> <input type="checkbox" name="viewport" value="viewport" id="viewport" checked disabled /> <label for="viewport">viewport</label> </li> <li> <input type="checkbox" name="travel_advisory" value="travelAdvisory" id="travel_advisory" /> <label for="travel_advisory">travelAdvisory</label> </li> <li> <input type="checkbox" name="optimized_intermediate_waypoint_indices" value="optimizedIntermediateWaypointIndices" id="optimized_intermediate_waypoint_indices" /> <label for="optimized_intermediate_waypoint_indices">optimizedIntermediateWaypointIndices</label> </li> <li> <input type="checkbox" name="localized_values" value="localizedValues" id="localized_values" checked disabled /> <label for="localized_values">localizedValues</label> </li> <li> <input type="checkbox" name="route_token" value="routeToken" id="route_token" /> <label for="route_token">routeToken</label> </li> <li> <input type="checkbox" name="speed_paths" value="speedPaths" id="speed_paths" /> <label for="speed_paths">speedPaths</label> </li> </ul> </div> </div> </section> <section> <div class="row"> <button class="button-primary" type="submit">Get routes</button> </div> </section> </form> </div> <div class="map-container"> <gmp-map id="map" class="map" center="-34.397, 150.644" zoom="4" map-id="DEMO_MAP_ID"></gmp-map> </div> </div> <!-- prettier-ignore --> <script>(g => { var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__", m = document, b = window; b = b[c] || (b[c] = {}); var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams, u = () => h || (h = new Promise(async (f, n) => { await (a = m.createElement("script")); e.set("libraries", [...r] + ""); for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]); e.set("callback", c + ".maps." + q); a.src = `https://maps.${c}apis.com/maps/api/js?` + e; d[q] = f; a.onerror = () => h = n(Error(p + " could not load.")); a.nonce = m.querySelector("script[nonce]")?.nonce || ""; m.head.append(a) })); d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)) }) ({ key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "beta" });</script> </body> </html>