Windows Azure Mobile Services is a great set of services which provide a cloud backend for your mobile applications. It provides native SDKs for Windows Store apps, Windows Phone, iOS and Android as well as HTML5. Those SDKs support amongst many other things push notifications, authentication via Microsoft, Facebook, Google or Twitter accounts as well as schedulers combined with the on-demand scaling that you expect from a cloud platform. Getting started is free and described in excellent tutorials for each of the supported platforms.
A while ago I blogged about using Windows Azure Mobile Services along with Bing Maps in a Windows Store app that supports live tiles as well as toast notifications. Another example that combines the two services was published by Nick Harris on the Windows Dev Center.
In this sample, we will build a Windows Store App that provides tile and toast notifications to inform you in regular intervals about the delay on your typical route to work. To build this application we use Windows Azure Mobile Services for the backend – including the job scheduler for periodic notifications – and Bing Maps to calculate the route, get the current traffic situation and generate maps for the tile and toast notifications.
Prerequisites
Since we are developing a Windows Store App, we require access to a Windows 8 machine as well as Visual Studio 2012. A free version of Visual Studio Express 2012 for Windows 8 is available here.
To access Windows Azure Mobile Services, we require a Windows Azure subscription. If you don’t have one yet, you can sign up for a free trial.
The “Bing Maps SDK or Windows Store Apps” can be installed directly from Visual Studio by selecting “Extensions and Updates” from the menu “Tools” and searching in the online gallery.
We will also require a Bing Maps Key. If you don’t have one, you can follow these instructions to get a free trial or basic key.
Getting Started
As mentioned earlier there are excellent tutorials to get started with Windows Azure Services. Rather than repeating those initial steps, I’m only going to point out the ones that help you over the first hurdles:
- Follow the tutorial Get started with Mobile Services for Windows Store apps. This will set up the mobile service, create a Windows Azure SQL Database and allow you to download a sample Windows Store app. For this example we chose JavaScript as language. The application will be pre-configured with the application key and Mobile Service URL.
- Next, follow the tutorial Get started with authentication in Mobile Services in JavaScript. I used Microsoft as the authentication provider, but as mentioned before, you can also choose Facebook, Google or Twitter.
- Finally, follow the tutorial Get started with push notifications in Mobile Services; again for JavaScript.
By now we have a sample application implementing a simple Todo list powered by Windows Azure Mobile Services.
Creating a New Table in Windows Azure Mobile Services
In the Windows Azure Management Portal we select the tab “Data” under our Mobile Service and create a new table RouteItem. We set the permissions to “Only Authenticated Users”.
One of the beauties of Windows Azure Mobile Services is that we don’t have to declare columns and data types at this point. By default, Mobile Services use a “Dynamic Schema”. We just define the columns in our code and Mobile Services handles the rest for us and adds new columns with the appropriate data types in the backend.
Modifying the Windows Store App
In the application, we want to be able to calculate a route and define it as the default for our way to work. We will store the information with start-point and end-point as well as the default time – assuming that there were no traffic – in Windows Azure Mobile Services. This will be the baseline against which Mobile Services periodically compares the traffic situation.
We start by adding a reference to Bing Maps for JavaScript to the project.
Now we open the default.html and make a few modifications.
We add the references for Bing Maps to the head of the document:
<!-- Bing Maps references --> <scriptsrc="/Bing.Maps.JavaScript/js/veapicore.js"></script><scriptsrc="/Bing.Maps.JavaScript/js/veapiModules.js"></script>
Then we modify the body such that we have div-elements that will host the map and another div-element where we will render the driving directions. We also add a few textboxes and buttons:
<divstyle="height: 100% ; " ><divstyle="display: -ms-grid; margin:30px; -ms-grid-columns: 1fr 1fr; -ms-grid-rows: auto 1fr; height: 100%"><divstyle="-ms-grid-column-span: 2;"><divstyle="color: #0094ff; font-size: 8pt; margin:0px 0px 0px 3px"> BING MAPS & WINDOWS AZURE MOBILE SERVICES</div><divstyle="color: #808080; font-size: 32pt">DonkeyRoutes</div></div><divstyle="-ms-grid-row: 2; -ms-grid-column: 1; -ms-grid-rows:auto auto auto; margin: 0px 0px 40px 3px;"><divstyle="display: -ms-grid; -ms-grid-row:1; -ms-grid-columns: 50px 1fr; -ms-grid-rows: auto auto; margin-right:4px;" ><divstyle="-ms-grid-column:1; -ms-grid-row-align:center"><astyle="color: #808080;">Start</a></div><inputstyle="-ms-grid-column:2; width:100%;"type="text"id="txtStart" /><divstyle="-ms-grid-column:1; -ms-grid-row:2; -ms-grid-row-align:center;"><astyle="color: #808080;">End</a></div><inputstyle="-ms-grid-column:2; -ms-grid-row:2; width:100%;"type="text"id="txtEnd" /></div><divstyle="display: -ms-grid; -ms-grid-row:2; -ms-grid-columns: 1fr 1fr 1fr 1fr; "><buttonstyle="-ms-grid-column:1; margin-right:5px"id="btnShowTraffic"> Show Traffic</button><buttonstyle="-ms-grid-column:2; margin-right:5px"id="btnHideTraffic"> Hide Traffic</button><buttonstyle="-ms-grid-column:3; margin-right:5px"id="btnGetRoute"> Get Route</button><buttonstyle="-ms-grid-column:4;"id="btnSaveRoute"> Save Route</button></div><divid="divMap"style="-ms-grid-row:3; position:relative; top:5px; margin-bottom:100px; width:100%; height:82.6%; left: 0px;"></div></div><divid="divDirections"style="margin:0px 0px 40px 10px; overflow-y:auto; -ms-grid-column: 2; -ms-grid-row: 2; "></div></div></div>
Tip: A good way to get a visual reference is to edit the default.html in Blend for Visual Studio.
Now we move on to the JavaScript. One of the beauties of the Bing Maps JavaScript Control is that it’s mostly identical with the Bing Maps AJAX Control for the web. That means we can play around with the Interactive SDK and we can use the same build-in modules that extend core-functionality to render traffic information or driving-directions, for example. Additionally, the community has picked up this modular concept and developed modules which extend the AJAX control and can also be used within a Windows Store app.
Now let’s look at the JavaScript code and start with the event app.onactivated. We leave the first part that handles the authentication and acquisition of the notification channel as is, remove the code that handles select, inserts or updates in the table todoitem and implement new code that will be inserting data into the Mobile Services table RouteItem, which we just created. We also create event-handlers for the various buttons.
app.onactivated = function (args) {if (args.detail.kind === activation.ActivationKind.launch) {var routeTable = client.getTable('RouteItem');var insertRouteItem = function (routeItem) { routeTable.insert(routeItem).done(function (item) {var md = new Windows.UI.Popups.MessageDialog('Your route is now saved.'); md.showAsync(); }); };btnGetRoute.addEventListener("click", createDirections);
btnSaveRoute.addEventListener("click", function () { insertRouteItem({ StartLat: startLat, StartLon: startLon, EndLat: endLat, EndLon: endLon, TimeWithoutTraffic: travelTime, channel: channel.uri }); } );btnShowTraffic.addEventListener("click", showTrafficLayer); btnHideTraffic.addEventListener("click", hideTrafficLayer);authenticate(); } };
Those were all the modifications required for the Mobile Services on the client-side. Now we move on to the map implementation.
We declare a few global variables:
var map = null;var directionsManager;var directionsErrorEventObj;var directionsUpdatedEventObj;var trafficLayer;var startLat = 0;var startLon = 0;var endLat = 0;var endLon = 0;var travelTime = 0;
Under the line app.start() we kick-off the loading of the map by adding an event-handler that fires when the entire DOM content is loaded. In this case, we execute a function initialize.
document.addEventListener("DOMContentLoaded", initialize, false);
In between the lines app.start() and this event handler, we add the map-specific code. We start with a function that loads the map-module with a callback-function initMap and optional parameters that can be useful for localization.
function initialize() { Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: initMap, culture: 'en-US', homeRegion: 'US' });
This callback function initMap defines some options for the map such as credentials. It then loads another module with a specific “theme” that determines the look and feel of navigation controls before it renders the map and moves on to load yet another module which handles the display of traffic-flow information.
function initMap() {try {var mapOptions = { credentials: "Your Bing Maps Key", mapTypeId: Microsoft.Maps.MapTypeId.collinsBart, enableClickableLogo: false, enableSearchLogo: false, theme: new Microsoft.Maps.Themes.BingTheme() }; Microsoft.Maps.loadModule('Microsoft.Maps.Themes.BingTheme', { callback: function () { map = new Microsoft.Maps.Map(document.getElementById("divMap"), mapOptions); } }); loadTrafficModule(); }catch (e) {var md = new Windows.UI.Popups.MessageDialog(e.message); md.showAsync(); } }function loadTrafficModule() { Microsoft.Maps.loadModule('Microsoft.Maps.Traffic', { callback: trafficModuleLoaded }); }
Once the traffic module is loaded, we set the center and zoom of the map and load the real-time traffic flow information into a new layer.
function trafficModuleLoaded() { setMapView(); }
function setMapView() { map.setView({ zoom: 10, center: new Microsoft.Maps.Location(47.603561, -122.329437) }) showTrafficLayer(); }function showTrafficLayer() { trafficLayer = new Microsoft.Maps.Traffic.TrafficLayer(map); trafficLayer.show(); }function hideTrafficLayer() { trafficLayer.hide(); }
Let’s see what we’e got so far. When we run the application, we should be able to authenticate as before, see a map and be able to toggle the traffic layer.
However, we haven’t implemented the driving directions yet. So let’s move on to that. We start by creating the function that handles the click-event for the button “Get Route”. This function will load the directions-module with a callback-function that requests the route based on the start- and end-locations that we entered in the text-boxes.
function createDirections() {if (!directionsManager) { Microsoft.Maps.loadModule('Microsoft.Maps.Directions', { callback: createDrivingRoute }); }else { createDrivingRoute(); } }function createDrivingRoute() {if (!directionsManager) { createDirectionsManager(); } directionsManager.resetDirections(); // Set Route Mode to driving directionsManager.setRequestOptions({ routeMode: Microsoft.Maps.Directions.RouteMode.driving });var startWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('txtStart').value }); directionsManager.addWaypoint(startWaypoint);var endWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('txtEnd').value }); directionsManager.addWaypoint(endWaypoint);// Set the element in which the itinerary will be rendered directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('divDirections') }); directionsManager.calculateDirections(); }function createDirectionsManager() { var displayMessage;if (!directionsManager) { directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map); } directionsManager.resetDirections(); directionsUpdatedEventObj = Microsoft.Maps.Events.addHandler( directionsManager, 'directionsUpdated', getWayPoints); }
The directions-manager (part of the directions-module) will take care of setting the map view and rendering the itinerary, but we require some post-processing so that we can submit the data to the Mobile Services and that’s what we do in the callback-function getWayPoints. We basically retrieve some coordinates and the travel-time from the JSON-response.
function getWayPoints(e) {
startLat = e.route[0].routeLegs[0].startWaypointLocation.latitude;
startLon = e.route[0].routeLegs[0].startWaypointLocation.longitude;
endLat = e.route[0].routeLegs[0].endWaypointLocation.latitude;
endLon = e.route[0].routeLegs[0].endWaypointLocation.longitude;
travelTime = e.route[0].routeLegs[0].summary.time;
}
This is it for the client-side. We can test the calculation of driving directions but before we can fully test the submission of the data to the Mobile Services, we will look at the server-side as well.
Inserting Data and Responding with Notification from Mobile Services
On the Mobile Services side, we want to respond to insert operations with a notification that includes a static image with the route and the current traffic-flow as well as the drive-time considering the actual traffic situation.
To do that, we navigate in the Windows Azure Management Portal to our table, open the tabulator “Script” and select the operation “Insert” from the dropdown-box.
Then we replace the existing script with the following. In this server-side JavaScript, we call the Bing Maps REST Route service to calculate the drive-time considering the current traffic situation and compare it to the drive-time without traffic in order to determine the possible delay. Then we send a push-notification back to the client using the Windows Notification Service. We update the tile with an image generated from the Bing Maps REST Imagery service as well as some text including the delay.
function insert(item, user, request) { item.userId = user.userId;
var httpRequest = require('request');var currentDriveTime = null;var delay = null;var addToNote = null;var uri = 'http://dev.virtualearth.net/REST/V1/Routes/Driving?' +'wp.0=' + item.StartLat + ',' + item.StartLon +'&wp.1=' + item.EndLat + ',' + item.EndLon +'&optmz=timeWithTraffic' +'&key=Your Bing Maps Key'; httpRequest(uri, function (err, response, body) {if (err) { console.log(statusCodes.INTERNAL_SERVER_ERROR, 'Unable to connect to Bing Maps.'); } else { currentDriveTime = JSON.parse(body).resourceSets[0].resources[0].travelDuration; delay = Math.round((currentDriveTime - item.TimeWithoutTraffic) / 60);if (delay > 10) { addToNote = 'Have another coffee'; }else { addToNote = 'Good time to go to work'; } } });request.execute({ success: function () { // Write to the response and then send the notification in the background request.respond();push.wns.sendTileWidePeekImageAndText02(item.channel, { image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/Routes?' +'wp.0=' + item.StartLat + ',' + item.StartLon +'&wp.1=' + item.EndLat + ',' + item.EndLon +'&ml=TrafficFlow' +'&ms=310,150&key=Your Bing Maps Key', image1alt: 'Your Route', text1: 'Current Delay:', text2: delay + ' min', text3: addToNote }, { success: function (pushResponse) { console.log("Sent push:", pushResponse); } }); } }); }
Save the script, run the Windows Store app again and this time calculate the route and insert the item to Mobile Services. You should see an updated tile for your application.
In the Windows Azure Management Portal you should also see the data that you just inserted.
Scheduling Periodic Updates
We’re on the final stretch. The last remaining work item in this sample is the periodic notification and for that, we use the job scheduler in Mobile Services. In the Windows Azure Management Portal, go to your Mobile Service and create a new scheduler job.
We can set up periodic intervals and alternatively run the job on-demand for our testing purposes. In the script that holds the logic for our scheduled jobs, we enter the following code. This will read the table with registered clients and follow the same procedure as we defined for the insert operation with the main difference being that we now send a tile and a toast notification to each client.
function sendUpdate() {var routeItems = tables.getTable('RouteItem');routeItems.read({ success: function (routes) { routes.forEach(function (route) {var httpRequest = require('request');var currentDriveTime;var delay;var addToNote;var uri = 'http://dev.virtualearth.net/REST/V1/Routes/Driving?' +'wp.0=' + route.StartLat + ',' + route.StartLon +'&wp.1=' + route.EndLat + ',' + route.EndLon +'&optmz=timeWithTraffic' +'&key=Your Bing Maps Key'; httpRequest(uri, function (err, response, body) {if (err) { console.log(statusCodes.INTERNAL_SERVER_ERROR, 'Unable to connect to Bing Maps.'); } else { currentDriveTime = JSON.parse(body).resourceSets[0].resources[0].travelDuration; delay = Math.round((currentDriveTime - route.TimeWithoutTraffic) / 60);if (delay > 10) { addToNote = 'Have another coffee'; }else { addToNote = 'Good time to go to work'; }push.wns.sendTileWidePeekImageAndText02(route.channel, { image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/Routes?' +'wp.0=' + route.StartLat + ',' + route.StartLon +'&wp.1=' + route.EndLat + ',' + route.EndLon +'&ml=TrafficFlow' + '&ms=310,150&key=Bing Maps Key', image1alt: 'Your Route', text1: 'Current Delay:', text2: delay + ' min', text3: addToNote }, { success: function (pushResponse) { console.log("Sent push:", pushResponse); } });push.wns.sendToastImageAndText04(route.channel, { image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/Routes?' +'wp.0=' + route.StartLat + ',' + route.StartLon +'&wp.1=' + route.EndLat + ',' + route.EndLon +'&ml=TrafficFlow' +'&ms=150,150&key=Bing Maps Key', image1alt: 'Your Route', text1: 'Current Delay:', text2: delay + ' min', text3: addToNote }, { success: function (pushResponse) { console.log("Sent push:", pushResponse); } }); } }); }); } }); }
And this completes our example.
Happy Mapping ☺