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