Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Ionic, Lumen, Firebase, Google Maps, Raspberry Pi, and Background Geolocation

DZone's Guide to

Ionic, Lumen, Firebase, Google Maps, Raspberry Pi, and Background Geolocation

Let's have some fun building a mobile app that displays geolocation data to a TV using Ionic, Google Maps, Firebase, and a Raspberry Pi.

· IoT Zone ·
Free Resource

I want to 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 played with Firebase and I want to learn a little bit. With this information, I will build a simple web application hosted on my Raspberry Pi. This web application will show a Google map with my last location. I will put this web application on my TV, and anyone in my house will see where I am.

That’s the idea. I want an MVP. First, the mobile application. For that, I will use the Ionic framework. I’m a 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>{{title}}</ion-title>
        <ion-buttons end>
            <ion-toggle color="light"
                        checked="{{isBgEnabled}}"
                        (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 the background color of the toolbar.

For background geolocation, I will use one Cordova plugin available as an 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 to 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 serve an HTML page with the map (using Google maps).

Mobile requests will be authenticated with a token in the header and the web application will use a basic HTTP authentication. Because of that, I will create two middlewares to handle 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 persist 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 = '{{ $formatedDate }}';
        drawMap({{ $latitude }}, {{ $longitude }}, 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. The source code is available on my GitHub account.
Topics:
iot ,mobile app development ,raspberry pi ,geolocation applications ,ionic ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}