Como trazar ruta GPS con Ionic3

Como trazar ruta GPS con Ionic3

[vc_row type=»in_container» scene_position=»center» text_color=»dark» text_align=»left» overlay_strength=»0.3″][vc_column column_padding=»no-extra-padding» column_padding_position=»all» background_color_opacity=»1″ background_hover_color_opacity=»1″ width=»1/1″][vc_column_text]

Aquí veremos como trazar correctamente la ruta que un usuario realiza con nuestra app de Ionic3, como por ejemplo hacen Runtastic o Wikiloc. Nos centraremos en crear un servicio que ofrezca a nuestra aplicación la ruta más exacta que hace el usuario, esto significa tener en cuenta que el GPS puede fallar, devolvernos puntos falsos, ir en zigzag, etc. Lo que vamos a hacer es aprender a filtrar todas las posiciones que nos facilita el GPS para intentar hacer un seguimiento del usuario lo más preciso posible.

Lo primero que haremos será preparar una aplicación de Ionic3 nueva:

$ ionic start gpstracker blank

Ya tenemos la estructura de nuestra app creada, ahora lo que haremos será añadir los plugins de Google Maps y de Geolocalización:

$ ionic plugin add cordova-plugin-geolocation
$ npm install --save @ionic-native/geolocation
$ ionic plugin add cordova-plugin-googlemaps --variable API_KEY_FOR_ANDROID="YOUR_ANDROID_API_KEY_IS_HERE" --variable API_KEY_FOR_IOS="YOUR_IOS_API_KEY_IS_HERE"
$ npm install --save @ionic-native/google-maps

Para los que usáis el plugin de Google Maps por primera vez, os recomendaría que leyerais el manual de instalación del mismo, ya que para obtener las claves de API hay que realizar varios pasos.

También necesitaremos una librería llamada simplify-js, que será la encargada de ir simplificando nuestra ruta mediante una combinación de los algoritmos Douglas Peucker y de Distancia Radial.

$ npm install --save simplify-js

Una vez tenemos los plugins listos, vamos a ponernos manos a la obra.

Primero de todo crearemos una carpeta models y dentro un archivo que vamos a llamar CustomGeoposition.ts, en donde tendremos nuestro propio modelo implementando Geoposition, al que le añadiremos dos variables, x e y, las cuales necesitarán la librería simplify-js para reconocer los puntos.

import {Geoposition} from "@ionic-native/geolocation";

export class CustomGeoposition implements Geoposition {
  public coords: Coordinates;
  public timestamp: number;
  public x;
  public y;


  constructor (geoposition: Geoposition){
    this.coords = geoposition.coords;
    this.timestamp = geoposition.timestamp;
    this.x = geoposition.coords.latitude;
    this.y = geoposition.coords.longitude;
  }

}

Una vez tenemos nuestro modelo auxiliar implementado, crearemos una carpeta services y dentro introducimos nuestro servicio de tracking, TrackingService.ts

 

import {Injectable} from "@angular/core";
import * as simplify from "simplify-js";
import {CustomGeoposition} from "../models/CustomGeoposition";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import {Observable} from "rxjs/Observable";
import {Geoposition} from "@ionic-native/geolocation";
import { Geolocation } from '@ionic-native/geolocation';

@Injectable()
export class TrackingService {
  // Here we set the min accuracy that we want to keep
  minAccuracy: number = 25;
  // This will determine how frequently we simplify and emit positions
  positionPile: number = 3;
  // We use positionArray to temporary store positions in order to simplify them later
  positionArray: CustomGeoposition[] = [];
  // We store the last position emitted here in order to avoid jumps that could be produced from simplification
  previousPosition: CustomGeoposition = null;
  // We define the Subject and the observable that will emit the position values to the subscribers
  private _actualPosition: BehaviorSubject<Geoposition> = new BehaviorSubject<Geoposition>(null);
  public actualPosition: Observable<Geoposition> = this._actualPosition.asObservable();
  constructor(private geolocation: Geolocation) {  }
  // This method should be called in order the service starts emitting positions
  public startTracking(): void {
    // We subscribe to the geolocation services
    this.geolocation.watchPosition({enableHighAccuracy: true}).subscribe(
      actualPosition => {
        // Check if the position is valid
        if(actualPosition.coords !== undefined){
          // Validate the position, if speed is 0 it means that user is stopped, so we don't need to track that, we also check the min accuracy
          if (actualPosition.coords.speed > 0 && actualPosition.coords.accuracy < this.minAccuracy) {
            //If the position is valid we push to our positions array
            this.positionArray.push(new CustomGeoposition(actualPosition));
            // Once we've reached the maximum stack of positions we process them
            if (this.positionArray.length > this.positionPile) {
              // Adding at first position the last position processed, to force simplification to start there
              if(this.previousPosition==null){
                this.previousPosition = this.positionArray[0];
              }
              this.positionArray.unshift(this.previousPosition);
              // Simplifying line
              let filteredPoints = simplify(this.positionArray, 15, true);
              // Resetting the array
              this.positionArray = [];
              filteredPoints.unshift(this.previousPosition);
              // We call a method that will emit the simplified positions
              this.parseFilteredPoints(filteredPoints);
            }
          }
        }
      }
    );
  }

  // This method gets the array of simplified positions and emits them to the subscribers
  parseFilteredPoints(filteredPoints: CustomGeoposition[]){
    filteredPoints.forEach(point => {
      this._actualPosition.next( point );
      this.previousPosition = point;
    });
  }
}

Nuestro servicio se encarga de suscribirse al servicio de posicionamiento, va recogiendo las posiciones que este emite, tratándolas y filtrándolas, y las va almacenando en una pila.

Cuando la pila llega a su límite, se simplifican los puntos almacenados en la misma y se emite la simplificación para todo lo que esté suscrito al Observable actualPosition.

Ahora, lo que tenemos que hacer es crear un componente que consuma nuestro servicio y que pinte en Google Maps la ruta que el usuario va trazando.

Lo haremos directamente sobre el Home page.

import {Component, OnInit} from '@angular/core';
import { NavController } from 'ionic-angular';
import {GoogleMap, GoogleMapsEvent, LatLng} from "@ionic-native/google-maps";
import {Geoposition} from "@ionic-native/geolocation";
import {TrackingService} from "../../services/TrackingService";
declare var plugin: any;

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage implements OnInit {

  map: GoogleMap;
  previousPosition: Geoposition;

  constructor(public navCtrl: NavController, private trackingService: TrackingService) {

  }

  ngOnInit(){
    this.setMap();
    this.loadMap();
    this.trackingService.startTracking();
    this.trackingService.actualPosition.subscribe(position => {
      if(position!=null){
        this.drawRoute(position);
      }
    })
  }

  setMap(){
    let controls: any = {compass: true, myLocationButton: false, indoorPicker: false, zoom: true, mapTypeControl: false, streetViewControl: false};
    this.map = new GoogleMap('map', {
      'backgroundColor': 'white',
      'controls': {
        'compass': controls.compass,
        'myLocationButton': controls.myLocationButton,
        'indoorPicker': controls.indoorPicker,
        'zoom': controls.zoom,
        'mapTypeControl': controls.mapTypeControl,
        'streetViewControl': controls.streetViewControl
      },
      'gestures': {
        'scroll': true,
        'tilt': true,
        'rotate': true,
        'zoom': true
      }
    });
  }

  loadMap(){
    this.map.on(GoogleMapsEvent.MAP_READY).subscribe(
      (map) => {
        map.clear();
        map.off();
        map.setMapTypeId(plugin.google.maps.MapTypeId.HYBRID);
        map.setMyLocationEnabled(true);
      },(error)=>{
        console.error("Error:", error);
      }
    );
  }

  drawRoute(pos:Geoposition):void{
    if(this.previousPosition==null){
      this.previousPosition = pos;
    }
    this.map.addPolyline(
      {
        points: [new LatLng(this.previousPosition.coords.latitude, this.previousPosition.coords.longitude), new LatLng(pos.coords.latitude, pos.coords.longitude)],
        visible: true,
        color:'#FF0000',
        width:4
      }).then(
      (res)=>{
        this.previousPosition = pos;
      }
    ).catch(
      (err)=>{
        console.log("err: "+JSON.stringify(err));
      }
    );
  }

}

¡Ya lo tenemos casi todo listo! Solamente faltaría añadir los providers necesarios al app.module, esto sería así:

 

providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    TrackingService,
    Geolocation
]

 

¡Ahora si, ya podemos probar nuestra app! Espero que os haya gustado y lo encontréis de utilidad. Podéis encontrar todo el código en mi repositorio Github.

 

CTA-Join-justdigital