Note: I'm migrating from gonzalo123.com to here. When I finish I'll swap the DNS to here. The "official" blog will be always gonzalo123.com

      Playing with Ionic, Lumen, Firebase, Google maps, Raspberry Pi and background geolocation

      I wanna do a simple pet project. The idea is to build a mobile application. This application will track my GPS location and send this information to a Firebase database. I’ve never play with Firebase and I want to learn a little bit. With this information I will build a simple web application hosted in my Raspberry Pi. This web application will show a Google map with my last location. I will put this web application in my TV and anyone in my house will see where I am every time.

      That’s the idea. I want a MVP. First the mobile application. I will use ionic framework. I’m big fan of ionic.

      The mobile application is very simple. It only has a toggle to activate-deactivate the background geolocation (sometimes I don’t want to be tracked :).

      <ion-header>
          <ion-navbar>
              <ion-title>
                  Ionic Blank
              </ion-title>
          </ion-navbar>
      </ion-header>
       
      <ion-header>
          <ion-toolbar [color]="toolbarColor">
              <ion-title></ion-title>
              <ion-buttons end>
                  <ion-toggle color="light"
                              checked=""
                              (ionChange)="changeWorkingStatus($event)">
                  </ion-toggle>
              </ion-buttons>
          </ion-toolbar>
      </ion-header>
       
      <ion-content padding>
      </ion-content>
      

      And the controller:

      import {Component} from '@angular/core';
      import {Platform} from 'ionic-angular';
      import {LocationTracker} from "../../providers/location-tracker/location-tracker";
       
      @Component({
          selector: 'page-home',
          templateUrl: 'home.html'
      })
      export class HomePage {
          public status: string = localStorage.getItem('status') || "-";
          public title: string = "";
          public isBgEnabled: boolean = false;
          public toolbarColor: string;
       
          constructor(platform: Platform,
                      public locationTracker: LocationTracker) {
       
              platform.ready().then(() => {
       
                      if (localStorage.getItem('isBgEnabled') === 'on') {
                          this.isBgEnabled = true;
                          this.title = "Working ...";
                          this.toolbarColor = 'secondary';
                      } else {
                          this.isBgEnabled = false;
                          this.title = "Idle";
                          this.toolbarColor = 'light';
                      }
              });
          }
       
          public changeWorkingStatus(event) {
              if (event.checked) {
                  localStorage.setItem('isBgEnabled', "on");
                  this.title = "Working ...";
                  this.toolbarColor = 'secondary';
                  this.locationTracker.startTracking();
              } else {
                  localStorage.setItem('isBgEnabled', "off");
                  this.title = "Idle";
                  this.toolbarColor = 'light';
                  this.locationTracker.stopTracking();
              }
          }
      }
      

      As you can see, the toggle button will activate-deactivate the background geolocation and it also changes de background color of the toolbar.

      For background geolocation I will use one cordova plugin available as ionic native plugin

      Here you can see read a very nice article explaining how to use the plugin with ionic. As the article explains I’ve created a provider

      import {Injectable, NgZone} from '@angular/core';
      import {BackgroundGeolocation} from '@ionic-native/background-geolocation';
      import {CONF} from "../conf/conf";
       
      @Injectable()
      export class LocationTracker {
          constructor(public zone: NgZone,
                      private backgroundGeolocation: BackgroundGeolocation) {
          }
       
          showAppSettings() {
              return this.backgroundGeolocation.showAppSettings();
          }
       
          startTracking() {
              this.startBackgroundGeolocation();
          }
       
          stopTracking() {
              this.backgroundGeolocation.stop();
          }
       
          private startBackgroundGeolocation() {
              this.backgroundGeolocation.configure(CONF.BG_GPS);
              this.backgroundGeolocation.start();
          }
      }
      

      The idea of the plugin is send a POST request to a url with the gps data in the body of the request. So, I will create a web api server to handle this request. I will use my Raspberry Pi3. to serve the application. I will create a simple PHP/Lumen application. This application will handle the POST request of the mobile application and also will serve a html page with the map (using google maps).

      Mobile requests will be authenticated with a token in the header and web application will use a basic http authentication. Because of that I will create two middlewares to handle the the different ways to authenticate.

      <?php
      require __DIR__ . '/../vendor/autoload.php';
       
      use App\Http\Middleware;
      use App\Model\Gps;
      use Illuminate\Contracts\Debug\ExceptionHandler;
      use Illuminate\Http\Request;
      use Laravel\Lumen\Application;
      use Laravel\Lumen\Routing\Router;
       
      (new Dotenv\Dotenv(__DIR__ . '/../env/'))->load();
       
      $app = new Application(__DIR__ . '/..');
      $app->singleton(ExceptionHandler::class, App\Exceptions\Handler::class);
      $app->routeMiddleware([
          'auth'  => Middleware\AuthMiddleware::class,
          'basic' => Middleware\BasicAuthMiddleware::class,
      ]);
       
      $app->router->group(['middleware' => 'auth', 'prefix' => '/locator'], function (Router $route) {
          $route->post('/gps', function (Gps $gps, Request $request) {
              $requestData = $request->all();
              foreach ($requestData as $poi) {
                  $gps->persistsData([
                      'date'             => date('YmdHis'),
                      'serverTime'       => time(),
                      'time'             => $poi['time'],
                      'latitude'         => $poi['latitude'],
                      'longitude'        => $poi['longitude'],
                      'accuracy'         => $poi['accuracy'],
                      'speed'            => $poi['speed'],
                      'altitude'         => $poi['altitude'],
                      'locationProvider' => $poi['locationProvider'],
                  ]);
              }
       
              return 'OK';
          });
      });
       
      return $app;
      

      As we can see the route /locator/gps will handle the post request. I’ve created a model to persists gps data in the firebase database:

      <?php
       
      namespace App\Model;
       
      use Kreait\Firebase\Factory;
      use Kreait\Firebase\ServiceAccount;
       
      class Gps
      {
          private $database;
       
          private const FIREBASE_CONF = __DIR__ . '/../../conf/firebase.json';
       
          public function __construct()
          {
              $serviceAccount = ServiceAccount::fromJsonFile(self::FIREBASE_CONF);
              $firebase       = (new Factory)
                  ->withServiceAccount($serviceAccount)
                  ->create();
       
              $this->database = $firebase->getDatabase();
          }
       
          public function getLast()
          {
              $value = $this->database->getReference('gps/poi')
                  ->orderByKey()
                  ->limitToLast(1)
                  ->getValue();
       
              $out                 = array_values($value)[0];
              $out['formatedDate'] = \DateTimeImmutable::createFromFormat('YmdHis', $out['date'])->format('d/m/Y H:i:s');
       
              return $out;
          }
       
          public function persistsData(array $data)
          {
              return $this->database
                  ->getReference('gps/poi')
                  ->push($data);
          }
      }
      

      The project is almost finished. Now we only need to create the google map.

      That’s the api

      <?php
      $app->router->group(['middleware' => 'basic', 'prefix' => '/map'], function (Router $route) {
          $route->get('/', function (Gps $gps) {
              return view("index", $gps->getLast());
          });
       
          $route->get('/last', function (Gps $gps) {
              return $gps->getLast();
          });
      });
      

      And the HTML

      <!DOCTYPE html>
      <html>
      <head>
          <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
          <meta charset="utf-8">
          <title>Locator</title>
          <style>
              #map {
                  height: 100%;
              }
       
              html, body {
                  height: 100%;
                  margin: 0;
                  padding: 0;
              }
          </style>
      </head>
      <body>
      <div id="map"></div>
      <script>
       
          var lastDate;
          var DELAY = 60;
       
          function drawMap(lat, long, text) {
              var CENTER = {lat: lat, lng: long};
              var contentString = '<div id="content">' + text + '</div>';
              var infowindow = new google.maps.InfoWindow({
                  content: contentString
              });
              var map = new google.maps.Map(document.getElementById('map'), {
                  zoom: 11,
                  center: CENTER,
                  disableDefaultUI: true
              });
       
              var marker = new google.maps.Marker({
                  position: CENTER,
                  map: map
              });
              var trafficLayer = new google.maps.TrafficLayer();
       
              trafficLayer.setMap(map);
              infowindow.open(map, marker);
          }
       
          function initMap() {
              lastDate = '';
              drawMap(, , lastDate);
          }
       
          setInterval(function () {
              fetch('/map/last', {credentials: "same-origin"}).then(function (response) {
                  response.json().then(function (data) {
                      if (lastDate !== data.formatedDate) {
                          drawMap(data.latitude, data.longitude, data.formatedDate);
                      }
                  });
              });
          }, DELAY * 1000);
      </script>
      <script async defer src="https://maps.googleapis.com/maps/api/js?key=my_google_maps_key&callback=initMap">
      </script>
      </body>
      </html>
      

      And that’s all just enough for a weekend. Source code is available in my github account

      comments powered by Disqus