Geo-fencing with Bing Spatial Data Services and Azure Mobile Services

Triggering certain actions such as sending notifications or alerts when a device enters or leaves an area is often referred to as geo-fencing. The geo-fence, the boundary of the area of interest, can be dynamic like a radius around a school or around your own device, it can be pre-defined such as a neighborhood, city or county, or it can be an area defined and digitized for a specific purpose.

For this example we narrow down the many different use cases to location-based notifications that leverage custom boundaries and address scenarios, such as

  • A parent who wants to receive a notification when his or her child arrives at school or is safely back home
  • Local news that want to send notifications or alerts to only the devices that are within a pre-defined area
  • Stores that want to advertise specials and promotions to individuals in the proximity of the store

To enable such scenarios we will leverage the Azure Mobile Services, which provide amongst others authentication, scheduling, and push-notifications, along with SDKs for Windows, Windows Phone, iOS, Android and HTML. No matter what platform, the Azure Mobile Service have you covered. We will also leverage the Bing Spatial Data Services (SDS), which allow you to batch-geocode and reverse-geocode your data, store your geospatial data in the cloud, and query them through a set of APIs. Additionally, the Bing SDS also provide an API to retrieve pre-defined boundaries.

In this end-to-end scenario we will look at the Bing SDS to store and query geo-fences, the tracked device, the Azure Mobile Services as the hub to process the tracked locations and send notifications as well as a Windows Store app to catch and display tile and toast notifications. Extending the notifications to a smart-watch could be an interesting add-on but we will save this for a later post.

The Geo-Fences

For this example I have created geo-fences that cover Microsoft offices in Bellevue, WA.

For the purpose of this blog post we will assume that we have already uploaded the geo-fences to the Bing SDS. Creating the geo-fences and uploading them to the Bing SDS is a topic that we can cover in a separate blog post.

To query the geo-fence that a location is in, we can execute a query such as

http://spatial.virtualearth.net/REST/v1/data/
  [Your Data Source ID]/
  [Your Data Source Name]/
  [Your Entity Name]
  ?SpatialFilter=intersects('POINT%20([longitude]%20[latitude])')
  &$format=json
  &key=[Your Bing Maps Key] 

The response will contain the geo-fence with its geography described as Well Known Text (WKT), its name, centroid and some other information as described here.

The array of results will be empty if the location does not fall into a geo-fence.

Setting Up the Azure Mobile Services

Azure Mobile Services come with a free tier for up 10 services. So we go ahead and create a new Mobile Service with a new database. If you already use your free database you can also add new tables to an existing database. A great tutorial on getting started with Azure Mobile Services is here. Once we have created the new service we can either download a starter app for several platforms or build a new app from scratch. We will do the latter and will require the URL to the service as well as the application key. We can get both from the dashboard in the Azure Portal.

While we are in the Azure Portal we also switch over to the Data tab and create a new table ‘Tracker’. For this tutorial we keep it simple and don’t change the default permissions.

We don’t need to add any columns either since the database schema is by default dynamic and Azure Mobile Services will automatically create columns based on the data it receives from the client.

We will come back later to the Azure Portal to monitor our apps and add additional functionality but for now we can move on to the mobile app for the devices that we are tracking. In this tutorial we will build a Windows Phone 8 app but as mentioned before Azure Mobile Services support also the Android and iOS platforms – directly through native code as well as through cross-platform solutions such as Xamarin or PhoneGap.

Creating the Windows Phone App

Rather than creating a tracking application from the start we will build upon a sample application for Windows Phone 8 that lets location tracking applications run in the background. For the purpose of this walk through we assume that you have completed the app as described in the tutorial.

We start modifying this existing application by adding the NuGet package for Azure Mobile Services. This will also automatically add a few dependencies.

Next we open the WMAppManifest.xml and add permit the app to retrieve the unique device ID in order to identify each tracked device.

In the App.xaml.vb we import the Azure Mobile Services namespace

Imports Microsoft.WindowsAzure.MobileServices

And then declare the connection to the Azure Mobile Service with its URL and App ID that we retrieved from the Azure Portal.

' declare the Azure Mobile Service Client
Public Shared MobileService As New MobileServiceClient("https://[your service].azure-mobile.net/", "[your app id]")

In Page2.xaml.vb we import a few namespaces and create a class ‘Tracker’ with properties that we want to submit to the Azure Mobile Services.

Imports Microsoft.WindowsAzure.MobileServices
Imports Microsoft.Phone.Info
Imports System.Threading

Public Class Tracker

    Public Property Id() As String
        Get
            Return m_Id
        End Get
        Set(value As String)
            m_Id = value
        End Set
    End Property
    Private m_Id As String

    <JsonProperty(PropertyName:="DeviceID")> _
    Public Property DeviceID() As String
        Get
            Return m_DeviceID
        End Get
        Set(value As String)
            m_DeviceID = value
        End Set
    End Property
    Private m_DeviceID As String

    <JsonProperty(PropertyName:="Lat")> _
    Public Property Lat() As Double
        Get
            Return m_Lat
        End Get
        Set(value As Double)
            m_Lat = value
        End Set
    End Property
    Private m_Lat As Double

    <JsonProperty(PropertyName:="Lon")> _
    Public Property Lon() As Double
        Get
            Return m_Lon
        End Get
        Set(value As Double)
            m_Lon = value
        End Set
    End Property
    Private m_Lon As Double

    <JsonProperty(PropertyName:="Alt")> _
    Public Property Alt() As Integer
        Get
            Return m_Alt
        End Get
        Set(value As Integer)
            m_Alt = value
        End Set
    End Property
    Private m_Alt As Integer

    <JsonProperty(PropertyName:="Course")> _
    Public Property Course() As Integer
        Get
            Return m_Course
        End Get
        Set(value As Integer)
            m_Course = value
        End Set
    End Property
    Private m_Course As Integer

    <JsonProperty(PropertyName:="Speed")> _
    Public Property Speed() As Integer
        Get
            Return m_Speed
        End Get
        Set(value As Integer)
            m_Speed = value
        End Set
    End Property
    Private m_Speed As Integer

    <JsonProperty(PropertyName:="VAcc")> _
    Public Property VAcc() As Integer
        Get
            Return m_VAcc
        End Get
        Set(value As Integer)
            m_VAcc = value
        End Set
    End Property
    Private m_VAcc As Integer

    <JsonProperty(PropertyName:="HAcc")> _
    Public Property HAcc() As Integer
        Get
            Return m_HAcc
        End Get
        Set(value As Integer)
            m_HAcc = value
        End Set
    End Property
    Private m_HAcc As Integer

    <JsonProperty(PropertyName:="GPSDate")> _
    Public Property GPSDate() As Date
        Get
            Return m_GPSDate
        End Get
        Set(value As Date)
            m_GPSDate = value
        End Set
    End Property
    Private m_GPSDate As Date

End Class

In the class Page2 we retrieve the table ‘Tracker’ from the Azure Mobile Service and declare a string-object that will hold our unique device ID. We will read this unique device ID into the string object when the page is first initialized.

Partial Public Class Page2
    Inherits PhoneApplicationPage

    Private trackTable As IMobileServiceTable(Of Tracker) = App.MobileService.GetTable(Of Tracker)()
    Private myDeviceID As String = ""

    Public Sub New()
        InitializeComponent()

        myDeviceID = Con-vert.ToBase64String(DeviceExtendedProperties.GetValue("DeviceUniqueId"))
    End Sub

Next we add a new dispatcher to the sub geolocator_PositionChanged as shown in the last line of the code-block below.

Private Sub geolocator_PositionChanged(sender As Geolocator, args As PositionChangedEven-tArgs)
        If Not App.RunningInBackground Then
            Dispatcher.BeginInvoke(Function()
                                       LatitudeTextBlock.Text = args.Position.Coordinate.Latitude.ToString("0.00")
                                       LongitudeTextBlock.Text = args.Position.Coordinate.Longitude.ToString("0.00")
                                   End Function)
        Else
            Dim toast As New Microsoft.Phone.Shell.ShellToast()
            toast.Content = args.Position.Coordinate.Latitude.ToString("0.00")
            toast.Title = "Location: "
            toast.NavigationUri = New Uri("/Page2.xaml", UriKind.Relative)

            toast.Show()
        End If

        Deployment.Current.Dispatcher.BeginInvoke(Function() MyPositionChanged(args))    End Sub

The function that we call from the dispatcher as well as the actual insert into the Azure Mobile Services will also be added to the class.

Private Async Function MyPositionChanged(ByVal args As PositionChangedEventArgs) As Tasks.Task
        Dim myLat As String = ""
        Try
            myLat = args.Position.Coordinate.Latitude.ToString("0.000000")
        Catch ex As Exception
        End Try
        Dim myLon As String = ""
        Try
            myLon = args.Position.Coordinate.Longitude.ToString("0.000000")
        Catch ex As Exception
        End Try
        Dim myAlt As String = ""
        Try
            myAlt = CInt(args.Position.Coordinate.Altitude).ToString
        Catch ex As Exception
        End Try
        Dim myCourse As String = ""
        Try
            myCourse = CInt(args.Position.Coordinate.Heading).ToString
        Catch ex As Exception
        End Try
        Dim mySpeed As String = ""
        Try
            mySpeed = CInt(args.Position.Coordinate.Speed).ToString
        Catch ex As Exception
        End Try
        Dim myVAcc As String = ""
        Try
            myVAcc = CInt(args.Position.Coordinate.SatelliteData.HorizontalDilutionOfPrecision).ToString
        Catch ex As Exception
            myVAcc = 0
        End Try
        Dim myHAcc As String = ""
        Try
            myHAcc = CInt(args.Position.Coordinate.SatelliteData.VerticalDilutionOfPrecision).ToString
        Catch ex As Exception
            myHAcc = 0
        End Try
        Dim myGPSDate As Date
        Try
            myGPSDate = args.Position.Coordinate.Timestamp.DateTime
        Catch ex As Exception
        End Try

        Dim thisTrackPoint As New Tracker With { _
            .DeviceID = myDeviceID,
            .Lat = myLat,
            .Lon = myLon,
            .Alt = myAlt,
            .Course = myCourse,
            .Speed = mySpeed,
            .VAcc = myVAcc,
            .HAcc = myHAcc,
            .GPSDate = myGPSDate
        }
        Await InsertTrackPoint(thisTrackPoint)

    End Function

    Private Async Function InsertTrackPoint(thisTrackPoint As Tracker) As Tasks.Task
        Await trackTable.InsertAsync(thisTrackPoint)
    End Function

For the purpose of this tutorial we’re done with this app. Let’s run it in the emulator and change the position a few times:

Now let’s double check in the Azure Portal. We open the table ‘Tracker’ under the tabulator data and verify that the columns have indeed been created and that we have some records in the table.

You will find the complete source code here on my OneDrive. Note: you will need to edit App.xaml.vb to add your own Azure Mobile Service URL as well as your own App ID.

Creating the Windows Store App

For the sake of brevity in this blog post we just download the sample application from the Azure Portal. I chose the JavaScript app.

Next we follow the tutorial Get started with push notifications in Mobile Services. Let’s check that everything works well so far by adding a few items in the sample app and verify that we receive notifications.

Bringing it All Together With the Scheduler

We will bring now all the different pieces together with our Scheduler in the Azure Mobile Services. Let’s create a new scheduler in the Azure Portal and plan to run it every 5 minutes but not activate it yet. We can always click on ‘Run One’ to test the code.

Now that we created our job we can navigate to the scheduler job and the script that we want to create. Again we keep it very simple here. There could be a lot of logic to filter messages by tracked device or type of alert (see Next Steps at the end of the tutorial Get started with push notifications in Mobile Services) but in this case we just verify if the last tracked location was inside a geo-fence, then we build toast- and tile-notifications each with a mini-map for that location created from the Bing Maps REST Imagery Services. You will find the full script below.

function DonkeyFence_job() {
    var sql = "select top 1 * from Tracker order by GPSDate desc";
    var trackTable = tables.getTable('Tracker');

    //Bing Maps
    var bmKey = "Your Bing Maps Key";
    var baseUrl = "http://spatial.virtualearth.net/REST/v1/data/";
    var dsIdGeofence = "Your Bing SDS Data Source ID";
    var dsNameGeofence = "Your Bing SDS Data Source Name";
    var entityNameGeofence = "Your Bing SDS Entity Name";

    var httpRequest = require('request');

    mssql.query(sql, {
        success: function (results) {
            if (results.length > 0) {
                //console.log('Lat:' + results[0].Lat.toString() + ' / Lon: ' + re-sults[0].Lon.toString());
                var geoFenceUrl =
                    baseUrl +
                    dsIdGeofence + "/" +
                    dsNameGeofence + "/" +
                    entityNameGeofence +
                    "?SpatialFilter=intersects('POINT (" + results[0].Lon.toString() + " " + results[0].Lat.toString() + ")')&$format=json&key=" + bmKey;
                //console.log(geoFenceUrl);
                httpRequest(geoFenceUrl, function (err, response, body) {
                    if (err) {
                        console.log(statusCodes.INTERNAL_SERVER_ERROR, 'Unable to connect to Bing Maps.');
                    }
                    else {
                        //console.log(JSON.parse(body));
                        var sdsResults = JSON.parse(body).d.results;
                        if (sdsResults.length > 0) {
                            var geoFenceName = sdsResults[0].Name;
                            var channelsTable = tables.getTable('Channels');
                            channelsTable.read({
                                success: function (devices) {
                                    devices.forEach(function (device) {
                                        push.wns.sendTileWidePeekImageAndText02(device.channelUri, {
                                            image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/AerialWithLabels/' +
                                                results[0].Lat.toString() + ',' + re-sults[0].Lon.toString() + '/16' +
                                                '?ms=310,150&key=' + bmKey,
                                            image1alt: 'Geofence',
                                            text1: 'Johannes',
                                            text2: 'arrived at',
                                            text3: geoFenceName
                                        }, {
                                            success: function (pushResponse) {
                                                console.log("Sent push:", pushResponse);
                                            }
                                        });

                                        push.wns.sendToastImageAndText04(device.channelUri, {
                                            image1src: 'http://dev.virtualearth.net/REST/v1/Imagery/Map/AerialWithLabels/' +
                                                results[0].Lat.toString() + ',' + re-sults[0].Lon.toString() + '/16' +
                                                '?ms=150,150&key=' + bmKey,
                                            image1alt: 'Geofence',
                                            text1: 'Johannes',
                                            text2: 'arrived at',
                                            text3: geoFenceName
                                        }, {
                                            success: function (pushResponse) {
                                                console.log("Sent push:", pushResponse);
                                            }
                                        });
                                    });
                                }
                            });
                        }
                        else {
                            console.log('No tracks found.');
                        }
                    }
                });
            }
            else {
                console.log('No tracks found.');
            }
        }
    });
}

Let’s start the mobile app and move a location into one of the geo-fences.

Now we Run the scheduler job once and see our tile and toast notifications come in.

Related blog posts:

And that’s it for now. Happy coding and we hope to see you soon back here.

- Bing Maps Team