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

This entry was posted in Bing Maps, Mobile Services, Windows 8, Windows Azure, Windows Phone, Windows Store and tagged , , , , , , , , . Bookmark the permalink.

8 Responses to Windows Azure Mobile Services, Maps & More

  1. Pingback: Windows Azure Community News Roundup (Edition #40) - Windows Azure - Site Home - MSDN Blogs

  2. Pingback: Windows Azure Community News Roundup (Edition #40) - Windows Azure Blog

  3. Pingback: Windows Azure Community News Roundup (Edition #40) - Platform as a Service Magazine

  4. Pingback: Windows Azure Community News Roundup (Edition #40) | MSDN Blogs

  5. Pingback: Windows Azure and Cloud Computing Posts for 10/12/2012+ - Windows Azure Blog

  6. Pingback: Links of the week | Jan @ Development

  7. r aditya says:

    Very nice tutorial.. :D

  8. Pingback: Traffic Notifications with Bing Maps & Windows Azure Mobile Services | Hannes's Virtual Earth

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s