项目作者: gonzalo123

项目描述 :
Playing with Ionic, Lumen, Firebase, Google maps, Raspberry Pi and background geolocation.
高级语言: TypeScript
项目地址: git://github.com/gonzalo123/locator.git
创建时间: 2018-02-17T16:53:06Z
项目社区:https://github.com/gonzalo123/locator

开源协议:

下载


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.
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 :).

  1. <ion-header>
  2. <ion-navbar>
  3. <ion-title>
  4. Ionic Blank
  5. </ion-title>
  6. </ion-navbar>
  7. </ion-header>
  8. <ion-header>
  9. <ion-toolbar [color]="toolbarColor">
  10. <ion-title>{{title}}</ion-title>
  11. <ion-buttons end>
  12. <ion-toggle color="light"
  13. checked="{{isBgEnabled}}"
  14. (ionChange)="changeWorkingStatus($event)">
  15. </ion-toggle>
  16. </ion-buttons>
  17. </ion-toolbar>
  18. </ion-header>
  19. <ion-content padding>
  20. </ion-content>

And the controller:

  1. import {Component} from '@angular/core';
  2. import {Platform} from 'ionic-angular';
  3. import {LocationTracker} from "../../providers/location-tracker/location-tracker";
  4. @Component({
  5. selector: 'page-home',
  6. templateUrl: 'home.html'
  7. })
  8. export class HomePage {
  9. public status: string = localStorage.getItem('status') || "-";
  10. public title: string = "";
  11. public isBgEnabled: boolean = false;
  12. public toolbarColor: string;
  13. constructor(platform: Platform,
  14. public locationTracker: LocationTracker) {
  15. platform.ready().then(() => {
  16. if (localStorage.getItem('isBgEnabled') === 'on') {
  17. this.isBgEnabled = true;
  18. this.title = "Working ...";
  19. this.toolbarColor = 'secondary';
  20. } else {
  21. this.isBgEnabled = false;
  22. this.title = "Idle";
  23. this.toolbarColor = 'light';
  24. }
  25. });
  26. }
  27. public changeWorkingStatus(event) {
  28. if (event.checked) {
  29. localStorage.setItem('isBgEnabled', "on");
  30. this.title = "Working ...";
  31. this.toolbarColor = 'secondary';
  32. this.locationTracker.startTracking();
  33. } else {
  34. localStorage.setItem('isBgEnabled', "off");
  35. this.title = "Idle";
  36. this.toolbarColor = 'light';
  37. this.locationTracker.stopTracking();
  38. }
  39. }
  40. }

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: https://ionicframework.com/docs/native/background-geolocation/

Here you can see read a very nice article explaining how to use the plugin with ionic: https://www.joshmorony.com/adding-background-geolocation-to-an-ionic-2-application/

As the article explains I’ve created a provider

  1. import {Injectable, NgZone} from '@angular/core';
  2. import {BackgroundGeolocation} from '@ionic-native/background-geolocation';
  3. import {CONF} from "../conf/conf";
  4. @Injectable()
  5. export class LocationTracker {
  6. constructor(public zone: NgZone,
  7. private backgroundGeolocation: BackgroundGeolocation) {
  8. }
  9. showAppSettings() {
  10. return this.backgroundGeolocation.showAppSettings();
  11. }
  12. startTracking() {
  13. this.startBackgroundGeolocation();
  14. }
  15. stopTracking() {
  16. this.backgroundGeolocation.stop();
  17. }
  18. private startBackgroundGeolocation() {
  19. this.backgroundGeolocation.configure(CONF.BG_GPS);
  20. this.backgroundGeolocation.start();
  21. }
  22. }

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 (one of mines :) 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.

  1. <?php
  2. require __DIR__ . '/../vendor/autoload.php';
  3. use App\Http\Middleware;
  4. use App\Model\Gps;
  5. use Illuminate\Contracts\Debug\ExceptionHandler;
  6. use Illuminate\Http\Request;
  7. use Laravel\Lumen\Application;
  8. use Laravel\Lumen\Routing\Router;
  9. (new Dotenv\Dotenv(__DIR__ . '/../env/'))->load();
  10. $app = new Application(__DIR__ . '/..');
  11. $app->singleton(ExceptionHandler::class, App\Exceptions\Handler::class);
  12. $app->routeMiddleware([
  13. 'auth' => Middleware\AuthMiddleware::class,
  14. 'basic' => Middleware\BasicAuthMiddleware::class,
  15. ]);
  16. $app->router->group(['middleware' => 'auth', 'prefix' => '/locator'], function (Router $route) {
  17. $route->post('/gps', function (Gps $gps, Request $request) {
  18. $requestData = $request->all();
  19. foreach ($requestData as $poi) {
  20. $gps->persistsData([
  21. 'date' => date('YmdHis'),
  22. 'serverTime' => time(),
  23. 'time' => $poi['time'],
  24. 'latitude' => $poi['latitude'],
  25. 'longitude' => $poi['longitude'],
  26. 'accuracy' => $poi['accuracy'],
  27. 'speed' => $poi['speed'],
  28. 'altitude' => $poi['altitude'],
  29. 'locationProvider' => $poi['locationProvider'],
  30. ]);
  31. }
  32. return 'OK';
  33. });
  34. });
  35. 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:

  1. <?php
  2. namespace App\Model;
  3. use Kreait\Firebase\Factory;
  4. use Kreait\Firebase\ServiceAccount;
  5. class Gps
  6. {
  7. private $database;
  8. private const FIREBASE_CONF = __DIR__ . '/../../conf/firebase.json';
  9. public function __construct()
  10. {
  11. $serviceAccount = ServiceAccount::fromJsonFile(self::FIREBASE_CONF);
  12. $firebase = (new Factory)
  13. ->withServiceAccount($serviceAccount)
  14. ->create();
  15. $this->database = $firebase->getDatabase();
  16. }
  17. public function getLast()
  18. {
  19. $value = $this->database->getReference('gps/poi')
  20. ->orderByKey()
  21. ->limitToLast(1)
  22. ->getValue();
  23. $out = array_values($value)[0];
  24. $out['formatedDate'] = \DateTimeImmutable::createFromFormat('YmdHis', $out['date'])->format('d/m/Y H:i:s');
  25. return $out;
  26. }
  27. public function persistsData(array $data)
  28. {
  29. return $this->database
  30. ->getReference('gps/poi')
  31. ->push($data);
  32. }
  33. }

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

That’s the api

  1. <?php
  2. $app->router->group(['middleware' => 'basic', 'prefix' => '/map'], function (Router $route) {
  3. $route->get('/', function (Gps $gps) {
  4. return view("index", $gps->getLast());
  5. });
  6. $route->get('/last', function (Gps $gps) {
  7. return $gps->getLast();
  8. });
  9. });

And the HTML

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
  5. <meta charset="utf-8">
  6. <title>Locator</title>
  7. <style>
  8. #map {
  9. height: 100%;
  10. }
  11. html, body {
  12. height: 100%;
  13. margin: 0;
  14. padding: 0;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div id="map"></div>
  20. <script>
  21. var lastDate;
  22. var DELAY = 60;
  23. function drawMap(lat, long, text) {
  24. var CENTER = {lat: lat, lng: long};
  25. var contentString = '<div id="content">' + text + '</div>';
  26. var infowindow = new google.maps.InfoWindow({
  27. content: contentString
  28. });
  29. var map = new google.maps.Map(document.getElementById('map'), {
  30. zoom: 11,
  31. center: CENTER,
  32. disableDefaultUI: true
  33. });
  34. var marker = new google.maps.Marker({
  35. position: CENTER,
  36. map: map
  37. });
  38. var trafficLayer = new google.maps.TrafficLayer();
  39. trafficLayer.setMap(map);
  40. infowindow.open(map, marker);
  41. }
  42. function initMap() {
  43. lastDate = '{{ $formatedDate }}';
  44. drawMap({{ $latitude }}, {{ $longitude }}, lastDate);
  45. }
  46. setInterval(function () {
  47. fetch('/map/last', {credentials: "same-origin"}).then(function (response) {
  48. response.json().then(function (data) {
  49. if (lastDate !== data.formatedDate) {
  50. drawMap(data.latitude, data.longitude, data.formatedDate);
  51. }
  52. });
  53. });
  54. }, DELAY * 1000);
  55. </script>
  56. <script async defer src="https://maps.googleapis.com/maps/api/js?key=my_google_maps_key&callback=initMap">
  57. </script>
  58. </body>
  59. </html>

And that’s all just enough for a weekend.