Traffic Notifications with Bing Maps & Windows Azure Mobile Services

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 account 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 had 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 2 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.

clip_image002

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:

  1. 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 choose JavaScript as language. The application will be pre-configured with the application key and Mobile Service URL.
  2. 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.
  3. 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.

clip_image003

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.

clip_image002

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- 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 compares periodically the traffic situation.

We start by adding a reference to Bing Maps for JavaScript to the project.

clip_image003[5]

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 -->
<script src="/Bing.Maps.JavaScript/js/veapicore.js"></script>
<script src="/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 one where we will render the driving directions. We also add a few textboxes and buttons:

<div style="height: 100% ; " >
  <div style="display: -ms-grid; margin:30px; -ms-grid-columns: 1fr 1fr; 
       -ms-grid-rows: auto 1fr; height: 100%">
    <div style="-ms-grid-column-span: 2;">
      <div style="color: #0094ff; font-size: 8pt; margin:0px 0px 0px 3px">
        BING MAPS & WINDOWS AZURE MOBILE SERVICES
      </div>
      <div style="color: #808080; font-size: 32pt">DonkeyRoutes</div>
    </div>

    <div style="-ms-grid-row: 2; -ms-grid-column: 1; 
         -ms-grid-rows:auto auto auto; margin: 0px 0px 40px 3px;">
      <div style="display: -ms-grid; -ms-grid-row:1; 
          -ms-grid-columns: 50px 1fr; -ms-grid-rows: auto auto; 
           margin-right:4px;" >
        <div style="-ms-grid-column:1; -ms-grid-row-align:center ">
          <a style="color: #808080;">Start</a>
        </div>
        <input style="-ms-grid-column:2; width:100%;" type="text" id="txtStart" />
        <div style="-ms-grid-column:1; -ms-grid-row:2; -ms-grid-row-align:center;">
          <a style="color: #808080;">End</a>
        </div>
        <input style="-ms-grid-column:2; -ms-grid-row:2; width:100%;" 
               type="text" id="txtEnd" />
      </div>

      <div style="display: -ms-grid; -ms-grid-row:2; 
           -ms-grid-columns: 1fr 1fr 1fr 1fr; " >
        <button style="-ms-grid-column:1; margin-right:5px" id="btnShowTraffic" >
          Show Traffic
        </button>
        <button style="-ms-grid-column:2; margin-right:5px" id="btnHideTraffic" >
          Hide Traffic
        </button>
        <button style="-ms-grid-column:3; margin-right:5px" id="btnGetRoute" >
          Get Route
        </button>
        <button style="-ms-grid-column:4;" id="btnSaveRoute" >
          Save Route
        </button>
      </div>

      <div id="divMap" style="-ms-grid-row:3; position:relative; top:5px; 
           margin-bottom:100px; width:100%; height:82.6%; left: 0px;">
      </div>
    </div>
     <div id="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.

clip_image002[5]

Now we move on to the JavaScript. One of the beauties of the Bing Maps JavaScript Control is, that it is 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 for example to render traffic information or driving-directions. 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.

clip_image004

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 already 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 have 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.

clip_image002[5]

However, we haven’t implemented the driving directions yet. So let’s move on to do 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 which 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 was 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.

clip_image002[7]

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.

clip_image004

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 and 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.

clip_image002[9]

In the Windows Azure Management Portal you should also see the data that you just inserted.

clip_image004[5]

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.

clip_image006

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 then main difference that we send now 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.

clip_image002[11]

Happy Mapping 🙂

Posted in Bing Maps, Mobile Services, Windows Azure, Windows Store | Tagged , , , , | Leave a comment

How to Load Spatial Data from SQLite in a Windows Store App

Sometimes it can be helpful to hold geospatial data locally and to distribute it with the application. In this example, we will use SQLite to store geospatial information and visualize it as a layer in the Bing Maps Control for Windows Store Apps.

SQLite is a software library that implements a self-contained, server-less, zero-configuration, transactional SQL database engine.

Preparing the Data

For this example we will be using trails which we retrieved from the King County GIS Data Portal. This data is available in Esri Shapefile format with coordinates in the North American Datum 1983 (NAD83) system. I converted them into a SQLite database with coordinates described as latitudes and longitudes with decimal degrees in the World Geodetic System 1984 (GS84) using the ogr2ogr command-line tool from Geospatial Data Abstraction Library (GDAL). The command for this conversion is:

ogr2ogr.exe -f sqlite -lco FORMAT=WKT "D:\Downloads\GeoData\King County\trail_SHP\trail.db" "D:\Downloads\GeoData\King County\trail_SHP\trail.shp" -t_srs EPSG:4326

Prerequisites

Since we are developing a Windows Store App, we need 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.

For this project we require the “Bing Maps SDK or Windows Store Apps” as well as “SQLite for Windows Runtime” you can install both from Visual Studio 2012 by selecting “Extensions and Updates” from the menu “Tools” and searching for the respective SDKs in the online gallery.

We will also require a Bing Maps Key. If you don’t have one yet, you can follow the instructions to get a free trial or basic key.

Preparing the Project

Let’s start by creating a new project using the blank Visual C# template for Windows Store Apps.

image

Next we add references to the Bing Maps SDK, the Visual C++ Runtime and SQLite.

image

The Bing Maps SDK requires that we compile separately for each processor architecture. So we need to open the “Configuration Manager” and change the platform from “Any CPU” to a specific one – here “x64”.

image

We also require the “sqlite-net” library and we can add this from NuGet by opening the menu “Tools” => “Library Package Manager” => “Manage NuGet Packages for Solution” and searching for “sqlite-net”.

image

Finally we add the SQLite database trail.db to the folder “Assets” of the project, set the property “Build Action” to “Content” and “Copy to Output Directory” to “Copy if Newer”.

image

Adding the Application Markup

Now that our project is prepared, we open the MainPage.xaml add the namespace for the Bing Maps control and define the user interface. In the user interface, we load Bing Maps centered to a location in King County at zoom-level 13 and specify that we want to display the aerial imagery.

<Page
    x:Class="SQLite_Blog.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SQLite_Blog"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:bm="using:Bing.Maps"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock x:Name="pageTitle" 
                   Text="WinRT and Spatial Data from SQLite" 
                   IsHitTestVisible="false" 
                   Style="{StaticResource PageHeaderTextStyle}" 
                   VerticalAlignment="Center" 
                   Margin="30,0,30,40"/>

        <bm:Map x:Name="myMap" 
                Grid.Row="1" 
                MapType="Aerial" 
                ZoomLevel="13" 
                Credentials="Your_Bing_Maps_Key">
            <bm:Map.Center>
                <bm:Location Latitude="47.702894" Longitude="-122.054860" />
            </bm:Map.Center>
        </bm:Map>

        <TextBlock x:Name="myAttribution" 
                   Text="Data provided by permission of King County" 
                   Grid.Row="2"/>
    </Grid>
</Page>

Adding the Code-Behind

In the code-file MainPage.xaml.cs we define a class that describes the SQLite table.

public class trail
{
  public string OGC_FID { get; set; }
  public string WKT_GEOMETRY { get; set; }
  public string kc_fac_fid { get; set; }
  public string trail_name { get; set; }
  public string trail_type { get; set; }
  public string surf_type { get; set; }
  public string sitefacfid { get; set; }
  public string sitename { get; set; }
  public string sitetype { get; set; }
  public string owner { get; set; }
  public string ownertype { get; set; }
  public string manager { get; set; }
  public string managertype { get; set; }
  public string maintd_by { get; set; }
  public string mainttype { get; set; }
  public string shape_len { get; set; }
}

For the class that will actually read the data and display it on the map, we import three libraries:

using Bing.Maps;
using Windows.Storage;
using Windows.UI.Popups;

The class that reads the trail-database copies it first in the local application directory. It creates a MapShapeLayer, reads through the table-records and adds the records for the trails to the layer before it adds the entire layer to the map.

One point to call out here is that the data is stored as Well Known Text (WKT) in SQLite. The WKT has the coordinates in the order Longitude and then Latitude while Bing Maps expects them in the order Latitude and then Longitude. So we have to swap the order of the coordinates.

public async void GetTrails()
{
  var uri = new Uri("ms-appx:///Assets/trail.db"); 
  var file = await StorageFile.GetFileFromApplicationUriAsync(uri);

  var destinationFolder = ApplicationData.Current.LocalFolder;//local appdata dir 
  try
  {
    await file.CopyAsync(destinationFolder); //copied application local folder}} 
  }
  catch { }

  var dbpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "trail.db");
  var db = new SQLite.SQLiteConnection(dbpath);
  var trails = db.Table<trail>();

  MapShapeLayer shapeLayer = new MapShapeLayer();
  int numLocs = 0;
  int numTrails = 0;

  foreach (trail thisTrail in trails)
  {
    var wkt = thisTrail.WKT_GEOMETRY.Replace("LINESTRING (", "").Replace(")", "");
    string[] wktArray = wkt.Split(',');
    LocationCollection bmPolylineLocs = new LocationCollection();
    for (var i = 0; i < wktArray.Length; i++)
    {
      var loc = wktArray[i];
      var locArray = loc.Split(' ');
      bmPolylineLocs.Add(new Location(Convert.ToDouble(locArray[1]), 
                                      Convert.ToDouble(locArray[0])));
      numLocs = numLocs + 1;
    }

    MapPolyline bmPolyline = new MapPolyline();
    bmPolyline.Locations = bmPolylineLocs;
    bmPolyline.Color = Windows.UI.Colors.Red;
    bmPolyline.Width = 5;
    shapeLayer.Shapes.Add(bmPolyline);
    numTrails = numTrails + 1;
  }

  myMap.ShapeLayers.Add(shapeLayer);

  var myMsg = new MessageDialog("Loaded " + numTrails.ToString() + 
                               " with " + numLocs.ToString() + 
                               " locations");
  await myMsg.ShowAsync();
}

Finally we add a call to this new class right after we initialize the app.

public MainPage()
{
  this.InitializeComponent();

  GetTrails();
}

And that’s it. Below you see a screenshot of the trails in King County on top of Bing Maps. You’ll find the complete source code here.

Happy Coding Smile

image

Posted in Bing Maps, SQLite, Windows Store | Tagged , , , , , , | Leave a comment

Automating the Windows Map App

Windows allows a Windows Store application to register as the default handler for a certain URI schema. This process is called Protocol Activation and it can be leveraged in the WinRT as well as the WinJS framework.

The Windows Map app is registered for protocol activation and the URI schema is documented here. Through protocol activation you can control the map view, find places or business listings, calculate routes or even map entire collections of points of interest.

image

In this quick example we leverage this capability to display the location where you captured a photo. Rather than starting from the scratch we build upon the Simple Image Sample from the Windows Dev Center. The first scenario in this application reads and writes properties of an image – including the coordinates (latitude and longitude) where we captured the photo.

image

There are a few changes that we need to apply to this app in order to launch the Map app and center on this location.

In the application markup language of scenario 1 we

  1. replace the boxes for degrees, minutes and seconds as well as the one for the compass orientation with a single text-box in which we will display the latitude and longitude in decimal degrees.
  2. add a button that will launch he map app with parameters.

The new XAML will look like this:

<TextBlock Grid.Column="0" Grid.Row="6" TextWrapping="Wrap" 
           Style="{StaticResource BasicTextStyle}" 
           HorizontalAlignment="Left" VerticalAlignment="Center">Latitude
</TextBlock>
<TextBox Grid.Column="1" Grid.Row="6" Margin="0,0,10,10" x:Name="LatTextbox" 
         HorizontalAlignment="Left"  Text="" Width="300" />

<TextBlock Grid.Column="0" Grid.Row="7" TextWrapping="Wrap" 
           Style="{StaticResource BasicTextStyle}" 
           HorizontalAlignment="Left" VerticalAlignment="Center">Longitude</TextBlock>
<TextBox Grid.Column="1" Grid.Row="7" Margin="0,0,10,10" x:Name="LonTextbox" 
         HorizontalAlignment="Left" Text="" Width="300" />

<Button x:Name="myPhotoLoc" Grid.Column="1" Grid.Row="8" 
        Click="myPhotoLoc_Click" Width="300" Margin="0,0,10,0" >Open Map</Button>

Next we look at the code behind and in the function GetImagePropertiesForDisplay we replace the code which splits the coordinates in degrees, minutes and seconds with the snippet below.

' Do a simple check if GPS data exists.
If (m_imageProperties.Latitude.HasValue) AndAlso (m_imageProperties.Longitude.HasValue) Then
  LatText = Math.Round(m_imageProperties.Latitude.Value, 6).ToString
  LatTextbox.Text = LatText

  LonText = Math.Round(m_imageProperties.Longitude.Value, 6).ToString
  LonTextbox.Text = LonText
End If

We also introduce a new function to handle the click-event on our button and launch the Map app:

Private Async Sub myPhotoLoc_Click(sender As Object, e As RoutedEventArgs)
  ' Create the URI to launch from a string.
  Dim uri = New Uri("bingmaps:?collection=name.Photo%20Locations~point." + _
    LatText + "_" + LonText + "_My%20Photo&sty=a")

  ' Launch the URI.
  Dim success As Boolean = Await Windows.System.Launcher.LaunchUriAsync(uri)
End Sub

And that’s it. You’ll find the complete source code here.

Happy Mapping Smile

image

Posted in Bing Maps, Windows Store | Tagged , , , | Leave a comment

6 Ways to Visualize Location-based Information from the Windows Azure Marketplace in Excel

Windows Azure is many things. It spans over Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) and provides a variety of components to enable both developers and IT-Pros in the public cloud as well as in a hybrid scenario where you connect securely to on-premise databases, individual machines or entire networks. But Windows Azure also provides a Marketplacewhich allows you to trade applications, services and data. If you have data that you want to sell or share for free, the Windows Azure Marketplace is something you should consider and if you are in need for data for your research or analysis, chances are, that you find them in the Marketplace.

Note:you do not have to have a Windows Azure subscription to access data from the Windows Azure Marketplace. You can sign-up for free and check out some of the free data sets without any cost. All you need is a Microsoft Account (formerly known as Windows Live ID).

image

While it is often a developer who integrates an application with the Windows Azure Marketplace, Information Worker can also use familiar tools such as Excel to access information, pivot them around and visualize them in a variety of ways. In this overview we have a look at some of the options to visualize data from the Windows Azure Marketplace in a location context.

Office Apps

With Office 2013 and the new Office Appsit is now easier than ever to access data from the Windows Azure Marketplace. Bing Maps has been implemented as such an Office App and it snuggles nicely into Outlook and Excel 2013. In Outlook 2013 it will identify address strings and allow you to map the location directly in your email.

image

But back to the topic: to visualize data from the Windows Azure in Excel 2013, start by identifying a data set that you are interested in. For the first example we use a free offering from the European Environment Agency that provides insights into Greenhouse Gas Emissions.

image

Once you subscribed to the offering you can view the data directly in the marketplace by clicking the link use.

On this page you can manipulate the query and identify the Service Root URL as well as the Primary Account Keythat is assigned to your Windows Azure Marketplace account. You will need both of these information to access the data from Excel.

image

In Excel 2013 go to the Ribbon Data and select Get External Data => From Other Sources => From Windows Azure Marketplace.

image

In the Data Connection Wizard enter the Service Root URL which you identified in the Windows Azure Marketplace under Location of the data feed and the Primary Account Key under Log on credentials. Then click Next.

image

Select the data set you are interested in – in this example there is only one – and click Nextagain.

image

Now save the data connection on your local machine by clicking Finish.

image

You have several options to import the data. In this case we might want to pivot the data and therefore we select PivotTable Report.

image

Once you have pivoted the data into a structure that has both a location and at least one column with values that you want to visualize, select the Ribbon Insert and then Apps for Office. In the screenshot below you see that there are already 2 Apps for Office installed. If you have not yet installed the Bing Maps App for Office click on See All.

image

This will show a dialog with Apps for Office that you have registered for – no matter if they are already installed on your machine or not. If you have not yet registered the Bing Maps App for Office click on Find more apps in the Office Store.

image

Then browse the Office Storeand install the free Bing Maps App for Office.

image

Now that you have the Bing Maps App for Office installed you can select the data you want to visualize, Bing Maps will then geocode them in the background and show them as sized circles on the map.

image

The Bing Maps App for Office does provide other visualization options such as pie charts as well but it wouldn’t allow you to color code geographic areas or create heat- or density-maps. If you are specifically interested in color-coding US States, there is another free app in the Office Store that you could use. For this example we select the Crime Statisticsfor the US that Data.gov makes available through the Windows Azure Marketplace. As in the previous example we load this data set as a PivotTable report into Excel.

image

The data provides statistics for different types of crime on a city level.

image

For the purpose of this example we want to aggregate the data by state and sum up the total number of crimes in relation to the population. While we can easily pivot the data in a way that aggregates fields by state, the second requirement, i.e. a computed column that sums up the total number of crimes from the different categories and relates it to the population, goes beyond simple pivoting. There are certainly other ways to accomplish this but for the purpose of this example we’re going to do this directly in the data model. Excel 2013 intentionally hides the data model underneath the Pivot-Tables to reduce complexity but if you feel the need to change the data model manually you can do that too. To activate the tools that let you access the data model, click on File and then select Options.

image

In the Options dialog select Add-Ins and at the bottom select COM Add-ins from the drop-down list Manage. Then click Go.

image

Now activate the check-box for Microsoft Office PowerPivot for Excel 2013to enable an additional Ribbon that gives you access to the data model.

image

Once you confirmed your changes and exited the dialog, you have an additional Ribbon for the PivotTable Tools. Click on Manageto open a window that allows you to manipulate the data model.

image

Now we scroll to the right and add a formula that adds up the different categories, divides it by the population and multiplies by 1,000. The result is the total number of crimes per thousand population.

image

This new computed column appears now in your spreadsheet and you can pivot the data around until you have a flat structure that has one column for the state and another one for the total number of crimes per thousand population. Then activate the Insert Ribbon and select the Geographic Heat Map (developed by Keyur Patel) from the Apps for Office. If you haven’t installed it yet, refer to the previous example to learn how to get to the Office Store and install this free app.

image

The Geographic Heat Map adds initially a placeholder. Click on the icon in the top left corner to open the settings.

image

Now select the data and the color schema and set a title.

image

Once you click save, you will see the corresponding map.

image

PowerView

One of the powerful new features in Excel 2013 is PowerView. It has been around as a server-side component for a while and now makes its appearance on the desktop, loaded with a lot of features – including Bing Maps for the visualization of data in a location context. To show just how powerful it really is, we use an appropriately large data set from the Windows Azure Marketplace: more than 2.4 Million records of flight delays provided by OakLeaf Systems.

image

The data set is large but fairly simple. It contains the airport code as the geographic information as well as the various flights of US carriers with origin, destination, arrival- and departure-delay on a daily basis.

image

As in the previous examples we import the data into Excel as a PivotTable Report but instead of pivoting the data first, like we did in the previous examples, we can now insert a PowerView Reportand start creating our charts right there.

image

You will be amazed to see how powerful and responsive the report is despite the size of the data set.

image

MapCite

If you are not yet blessed with Excel 2013, you don’t have the option to use Office Apps or PowerView and access to the Windows Azure Marketplace does not come out-of-the-box either but there are still options. First you need to download the Excel Plug-In for the Windows Azure Marketplace. You will find the download link on the Windows Azure Marketplace by selecting Learn and then under Find and Discover.

image

Once installed you will notice an additional button in the DataRibbon. Sign in with your Microsoft Account to explore the data sets you have already subscribed to.

image

In this case we select Places.SG– a free data set with various categories of points of interest in Singapore – and for the purpose of this example we want to identify areas with the highest density of bars and pubs.

image

For the mapping we choose a free Excel Add-On that has been developed by MapCiteand uses Bing Maps for the mapping. The data in Places.SG already comes with geocodes (latitudes and longitudes) and is grouped into categories so we can easily pivot or just filter the data in a way that we show only those categories we are interested in.

MapCite comes with its own Ribbon. Click on Add Datato open a wizard that will guide you through the process of mapping your spreadsheet.

image

In this wizard you assign columns for latitudes and longitudes, labels, icons and optionally other properties.

image

Once added to the map you can change the visualization from individual points to Heatmapand you will easily identify the areas with the highest density of bars and pubs. Prost!

image

Layerscape

Layerscape provides an Excel Add-In to visualize your spreadsheets on Microsoft Research’s WorldWide Telescope. It supports time series and 2D as well as 3D visualizations for point data and polygons. For this example we use the free data set World Population Prospectsprovided by the United Nations. In order to use Layerscape with Excel you must install both the WorldWide Telescope and the Excel Plugin.

image

After importing the data into Excel – similar to the previous example – we pivot the data around, transpose columns to rows and merge with country-polygons as provided in the samples that come with the Excel Add-In. In the WorldWide Telescope Ribbon you can then open the Layer Manager in order to map the columns for the polygons, the time and the value you want to visualize to the objects that WorldWide Telescope expects. Once you mapped the columns click on View in WWT(make sure that WorldWide Telescope is already running in the background).

image

Now you can use the Time Scrubberin WorldWide Telescope to visualize the change of population over the time axis.

image

Esri Maps for Office

Esri Maps for Office is a commercial offering with a free 30 day trial. It integrates nicely into Excel and PowerPoint, provides a variety of base-maps and allows you to publish the data to ArcGIS Online (leveraging Windows Azure). From there you can publish the maps further to the web, integrate into SharePoint or view them in the free apps for your desktop or mobile devices.

For this example we’re using the Environmental Hazard Rank provided by Environmental Data Resources(EDR) through the Windows Azure Marketplace.

image

After loading the data from the Windows Azure Marketplace, we can map the Environmental Hazard Index or -Rank on a zip-code level with Esri Maps for Office and then publish from there into ArcGIS Online.

image

Once published, you can use ArcGIS Online as the hub and publish from there to the web, integrate into SharePoint, build your own applications around it or use the free apps for Windows, Windows Phone, Android (incl. Kindle Fire) or iOS to access the maps from any device.

image

Happy Mapping!

Posted in Bing Maps, ESRI, Excel, MapCite, Marketplace, Office Apps, PowerView, Windows Azure | Tagged , , , , , , , , , , , | Leave a comment

Windows Azure Mobile Services, Maps & More

Introduction

Windows Azure Mobile Services are an incredibly simple to use and yet powerful set of services that allow you to develop rich mobile applications with the power of “the Cloud” and without having to consider the details of the implementation in the backend. You concentrate on the app itself right away rather than spending time on the set-up and configuration of storage and middle-ware. It also allows you to easily integrate user authentication and push notifications. The current preview targets specifically the Windows 8 platform but the original announcement promised already that support for Windows Phone, iOS and Android will follow soon. Well, it seems soon was not soon enough and so the community took immediately advantage of the fact that the SDK has been open-sourced on github and took things in their own hands. So it is not surprising that today you find already Windows Azure Mobile Services SDKs for Windows Phone, iOS and Androidon the web.

Much has been said and written down about Windows Azure Mobile Services, how to get started and how to do more complex things but since one of the essential ingredients of mobile applications is often location I wanted to add a bit of mapping to the buzz. In this quick sample we will build upon the tutorials that walk you through the getting started as well as push notifications. At the end we will have an applications that allows you to

  • Capture locations from Windows 8 or Windows Phone
  • Send toast and tile push notifications to all users running the Windows 8 app
  • Intercept the notification on your Windows 8 device and center a map to the location of the newly captured place

image

If you are already got started with Windows Azure Mobile Services and are familiar with push-notification feel free to skip the first 2 chapters and go straight to the chapter Geolocation. You can also download the entire post here.

Requirements

  • Windows Azure Subscription (free trial available here; also available as a benefit for MSDN subscribers)
  • Bing Maps Account (free developer account available here)
  • Visual Studio 2012 (for the Windows 8 app; available here)
  • Windows Store Developer Account (you can sign up here; MSDN subscribers receive a free registration code through the MSDN portal)
  • Visual Studio 2010 and Windows Phone SDK (for the Windows Phone app; available here)

Getting Started

Once you have signed up for your Windows Azure account you can add preview features to your subscription through the management portal by visiting Account and then Preview Features.

image

Once you are activated you will find the Windows Azure Mobile Services in the Preview Portal.

image

Click on Create and select New => Compute => Mobile Service => Create.

image

You can now specify the URL for your new service, if you want to create a new or use an existing database as well as the region where this service should be deployed.

image

If you choose to create a new database you have then the option to also create a new database server or select one that was previously created.

image

Once you click on the submit button the database will be created and a new service will be set up for you and appears in the management portal.

image

When you click on your new service you’ll find several management options. First we need to decide if we want to create a new Windows Store app or connect to an existing one. Let’s start with a new one.

image

There are 3 simple steps to follow: Install the SDK, create a table and download a sample app that is already preconfigured with the URL and the application key for your new service. This sample app is a simple To-Do list and we will use it as a starting point. You have the options to download the app either for JavaScript or .NET development. We will use the JavaScript app.

image

Once you created the table you can examine it from the Data tabulator. You will find that an index was created and – not surprisingly – no data has been inserted yet.

image

What might come as a surprise though is that when you select the table itself you will find only the column for the index but none for the data yet. That is because the default table for the sample app uses a dynamic schema and will add columns based on the code that you write for your app. By adding more properties in your code you will automatically create more columns in the backend. If you would prefer to have a static schema for your tables you can do so of course as well.

image

Once you downloaded the sample application and open it in Visual Studio 2012 you will find that the reference to the Windows Azure Mobile Services SDK has been added and a default page with corresponding JavaScript was added.

image

The default.html contains a textbox to enter a new to-do item, a data-bound ListView of checkbox items and a few buttons to insert and refresh the data.

image

In the JavaScript file you will find the connection details for your Windows Azure Mobile Service.

image

Without any further modification you can run the application and add your first to-do items. When you add items, the data-bound list of To-Do items on the right-hand side will automatically refresh.

image

Add an item and go back to the Windows Azure management portal. You will find that 2 columns have automatically been added – one for the text of the to-do item and one for its status.

image

You can also browse the data through the portal itself.

image

Adding Push-Notification

To enable push notification for your Windows 8 application you must associate the application with the Windows Store, configure the Windows Azure Mobile Services and make a small modification in the client-side part.

To register your app with the Windows Store go to Visual Studio 2012 and right-click on the project in the Solution Explorer. Now select Store => Associate App with the Store from the context menu

image

You will be prompted to sign in with your Windows Developer Account.

image

Once you signed in with your Microsoft Account you will see applications that you previously created in the Windows Developer Center. If you have not created any applications yet you can follow the link to get to the Dashboard and reserve a new app name.

image

In the Dashboard click on App name.

image

Now enter a name for the app and click Reserve app name.

image

Then save it…

image

…and see the progress in the dashboard.

image

Back in Visual Studio select the newly created app and click Next.

image

Click Associateto update the manifest with the new information.

image

This will also create a certificate.

image

You have now associated your app with the Windows Store but you still need to enable the push-notification in the Windows Azure Mobile Services. To do so, navigate your browser to the Live Connect Developer Center and select your application. In the API Settings you will find your Client secret as well as your Package SID.

image

In the Windows Azure Management Portal navigate to your application in the Windows Azure Mobile Services section, select the Push tabulator and enter the Client secret and the Package SID.

image

While we are in the Windows Azure Management Portal navigate to the Data tabulator we create a new table Channel. This table will hold the URIs for the notification-channels. Leave the default permissions as is.

image

Back in Visual Studio we enter the code to create and register a notification channel right after the declaration of the client.

image

//Register Channel for Push-Notification
var channelTable = client.getTable('Channel');
Windows.Networking.PushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().done(function (channel) {
  channelTable.insert({
    channelUri: channel.uri
  });
});

We also double-check in the application manifest under Application UI that the app can receive toast messages.

image

The push-notification will be triggered by server-side scripts and we can write these as JavaScript directly in the Windows Azure Management Portal. There are 2 places where we need to make modifications. First we navigate to the Channel table, select the tabulator Script and in the drown-down list the one for Insert operations. We replace the existing script with the code snippet below. The server-side script receives a channel-URI from the client and checks if it is already in the Channel-table. If so, it does nothing but if the URI is not already in the table it will insert a new one.

image

function insert(item, user, request) {
  var channelTable = tables.getTable('Channel');
  channelTable
    .where({ channelUri: item.channelUri })
    .read({ success: insertChannelIfNotFound });

  function insertChannelIfNotFound(existingChannels) {
    if (existingChannels.length > 0) {
      request.respond(200, existingChannels[0]);
    } 
    else {
      request.execute();
    }
  }
}

Still in the Windows Azure Management Portal we navigate to the TodoItem-table and select the Insert script as well. This script sets basically a trigger that, upon successful insert of a new record, sends a toast push-notification back to all clients running this application.

Note: there are several templates for toast notifications and you can find out more about them here.

image

function insert(item, user, request) {
  request.execute({
    success: function() {
      request.respond();
      sendNotifications();
    }
  });

  function sendNotifications() {
    var channelTable = tables.getTable('Channel');
    channelTable.read({
      success: function(channels) {
        channels.forEach(function(channel) {
          push.wns.sendToastText04(channel.channelUri, {
            text1: item.text
          }, {
            success: function(pushResponse) {
              console.log("Sent push:", pushResponse);
            }
          });
        });
      }
    });
  }
}

That’s it. Now we can run the application and verify that the push-notification works.

image

Adding Geolocation

In this part we will add geolocation to the application as well as a map that will later show the location of a newly added item. As map service we leverage Bing Maps and to do that we need to install the Bing Maps SDK for Windows Store Apps. In Visual Studio 2012 open the menu Tools and select Extensions and Updates; then search for Bing Maps and install the SDK.

image

Once the Bing Maps SDK for Windows Store Apps is installed, right-click on the folder References in the Solution Explorer and select Add Reference from the context menu.

image

Activate the checkbox next to Bing Maps for JavaScript – you will find it under Windows => Extensions – and click OK.

image

We also need to allow the application to access locations sensors. To do so we open the application manifest, navigate to the tabulator Capabilities and activate the checkbox next to Location. Even though we enable location in principal, the end user will still be prompted on first use and have to give his consent to the use of his location sensor for obvious privacy reasons.

image

Next we edit the default.html. In the header we add a reference to the Bing Maps SDK.

<script type="text/javascript" src="/MobileServicesJavaScriptClient/MobileServices.js"></script>
<script src="/Bing.Maps.JavaScript/js/veapicore.js"></script>

<!-- QuickStart references -->

In the body we add a button to start the geolocation-process, a div-element to host the result of the geolocation and another div-element that will contain the map.

<div style="margin: 5px 0px 0px 72px; -ms-grid-column: 2">
  <input type="text" id="textInput" />
  <button id="buttonLoc" style="margin-left: 5px">Location</button>
  <button id="buttonSave" style="margin-left: 5px">Save</button>
</div>
<div style="margin: 5px 0px 0px 72px; -ms-grid-column: 2">
  <div id="lblLoc">[get location]</div>
</div>
<div id="myMap" style="margin: 5px 0px 0px 72px; -ms-grid-column: 2; height:380px; width:480px"></div>

Now we move on to the JavaScript. Under the global declarations we add variables for the map, the geolocator, the location object and the coordinates that it returns.

var map = null;
var geolocator = null;
var thisLoc = null;
var lat = 0;
var lon = 0;

At the very end of the script-file just underneath the line app.start() we add the following script. If you haven’t done so yet, now is the time to go to the Bing Maps Account Center and create your free developer account. The script below fires when the DOM is completely loaded, loads the Bing Maps module and initializes the map as well as the geolocator.

function initialize() {
  Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: initMap });
  geolocator = Windows.Devices.Geolocation.Geolocator();
}

function initMap() {
  try {
    var mapOptions =
      { credentials: "YOUR BINGS MAPS KEY",
        mapTypeId: "r",
        enableClickableLogo: false,
        enableSearchLogo: false,
        width: 480, height: 380
      };
    map = new Microsoft.Maps.Map(document.getElementById("myMap"), mapOptions);
  }
  catch (e) {
    var md = new Windows.UI.Popups.MessageDialog(e.message);
    md.showAsync();
  }
}

document.addEventListener("DOMContentLoaded", initialize, false);

})();

Just before the event-listener that handles the click on the Save-button we add another event-listener that shall handle the Location-button. It will request the geo-location asynchronously and update the UI.

buttonLoc.addEventListener("click", function () {
  thisLoc = geolocator.getGeopositionAsync();
  thisLoc.done(
    function (pos) {
      var coord = pos.coordinate;

      lat = coord.latitude;
      lon = coord.longitude;
      document.getElementById("lblLoc").innerHTML = 'Latitude: ' + lat + ' / Longitude: ' + lon; 
    },
    function (err) {
      if (pageLoaded) {
        document.getElementById("lblLoc").innerHTML = err.message;
      }
    }
  );
});

buttonSave.addEventListener("click", function () {

Finally we modify the event-listener that handles the Save-button and add two more properties for the coordinates. Note that we do not have to make any modifications in the backend. Since the schema for our database table is dynamic, the Windows Azure Mobile Services will automatically create two additional columns.

buttonSave.addEventListener("click", function () {
  insertTodoItem({
    text: textInput.value,
    complete: false,
    latitude: lat,
    longitude: lon
  });
});

Let’s run and test our modification so far. Enter a new string and click on Location before you click on Save.

image

Now navigate to the Windows Azure Management Portal and browse the table to verify that the 2 columns have indeed been added and are now being populated with location information.

image

Modifying the Push-Notifications on the Server-Side

In this part we will update the push-notification to add a map of the newly created item to the toast message and also update the tile of the application itself with a map and some text. Since tile notifications are more effective with wider tiles we start by adding a new default-image of size 310 x 150 pixel to the image folder and declare it in the application manifest as the wide-logo.

image

Back in the Windows Azure Management Portal we navigate to the TodoItem-table and open the Insert-Script to make the following modifications in the function sendNotifications which we created earlier.

image

We are now creating two push-notifications – one for the toast and one for the tile. For each notification we select a template that supports text and images. You will find more information on toast templates here and on tile templates here. The images are in this case maps that we create dynamically through the Bing Maps REST Services. The service requires coordinates for the center point of the map and we can retrieve those from the item-properties of the insert operations.

function sendNotifications() {
  var channelTable = tables.getTable('Channel');
  channelTable.read({
    success: function (channels) {
      channels.forEach(function (channel) {
        push.wns.sendTileWidePeekImageAndText02(channel.channelUri, {
          image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/' + 
            item.latitude + ',' + item.longitude + '/15?pp=' +
            item.latitude + ',' + item.longitude + 
            ';15&ms=310,150&key= YOUR BING MAPS KEY',
          image1alt: 'New Place',
          text1: 'New Place',
          text2: item.text,
          text3: item.latitude + ',' + item.longitude,
        }, {
          success: function (pushResponse) {
            //console.log("Sent push:", pushResponse);
          }
        });
        push.wns.sendToastImageAndText04(channel.channelUri, {
          image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/' + 
            item.latitude + ',' + item.longitude + '/15?pp=' + 
            item.latitude + ',' + item.longitude + 
            ';15&ms=150,150&key=YOUR BING MAPS KEY',
            image1alt: 'New Place',
            text1: 'New Place',
            text2: item.text,
            text3: item.latitude + "," + item.longitude
          }, {
            success: function (pushResponse) {
              //console.log("Sent push:", pushResponse);
          }
        });
      });
    }
  });
}

That was already it lets test the modifications. You should receive a toast message with the map of the location at the left.

image

You should also notice that the application tile has been updated with a map and the text that you inserted.

image

Intercepting the Push-Notification on the Client

In the final part of the Windows 8 application we are going to intercept the push-notification and center the map to the location that is part of its payload.

The only minor modification we have to make is an addition to the PushNotificationChannelManager. We add an event-listener that intercepts the toast message and retrieves the string that contains the coordinates. We take these coordinates and parse it in a Bing Maps location-object in order to center and zoom the map on this location.

//Register Channel for Push-Notification
var channelTable = client.getTable('Channel');
Windows.Networking.PushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().done(function (channel) {
  channelTable.insert({
    channelUri: channel.uri
  });
  channel.addEventListener("pushnotificationreceived", function (e) {
    if (e.notificationType == 0) {
      var thisLL = e.toastNotification.content.getElementsByTagName("text")[2].innerText;
      var thisLLArray = thisLL.split(",");
      var thisLoc = new Microsoft.Maps.Location(
        parseFloat(thisLLArray[0]), parseFloat(thisLLArray[1]));
      map.setView({ zoom: 15, center: thisLoc });
      map.entities.clear();
      var pin1 = new Microsoft.Maps.Pushpin(thisLoc);
      map.entities.push(pin1);
      refreshTodoItems();
    }
  });
});

That was already it.

image

Building the Windows Phone Application

So far we have developed a Windows 8 Store application than communicates with Windows Azure Mobile Services and can receive push-notifications. If one user enters a new item, a new notification will be send to all users who run the same application since they are all registered to the same notification channel. To round up this quick tutorial we will now create a Windows Phone client that allows you to enter a new item from a mobile device. Users of the Windows 8 Store application should then still receive push-notifications. As mentioned at the very beginning the current preview of the Windows Azure Mobile Services only provides an SDK for the Windows 8 Store Applications but it has been open sourced and published on github. In addition there is also a REST API available and documented here and hence it is not surprising that there are already SDKs out there that allow you to build Windows Azure Mobile Services clients for Android, iOS and Windows Phone.

For this sample we will use the Windows Phone SDK that Ken Egozi developed and describes here. Ken has also published the SDK on github.

We start by creating a new Silverlight for Windows Phone application.

image

As target OS version we select Windows Phone OS 7.1.

image

Ken’s SDK leverages Newtonsoft’s Json.NET so we download this from CodePlex and add a reference to the Newtonsoft.Json.dll that was compiled for the Windows Phone.

image

Next we head over to Ken’s github repository, navigate to the source code of the MobileServiceClient.cs class, right-click on the Raw button and save the file directly from the browser to our project folder.

image

In the Solution Explorer in Visual Studio we select Show all Files, right-click on the downloaded file and select Include in Project from the context menu.

image

In the App.xaml.cs we add a using statement for the MobileServices.Sdk and at the beginning of the App-class we instantiate a new MobileServicesClient. The MobileServiceClient requires 2 parameters: the URL and the key.

using Microsoft.Phone.Shell;
using MobileServices.Sdk;

namespace BlogPostWP
{
    public partial class App : Application
    {
        public static MobileServiceClient MobileServiceClient = new MobileServiceClient(
            "https://blogpost.azure-mobile.net/",
            "EDUEMKgUSiRqTZuEAxlpfwmaOQBPkv56");

        /// <summary>

You can retrieve the URL from the dashboard of your Windows Azure Mobile Service in the Windows Azure Management Portal.

image

When you click on Manage Keys you can view or re-generate the Application Keys.

image

In the MainPage.xaml we add a few UI elements to enter new items, start the geolocation, submit the data to Windows Azure Mobile Services and display a map.

image

<TextBox Height="78" HorizontalAlignment="Left" Name="txtName" Text="" 
  VerticalAlignment="Top" Width="456" />
<TextBlock HorizontalAlignment="Left" Margin="12,82,0,0" Name="lblStatus" Text="Ready" 
  VerticalAlignment="Top" Width="438" />
<Button Content="Start Tracker" Height="72" HorizontalAlignment="Left" Margin="0,148,0,0" 
  Name="btnStartLoc" VerticalAlignment="Top" Width="212" Click="btnStartLoc_Click"/>
<Button Content="Stop Tracker" Height="72" HorizontalAlignment="Left" 
  Margin="244,148,0,0" Name="btnStopLoc" VerticalAlignment="Top" Width="212" 
  Click="btnStopLoc_Click"/>
<TextBlock HorizontalAlignment="Left" Margin="12,115,0,0" Name="lblLat" Text="0.0" 
  VerticalAlignment="Top" Width="181" />
<TextBlock HorizontalAlignment="Left" Margin="269,115,0,0" Name="lblLon" Text="0.0" 
  VerticalAlignment="Top" Width="181" />
<Image Height="297" HorizontalAlignment="Left" Margin="12,226,0,0" Name="imgMap" 
  Stretch="Fill" VerticalAlignment="Top" Width="445" />
<Button Content="Add Place" Height="72" HorizontalAlignment="Left" Margin="1,529,0,0" 
  Name="btnInsert" VerticalAlignment="Top" Width="456" Click="btnInsert_Click"/>

In the code behind MainPage.xaml we will handle the geolocation as well as any interaction with the Windows Azure Mobile Services and other services. In order to access geolocation services on the Windows Phone we first add a reference to System.Device. We will also need System.Runtime.Serialization.

image

In MainPage.xaml.cs we add a few using statements:

using System.Runtime.Serialization;
using MobileServices.Sdk;
using System.Device.Location;
using System.Windows.Media.Imaging;

Before the class MainPage we add a new class which describes a TodoItem which we want to send to Windows Azure Mobile Services.

public class TodoItem
{
  public int Id { get; set; }

  [DataMember(Name = "text")]
  public string Text { get; set; }

  [DataMember(Name = "complete")]
  public bool Complete { get; set; }

  [DataMember(Name = "latitude")]
  public double Latitude { get; set; }

  [DataMember(Name = "longitude")]
  public double Longitude { get; set; }
}

In the class MainPage we declare a few class-wide variables for the GeoCoordinateWatcher which monitors the geolocation process as well as for the coordinates which we retrieve from the device.

GeoCoordinateWatcher watcher;
double thisLat = 0.0;
double thisLon = 0.0;

Next we handle the start, stop and event-handling of the geolocation in the class MainPage. This is well documented and comes with sample code in the Windows Phone Developer Center so I will not go into details here but you’ll find the complete code below.

private void btnStartLoc_Click(object sender, RoutedEventArgs e)
{
  StartLocationService(GeoPositionAccuracy.Default);
}

private void StartLocationService(GeoPositionAccuracy accuracy)
{
  // Reinitialize the GeoCoordinateWatcher 
  lblStatus.Text = "Starting Geolocation";
  watcher = new GeoCoordinateWatcher(accuracy);
  watcher.MovementThreshold = 20;

  // Add event handlers for StatusChanged and PositionChanged events 
  watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
  watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);

  // Start data acquisition 
  watcher.Start();
}

void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
  Deployment.Current.Dispatcher.BeginInvoke(() => MyStatusChanged(e));
}

void MyStatusChanged(GeoPositionStatusChangedEventArgs e)
{
  switch (e.Status)
  {
    case GeoPositionStatus.Disabled:
      // The location service is disabled or unsupported. 
      // Alert the user 
      lblStatus.Text = "location is unsupported on this device";
      break;
    case GeoPositionStatus.Initializing:
      // The location service is initializing. 
      // Disable the Start Location button 
      lblStatus.Text = "initializing location service";
      break;
    case GeoPositionStatus.NoData:
      // The location service is working, but it cannot get location data 
      // Alert the user and enable the Stop Location button 
      lblStatus.Text = "data unavailable";
      break;
    case GeoPositionStatus.Ready:
      // The location service is working and is receiving location data 
      // Show the current position and enable the Stop Location button 
      lblStatus.Text = "receiving data";
      break;
  }
}

void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
  Deployment.Current.Dispatcher.BeginInvoke(() => MyPositionChanged(e));
}

void MyPositionChanged(GeoPositionChangedEventArgs<GeoCoordinate> e)
{
  // Update the TextBlocks to show the current location 
  thisLat = e.Position.Location.Latitude;
  lblLat.Text = thisLat.ToString("0.000");
  thisLon = e.Position.Location.Longitude;
  lblLon.Text = thisLon.ToString("0.000");
}

private void btnStopLoc_Click(object sender, RoutedEventArgs e)
{
  if (watcher != null)
  {
    watcher.Stop();
  }
  lblStatus.Text = "location service is off";
  lblLat.Text = "0.0";
  lblLon.Text = "0.0";
}

The final function that we need to look at handles the Save-button. It sends the data into the Windows Azure Mobile Services and calls the Bing Maps REST Services in order to add a map-image to our MainPage.

private void btnInsert_Click(object sender, RoutedEventArgs e)
{
  MobileServiceTable<TodoItem> todoTable = App.MobileServiceClient.GetTable<TodoItem>();
  var item = new TodoItem { Text = txtName.Text, Complete = false, Latitude = thisLat, Longitude = thisLon };
  todoTable.Insert(item, (res, err) =>
  {
    if (err != null)
    {
      MessageBox.Show("INSERT FAILED", "ERROR", MessageBoxButton.OK);
      return;
    }
    imgMap.Source = new BitmapImage(new Uri("http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/" 
      + lblLat.Text + "," + lblLon.Text + "/15?pp=" + lblLat.Text + "," + lblLon.Text 
      + ";15&ms=445,297&key=YOUR_BING_MAPS_KEY", UriKind.RelativeOrAbsolute));
    MessageBox.Show("PLACE SAVED", "SUCCESS", MessageBoxButton.OK);
  });
}

That’s It. Test the app, watch the toast and tile-notifications pop up and see how Bing Maps on Windows 8 magically moves the map when you experiment with the app on your phone.

image

Posted in Bing Maps, Mobile Services, Windows 8, Windows Azure, Windows Phone, Windows Store | Tagged , , , , , , , , | 8 Comments

Loading GeoRSS-Feeds with Dynamic Modules in the Bing Maps AJAX Control v7

Introduction

In May several Updates have been released to the Bing Maps AJAX Control v7 which Keith Kinnan has summarized here. Possibly the most significant one is the ability to dynamically load additional modules.

When the Bing Maps AJAX Control had been re-written for v7 some of the design goals had been to improve performance and to support mobile devices. In order to achieve these goals the Bing Maps AJAX control supports now HTML5 and it was also put on a diet in order to slim down to a size that can load quickly even on mobile devices. As a consequence the core-control itself supports only the minimum requirements that most mapping sites will have. However, the control was designed in such a way that additional modules can be optionally and dynamically added later on when needed. The general principal is explained in the SDK and in the interactive SDK you will find an example that implements client-side clustering. This module for client-side clustering adds about 14kB to the weight of the website and while it is a useful feature for some not everybody might need it and therefore it seems to be a good idea to stick it into a module and let the developer decide if and when he needs it.

Another feature that may be helpful for some is the ability to import GeoRSS-feeds. In this blog-post we will have a look at the steps to create such a custom module and load it on demand. To limit the amount of code for this blog-post we parse only GeoRSS-feeds following the Simple serialization. However a similar approach could be used to parse GeoRSS-feeds derived from GML as well as GPX- or KML-files.

GeoRSS-Feed

A GeoRSS-feed following the Simple serialization contains tags and to describe points (<georss:point>), lines () and polygons () as a sequence of latitudes and longitudes. The example below is a GeoRSS-feed that contains a polygon representing the Microsoft Office in London, a point representing the nearest underground station and a line representing the walk from the tube station to the Microsoft Office.

<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:georss="http://www.georss.org/georss" version="2.0">
  <channel>
    <title>Microsoft London</title>
    <link>http://www.bing.com/maps</link>
    <description />
    <language>en-gb</language>
    <item>
      <title>Microsoft</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>Cardinal Place, 100 Victoria Street, London, SW1E 5JL</description>
      <guid isPermaLink="false">e7aea3b0d2e1c7b8</guid>
      <pubDate>Fri May 27 22:37:13 UTC 0100 2011</pubDate>
      <georss:polygon>51.49673999638715 -0.14145107778161137 51.496997150067386 -0.1394555142745313 51.49772184808794 -0.1397022775039014 51.497568226428456 -0.1408288052901563 51.49676003438838 -0.1414403489455518 51.49673999638715 -0.14145107778161137</georss:polygon>
    </item>
    <item>
      <title>Victoria</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>Underground Station</description>
        <guid isPermaLink="false">b03dc79bd7bdb81e</guid>
      <pubDate>Fri May 27 22:37:39 UTC 0100 2011</pubDate>
      <georss:point>51.49644610469038 -0.14391334565724278</georss:point>
    </item>
    <item>
      <title>Walk from Victoria to Cardinal Place</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>180m</description>
      <guid isPermaLink="false">bf8ee4e437813477</guid>
      <pubDate>Fri May 27 22:38:24 UTC 0100 2011</pubDate>
      <georss:line>51.496643145923684 -0.14391334565724278 51.496506219055234 -0.14225574048603917 51.49657969206017 -0.1415208152159586 51.4967199583771 -0.14146180661763097</georss:line>
    </item>
  </channel>
</rss>

We will use the location tags to draw points, lines and polygons and the text within the title and description tags as content for the InfoBox that pops up when we click on the object.

The Website

On the website we have a simple map and add a text-box to enter the path to the GeoRSS-feed as well as a button to load the module and import the GeoRSS-feed.

image

The code so far is shown below.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script type="text/javascript">
        var map = null;
        var MM = Microsoft.Maps;

        function GetMap() {
            var options = { credentials: "YOUR BING MAPS KEY",
                            enableClickableLogo: false,
                            enableSearchLogo: false,
                            mapTypeId: Microsoft.Maps.MapTypeId.road,
                            center: new MM.Location(51.4611794944098, -0.9259434789419174),
                            zoom:17 };
                        map = new MM.Map(document.getElementById('mapDiv'), options);

                        // Hide the info box when the map is moved.
                        MM.Events.addHandler(map, 'viewchange', hideInfobox);
        }
      </script>
</head>
<body onload="GetMap();">
    <div id='mapDiv' style="position:relative; width:800px; height:600px;"></div><br />
    <a>GeoRSS-Feed</a><input id="txtGeoRSS" type="text" value="MSFT_London.xml" />
    <input id="Button2" type="button" value="Import" onclick="LoadModule()" />
</body>
</html>

We add now to the website a function to register and dynamically load an additional module. To register the module we give it a unique name and point to the location of the JavaScript that contains the module. When we load the module we can optionally specify a callback-function that is being executed when the loading is completed.

        function LoadModule(){
            // Register and load a new module
            MM.registerModule("GeoRSSModule", "./GeoRSSModule.js");
            MM.loadModule("GeoRSSModule", { callback: ModuleLoaded });
        }

The Module

The module is a basically a separate JavaScript-file that we can register on demand in our website and that can make use of the namespace Microsoft.Maps. In this module we start by defining the style of lines and polygons.

function GeoRSSModule(map) {
    var myFillColor = new Microsoft.Maps.Color(100,255,165,0);
    var myStrokeColor = new Microsoft.Maps.Color(200,255,165,0);
    var myStrokeThickness = 5;

    var myPolygonOptions={fillColor: myFillColor,
                         strokeColor: myStrokeColor,
                         strokeThickness: myStrokeThickness};
    var myPolylineOptions={strokeColor: myStrokeColor,
                           strokeThickness: myStrokeThickness};

Next we extend the Pushpin, Polyline and Polygon classes in the namespace Microsoft.Maps with properties that can hold the title and description for these objects. For Polylines and Polygons we also add properties that can hold the position where we want the InfoBox to appear.

    Microsoft.Maps.Pushpin.prototype.title = null;
    Microsoft.Maps.Pushpin.prototype.description = null;
    Microsoft.Maps.Polyline.prototype.title = null;
    Microsoft.Maps.Polyline.prototype.description = null;
    Microsoft.Maps.Polyline.prototype.anchorLat = null;
    Microsoft.Maps.Polyline.prototype.anchorLon = null;
    Microsoft.Maps.Polygon.prototype.title = null;
    Microsoft.Maps.Polygon.prototype.description = null;
    Microsoft.Maps.Polygon.prototype.anchorLat = null;
    Microsoft.Maps.Polygon.prototype.anchorLon = null;

A module can have one or more functions and for our module the main logic is implemented in the function ImportGeoRSS. Before we load the GeoRSS-feed we first remove all other entities from the map.

    this.ImportGeoRSS = function (MyFeed) {
        map.entities.clear();

Next we load the GeoRSS-feed.

        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", MyFeed, false);
        xmlhttp.send();
        var xmlDoc = xmlhttp.responseXML;

In the following part we parse the XML-feed into objects of type Microsoft.Maps.Pushpin, Polyline and Polygon – including the additional properties that we prototyped before.

        var itemCount = xmlDoc.getElementsByTagName("item").length;
        var allLocs = new Array()

        for (i = 0; i <= itemCount - 1; i++) {
            var childNodeCount = xmlDoc.getElementsByTagName("item")[i].childNodes.length;
            var tagName = null;
            var geomType = null;
            var geom = null;
            var myTitle = null;
            var myDesc = null;
            var anchorLat = null;
            var anchorLon = null;
            for (j = 0; j <= childNodeCount - 1; j++) {
                tagName = xmlDoc.getElementsByTagName("item")[i].childNodes[j].nodeName;
                if (tagName in { 'georss:point': '', 'georss:line': '', 'georss:polygon': '' }) {
                    geomType = tagName;
                    geom = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                }
                else if (tagName == "title") {
                    try {
                        myTitle = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                    }
                    catch (err) {
                    }
                }
                else if (tagName == "description") {
                    try {
                        myDesc = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                    }
                    catch (err) {
                    }
                }
            }
            var coords = new Array();
            coords = geom.split(" ");
            var thisLocs = new Array()

            var anchorCoord = null;
            if ((coords.length/2) % 2) {
                anchorCoord = coords.length / 2-1;
            }
            else {
                anchorCoord = coords.length / 2;
            }

            for (k = 0; k <= coords.length - 1; k = k + 2) {
                var thisLoc = new Microsoft.Maps.Location(coords[k], coords[k + 1]);
                thisLocs.push(thisLoc);
                allLocs.push(thisLoc);

                if (k == anchorCoord) {
                    anchorLat = coords[k];
                    anchorLon = coords[k + 1];
                }
            }

            var shape = null;
            switch (geomType) {
                case "georss:point":
                    shape = new Microsoft.Maps.Pushpin(thisLocs[0]);
                    break;
                case "georss:line":
                    shape = new Microsoft.Maps.Polyline(thisLocs, myPolylineOptions);
                    shape.anchorLat = anchorLat;
                    shape.anchorLon = anchorLon;
                    break;
                case "georss:polygon":
                    shape = new Microsoft.Maps.Polygon(thisLocs, myPolygonOptions);
                    shape.anchorLat = anchorLat;
                    shape.anchorLon = anchorLon;
                    break;
            }
            shape.title = myTitle;
            shape.description = myDesc;

We also attach an event to the object that will show an InfoBox with further information before we add the object to the map.

            pushpinClick = Microsoft.Maps.Events.addHandler(shape, 'click', showInfoBox);
            map.entities.push(shape);
        }

When all objects are added to the map we set the map-view to a zoom-level and centre-point that shows all objects.

        map.setView({ bounds: Microsoft.Maps.LocationRect.fromLocations(allLocs) });
    }
}

Finally we signal back to the map that the module is now loaded and trigger the execution of the callback-function.

Microsoft.Maps.moduleLoaded('GeoRSSModule');

Back to the Website

We had already prepared the website with a function to load the module but we still have to add the callback-function. In this callback-function we execute the ImportGeoRSS-function on a feed as specified in the text-box.

        function ModuleLoaded() {
            // Use the function provided by the newly loaded module
            var myModule = new GeoRSSModule(map);
            myModule.ImportGeoRSS(document.getElementById("txtGeoRSS").value);
            collectionInfoBox = new MM.EntityCollection;
            map.entities.push(collectionInfoBox);
        }

Finally we add some code to handle the InfoBoxes and we’re done.

image

The complete source of our website is listed below. To see the code in action follow this link and select “GeoRSS” under the accordion-pane “Miscellaneous”.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script type="text/javascript">
        var map = null;
        var MM = Microsoft.Maps;
        var infobox = null;
        var collectionInfoBox = null;

        function GetMap() {
            var options = { credentials: "YOUR BING MAPS KEY",
                            enableClickableLogo: false,
                            enableSearchLogo: false,
                            mapTypeId: Microsoft.Maps.MapTypeId.road,
                            center: new MM.Location(51.4611794944098, -0.9259434789419174),
                            zoom:17 };
                        map = new MM.Map(document.getElementById('mapDiv'), options);

                        // Hide the info box when the map is moved.
                        MM.Events.addHandler(map, 'viewchange', hideInfobox);
        }

        function LoadModule(){
            // Register and load a new module
            MM.registerModule("GeoRSSModule", "./GeoRSSModule.js");
            MM.loadModule("GeoRSSModule", { callback: ModuleLoaded });
        }

        function ModuleLoaded() {
            // Use the function provided by the newly loaded module
            var myModule = new GeoRSSModule(map);
            myModule.ImportGeoRSS(document.getElementById("txtGeoRSS").value);
            collectionInfoBox = new MM.EntityCollection;
            map.entities.push(collectionInfoBox);
        }

        //Display InfoBox
        function showInfoBox(e) {
            if (e.targetType == "pushpin") {
                collectionInfoBox.clear();
                infobox = new MM.Infobox(e.target.getLocation(), { title: e.target.title, description: e.target.description, offset: new MM.Point(0, 30), visible: true });
                collectionInfoBox.push(infobox);
            }
            else if (e.targetType == "polygon" || e.targetType=="polyline") {
                collectionInfoBox.clear();
                infobox = new MM.Infobox(new MM.Location(e.target.anchorLat, e.target.anchorLon), { title: e.target.title, description: e.target.description, offset: new MM.Point(0, 0), visible: true });
                collectionInfoBox.push(infobox);
            }
        }

        function hideInfobox(e) {
            try {
                infobox.setOptions({ visible: false });
            }
            catch (err) {
            }
        }
      </script>
</head>
<body onload="GetMap();">
    <div id='mapDiv' style="position:relative; width:800px; height:600px;"></div><br />
    <a>GeoRSS-Feed</a><input id="txtGeoRSS" type="text" value="MSFT_London.xml" />
    <input id="Button2" type="button" value="Import" onclick="LoadModule()" />
</body>
</html>

Tip: Importing Bing Maps Collections in your own Website

On the Bing Maps consumer site you can create your own collections. If you wanted to use such a collection in your own websites you can simply use the “My Places Editor” to export to GeoRSS and then use the module above to import it.

image

Posted in AJAX, Bing Maps | Tagged , , | Leave a comment

Dynamic Tile-Layers with Windows Azure and SQL Azure

Introduction

Recently I had the honour to support our friends and colleagues in Japan in the development of a service that shows the status of roads following the tragic earthquake and the even more disastrous tsunami. Setting up this service in the cloud took only a few hours and in the following I will walk through the components of this solution and the thoughts the led to this particular implementation path.

You will find the sample website here.

image

I have also created a Bing Map App here.

image

Components and Reasoning to Choose this Implementation Path

Bing Maps is a cloud-based web mapping service that guarantees high availability and scalability. It is accessible through a consumer site and through a set of APIs which include SOAP and REST web services as well as more interactive AJAX and Silverlight Controls. The AJAX and the Silverlight controls allow you to overlay your own data in vector format or rasterized into tile-layers. Since version 7 the AJAX control is not only supported on the PC and Mac but also on the iPhone, Android and Blackberry browsers. You will find a list of supported browsers here.
In order to remain independent from browser plugins and also to support multiple platforms we settled for the Bing Maps AJAX Control version 7.

When you have a large amount of vector data it might be advisable to rasterize these data into tile layers before overlaying them on your web mapping solution. In this case the source data (provided by Honda) was available as a KMZ-file. The uncompressed size of this data is 40 MB and even compressed it is still 8.7 MB. Downloading such an amount of data would take a while. Particularly if you also intend to support mobile devices it might not be the best option. In addition you have to consider the amount of objects that you intend to render. In this example the data contained 53,566 polylines and rendering would take a while, deteriorating the user experience even further.
Therefore we decided to rasterize the data and overlay them as a tile layer.

After the decision was made to rasterize the data and visualize them as a tile layer on Bing Maps the question was, if we create a static tile layer or rasterize the data on the fly. In this example the data covers 75,628 road-kilometres and an area of 156,624 square kilometres. Creating a complete tile-set down to level 19 would result in 61,875,766 tiles. Rendering all these tiles would take many hours and daily updates would not have been practical. On the other side it is not likely that all these tiles will be needed. There might be regions that people wouldn’t look at at all zoom-levels. In fact it turned out that only a few thousand different tiles are being retrieved every day.
Therefore we decided to set up a solution that creates the tiles on demand and implement a tile-cache in order to enhance performance.

When we started the project we had no idea what traffic we had to expect. Therefore we wanted to be able to scale he solution up and down depending on the traffic. We also wanted to make sure that data and services are held close to the end-users in order to keep latency times low.
Therefore we chose to deploy the solution on Windows Azure and enable the Content Delivery Network (CDN) for the tile cache. Vector data will be stored as spatial data types in SQL Azure.

The last ingredient was the Spatial ETL tool that allows us to load the KMZ-file into SQL Azure and we chose Safe FME for this task. Safe supports hundreds of spatial data formats – including KML/KMZ and SQL Server Spatial data formats.

On a high level the components of the proposed solution look like this.

image

The solution is accessible as a Map App from the Bing Maps consumer website as well as a “regular” website from PC, Mac and mobile devices.

image

Loading the Data into SQL Azure

As mentioned above we use Safe FME to load the data into SQL Azure. We only need an OGCKML-reader and the MSSQL_SPATIAL writer.

image

At the reader we expose the kml_style_url Format Attribut because this holds the information that allows us to distinguish between road segments that were open to traffic and those where traffic had not been observed.

image

On the writer we create a new User Attribute Color and link the kml_style_url from the reader to it.

image

Also on the writer we define under the tab parameters a SQL script for the Spatial Index Creation.

image

alter table JapanRoads0319 add MyID int identity;
ALTER TABLE JapanRoads0319 ADD CONSTRAINT PK_JapanRoads0319 PRIMARY KEY CLUSTERED (MyID);
CREATE SPATIAL INDEX SI_JapanRoads0319 ON JapanRoads0319(GEOM) USING GEOMETRY_GRID WITH( BOUNDING_BOX  = ( xmin  = 122.935256958008, ymin  = 24.2508316040039, xmax  = 153.965789794922, ymax  = 45.4863815307617), GRIDS  = ( LEVEL_1  = MEDIUM, LEVEL_2  = MEDIUM, LEVEL_3  = MEDIUM, LEVEL_4  = MEDIUM), CELLS_PER_OBJECT  = 16);

In the navigator on the left hand side of the FME workbench we also define a SQL statement that runs after we completed the loading process and validates potentially invalid geometries.

image

update JapanRoads0319 set geom=geom.MakeValid();

Now that we have the data in SQL Azure we can already start to analyse and find out how many road-kilometres are open to traffic and on how many road-kilometres traffic has not been verified, e.g.

SELECT color, sum(geography::STGeomFromWKB(GEOM.STAsBinary(), 4326).STLength())/1000 as [1903] from JapanRoads0319 group by color

If we keep doing this with each of the daily data updates the results allow us to create graphs showing the improvements over time.

image

Tile Rendering and Caching

The tile rendering and cache handling is implemented in a Generic Web Handler (SqlTileServer.ashx). When you overlay a tile layer on top of Bing Maps the control will send for each tile in the current map view a HTTP-GET-Request to a virtual directory or in our case a web service. The request passes the quadkey of the tile as a parameter. In our service we will need to

  • determine if we have already cached the tile and if so retrieve it from the cache and return it to the map
  • If we haven’t cached the tile
    • determine the bounding box of the tile. This is where we use a couple of functions as listed in this article.
    • query the database and find all spatial objects that intersect this bounding box.
    • create a PNG image
    • write this image to cache and also
    • return the image to the map

In order to keep the communication between SQL Azure and the web service efficient we transport the data as binary spatial data types. This requires then of course that we decode the binary data in the web service. Fortunately the SQL Server Spatial team provides us with the means to do that. As part of the SQL Server 2008 R2 Feature Pack you will find the “Microsoft System CLR Types for SQL Server 2008 R2” which include the spatial data types and spatial functions and can be integrated in .NET applications and services even if you don’t have SQL Server installed.

image

Note: The package is available in 32bit, 64bit and Itanium versions. Windows Azure is based on Windows Server 2008 R2 64bit. If you want to deploy your solution on Windows Azure make sure you use the 64bit version of the “Microsoft System CLR Types for SQL Server 2008 R2”.

In the database we have a stored procedure that executes our spatial query.

CREATE PROCEDURE [dbo].[GetJapanRoads]
@nwLon nvarchar(10),
@nwLat nvarchar(10),
@seLon nvarchar(10),
@seLat nvarchar(10)
AS
BEGIN
DECLARE @bbox geometry;
SET @bbox = geometry::STPolyFromText('POLYGON(('+@nwLon+' '+@nwLat+', '+@nwLon+' '+@seLat+', '+@seLon+' '+@seLat+', '+@seLon+' '+@nwLat+', '+@nwLon+' '+@nwLat+'))', 4326);
SELECT GEOM, Color
FROM JapanRoads0319
WHERE (GEOM.STIntersects(@bbox) = 1)
END;

In order to convert geographic coordinates into pixel coordinates for the Bing Maps Tile System we use a couple of functions as listed in this article.

Since we want to cache the tiles in the Windows Azure Blob Storage we will also need to add references to the assemblies

  • Microsoft.WindowsAzure.ServiceRuntime and
  • Microsoft.WindowsAzure.StorageClient

Both assemblies are part of the Windows Azure SDK and the Windows Azure Tools for Microsoft Visual Studio, which includes the Windows Azure SDK.

Once we have the stored procedure in SQL Azure, the 3 assemblies (2 for Azure, 1 for SQL Server Spatial) and the helper functions for Bing Maps in place the code for the tile rendering looks like this.

Imports System.Web
Imports System.Web.Services
Imports Microsoft.SqlServer.Types
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.IO
Imports System.Drawing.Drawing2D
Imports Microsoft.WindowsAzure.StorageClient
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.ServiceRuntime
Imports System.Net

Public Class SqlTileServer
Implements System.Web.IHttpHandler

Public quadkey As String
Public lvl As Integer
Public tileX As Integer
Public tileY As Integer
Public nwX As Integer
Public nwY As Integer
Public nwLon As Double
Public nwLat As Double
Public seLon As Double
Public seLat As Double
Public myPixelX As Integer
Public myPixelY As Integer
Public myBaseUri As String = http://YOUR_WINDOWS_AZURE_ACCOUNT.blob.core.windows.net/YOUR_WINDOWS_AZURE_CONTAINER
Public myBaseUriCDN As String = http://YOUR_WINDOWS_AZURE_CDN_TOKEN.vo.msecnd.net/YOUR_WINDOWS_AZURE_CONTAINER
Public myBlobUri As String
Public isCached As Boolean = False

Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
'Fetch URL-Parameters
quadkey = context.Request.Params("quadkey")

'Determine Zoom-Level
lvl = quadkey.Length

'Is file cached?
myBlobUri = myBaseUriCDN + "/" + quadkey + ".png"

Try
Dim req As WebRequest = WebRequest.Create(myBlobUri)
Dim response As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebRe-sponse)
Dim myImage As New Bit-map(System.Drawing.Image.FromStream(response.GetResponseStream))
isCached = True
WritePngToStream(myImage, context.Response.OutputStream)
Catch ex As Exception
isCached = False
'Get TileXY-Coordinates
QuadKeyToTileXY(quadkey, tileX, tileY, lvl)

'Get PixelXY of the North-West Corner of the tile
TileXYToPixelXY(tileX, tileY, nwX, nwY)

'Get Latitude and Longitude of the North-West Corner
PixelXYToLatLong(nwX, nwY, lvl, nwLat, nwLon)
PixelXYToLatLong(nwX + 256, nwY + 256, lvl, seLat, seLon)

'Retrieve Database Setting from web.config
Dim settings As ConnectionStringSettings = ConfigurationManag-er.ConnectionStrings("azure")

'Open a connection to the database
Dim myConn As New SqlConnection(settings.ConnectionString)
myConn.Open()
Dim cmd As New SqlCommand()

'Set SQL Parameters
cmd.Connection = myConn
cmd.CommandType = Data.CommandType.StoredProcedure
cmd.Parameters.Add(New SqlParameter("nwLon", nwLon))
cmd.Parameters.Add(New SqlParameter("nwLat", nwLat))
cmd.Parameters.Add(New SqlParameter("seLon", seLon))
cmd.Parameters.Add(New SqlParameter("seLat", seLat))

'Specify the stored procedure name as the command text
cmd.CommandText = "GetJapanRoads"

'Create a new image
Dim myBitmap As New Bitmap(256, 256, Imaging.PixelFormat.Format32bppArgb)

'Read data and draw image
Dim myReader As SqlDataReader = cmd.ExecuteReader()
While myReader.Read()
'Get the Geometry
Dim myGeom As SqlGeometry = myReader(0)

'Deteremine the number of Geometries in the object
Dim numGeom As Integer = myGeom.STNumGeometries
For j = 1 To numGeom
Dim curGeom As SqlGeometry = myGeom.STGeometryN(j)
Dim numPoints As Integer = curGeom.STNumPoints
Dim myPointArray(numPoints - 1) As Point
For i = 1 To numPoints
Dim myPoint As SqlGeometry = curGeom.STPointN(i)
Dim myLon As Double = myPoint.STX
Dim myLat As Double = myPoint.STY
LatLongToPixelXY(myLat, myLon, lvl, myPixelX, myPixelY)
myPointArray(i - 1) = New Point(myPixelX - nwX, myPixelY - nwY)
Next

'Draw the Graphics
Dim g As Graphics = Graphics.FromImage(myBitmap)
Dim myBrush As New SolidBrush(Color.Transparent)
Dim myPen As Pen
Select Case myReader(1).ToString
Case "#blue"
myPen = New Pen(Brushes.Green)
Case Else
myPen = New Pen(Brushes.Gray)
End Select
myPen.Width = 3
g.DrawLines(myPen, myPointArray)
Next
End While
myReader.Close()
myConn.Close()
WritePngToStream(myBitmap, context.Response.OutputStream)
End Try
End Sub

Private Sub WritePngToStream(ByVal image As Bitmap, ByVal outStream As Stream)
Dim writeStream As New MemoryStream()
image.Save(writeStream, Imaging.ImageFormat.Png)
If isCached = False Then
writeStream.Seek(0, SeekOrigin.Begin)
Dim MyBlob = Me.GetContainer().GetBlobReference(quadkey + ".png")
MyBlob.Properties.ContentType = "image/x-png"
MyBlob.UploadFromStream(writeStream)
End If
writeStream.WriteTo(outStream)
image.Dispose()
End Sub

We also need a helper function to access the Windows Azure Blob Storage.

Private Sub EnsureContainerExists()
Dim container = GetContainer()
container.CreateIfNotExist()
Dim permissions = container.GetPermissions()
permissions.PublicAccess = BlobContainerPublicAccessType.Container
container.SetPermissions(permissions)
End Sub

Private Function GetContainer() As CloudBlobContainer
' Get a handle on account, create a blob storage client and get container proxy
Dim account = CloudStorageAc-count.Parse("DefaultEndpointsProtocol=http;AccountName=YOUR_WINDOWS_AZURE_ACCOUNT_NAME;AccountKey=YOUR_WINDOWS_AZURE_ACCOUNT_KEY")
Dim client = account.CreateCloudBlobClient()
Return client.GetContainerReference("YOUR_CONTAINER")
End Function

Calling the Service from Bing Maps

Adding a tile layer in Bing Maps is very simple. Below you find the source code for a complete web page that calls our service.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=ja-JP"></script>
<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;
function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "YOUR_BING_MAPS_KEY",
center: new MM.Location(38.80131086471941, 139.5055539160967),
mapTypeId: "r",
zoom: 8,
enableClickableLogo: false,
enableSearchLogo: false
});

map.entities.push(
new MM.TileLayer({
mercator: new MM.TileSource({
uriConstructor: http://YOUR_WINDOWS_AZURE_ACCOUNT.cloudapp.net/BM-AJ-Japan/SqlTileServer.ashx?quadkey={quadkey}
}), opacity: .7
}));
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>

And that’s already it. In a very short time we were able to develop a service and deploy it as a scalable and highly available service in the cloud.

One final tip: If you also plan to use this service from other Silverlight applications in domains outside your Windows Azure environment you will need to publish a ClientAccessPolicy.xml file to the root of your Windows Azure hosted service. In our case that was necessary since we will access the service not only from the website itself but also from a Bing Map App.

Posted in AJAX, Bing Maps, Spatial, SQL Azure, Windows Azure | Tagged , , , , , , | 4 Comments