Building an Ionic Geolocation Tracker with Google Map and Track Drawing Last update: 2018-05-01

Building an Ionic Geolocation Tracker with Google Map and Track Drawing

Within your Ionic App you can easily track the position of your users with the Ionic Geolocation plugin. It is also easy to add a Google Map to your app, so why not combine these 2 features into a useful app?

A few weeks ago members of the Ionic Academy brought up the idea of a geolocation tracking app like Runtastic or other apps you might know. And this can be build with basic plugins and not super hard code!

In this tutorial we will build an Ionic Geolocation Tracker which can track the route of users, display the path the user has walked inside a Google map and finally also save those information to display previous runs. It’s gonna be fun!

ionic-geolocation-tracker

Starting our Geolocation Tracker

We start by creating a blank new Ionic app and install the Geolocation plugin and also the SQLite storage so Ionic Storage uses a real database inside our final app. Right now we already add a variable to the Geolocation plugin but we need another fix for iOS later on as well:

ionic start devdacticLocationTracker blank
cd devdacticLocationTracker
ionic cordova plugin add cordova-plugin-geolocation --variable GEOLOCATION_USAGE_DESCRIPTION="To track your walks"
ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic-native/geolocation

As we want to use Google Maps we also need to load the SDK and therefore we can simply add this directly to our src/index.html right before the cordova import:

<script src="http://maps.google.com/maps/api/js?key=YOUR_API_KEY_HERE"></script>

Also, you need to insert your own key here (simply replace YOUR_API_KEY_HERE) but you can easily generate an API key by going to this page. Now we also need to load our plugins, therefore make sure to import and connect them inside your app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { Geolocation } from '@ionic-native/geolocation';
import { IonicStorageModule } from '@ionic/storage';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicStorageModule.forRoot(),
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Geolocation
  ]
})
export class AppModule {}

Finally, I already mentioned that we need some fix for iOS. If you build your app you will get a warning that you haven’t added the according information to your *.plist why you want to use the Geolocation, but you can simply add this little snippet to your config.xml which will always add the NSLocationWhenInUseUsageDescription when you build the iOS app:

<edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
    <string>This App wants to track your location</string>
</edit-config>

Basic View and Google Map Positions

We’ll split the next part into 2 sections, and we start with the basic view of our Google Map. Our view mainly consists of a button to start and stop our tracking, the actual Google map and also a list of previous routes which can be loaded once again into our map just lake you are used to it from your running apps!

Go ahead and start with the view by changing the pages/home/home.html:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      GPS Tracking
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

  <button ion-button full icon-left (click)="startTracking()" *ngIf="!isTracking">
      <ion-icon name="locate"></ion-icon>
      Start Tracking
    </button>
  <button ion-button full color="danger" icon-left (click)="stopTracking()" *ngIf="isTracking">
      <ion-icon name="hand"></ion-icon>
      Stop Tracking
  </button>

  <div #map id="map"></div>

  <ion-list>
    <ion-list-header>Previous Tracks</ion-list-header>
    <ion-item *ngFor="let route of previousTracks">
      {{ route.finished | date }}, {{ route.path.length }} Waypoints
      <button ion-button clear item-end (click)="showHistoryRoute(route.path)">View Route</button>
    </ion-item>
  </ion-list>
</ion-content>

Nothing fancy yet, the map is really just one element and we’ll do all the logic from inside the class but first we have to add some styling to make the map look good inside our app.

Therefore, add this bit of styling inside your pages/home/home.scss:

page-home {
    #map {
        width: 100%;
        height: 300px;
      }
}

We want to load the map on start of the app and also focus it on our current position so we will notice when the track drawing begins.

First of all we need a reference to our map inside the view using @ViewChild and then we can create a new map using new google.maps.Map with additional options. In this case we disable the different view options displayed on the map and pick the Roadmap style for the map.

We’ll also keep track of this new map variable as we need it later to draw our tracks!

After the map initialisation we also try to get the current position of the user with the geolocation plugin and if we get back the coordinates we can focus our map on this spot! We could also display a marker here but that’s left for the reader.

Go ahead and change your pages/home/home.ts to:

import { Component, ViewChild, ElementRef } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Geolocation } from '@ionic-native/geolocation';
import { Subscription } from 'rxjs/Subscription';
import { filter } from 'rxjs/operators';
import { Storage } from '@ionic/storage';

declare var google;

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  @ViewChild('map') mapElement: ElementRef;
  map: any;
  currentMapTrack = null;

  isTracking = false;
  trackedRoute = [];
  previousTracks = [];

  positionSubscription: Subscription;

  constructor(public navCtrl: NavController, private plt: Platform, private geolocation: Geolocation, private storage: Storage) { }

  ionViewDidLoad() {
    this.plt.ready().then(() => {
      this.loadHistoricRoutes();

      let mapOptions = {
        zoom: 13,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        mapTypeControl: false,
        streetViewControl: false,
        fullscreenControl: false
      }
      this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);

      this.geolocation.getCurrentPosition().then(pos => {
        let latLng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
        this.map.setCenter(latLng);
        this.map.setZoom(16);
      }).catch((error) => {
        console.log('Error getting location', error);
      });
    });
  }

  loadHistoricRoutes() {
    this.storage.get('routes').then(data => {
      if (data) {
        this.previousTracks = data;
      }
    });
  }
}

You might have noticed that we also added declare var google at the top which prevents TypeScript errors later inside our code.

Right now, our app is also loading historic routes from the storage using loadHistoricRoutes but of course that’s still empty. Later this will be an array of objects, so let’s work on adding data to that array!

Tracking Geolocation and Drawing on Google Map

We now need to do 2 things:

  • Watch the current GPS position of the user
  • Draw a path on our map

After that we also need to add the tracked route to the storage, but that’s the easiest part of our app.

Inside the startTracking function we subscribe to the position of the user which means we automatically get new coordinates from the Observable!

We can then use those values to first of all add a new entry to our trackedRoute array (to have a reference to all the coordinates of the walk) and then call the redrawPath if we want to immediately update our map.

Inside that function we also check a variable currentMapTrack which always holds a reference to the current path on our map!

To actually draw we can use a Polyline which can have a few options and most important the path which expects an array of objects like [{ lat: '', lng: '' }]

This function will then automatically connect the different coordinates and draw the line, and finally after we have constructed this line we call setMap on this line which draws it onto our map!

Now go ahead and add these 2 functions below the previous functions inside pages/home/home.ts:

startTracking() {
    this.isTracking = true;
    this.trackedRoute = [];

    this.positionSubscription = this.geolocation.watchPosition()
      .pipe(
        filter((p) => p.coords !== undefined) //Filter Out Errors
      )
      .subscribe(data => {
        setTimeout(() => {
          this.trackedRoute.push({ lat: data.coords.latitude, lng: data.coords.longitude });
          this.redrawPath(this.trackedRoute);
        }, 0);
      });

  }

  redrawPath(path) {
    if (this.currentMapTrack) {
      this.currentMapTrack.setMap(null);
    }

    if (path.length > 1) {
      this.currentMapTrack = new google.maps.Polyline({
        path: path,
        geodesic: true,
        strokeColor: '#ff00ff',
        strokeOpacity: 1.0,
        strokeWeight: 3
      });
      this.currentMapTrack.setMap(this.map);
    }
  }

The last missing function is when we stop the tracking. Then we need to store the information of the previous walk and also clear our map again.

Along with the tracked route we also store the current date to have some sort of information on the actual element, of course a running app would allow to review and share your path and then perhaps add more information like the time it took, distance covered and so on.

For now though this is enough, and the last function of this tutorial is the loading of historic routes - which is actually only a call to our redraw function with the information stored in the object! Therefore, wrapn this up by adding the last functions to your pages/home/home.ts:

stopTracking() {
  let newRoute = { finished: new Date().getTime(), path: this.trackedRoute };
  this.previousTracks.push(newRoute);
  this.storage.set('routes', this.previousTracks);

  this.isTracking = false;
  this.positionSubscription.unsubscribe();
  this.currentMapTrack.setMap(null);
}

showHistoryRoute(route) {
  this.redrawPath(route);
}

That’s it, now make sure to run your app on a real device or at least a simulator where you can change the location to test out the functionality like I did within the initial Gif at the top of this tutorial!

Conclusion

An app with features like this was requested by members of the Ionic Academy and it’s actually pretty cool and easy to implement this kind of geolocation tracker like popular apps do it!

If you have developed any apps with the help of this tutorial and the plugins definitely let us know in the comments below.

You can also find a video version of this article below.