Calculating the Shortest Route With a HERE API and Vue.js
Why use a third-party web app to calculate the shortest route for Google Maps when you can just build it yourself?
Join the DZone community and get the full member experience.
Join For FreeWhen it comes to routing, knowing how to get from point A to point B in the most efficient manner isn't exactly the only use-case. When it comes to modern applications or use-cases, a lot more can be and should be accomplished.
Let's take the scenario of a package delivery service. When it comes to a delivery driver, they fill up their truck at the warehouse and have to navigate to every address that exists on a package in their truck before returning the truck to the warehouse. The driver can't just pick random packages from the truck and deliver them randomly because that would be very inefficient. Instead, each delivery should be carefully planned to optimize how many packages can be delivered and the quickest.
So given X number of random addresses, how do you plan your route so that efficiency is the priority?
This is where the HERE Waypoint Sequence Extension API comes into play. Give it a series of positions and it will return the order that those positions should be navigated to. Then you can take that ordered sequence and calculate the actual route between them.
We're going to explore the Waypoint Sequence API in this tutorial using the Vue.js JavaScript framework.
To get an idea of what we want to accomplish, take the following animated image:
What you can't see in the above image is that I provided a bunch of random positions. After receiving an ordered sequence from the Waypoint Sequence API, numbers are attached to each marker to represent the order of access. Then a route is calculated and drawn between the markers.
This was all done using Leaflet.js with Vue.js and a fancy polyline animation library.
Create a New Vue.js Project With the Vue CLI
Because this will be a Vue.js example, a new project needs to be created with the Vue CLI. Assuming the CLI has been installed, execute the following:
vue create wse-project
We're not in the clear yet. Because we'll be making HTTP requests to the HERE APIs, we'll need to install a particular package to get the job done. From the CLI, execute the following:
npm install axios --save
We'll be using the axios library to make HTTP requests. There are plenty of other ways to do this, but axios makes it very clean. If you'd like to learn about other ways to make HTTP requests with JavaScript, check out my previous tutorial titled, Execute HTTP Requests in JavaScript Applications.
No further NPM modules are necessary, but we will be needing a few browser dependencies. Open the project's public/index.html file and include the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>WSE Project</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" />
</head>
<body>
<noscript>
<strong>We're sorry but the app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet.polyline.snakeanim@0.2.0/L.Polyline.SnakeAnim.min.js"></script>
</body>
</html>
You'll notice a few things in the above HTML. We're adding both the Leaflet.js CSS and JavaScript dependencies as well as the snake animation dependency to give us a nice polyline animation.
At this point in time we can start developing our component.
Design a Leaflet.js Component for Maps and Location Functionality
To keep our code clean and reusable, we're going to create a separate Vue component for everything related to maps and location. Within the project's src/components directory create a LeafletMap.vue file with the following boilerplate code:
<template>
<div>
<div ref="map" style="width: 100vw; height: 100vh;"></div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "LeafletMap",
data() {
return {
platform: {},
map: {},
sequenceMarkerGroup: {}
}
},
props: {
appId: String,
appCode: String,
latitude: String,
longitude: String
},
created() { },
async mounted() { },
methods: {
dropMarker(position, text) { },
async calculateRouteSequence(places) { },
drawRoute(sequence) { }
}
}
</script>
<style></style>
You'll notice in the above code that we have several properties, several methods, and a few life-cycle events that are a part of Vue. The Props
will represent properties passed when the component is created, the mounted
method will load the map when the application starts, and each of the methods will help us with our Waypoint Sequence and drawing.
A lot of the above code was explored in other tutorials that I've written on the subject of Leaflet.js.
Let's first draw our map. Within the mounted
method, include the following:
async mounted() {
const tiles = "https://1.base.maps.api.here.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/512/png8?app_id={appId}&app_code={appCode}";
this.map = new L.Map(this.$refs.map, {
center: [this.latitude, this.longitude],
zoom: 10,
layers: [L.tileLayer(tiles, { appId: this.appId, appCode: this.appCode })]
});
},
To use Leaflet, we need to supply it map tiles. Using the HERE Map Tile API and a valid app id and app code from the HERE Developer Portal, we can configure our map.
Make note that the this.$refs.map
parameter references the ref
attribute found within the <template>
block.
To use this component, open the project's src/App.vue file and include the following:
<template>
<div id="app">
<LeafletMap
ref="map"
appId="APP_ID_HERE"
appCode="APP_CODE_HERE"
latitude="37.7297"
longitude="-121.4252"
/>
</div>
</template>
<script>
import LeafletMap from './components/LeafletMap.vue'
export default {
name: 'app',
components: {
LeafletMap
},
async mounted() {
let map = this.$refs.map;
}
}
</script>
<style>
body {
margin: 0;
}
</style>
In the above code we are importing the LeafletMap
class and including it with the necessary properties for rendering. We should be up to speed now when it comes to the Leaflet.js foundation.
Calculate the Waypoint Sequence With HERE and HTTP Requests
The focus of this tutorial is around the HERE Waypoint Sequence API and calculating the sequence of waypoints for a optimal route. Before we start playing around with the API, let's define a bunch of positions to be used.
Within the src/App.vue file, add the following to the mounted
function:
let mockPlaces = [
{ latitude: 37.74682893940135, longitude: -121.4198684692383 },
{ latitude: 37.73488314788311, longitude: -121.44561767578126 },
{ latitude: 37.72076290898376, longitude: -121.41712188720705 },
{ latitude: 37.74251196215947, longitude: -121.435 },
{ latitude: 37.731793096495316, longitude: -121.41770044283479 },
{ latitude: 37.74, longitude: -121.46 },
{ latitude: 37.72, longitude: -121.455 }
];
The above is just an array of positions in no particular order, and existing somewhere around the center point of my map. Now that we have some data, let's make use of the API.
In the calculateRouteSequence
function of the project's src/components/LeafletMap.vue file, include the following:
async calculateRouteSequence(places) {
let waypoints = {};
for(let i = 0; i < places.length; i++) {
waypoints["destination" + (i + 1)] = `${places[i].latitude},${places[i].longitude}`;
}
return axios({
"method": "GET",
"url": "https://wse.api.here.com/2/findsequence.json",
"params": {
"start": `${this.latitude},${this.longitude}`,
...waypoints,
"end": `${this.latitude},${this.longitude}`,
"mode": "fastest;car;traffic:enabled",
"departure": "now",
"app_id": this.appId,
"app_code": this.appCode
}
}).then(response => {
return response.data.results[0].waypoints
});
},
The places
parameter represents the mockPlaces
array that we had just created. We need to properly format that data in a way that the API expects, so we loop through the array and format it more like the following:
waypoints: {
"destination0": "...",
"destination1": "...",
"destination2": "...",
}
Once we have all of our properly formatted waypoints in no particular order, we can make a request with the axios library. The start and end positions can represent the warehouse that we're starting at and the warehouse that we need to return to after all of our packages are delivered.
The response to our request will be our ordered sequence of waypoints.
To use the calculateRouteSequence
function, go to the project's src/App.vue file and include the following in the mounted
function:
let sequence = await map.calculateRouteSequence(mockPlaces);
Having the sequence as raw data isn't particularly exciting. Instead we can draw markers at each point in the sequence and give them a number. This way we can see which marker represents which stop in our path.
Within the project's src/components/LeafletMap.vue file, add the following to the dropMarker
method:
dropMarker(position, text) {
let icon = L.divIcon({
html: `
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">
<circle cx="25" cy="25" r="25" fill="#000000" />
<text x="50%" y="50%" text-anchor="middle" fill="white" font-size="25px" dy=".3em">${text}</text>
</svg>
`.trim()
});
L.marker([position.lat, position.lng], { icon: icon }).addTo(this.sequenceMarkerGroup);
this.sequenceMarkerGroup.addTo(this.map);
this.map.fitBounds(this.sequenceMarkerGroup.getBounds());
},
Leaflet doesn't have a good way to add text to markers unless you're using an SVG marker. For that reason, we can take a position and some text, whip up a dynamic SVG, then add it to the map.
It is a good idea to use a marker group so the markers can be centered on. You can initialize your marker group in the created
method:
created() {
this.sequenceMarkerGroup = new L.featureGroup();
},
To drop these markers, we need to revisit the project's src/App.vue file:
for(let i = 0; i < sequence.length - 1; i++) {
map.dropMarker(sequence[i], i.toString());
}
Because the starting point and the ending point are the same, we're not looping until the very last point, otherwise, our starting marker would be replaced with our ending marker. Not a huge problem, but it can be confusing to look at.
Now that we have the best sequence discovered, we can work towards actually navigating between these waypoints.
Calculating the Path Between Waypoints With the HERE Routing API
To calculate a route that can be navigated, the standard HERE Routing API can be used.
In the project's src/components/LeafletMap.vue file, add the following to the drawRoute
function:
drawRoute(sequence) {
let waypoints = {};
for(let i = 0; i < sequence.length; i++) {
waypoints["waypoint" + i] = `${sequence[i].lat},${sequence[i].lng}`;
}
axios({
"method": "GET",
"url": "https://route.api.here.com/routing/7.2/calculateroute.json",
"params": {
"mode": "fastest;car;traffic:enabled",
"representation": "display",
...waypoints,
"app_id": this.appId,
"app_code": this.appCode
}
}).then(result => {
let shape = result.data.response.route[0].shape;
let line = shape.map(point => {
let [lat, lng] = point.split(",");
return { lat: lat, lng: lng };
});
new L.Polyline(line, {snakingSpeed: 500}).addTo(this.map).snakeIn();
}, error => {
console.error(error);
});
}
What's happening in the drawRoute
function isn't too much different than what's happening in the calculateRouteSequence
function. First the sequence data is formatted and it is added to the axios request to the API.
The results of the route calculation will consist of points which can be added to a polyline and rendered on the map. We're using the snake animation library here so that way a nice animation happens for our long route between start and finish.
To make use of this drawRoute
function, we can add it to the project's src/App.vue file like so:
map.drawRoute(sequence);
Just like that, we have a best-calculated route for our driver or whoever might need an optimized route between waypoints.
Conclusion
The Waypoint Sequence Extension API that HERE offers is very powerful and is a great candidate for solving the "traveling salesman" problem that a lot of organizations face. While we didn't need to use Leaflet, I felt the animation library for polylines gave a great visualization to what we were trying to solve.
Further Reading
Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments