Introduction
Both Google and Bing maps are incredibly useful tools. The APIs they have opened to developers offer an amazing ability to add value to the solutions we provide. We don't have to use ALL OF THE THINGS, sometimes just a snippet here and there can make a big difference to our users. This article shows how I used some of the Google Map APIs to help office staff work out how to best route mobile staff during their working day.
Background
When you have a team of staff on the road, working from cars and vans, they generally fit into one of two categories. Those that are heavily pre-planned (say commercial transport delivery vehicles), and those that have an aspect of planning but need to adjust during the day as needs change demands require. Its this last one we are interested in for this article. Take for example plumbers - sure, they have planned jobs during the day, however, in a big city and a busy service company, there may be multiple emergencies, burst pipes, water leaks etc during the day that need to be prioritized and transport routes carefully, but quickly planned out. One of the methods used to decide where to send personnel is a triage system that asks three questions:
- how important/urgent is the task?
- how close is the location of the job to one of our mobile staff?
- what's the soonest one of our mobile staff can get from where they are, to the new location?
This understandably can get very complex very quickly, but by combining different technologies such as GPS/Geo-location on mobile phones to monitor the current location of remote workers, with the co-ordinates of the different places you potentially need them to go, you can put some very useful solutions together for your users.
The code I am going to describe, allows the user to input the current location of a remote worker, and a list of different 'emergency call out' destinations. Using Google maps, we will be able to display to the user the distance the worker is from each location, and the time it will take them to get to each. Using this knowledge, they can make better decisions about what call-out job to prioritize with what remote worker.
In the production version of this sample code, I combined numerous additional pieces of information to get to the 'your best choice/route is...' algorithm. This comprised:
You can see that using all of the information above, combined with the data we are able to get from a mapping API can allow us to do some very useful and time saving things in a solution.
Before we start into the code, as a quick aside, I discussed using the Google Map API before in an article I wrote describing how to implement custom information window popups and markers for the Google map API in an ASP.net MVC solution. You may find that article useful to give you other ideas for functionality.
Google API Key
In order to get started, you will need to set yourself up with a Google API key. The signup process is very simple, and really, unless you are doing this in large volumes there is no charge. At the time of writing this article you can get 25,000 map loads free per every 24 hours of usage - pretty generous, and if you need more than this, I'm sure someone somewhere is paying, so you can get a commercial license.
Heres the basics of how to get yourself a license key ... note that in my sample code, you MUST replace your own key... I can't for obvious reasons let you have mine :)
Getting a key is really simple ... first go to the Google Map API page, click the 'Get API' button, give a project name, and in a few seconds you are ready to go...
Keep the key safe, we'll use it later on...
Settings things up
I have written the code for the article in ASP.net MVC targeted at .net v4.5 in Visual Studio 2015. To get started, we create a web project, and in this case, I cleaned up the _Layout.cshtml as I didn't want any of the standard 'application login' functionality etc floating at the top of the demo app - this leaves us with something simple like the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - My ASP.NET Application</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
In the index.cshtml page, I have html that allows the user to switch between the two different code demos for this article, with corresponding controllers.
<div class="row">
<div class="col-md-12">
<h2><a href="demo1">Example 1</a> - simple distance between two points</h2>
<p>
This demonstrates getting and displaying the distance between two points
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h2><a href="demo2">Example 2</a> - distance between one origin and multiple target destinations</h2>
<p>
This demonstrates getting and displaying the distance between multiple points
</p>
</div>
</div>
public ActionResult Demo1()
{
return View();
}
public ActionResult Demo2()
{
return View();
}
Distance and route between two points
To understand the basics, lets go to demo 1. Here we will look at setting up a Google map, and using the API to request a route and distance between two points on a map.
The first thing we will do is setup the HTML and layout the page. The page has a map, and two input boxes, allowing the user to enter the location where they are starting, and the location or destination they want to go to. To save typing in testing (ever lazy!), I have defaulted these input values to to locations in the UK.
<div class="row">
<div class="col-md-12">
<h2>Example 1 - simple distance between two points</h2>
<p>
This demonstrates getting and displaying the distance between two points
</p>
<div>
<div>
Travel From : <input id="travelfrom" type="text" name="name" value="Chichester, UK" />
To : <input id="travelto" type="text" name="name" value="Goodwood aerodrome, UK" />
<input type="button" value="Get Route" onclick="GetRoute()" />
</div>
<br />
<div>
<div id="dvDistance">
</div>
</div>
</div>
<div id="dvMap" style="min-height:500px"></div>
</div>
</div>
The key controls above are the inputs 'travelfrom', 'travelto' and the button that calls the 'GetRoute()' function.
The rests of the code is all in the script section. Here is where we start to use that API key we generated earlier:
@section scripts{
<!--ENSURE YOU CHANGE TO YOUR OWN API KEY HERE !!! -->
<script src="https://maps.googleapis.com/maps/api/js?libraries=places&key=YOUR_KEY">
</script>
** Important ** - You Need to Replace 'Your_key' Above, With the Key That You Generate Yourself.
The first thing we do is set up some Javascript variables. These will contain values we use when calling the Map API.
var source, destination;
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
Next up, we take the div element 'dvMap' we specified int he HTML Markup earlier, and tell the Map API to use this as a container in which to draw the map. I am also using the latitude and longitude of Chichester in England to center the map. In production, I have some script that changes this centre view between the main office, the remote worker and the customer location - making it easier for the user to orientate their planning.
var map = new google.maps.Map(document.getElementById('dvMap'), {
center: { lat: 50.834697, lng: -0.773792 },
zoom: 13,
mapTypeId: 'roadmap'
});
The map API is vast, and has many little nuggets that really assist when coding solutions. For example, there is an API you can use to connect an input box with a 'type ahead' location lookup - this makes it easier for the user to find locations. In this next snippet of code, we call the 'Searchbox' method to link our 'travelfrom' and 'travelto' input boxes to this typeahead service. There is also some code to allow the user to move the map around by clicking/dragging with their mouse.
google.maps.event.addDomListener(window, 'load', function () {
new google.maps.places.SearchBox(document.getElementById('travelfrom'));
new google.maps.places.SearchBox(document.getElementById('travelto'));
directionsDisplay = new google.maps.DirectionsRenderer({ 'draggable': true });
});
Example 1 - main code
We have a core function called 'GetRoute()'. This takes as input the 'travelfrom' and 'travelto' locations, sends these to the API, and then draws the result from the API onto the visual map for the user.
The first thing we do is set up request parameters to send into the API. We then call the 'directionsService.route' API method to draw the response for the user ('directionsDisplay.setDirections').
directionsDisplay.setMap(map);
source = document.getElementById("travelfrom").value;
destination = document.getElementById("travelto").value;
var request = {
origin: source,
destination: destination,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function (response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
}
});
Its one thing showing the pretty map to the user, but for our purposes, we also want to get specific details on the route such as the distance between the two points, and the time it would take to travel (in this case, assuming the remote worker was traveling in a car/van). To work this out, we are going to call the 'Distance matrix service'.
Step one sets things up, giving the origin and destination locations, setting the travel mode to DRIVING
, and the measurement system to METRIC
.
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix({
origins: [source],
destinations: [destination],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
Step two handles the response form the API, extracting the values for distance and duration, and displaying them for the user into the html element 'dvDistance'.
}, function (response, status) {
if (status == google.maps.DistanceMatrixStatus.OK &&
response.rows[0].elements[0].status != "ZERO_RESULTS") {
var distance = response.rows[0].elements[0].distance.text;
var duration = response.rows[0].elements[0].duration.value;
var dvDistance = document.getElementById("dvDistance");
duration = parseFloat(duration / 60).toFixed(2);
dvDistance.innerHTML = "";
dvDistance.innerHTML += "Distance: " + distance + "<br />";
dvDistance.innerHTML += "Time:" + duration + " min";
Heres how the output looks once rendered, showing the metrics the user requires.
Distance and route between multiple points
To give the user more flexibility and allow them to plan a block of time and a remote workers travel, we are now going to see how we can use the 'waypoint' API in Google Maps to work out the distance between multiple points, and give a visual representation of this to the user on the map view.
The first thing we are going to do is set up the UI so that the user can enter a series of possible destinations. In my production version, this is all fed automatically from a database of existing customer addresses/unassigned but pending diary appointments.
As you can see laziness strikes again, so for testing I have hard-coded in a starting point (Chichester) and some possible destinations (Tagmere and Bosham).
In our HTML layout, the main difference from the first version is that we add in a few things:
- we have an array of possible destinations in Javascript, so there is a '
PushDestination()
' method to allow us to add destinations to that array
- my laziness gives us a quick way to test some locations with '
setDestination()
'
- the 'destinations' div stores the list of destinations *before* we calculate our distances and routes
- finally, we have a table that displays the results from the API in a nicely formatted manner
<div> Add Destination</div>
<div>
<input id="travelto" type="text" name="name" value="Oving, UK" />
<input type="button" value="Add" onclick="PushDestination()" />
<a href="#" onclick="setDestination('Tagmere, UK')">Tagmere, UK. </a>
<a href="#" onclick="setDestination('Bosham, UK')">Bosham, UK</a>
</div>
<div id="destinations"></div><br />
Source : <input id="travelfrom" type="text" name="name" value="Chichester, UK" />
<input type="button" value="Calculate" onclick="GetRoute()" />
<p></p>
<br />
<div id="dvDistance">
<table id="tblResults" border="1" cellpadding="10">
<tr>
<th> Start </th>
<th> End </th>
<th> Distance </th>
<th> Duration </th>
</tr>
</table>
</div>
<div id="dvMap" style="min-height:500px"></div>
The Javascript builds on the previous code we have, with the same setup as before. The difference here is the addition of a 'locations' array.
var source, destination;
var locations = [];
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map = new google.maps.Map(document.getElementById('dvMap'), {
center: { lat: 50.834697, lng: -0.773792 },
zoom: 13,
mapTypeId: 'roadmap'
});
google.maps.event.addDomListener(window, 'load', function () {
new google.maps.places.SearchBox(document.getElementById('travelfrom'));
new google.maps.places.SearchBox(document.getElementById('travelto'));
directionsDisplay = new google.maps.DirectionsRenderer({ 'draggable': true });
});
Our array of destination locations is managed by the 'PushDestination()
' method. This takes whatever value is in the 'travelto' input control, and adds it to the array. It also updates the screen adding the new item to the div 'destinations'.
function PushDestination() {
destination = document.getElementById("travelto").value;
locations.push(destination);
document.getElementById("travelto").value = "";
destinationArray = document.getElementById("destinations");
destinationArray.innerHTML += destination + "<br />";
}
My helped method adds items to the input box and calls the PushDestination()
method to keep things moving along.
function setDestination(dest)
{
document.getElementById('travelto').value = dest;
PushDestination();
}
The GetRoute()
method starts out the same...
function GetRoute() {
directionsDisplay.setMap(map);
source = document.getElementById("travelfrom").value;
destination = document.getElementById("travelto").value;
but then adds an array of 'Waypoints'.
var waypoints = [];
for (var i = 0; i < locations.length; i++) {
var address = locations[i];
if (address !== "") {
waypoints.push({
location: address,
stopover: true
});
}
}
The request param is tweaked slightly as there is more than one destination involved. We add in our array of waypoints, and tell the API to optimize for waypoints.
var request = {
origin: source,
destination: waypoints[0].location,
waypoints: waypoints,
optimizeWaypoints: true,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
Finally, we send in the request, and parse the waypoint results we get back, displaying the output into the table we have ready in the html. As you can see, the key here is to look into each 'LEG' of the response routes that come back and extract the information we want from there.
directionsService.route(request, function (response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var dvDistance = document.getElementById("dvDistance");
var distance = 0;
var minute = 0.00;
response.routes[0].legs.forEach(function (item, index) {
if (index < response.routes[0].legs.length - 1) {
distance = distance + parseInt(item.distance.text);
minute = parseFloat(minute) + parseFloat(item.duration.value / 60);
tbl = document.getElementById("tblResults");
var row = tbl.insertRow(1);
var cell = row.insertCell(0);
cell.innerText = source;
var cell = row.insertCell(1);
cell.innerText = item.end_address;
var cell = row.insertCell(2);
cell.innerText = distance;
var cell = row.insertCell(3);
cell.innerText = minute.toFixed(2) + " min";
}
});
directionsDisplay.setDirections(response);
}
else {
}
})
And heres the final output - just as we want it.
This is but one way of implementing this kind of solution - I hope you find it useful. Like all of these things, APIs keep changing, so if you get an error, its best to look at the browser console for any feedback from the API!
I attach a sample project for you to play with - don't forget to get your own API key and insert it where needed!
Points of Interest
In a previous article I wrote about using the Google Map API, there was no need for an API key to use the code, indeed, if you try out the code on for example JSFiddle, it seems you don't need a key. Using it outside of such a testing environment however causes the API to throw a browser console error stating you need an API ... so beware, always look to the console when things don't work as expected :)
History
Version 1 - published 5/Feb/2017