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 , , , , , , , , | 7 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

Bing Maps AJAX Control version 7 – Adding Vector Data

After a quick overview on the new features in the Bing Maps AJAX Control v7 as well as a look at loading the map and displaying localized labels we are now moving on to the various options to add your own data. As before we will compare version 6.3 and version 7 and you will find live examples with source code on my Windows Azure site.

In version 6.3 vector data can be dropped as VEShape-objects of type Pushpin, Polyline or Polygon into the map directly or grouped into VEShapeLayer.

image

The concept is very much the same in version 7 but the namespace and object-model is different. In version 7 objects can as well be dropped as entities into the map directly or grouped into EntityCollections. In v6.3 VEShape-objects and VEShapeLayer had to be vector data while raster data were added as VETileLayer. In v7 vector and raster data are now all entities and we only distinguish the entity-type Pushpin, Polyline, Polygon or TileLayer.

image

Adding a Default Pushpin in v6.3

To add a Pushpin in v6.3 we need at a minimum the latitude and longitude where we want to place it on the map.

<!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 - v6.3</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=6.3"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(51.46117933094501, -0.9259434789419174), 18, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(51.46117933094501, -0.9259434789419174));
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding a Default Pushpin in v7

The basic idea is the same instead of “VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)” we use now Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(lat, lon)

<!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"></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(51.46117933094501, -0.9259434789419174),
mapTypeId: "a",
zoom: 18
});

var pushpin = new MM.Pushpin(new MM.Location(51.46117933094501, -0.9259434789419174));
map.entities.push(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Btw: I think that the new default Pushpin is definitely an improvement Smile

image

Customizing Pushpins in v6.3

Customizing Pushpins in v6.3 is quite simple with regards to changing icons but it would be a bit more fiddling to add labels to the pushpin or next to it (see for example Keith Kinnan’s blog). To use a custom icon we only need to add 1 line of code:

<!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 - v6.3</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=6.3"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(51.46117933094501, -0.9259434789419174), 18, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(51.46117933094501, -0.9259434789419174));
pushpin.SetCustomIcon('./IMG/pushpin.png');
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Customizing Pushpins in v7

In v7 you can define a much larger variety of PushpinOptions. Text and textOffset for example can simply be defined as a PushpinOption. So is the icon that you want to use on the map.

<!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"></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(51.46117933094501, -0.9259434789419174),
mapTypeId: "a",
zoom: 18
});

var pushpinOptions = { icon: './IMG/pushpin.png' };
var pushpin = new MM.Pushpin(new MM.Location(51.46117933094501, -0.9259434789419174), pushpinOptions);
map.entities.push(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding an InfoBox or Popup in v6.3

Now here comes the first major difference: In version 6.3 you can simply set additional Pushpin-properties such as a title or a description. Once you set at least a title, an InfoBox pops up when you mouse-over the Pushpin. Further customization of this InfoBox is possible by defining styles in the Cascading Style Sheet and clearing the default InfoBox styles.

In the example below we will increase the size of the InfoBox and load a Photosynth-collection in the description area.

<!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 - v6.3</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=6.3"></script>
<style type="text/css">
.customInfoBox-previewArea {
width:500px;
height:420px;
}
</style>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(32.362980181127874, -64.71483707427978), 17, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

//Set Style for InfoBox
map.ClearInfoBoxStyles();

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(32.362980181127874, -64.71483707427978));
pushpin.SetCustomIcon('./IMG/pushpin.png');
pushpin.SetTitle('Martello Tower');
pushpin.SetDescription('<iframe frameborder=0 src="http://photosynth.net/embed.aspx?cid=ba12ab48-6899-4d7f-b28c-624f5f7ff4f0&delayLoad=false&slideShowPlaying=false" width="500" height="400"></iframe>');
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding an InfoBox or Popup in v7

In version 7 there is at present no build-in mechanism to create InfoBoxes or pop-ups but here is how it could be done.

  1. we extend the properties of the Pushpin-object using the JavaScript prototype function in order to provide “title” and “description” for a Pushpin object
  2. We create a div-element that can we positioned, hidden or shown to show the InfoBox
  3. We add an event-handler to the map that opens the InfoBox whenever we click on the pin and another one that hides the InfoBox again when we change the map-view.

Admittedly this is a bit more effort in v7 than it is in v6.3 but once the framework is in place it can be re-used.

<!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"></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(32.362980181127874, -64.71483707427978),
mapTypeId: "a",
zoom: 17
});

var pushpinOptions = { icon: './IMG/pushpin.png' };
var pushpin = new MM.Pushpin(new MM.Location(32.362980181127874, -64.71483707427978), pushpinOptions);

//extend the pushpin class to store information for popup
MM.Pushpin.prototype.title = null;
pushpin.title = "Martello Tower";
MM.Pushpin.prototype.description = null;
pushpin.description = '<iframe frameborder=0 src="http://photosynth.net/embed.aspx?cid=ba12ab48-6899-4d7f-b28c-624f5f7ff4f0&delayLoad=false&slideShowPlaying=false" width="500" height="400"></iframe>';

//add a click event
pushpinClick = MM.Events.addHandler(pushpin, 'click', displayEventInfo);

//close the infobox when the map is panned or zoomed
MM.Events.addHandler(map, 'viewchangestart', closeInfoBox);

map.entities.push(pushpin);
}

function displayEventInfo(e) {
if (e.targetType = "pushpin") {
var pix = map.tryLocationToPixel(e.target.getLocation(), MM.PixelReference.control);
var ibTitle = document.getElementById('ibTitle');
ibTitle.innerHTML = e.target.title;
var ibDescription = document.getElementById('ibDescription');
ibDescription.innerHTML = e.target.description;
var infobox = document.getElementById('infoBox');
infobox.style.top = (pix.y - 60) + "px";
infoBox.style.left = (pix.x + 20) + "px";
infoBox.style.visibility = "visible";
document.getElementById('myMap').appendChild(infoBox);
}
}

function closeInfoBox() {
var infobox = document.getElementById('infoBox');
infoBox.style.visibility = "hidden";
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
<div id='infoBox' style="visibility:hidden; position:absolute; top:0px; left:0px; max-width:550px; z-index:10000; font-family:Verdana; font-size:12px">
<img src="IMG/leftbeak.png" alt="Left Beak" style="position:absolute;top:10px; left:0px;" />
<table style="width:510px; border:medium solid Orange; position:absolute;top:0px; left:22px; background-color:White">
<tr style="width:510px">
<td style="width:510px"><b id='ibTitle'></b></td>
<td align="right" valign="top"><img src="IMG/close.png" alt="close" onclick="closeInfoBox()" /></td>
</tr>
<tr>
<td colspan="2"><a id='ibDescription'></a></td>
</tr>
</table>
</div> 
</body>
</html>

image

As mentioned before you will find these and other examples – including Polylines and Polygons here.

Previous Postings on the Bing Maps AJAX Control v7

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

Bing Maps AJAX Control version 7 – Loading the Map & Localized Labels

In a previous blog post I had introduced some of the new features in the Bing Maps AJAX Control version 7. I had mentioned that we are very excited about this new version but also that it is a complete redesign with breaking changes. Don’t worry we have not planned for the end-of-life of version 6.3 yet and when we make an announcement it will be with ample time to migrate your applications. However, since v7 is indeed new and different I would like to start with a series of blog posts that walk you through some of the more frequently used functions in comparison to v6.3. All blog posts will be accompanied by interactive examples which demonstrate functionality and provide access to the source code. You will find this example on my Windows Azure account here.

Version 6.3
When you load the Bing Maps AJAX Control in version 6.3 you can add market-parameters for localized labels as well as various options for example to set the dashboard-colour and to use the new map style.

<!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 - v6.3</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=6.3&mkt=de-DE"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(54.87350326912944, 15.333815098500053), 4, 'r', false, VEMapMode.Mode2D, true, 0, mapOptions);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>

Version 7.0
In version 7 you have the same options to provide market-parameters for localzed labels but namespace and object structure have changed. The Bing Maps Key which was previously technically not enforced is now a mandatory requirement. If you don’t have such a Bing Maps Key yet you can get one at the Bing Maps Account Centre.

<!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=de-DE"></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(54.87350326912944, 15.333815098500053),
mapTypeId: "r",
zoom: 4
});
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>


 

Posted in AJAX, Bing Maps | Tagged , , , , , | 5 Comments

Did you know…

… if you lost your Windows Phone 7 you can use Bing Maps & http://windowsphone.live.com to

  • map its location
    image
  • Ring it
  • Lock it
  • Erase it

Posted in AJAX, Bing Maps, Windows Phone 7 | Tagged , , , | Leave a comment