Route finding is something that many of us use almost everyday to go from one place to another. Depending on our daily routines, personal preferences, or occupation, we may rely on different modes of transportation, choose different times of day, or consider some restrictions (or avoidances) to get to our destination(s). We usually use an app for this purpose and there are many of them out there. In this post, you will learn how to build one yourself on AWS in seven steps.
We will build a Vue app using Amazon Location Service, as the basemap provider and routing API, MapLibre GL JS, as the map rendering library, and Naive UI, as the UI component library. The app will have a map with navigation controls and a route calculator that finds the fastest route using a number of parameters such as different modes of transportation (car, truck, and walking), departure time, avoidances (ferries and tolls), and weight and size limitations for trucks — the following screenshot shows the end results.
Prerequisites
We will create the AWS resources for this project using AWS CLI. Before we start
- Create an AWS account, if you do not have one already.
- Install and configure AWS CLI on your machine.
1. Create a map resource
Amazon Location Service’s Maps API offers a set of map styles, professionally designed to support different applications and use cases — see Esri map styles and HERE map styles for more details. Now, create a map resource using the map style Esri Navigation
.
aws location create-map \
--map-name=esri-map-navigation \
--configuration Style=VectorEsriNavigation \
--profile={YOUR_AWS_PROFILE}
Once a new map resource is created, you will see an output that contains the map resource’s creation time, ARN, and name.
{
"CreateTime": "2022-09-03T21:00:41.391000+00:00",
"MapArn": "arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:map/esri-map-navigation",
"MapName": "esri-map-navigation"
}
2. Create a route calculator resource
Amazon Location Service’s Routes API provides a route calculator resource that finds optimal routes based on up-to-date road networks and traffic information from two global data providers, Esri and HERE. Let’s create a route calculator resource, with Esri as our data provider.
aws location create-route-calculator \
--calculator-name "esri-route-calculator" \
--data-source "Esri" \
--profile={YOUR_AWS_PROFILE}
Once a new route calculator resource is created, you will see an output that contains the route calculator resource’s creation time, ARN, and name.
{
"CreateTime": "2022-09-03T21:25:36.297000+00:00",
"CalculatorArn": "arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:route-calculator/esri-route-calculator",
"CalculatorName": "esri-route-calculator"
}
3. Grant access to Amazon Location resources
Now, you need to grant access to the resources that you have created so far by creating an Amazon Cognito identity pool and an IAM policy. This way a frontend application can send signed HTTP requests to Amazon Cognito and receive temporary, scoped-down credentials that are valid for an hour. Then, it can use those credentials to request map tiles and optimal routes from Amazon Location Service’s Maps and Routes APIs.
For this project, we will allow for unauthenticated guest access to our application — see Granting access to Amazon Location Service to explore more options. First, create an Amazon Cognito identity pool.
aws cognito-identity create-identity-pool \
--identity-pool-name routing-app \
--allow-unauthenticated-identities \
--profile={YOUR_AWS_PROFILE}
Once a new identity pool is created, you will see an output that contains the identity pool ID, name, and a confirmation for allowing unauthenticated access.
{
"IdentityPoolId": "{IDENTITY_POOL_ID}",
"IdentityPoolName": "routing-app",
"AllowUnauthenticatedIdentities": true,
"IdentityPoolTags": {}
}
Next, create a new IAM role that you want to use with your identity pool. Note that you must provide a policy document, as an input parameter, to establish trust between Amazon Cognito and AWS Security Token Service (STS). This will allow your identity pool to request temporary tokens from STS. Here is an example of a policy document in JSON — make sure to convert it to string before using it with the CLI command.
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Principal":{
"Federated":"cognito-identity.amazonaws.com"
},
"Action":"sts:AssumeRoleWithWebIdentity",
"Condition":{
"StringEquals":{
"cognito-identity.amazonaws.com:aud":"{IDENTITY_POOL_ID}"
},
"ForAnyValue:StringLike":{
"cognito-identity.amazonaws.com:amr":"unauthenticated"
}
}
}
]
}
Now, let’s create a new IAM role.
aws iam create-role \
--role-name Cognito_routing_app_Unauth_Role \
--assume-role-policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":\"cognito-identity.amazonaws.com\"},\"Action\":\"sts:AssumeRoleWithWebIdentity\",\"Condition\":{\"StringEquals\":{\"cognito-identity.amazonaws.com:aud\":\"{IDENTITY_POOL_ID}\"},\"ForAnyValue:StringLike\":{\"cognito-identity.amazonaws.com:amr\":\"unauthenticated\"}}}]}" \
--profile={YOUR_AWS_PROFILE}
Once a new IAM role is created, you will see an output that contains some metadata about the role including the role name and the policy document you provided in the request.
{
"Role": {
"Path": "/",
"RoleName": "Cognito_routing_app_Unauth_Role",
"RoleId": "AROA3K4ALINNDDUQ3Q2XB",
"Arn": "arn:aws:iam::{AWS_ACCOUNT}:role/Cognito_routing_app_Unauth_Role",
"CreateDate": "2022-09-03T22:19:56+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "{IDENTITY_POOL_ID}"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "unauthenticated"
}
}
}
]
}
}
}
Then, attach an inline policy document to the IAM role that you have just created. The policy will grant access to your map and route calculator resources. Here is an example of a policy document in JSON — make sure to convert it to string before using it with the CLI command.
{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"MapsRoutesReadOnly",
"Effect":"Allow",
"Action":[
"geo:GetMap*",
"geo:CalculateRoute"
],
"Resource":[
"arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:map/esri-map-navigation",
"arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:route-calculator/esri-route-calculator"
]
}
]
}
Now, let’s attach the policy to the IAM role.
aws iam put-role-policy \
--role-name Cognito_routing_app_Unauth_Role \
--policy-name Cognito_routing_app_Unauth_Role_Policy \
--policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"MapsRoutesReadOnly\",\"Effect\":\"Allow\",\"Action\":[\"geo:GetMap*\",\"geo:CalculateRoute\"],\"Resource\":[\"arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:map/esri-map-navigation\",\"arn:aws:geo:{AWS_REGION}:{AWS_ACCOUNT}:route-calculator/esri-route-calculator\"]}]}" \
--profile={YOUR_AWS_PROFILE}
Finally, add the IAM role to your identity pool.
aws cognito-identity set-identity-pool-roles \
--identity-pool-id "{IDENTITY_POOL_ID}" \
--roles unauthenticated="arn:aws:iam::{AWS_ACCOUNT}:role/Cognito_routing_app_Unauth_Role" \
--profile={YOUR_AWS_PROFILE}
4. Set up a new project
Create a new Vue project using Vite.
npm create vite@latest
Choose a name for your project and pick vue
as the framework and variant.
✔ Select a variant: › vue
✔ Project name: amazon-location-route-planner
✔ Select a framework: › vue
Next, go into your project’s root directory and install the dependencies.
cd amazon-location-route-planner
npm install
Then, replace the content of App.vue
with the following code.
<template>
</template>
<script setup>
</script>
<style>
</style>
Finally, add the following configuration to vite.config
.
export default defineConfig({
...
define: {
global: {}
}
});
5. Add auth configurations
First, install a few dependencies.
npm install @aws-amplify/core @aws-sdk/client-cognito-identity @aws-sdk/credential-provider-cognito-identity
Next, create a new file, config.js
, under src
directory and enter your Cognito identity pool ID.
const identityPoolId = "{IDENTITY_POOL_ID}";
export { identityPoolId };
Then, create a new file, auth.js
, under src
directory and add the following code to the new file. This will provide a few variables and functions that we will use later on to communicate with Amazon Location Service’s APIs:
-
region
which indicates your AWS region -
credentials
, which are the credentials obtained from Amazon Cognito -
refreshCredentials
, which is a function that automatically renews credentials before they expire -
transformRequest
, which is a function that signs HTTP request sent to the Amazon Location Service’s Maps API using AWS SigV4 with the credentials obtained from Amazon Cognito
import { Signer } from "@aws-amplify/core";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { identityPoolId } from "./config";
const region = identityPoolId.split(":")[0];
const identityProvider = fromCognitoIdentityPool({
client: new CognitoIdentityClient({
region: region,
}),
identityPoolId,
});
let credentials;
const refreshCredentials = async () => {
credentials = await identityProvider();
setTimeout(refreshCredentials, credentials.expiration - new Date());
};
const transformRequest = (url, resourceType) => {
if (resourceType === "Style" && !url.includes("://")) {
url = `https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${url}/style-descriptor`;
}
if (url.includes("amazonaws.com")) {
return {
url: Signer.signUrl(url, {
access_key: credentials.accessKeyId,
secret_key: credentials.secretAccessKey,
session_token: credentials.sessionToken,
}),
};
}
return { url };
};
export { region, credentials, refreshCredentials, transformRequest };
6. Add a map
First, install maplibre-gl
, our map rendering library.
npm install maplibre-gl
Next, open src/config.js
and add the name of your map resource.
...
const mapName = "esri-map-navigation";
export { identityPoolId, mapName };
Then, open App.vue
and add a div
container for the map.
<template>
<div id="map"></div>
</template>
Afterwards, add a new map instance with navigation controls.
<script setup>
import maplibregl from "maplibre-gl";
import { refreshCredentials, transformRequest } from "./auth";
import { mapName } from "./config";
let map = null;
const initializeApp = async () => {
await refreshCredentials();
map = new maplibregl.Map({
container: "map",
center: [-114.067375, 51.046333],
zoom: 16,
style: mapName,
hash: true,
transformRequest,
});
map.addControl(new maplibregl.NavigationControl(), "bottom-right");
}
initializeApp();
</script>
Finally, add the following CSS to produce a full screen map.
<style>
@import "maplibre-gl/dist/maplibre-gl.css";
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
}
</style>
Now, if you run npm run dev
in your terminal and go to your browser, you will see an interactive map on the page.
7. Add a route calculator
An Amazon Location Service’s route calculator resource provides an operation called CalculateRoute
that finds optimal routes between an origin, a destination, and up to 23 stops along the way (or waypoints) for different modes of transportation, avoidances, and departure times — check out the API docs to see all the supported parameters.
For this project, we will build a route calculator widget that supports the following parameters:
-
DeparturePosition
— It specifies the starting point as[longitude, latitude]
. -
DestinationPosition
— It specifies the destination as[longitude, latitude]
. -
WaypointPositions
— It specifies an ordered list of up to 23 intermediate positions to include along a route between the departure and destination positions as[[longitude, latitude], [longitude, latitude]]
. -
TravelMode
— It specifies the mode of transportation when calculating a route, includingCar
,Truck
, orWalking
. -
DistanceUnit
— It specifies the unit of measurement for travel distance, includingKilometers
orMiles
. -
CarModeOptions
— It specifies route preferences when traveling by Car, including avoiding routes that use ferries ,AvoidFerries
, and tolls,AvoidTolls
. -
TruckModeOptions
— It specifies route preferences when traveling by Truck, including avoiding routes that use ferries,AvoidFerries
, and tolls,AvoidTolls
. It also specifies the weight,Weight
, and size, includingHeight
,Length
, andWidth
, of the truck to avoid routes that will not accommodate it. -
DepartNow
— It sets the time of departure as the current time. -
DepartureTime
— It specifies the desired time of departure.
The calculated route will include the overall travel time and distance, unit of measurement, and route geometry, among other things — check out the API docs to see the API response elements.
Now, let’s start with installing a few dependencies.
npm install @aws-sdk/client-location @turf/helpers
npm install -D naive-ui vfonts @vicons/carbon
Next, open src/config.js
and add the name of your route calculator resource.
...
const routeCalculatorName = "esri-route-calculator";
export { identityPoolId, mapName, routeCalculatorName };
Afterwards, open App.vue
and add a card with seven sections for (1) transportation mode, (2) route summary, (3) starting point, waypoints, and destination, (4) unit of measurement options, (5) avoid options, (6) departure time options, and (7) truck options. Note that the last three sections are only for cars and trucks.
<template>
...
<n-card>
<n-space vertical>
<!-- Travel mode options -->
<n-radio-group v-model:value="travelModeOptions">
<n-radio-button value="Car"> Car </n-radio-button>
<n-radio-button value="Truck"> Truck </n-radio-button>
<n-radio-button value="Walking"> Walking </n-radio-button>
</n-radio-group>
<n-space style="margin: 20px 0 20px 0">
Click on the map to choose your starting point and destination(s).
</n-space>
<!-- Route summary -->
<n-h3 v-if="route.features.length > 0">
<n-text type="primary">
{{ Math.round(route.features[0].properties.DurationSeconds / 60) }}
Minutes
</n-text>
<n-text depth="3">
({{ route.features[0].properties.Distance.toFixed(1) }}
{{ route.features[0].properties.DistanceUnit }})
</n-text>
</n-h3>
<!-- Starting point and destinations -->
<n-space>
<n-timeline>
<n-timeline-item
v-for="position in positions.features"
type="info"
:content="position.geometry.coordinates.join(', ')"
:title="position.properties.title"
line-type="dashed"
/>
</n-timeline>
</n-space>
</n-space>
<n-collapse style="margin-top: 20px">
<!-- Unit of measurement options -->
<n-collapse-item title="Unit of measurement" name="1">
<n-radio-group v-model:value="unitOfMeasurementOptions">
<div>
<n-radio value="metric"> Metric </n-radio>
</div>
<div>
<n-radio value="imperial"> Imperial </n-radio>
</div>
</n-radio-group>
<template #header-extra>
<n-icon :component="RulerAlt" />
</template>
</n-collapse-item>
<!-- Avoid options -->
<n-collapse-item
v-if="travelModeOptions === 'Car' || travelModeOptions === 'Truck'"
title="Avoid options"
name="2"
>
<n-checkbox-group v-model:value="avoidOptions">
<div>
<n-checkbox value="AvoidTolls" label="Tolls" />
</div>
<div>
<n-checkbox value="AvoidFerries" label="Ferries" />
</div>
</n-checkbox-group>
<template #header-extra>
<n-icon :component="DirectionFork" />
</template>
</n-collapse-item>
<!-- Truck options -->
<n-collapse-item
v-if="travelModeOptions === 'Truck'"
title="Truck options"
name="3"
>
<n-input
clearable
:placeholder="`Height (${units[unitOfMeasurementOptions]['truck']['dimension']})`"
v-model:value="truckHeight"
>
<template #prefix>
<n-icon :component="FitToHeight" />
</template>
</n-input>
<n-input
clearable
:placeholder="`Width (${units[unitOfMeasurementOptions]['truck']['dimension']})`"
v-model:value="truckWidth"
style="margin-top: 10px"
>
<template #prefix>
<n-icon :component="CenterToFit" />
</template>
</n-input>
<n-input
clearable
:placeholder="`Length (${units[unitOfMeasurementOptions]['truck']['dimension']})`"
v-model:value="truckLength"
style="margin-top: 10px"
>
<template #prefix>
<n-icon :component="FitToWidth" />
</template>
</n-input>
<n-input
clearable
:placeholder="`Weight (${units[unitOfMeasurementOptions]['truck']['weight']})`"
v-model:value="truckWeight"
style="margin-top: 10px"
>
<template #prefix>
<n-icon :component="Scales" />
</template>
</n-input>
<template #header-extra>
<n-icon :component="DeliveryTruck" />
</template>
</n-collapse-item>
<!-- Departure time options -->
<n-collapse-item
v-if="travelModeOptions === 'Car' || travelModeOptions === 'Truck'"
title="Departure time"
name="4"
>
<n-radio-group v-model:value="departureTimeOptions">
<div>
<n-radio value="default"> Optimal traffic conditions </n-radio>
</div>
<div>
<n-radio value="now"> Now </n-radio>
</div>
<div>
<n-radio value="custom"> Leave at </n-radio>
</div>
</n-radio-group>
<n-input-group
v-if="departureTimeOptions === 'custom'"
style="margin-top: 15px"
>
<n-date-picker v-model:value="timestamp" type="datetime" clearable />
</n-input-group>
<template #header-extra> <n-icon :component="Time" /> </template>
</n-collapse-item>
</n-collapse>
</n-card>
</template>
Next, import the required configurations, functions, and components into App.vue
.
<script setup>
...
mport { ref, watch, computed, reactive } from "vue";
import {
LocationClient,
CalculateRouteCommand,
} from "@aws-sdk/client-location";
import {
NCard,
NRadioGroup,
NRadioButton,
NRadio,
NCheckboxGroup,
NCheckbox,
NInput,
NInputGroup,
NDatePicker,
NIcon,
NSpace,
NCollapse,
NCollapseItem,
NTimeline,
NTimelineItem,
NH3,
NText,
} from "naive-ui";
import {
RulerAlt,
DirectionFork,
Time,
DeliveryTruck,
FitToHeight,
FitToWidth,
CenterToFit,
Scales,
} from "@vicons/carbon";
import { point, lineString, featureCollection } from "@turf/helpers";
import {
region,
credentials,
refreshCredentials,
transformRequest,
} from "./auth";
import { mapName, routeCalculatorName } from "./config";
...
</script>
Then, define the required variables to capture the transportation mode, positions (departure, waypoints, and destination), calculated route, unit of measurement, avoid option, departure time option, and the weight and size of trucks.
<script setup>
...
let travelModeOptions = ref("Car");
const positions = reactive(featureCollection([]));
const route = reactive(featureCollection([]));
let unitOfMeasurementOptions = ref("metric");
const units = {
metric: {
route: { distance: "Kilometers" },
truck: { dimension: "Meters", weight: "Kilograms" },
},
imperial: {
route: { distance: "Miles" },
truck: { dimension: "Feet", weight: "Pounds" },
},
};
const avoidOptions = ref([]);
let departureTimeOptions = ref("default");
let timestamp = ref(Date.now());
let truckHeight = ref(null);
let truckWidth = ref(null);
let truckLength = ref(null);
let truckWeight = ref(null);
...
</script>
Afterwards, add an event handler to capture clicks on the map and store them in positions
as GeoJSON.
<script setup>
...
const initializeApp = async () => {
...
let counter = 0;
map.on("click", async (e) => {
counter++;
const { lngLat } = e;
const p = point([lngLat.lng.toFixed(5), lngLat.lat.toFixed(5)], {
title: `Point ${counter}`,
});
positions.features.push(p);
});
}
...
</script>
Next, add a computed property to build the request body for the API call.
<script setup>
...
const requestParams = computed(() => {
const params = {
CalculatorName: routeCalculatorName,
TravelMode: travelModeOptions.value,
DistanceUnit: units[unitOfMeasurementOptions.value]["route"]["distance"],
IncludeLegGeometry: true,
};
// Positions (DeparturePosition, WaypointPositions, and DestinationPosition)
if (positions.features.length > 1) {
params.DeparturePosition = positions.features[0].geometry.coordinates;
params.DestinationPosition =
positions.features[positions.features.length - 1].geometry.coordinates;
params.WaypointPositions = positions.features
.slice(1, positions.features.length - 1)
.map((feature) => feature.geometry.coordinates);
}
// DepartureTimeOptions
if (departureTimeOptions.value === "default") {
delete params.DepartNow;
delete params.DepartureTime;
}
if (departureTimeOptions.value === "now") {
delete params.DepartureTime;
params.DepartNow = true;
}
if (departureTimeOptions.value === "custom") {
delete params.DepartNow;
params.DepartureTime = new Date(timestamp.value);
}
// CarModeOptions
if (travelModeOptions.value === "Car") {
params.CarModeOptions = {
...params.CarModeOptions,
AvoidFerries:
avoidOptions.value.findIndex((option) => option === "AvoidFerries") >
-1,
};
params.CarModeOptions = {
...params.CarModeOptions,
AvoidTolls:
avoidOptions.value.findIndex((option) => option === "AvoidTolls") > -1,
};
}
// TruckModeOptions
if (travelModeOptions.value === "Truck") {
params.TruckModeOptions = {
...params.TruckModeOptions,
AvoidFerries:
avoidOptions.value.findIndex((option) => option === "AvoidFerries") >
-1,
};
params.TruckModeOptions = {
...params.TruckModeOptions,
AvoidTolls:
avoidOptions.value.findIndex((option) => option === "AvoidTolls") > -1,
};
if (
truckHeight.value != null ||
truckLength.value != null ||
truckWidth.value != null
) {
params.TruckModeOptions.Dimensions = {
Height: truckHeight.value,
Length: truckLength.value,
Width: truckWidth.value,
Unit: units[unitOfMeasurementOptions.value]["truck"]["dimension"],
};
}
if (truckWeight.value != null) {
params.TruckModeOptions.Weight = {
Total: truckWeight.value,
Unit: units[unitOfMeasurementOptions.value]["truck"]["weight"],
};
}
}
return params;
});
...
</script>
Then, add a function to initiate an Amazon Location Service’s client, call the API, and store the results in route
as GeoJSON.
<script setup>
...
const calculateRoute = async () => {
const client = new LocationClient({
credentials: credentials,
region: region,
});
if (
requestParams.value.DeparturePosition &&
requestParams.value.DestinationPosition
) {
const command = new CalculateRouteCommand(requestParams.value);
const response = await client.send(command);
const routeFeature = lineString(
response.Legs.flatMap((leg) => leg.Geometry.LineString),
response.Summary
);
route.features.length = 0;
route.features.push(routeFeature);
}
};
...
</script>
Afterwards, add three map layers to display positions, their label, and calculated route on the map.
<script setup>
...
const initializeApp = async () => {
...
map.on("load", () => {
// Add a layer for rendering departure, waypoint, and destination positions on the map
map.addLayer({
id: "positions",
type: "circle",
source: { type: "geojson", data: positions },
paint: {
"circle-radius": 5,
"circle-color": "#ffffff",
"circle-stroke-color": "#00b0ff",
"circle-stroke-width": 3,
},
});
// Add a layer for rendering position labels on the map
map.addLayer({
id: "positions-label",
type: "symbol",
source: { type: "geojson", data: positions },
layout: {
"text-field": ["get", "title"],
"text-variable-anchor": ["left"],
"text-radial-offset": 0.5,
"text-justify": "auto",
"text-font": ["Noto Sans Regular"],
},
});
// Add a layer for rendering routes on the map
map.addLayer(
{
id: "route",
type: "line",
source: { type: "geojson", data: route },
layout: {
"line-join": "round",
"line-cap": "round",
},
paint: {
"line-color": "#00b0ff",
"line-width": 5,
"line-opacity": 0.7,
},
},
"positions"
);
});
}
...
</script>
Next, add watchers to keep track of state changes, and re-calculate the route and re-render the map layers in reaction to changes.
<script setup>
...
// Route
watch(route, (value) => {
map.getSource("route").setData(value);
});
watch(travelModeOptions, async () => {
await calculateRoute();
});
watch(positions, async (value) => {
map.getSource("positions").setData(value);
map.getSource("positions-label").setData(value);
await calculateRoute();
});
watch(unitOfMeasurementOptions, async () => {
await calculateRoute();
});
watch(departureTimeOptions, async () => {
await calculateRoute();
});
watch(timestamp, async () => {
await calculateRoute();
});
watch(avoidOptions, async () => {
await calculateRoute();
});
watch(truckHeight, async (value) => {
if (value === "") {
truckHeight.value = null;
}
await calculateRoute();
});
watch(truckWidth, async (value) => {
if (value === "") {
truckWidth.value = null;
}
await calculateRoute();
});
watch(truckLength, async (value) => {
if (value === "") {
truckLength.value = null;
}
await calculateRoute();
});
watch(truckWeight, async (value) => {
if (value === "") {
truckWeight.value = null;
}
await calculateRoute();
});
...
</script>
Finally, add some CSS to style the route calculator.
<style>
...
.n-card {
max-width: 400px;
margin: 10px 0 0 10px;
box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.08),
0 3px 6px 0 rgba(0, 0, 0, 0.06), 0 5px 12px 4px rgba(0, 0, 0, 0.04);
}
</style>
Now, if you save your project and go to your browser, you will see the final result.
Wrap up
In this tutorial, you learned how you can build an online route planner that finds optimal routes between an origin, a destination, and up to 23 stops along the way (waypoints) for different modes of transportation, avoidances, and departure times. You can use this project as a starting point for building apps with maps and routing functionality and customize it to fit your purpose. Here are a few things you can do:
- Include a number of different map styles using a layer control — see this post for an example.
- Include a location search widget with geocoding, reverse-geocoding, and autocomplete support to allow users to search for starting point, waypoints, and destination via addresses or geographic coordinates — see this post for an example.
- Customize the look and feel of the route calculator widget to match your brand and satisfy your use case.
I will end this post with a few notes:
- Routing coverage — Amazon Location Service offers routing services from different data providers. Which data provider should you use for your project? The answer depends on your area of interest. Different data provides may gather and curate their data differently — for example, they may use a combination of authoritative sources, open data, and telemetry data to build their road network and traffic databases. That is why you may notice that a data provider has a better routing coverage or up-to-date traffic information for a region compared to other providers. To choose a data provider, find out what data providers are available in your region, compare their routing coverage in your area of interest, and choose the one that fits your project’s requirements. For example, visit Esri’s network analysis coverage, HERE’s car routing coverage and HERE's truck routing coverage to get more information about Esri’s and HERE’s coverage in your region.
- Routing with Esri — If Esri is the data provider for your route calculator, the travel distance cannot be greater than 400km. If you specify
Walking
for the travel mode, the departure and destination positions must be within 40km. - Traffic-aware routing — Amazon Location Service takes traffic into account when calculating a route based on the departure time that you specify. You can specify to depart now, or you can provide a specific time that you want to leave, which will affect the route result by adjusting for traffic at the specified time. If you do not provide any value, it will use the best time of day to travel with the best traffic conditions to calculate the route.
- Map matching — If you specify a position (departure, waypoint, or destination) that is not located on a road, Amazon Location Service will snap it to the nearest road.
- Arrival time — The routing API response includes travel time, reported as
DurationSeconds
. You can use travel time and departure time to calculate arrival time. - Asset management and tracking use cases — If your application is tracking or routing assets that you use in your business, such as delivery vehicles or employees, you may only use HERE as your data provider. See section 82 of the AWS service terms.
The code for this project is on GitHub. I’d love to hear your feedback, so please reach out to me if you have any questions or comments.
Top comments (2)
Great article @mepa1363 !
I'm so new to the AWS environment that I didn't know about the AWS CLI 😅. When I followed the Quick start with Amazon Location Service I spent more time than I would have liked using the UIs. Just for that alone, the article was worth reading! ❤️.
Thanks Raul. Glad to hear it. I'm in the same boat; I'd like to do everything in my terminal and never open the console 😁