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>{{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 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 = '{{ $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. Source code is available in my github account
Posted on February 19, 2018, in js, php, Technology and tagged Firebase, google maps, ionic, Lumen, php, Raspberry Pi. Bookmark the permalink. 1 Comment.
Pingback: Brincando com Ionic, Lumen, Firebase, Google Maps, Raspberry Pi e geolocalização de background -