Geolocation Tracking with Beebotte

This tutorial presents how Beebotte can be used to build a Geolocation tracking application.

In this tutorial we will create a Web Application that uses HTML5's Geolocation API to publish and track the location of a mobile phone (or compatible device). We will use Beebotte client API to publish the device location to a predefined channel resource. Then, we will subscribe to that channel resource to receive published location updates in real time and display them on a Google Map.
This tutorial assumes that you have successfully registered to Beebotte and obtained your API and Secret keys.

By the end of this tutorial, you will learn how to:

  • Publish and subscribe data with Beebotte
  • Create an authentication endpoint for interacting with Beebotte
  • Play with Geolocation API
  • Use Google Maps API to place and update markers

Tutorial contents:

Requirements

We only need a device with a Geolocation capable browser. Geolocation is most accurate for devices with GPS as smartphones and tablets. For obvious privacy reasons, the device location is not available unless the user approves it.

Demo

Setting up the project

We will use Node.js for the application's backend. However, you can use an alternative technology of your choice. Porting the backend should not be an issue.

The structure of the project will be straightforward. We will create the following files:

  • app.js: This Node.js code creates a very basic Web Server. It implements the authentication endpoint that will grant your users access to Beebotte (required by the track.html and monitor.html web pages described below).
  • public/monitor.html: This page will show the Google Maps with a marker that will get updated everytime the location changes.
  • public/track.html: This page will simply activate/deactivate Geolocation tracking. When Geolocation is activated, the location of the device will be published to Beebotte on specific channel resource.
  • public/js/monitor.js: This Javascript file will include the source code that uses the Google Maps API to draw the map and Beebotte API to subscribe to the location channel resource to get notified once a new value is published to Beebotte.
  • public/js/track.js: This Javascript file will include the source code that contains the functions that retrieve the current location using the Geolocation API and publish it to Beebotte.

You can fork the project from its Github repository.

Initializing Beebotte

Beebotte provides a javascript library to add real-time communication between a browser (Web Page) and Beebotte platform. You need to first include the javascript client library in your HTML page.

<script src='socket.io-1.1.0.js'></script>
<script src='//beebotte.com/bbt.js' ></script>

Initialize Beebotte by indicating the API Key associated with your account, and the Authentication Endpoint to grant your users access to publish data to Beebotte and to subscribe to channel resources.

Remember, you are adding a realtime functionality to your application. Your users are not necessarily on beebotte. It is your responsibility to grant permissions to authenticated users.

var bbt = new BBT('API_KEY', {auth_endpoint: '/auth'});

Working with Geolocation API

We will use the Geolocation API of HTML5 to retreive the device location. Geolocation provides two mechanisms for retrieving location:

  • getCurrentPosition: gets the current position of the device.
  • watchPosition: registers a handler that will be called automatically everytime the position changes.

In this tutorial, we will use watchPosition to get position change updates. For one time position retrieval, you can use getCurrentPosition instead.

// Options object
var opts = {
  enableHighAccuracy: true,
  timeout: 5000, // timeout before returning the position
  maximumAge: 0 // get a fresh position
}

var tracker = null

function success (position) {
  // Do something with location object
}

function error (error) {
  alert('Error when getting Geolocation')
}

if (navigator.geolocation) {

  tracker = navigator.geolocation.watchPosition(
    success, // on success function
    error, // on error function
    opts // options
  )
} else {
  alert('Geolocation is not supported by this browser')
}

A watchPosition handler can be cancelled as follows:

if (tracker != null) {
  navigator.geolocation.clearWatch(tracker)
  tracker = null
}

Publishing location to Beebotte

Publishing the location messages from the client side requires write permission to be granted. Your application is responsible for granting permissions to your users, this can be accomplished by setting write to true in the subscription:

/* Request write access to the resource. Permissions are granted at the resource level */
bbt.subscribe({
  channel: 'private-mychannel',
  resource: 'location',
  read: false,
  write: true // We need to publish data
}, function(msg){
  // read access was set to false, we will never get here
});

The subscribe API will first contact the authentication end-point of your application to request write permission for the specified resource. If the permission is granted, it will send its permission grant to Beebotte. After this, you will be able to publish location messages as follows:

bbt.publish({
  channel: 'private-mychannel',
  resource: 'location'
}, {
  'latitude': latitude,
  'longitude': longitude
})

This will send a private message on the given channel resource.

Integrating both codes will be as follows:

// Options object
var opts = {
  enableHighAccuracy: true,
  timeout: 5000, // timeout before returning the position
  maximumAge: 0 // get a fresh position
}

function init () {

  var tracker = null

  var bbt = new BBT('YOUR_API_KEY', {
    auth_endpoint: '/auth'
  })

  bbt.subscribe({
    channel: 'private-mychannel', // replace this with your channel
    resource: 'location', // replace this with your channel GPS resource
    read: false, // No need to subscribe to data we are publishing
    write: true // We need to publish data
  }, function(message) {})

  function success (position) {

    var latitude = position.coords.latitude
    var longitude = position.coords.longitude

    console.log('publishing position: ', latitude, longitude)
    bbt.publish({
      channel: 'private-mychannel',
      resource: 'location'
    }, {
      'latitude': latitude,
      'longitude': longitude
    })
  }

  function error (error) {
    var errors = {
      1: 'Permission denied',
      2: 'Position unavailable',
      3: 'Request timeout'
    }

    alert('Error: ' + errors[error.code])
  }

  function startTracking () {

    if (navigator.geolocation) {
      tracker = navigator.geolocation.watchPosition(
        success,
        error,
        opts
      )
    } else {
      alert('Geolocation is not supported by this browser')
    }
  }

  function stopTracking () {
    if (tracker != null) {
      navigator.geolocation.clearWatch(tracker)
      tracker = null
    }
  }

  return {
    startTracking: startTracking,
    stopTracking: stopTracking
  }
}

Subscribing to location updates

In order to receive Geolocation messages published to your channel resource, you need to register to it first.

As we only want to receive new location messages, we will only request read access.

//Subscribe to a private channel resource
bbt.subscribe({
  channel: 'private-mychannel',
  resource: 'location',
  read: true,
  write: false
}, function(msg){
  //Do something with the received message
});

To subscribe to real-time data, you need to specify the channel and resource where the data is being published. In this example, we assume the data messages are private (that's why we added the private- prefix to the channel name). Subscribing to private channels requires explicit authorization.

Marking location on Google Maps

In order to display the received location on a map, we will use Google Maps API to create the map and to place a marker accordingly. The marker position needs to be updated whenever a new location is received.

We will update the subscribe code above to create the map and update the marker position. The complete code will be as follows:

function init () {

  var map = null
  var marker = null

  var bbt = new BBT('YOUR_API_KEY', {
    auth_endpoint: '/auth'
  })

  bbt.subscribe({
    channel: 'private-mychannel',
    resource: 'location',
    read: true,
    write: false
  }, function(msg) {
    console.log('received position: ', msg.data.latitude, msg.data.longitude)
    displayLocation(msg.data.latitude, msg.data.longitude)
  })

  // Initialize GoogleMaps map
  function initializeMap (position) {
    var options = {
      zoom: 17,
      center: position, // center on received position
      mapTypeId: google.maps.MapTypeId.PLAN
    }

    // Assuming 'map' is a valid element id in your DOM
    map = new google.maps.Map(document.getElementById('map'), options)
  }

  // Process received location
  function displayLocation(lat, lng) {

    // initialize map at first received location
    if (!map) {
      initializeMap(new google.maps.LatLng(lat, lng))
    }

    // remove previous marker if already exists
    // if marker exists remove it
    if (marker) {
      marker.setMap(null)
    }

    // set the marker to the new position
    marker = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: {
        path: google.maps.SymbolPath.CIRCLE,
        scale: 8,
        strokeColor: '#FF0000'
      },
      draggable: false,
      map: map
    })
  }
}

Implementing the Authentication Endpoint

The authentication endpoint is a mechanism that needs to be implemented on your backend to grant your users read and write access to your Beebotte channel resources. The authentication endpoint signs your users subscribe requests. These signatures are then attached to subscriptions sent to Beebotte. For details about the client authentication in Beebotte, you can refer to the following link.

In Node.js, the authentication endpoint can be implemented as follows:

const bbt = require('beebotte')

...

// Replace by your ACCESS and SECRET Keys
const bclient = new bbt.Connector({
  apiKey: 'YOUR_API_KEY',
  secretKey: 'YOUR_SECRET_KEY',
})

...

app.get( '/auth', function (req, res, next) {

  const channel = req.query.channel
  const resource = req.query.resource || '*'
  const ttl = req.query.ttl || 0
  const read = req.query.read === 'true'
  const write = req.query.write === 'true'
  const sid = req.query.sid

  if (!sid || !channel) {
    return res.status(403).send('Unauthorized')
  }

  const retval = bclient.sign(
    // string to sign
    `${sid}:${channel}.${resource}:ttl=${ttl}:read=${read}:write=${write}`
  )

  return res.send(retval)
})
...

Creating a channel to persist location messages

So far we considered location messages as transient. This means location will be routed through Beebotte from publishers to subscribers without being saved in Beebotte.

If you wish to save location data for future use (save where you parked your car, car trajects, etc.), you need to create a channel for that puspose.

In your account home page, click on Create New and follow the instructions to create your channel.


Now instead of publishing location data to Beebotte, we will write location data to Beebotte. This allows to save data for future use.

The only code modification you need is the following:

bbt.write({
  channel: 'private-mychannel',
  resource: 'location'
}, {
  'latitude': latitude,
  'longitude': longitude
})