Profundizando en un stack tecnológico para seguimiento en tiempo real utilizando Google Maps, Angular y Firebase


Las aplicaciones de tipo “Uber Like” (como Uber) o “Rappi Like” se han venido popularizando. Una gran cantidad de soluciones tecnológicas hoy en día tratan de emular el comportamiento o la funcionalidad de este tipo de aplicaciones. Esto se debe a que algunas compañías empiezan a incursionar en el mundo del delivery o los ecommerce y tratan de atarse a casos de éxito ya establecidos. Para nadie es un secreto que es increíble poder ver al conductor de Uber en tiempo real acercarse a nuestra ubicación y poder estar preparados casi con exactitud para subirnos al vehículo. De igual manera, cuando ordenamos un domicilio a algún restaurante o a alguna farmacia nos gusta saber siempre cuando la persona encargada de recoger el domicilio lo hizo y poder visualizar dónde está en cada momento. Por eso, en este artículo les enseñaremos cómo funcionan las aplicaciones que hacen seguimiento en tiempo real de un objeto y aprenderemos a visualizar utilizando Google Maps, Angular y Firebase.

Empecemos por lo básico. En este tipo de aplicaciones siempre tenemos un emisor (conductor) y un receptor (cliente), como en un proceso comunicativo. La diferencia es que el emisor va a estar en constante movimiento y es la ubicación de éste lo único que le interesa al receptor. Sin embargo, tenemos en el medio algún tipo de plataforma que también visualice y vele porque no ocurra ningún error en el medio; por lo tanto, tendríamos un esquema de tres entidades::


Figura 1. Tres entidades para el seguimiento en tiempo real
Este esquema permite que el emisor emita su ubicación bajo determinadas condiciones a la entidad central que por ahora llamaremos una base de datos y esta estará disponible para ser consultada por el receptor cuando lo desee y refleja con exactitud la última posición conocida del emisor.
Listemos entonces las operaciones que necesitamos para cada una de las entidades: enviarPosicionCada(xTiempo ó x Distancia), guardarUltimaPosicion(), notificarReceptor() y actualizarMapa(), Por tanto podríamos reflejar estas consideraciones en nuestro diagrama de la siguiente manera:
Figura 2. Operaciones de las entidades para el seguimiento en tiempo real

Tanto si queremos hacer la aplicación del domiciliario/driver o la aplicación del usuario que solicita los domicilios, es clave poder detectar las posición de ambos. También vamos a tener que programar una función que se encargue de actualizar dicha posición para el caso del domiciliario según nuestros requerimientos. Finalmente el usuario deberá ser capaz de ver en todo momento dónde está el domiciliario y la ruta que está siguiendo y para esta parte utilizaremos Google Maps en Angular. Listemos entonces estas funcionalidades:
  1. Geolocalización del cliente y del domiciliario/conductor.
  2. Envió cada x tiempo la posición del domiciliario a una base de datos
  3. Cada vez que se actualice la posición del domiciliario enviarla a la aplicación del cliente para que el mapa se actualice
  4. Como usuario permitir visualizar el mapa con la posición del domiciliario y mi posición.

Geolocalización con Javascript

Exploremos entonces como Javascript mediante la API de geolocalización del navegador nos permite detectar dónde está el usuario:
window.navigator.geolocation.
       getCurrentPosition(
             funcionQueLlamoEnCasoDeExito,
             functionQueLlamoEnCasoDeFracaso)

Nota que en Google Chrome puedes acceder muy fácilmente a la ubicación de quien abre tu aplicación con esas simples instrucciones de código javascript. Nota que debes tener definidas las funciones para el caso en el que el GPS obtenga la posición o para los casos en que no obtenga la posición, la función que llamas en caso de éxito recibirá como parámetros las coordenadas que el GPS pudo encontrar, de la siguiente manera:

funcionQueLlamoEnCasoDeExito(position) {
  console.log(position)
  {…
       coords: {
         latitude: 6.208488,
         longitude: 75.563577
       }
   …
  }
}

Y la función que llamarás en caso de error simplemente recibirá como parámetro el error que obtuvo el GPS:

funcionQueLlamoEnCasoDeFracaso(error) {// Haz algo}

Los tipos de errores más probables son que el usuario no le dió permiso el navegador de acceder a su localización o que definitivamente el GPS no pudo encontrar dónde estamos. Esto puede ser ocasionado si estamos en un sótano desde un celular o si el computador donde estemos no tiene conexión a internet. Veamos ahora qué uso le podemos dar a dicha posición para que en ambas aplicaciones se pueda tanto al cliente como al domiciliario/conductor.
Enviando y almacenando la posición del domiciliario
Ahora que sabemos cómo localizar al usuario a través del navegador usando el acceso al GPS y la geolocalización que se nos provee, necesitamos hacer algo con dicha posición. Un primer borrador de cómo se vería dicha función sería algo similar a esto:

async function gpsTracking() {
  const coords = await getCurrentPosition();
  await sendToServer(coords)
  // optional delay;
  gpsTracking();
}

Esta función obtendrá las coordenadas y tan pronto como las obtenga las enviará al servidor, luego esperará un poco y se volverá a ejecutar indefinidamente mientras la aplicación esté abierta. Con Firebase es muy simple hacer esto si tenemos instalado y configurado firebase en nuestro proyecto bastará con modificar un poco nuestra función para guardar las coordenadas en una colección de firestore o en un nuevo objeto de la base de datos on realtime de Firebase.

await this.driversCollection.doc(driverId).set(coords)

Con esta simple línea estamos guardando en el id del conductor la posición actual de manera que Firebase almacenaría dicha posición en un objeto clave valor de la siguiente manera:

{
      “Uja21-w12-jklj”: {
     latitude: 6.208488,
     longitude: 75.563577
   }
}

Colocando todo esto junto, nuestra función se vería de la siguiente manera:

updateDriversPosition(driverId) {
   let position = {
       lat: 4.682905,
       lng: 74.070275
   };
   const step = 0.000006; // Play with the distance of every driver step
   const refreshTime = 400; // Play with the refresh position of the driver
   const refreshId = setInterval(async () => {
       position.lat -= step;
       position.lng += step;
       await this.driversCollection.doc(driverId).set(position)
       if (position.lat < 4.679705) {
           clearInterval(refreshId);
       }
   }, refreshTime);
}
Hemos puesto 400ms en el tiempo de refresh para enviar una nueva posición pero tu puedes jugar con esto como quieras para obtener distintos comportamientos. Ahora exploremos cómo el usuario que encargó el domicilio o el conductor obtiene dicha posición.
Recibiendo la posición del domiciliario
Para obtener la posición del domiciliario nos vamos a valer de las funcionalidades que tiene Firebase ya que cada vez que se actualiza la posición del lado del conductor y sin hacer absolutamente nada más que estar suscrito a la colección del driver nos van a llegar las posiciones automáticamente y listas para ser pintadas y dibujadas. ¿Cómo es esto posible?. En la siguiente porción de código lo vemos:

async requestTo(destination) {
const response = await this.lookForDriverTo(destination)
this.assignedDriverPosition$ =            
   this.driversCollection.doc(response.driver.id).valueChanges();
}

Cuando se solicita un conductor se llamará esta función y luego se invocará al servidor para que encuentre el mejor conductor disponible, dicha respuesta vendrá con un objeto driver y un id que es la que utilizaremos para suscribirnos y ver los cambios de las posiciones:

this.assignedDriverPosition$ =            
   this.driversCollection.doc(response.driver.id).valueChanges();
this.assignedDriverPosition$.subscribe(position => {
       /**Paint driver or do something with the position */
})

Así pués no queda más que aprender a pintar dicha posición en un mapa usando la API de Google Maps para Javascript.
Pintando las posiciones en el mapa
Empezar con la API de Google Maps para Javascript es muy muy fácil solo necesitamos tener una key valida que la puedes sacar de la consola de Google e importar el script en tu archivo index.html:

<script async defer src=«https://maps.googleapis.com/maps/api/js?key=<TuSuperKeySeguraYSecreta»
type=«text/javascript»>
</script>

Al hacer esto se nos incluye en el entorno de ejecución Google como objeto y podemos empezar a hacer cosas increíbles con el Google.Maps, como entonces pintamos un mapa dentro de un componente de Angular:

@ViewChild(«map», { 
   read: false, static: true 
}) mapElement: ElementRef;

<h1>…</h1>
<div #map id=«map»></div>
<otro-componente></otro-componente>

Necesitamos usar ViewChild para enlazar nuestro componente con el mapa y el html mediante el id, luego de esto tendremos la propiedadmap disponible en nuestro componente.
Para poder pintar un marcador en el mapa vamos a requerir primero crear una latitud y una longitud, luego configurar algunas opciones del mapa y finalmente enlazarlo con la referencia que tenemos a nuestro mapa con la propiedadthis.map.
let latLng = new google.maps.LatLng(6.236654, 75.580432);
   let mapOptions = {
     center: latLng,
     zoom: 13,
     mapTypeId: google.maps.MapTypeId.ROADMAP,
     Styles: […]
   };
   this.map = new google.maps.Map(
   this.mapElement.nativeElement, mapOptions);
Hay una propiedad especial que es la propiedad styles que nos permite personalizar los estilos del mapa para que luzcan mejor acorde a las necesidades que tengamos, aquí podrás encontrar más estilos para tus mapas.
Lo que nos quedaría entonces sería sobre nuestro mapa ser capaces de pintar la posición del domiciliario mediante un marcador, pero en realidad lo que va a suceder es que el marcador se borrará y pintará cada vez que llegue una posición ya que no es posible actualizar la posición del marcador de manera directa. Veamos cómo sería esto:

let latLng = new google.maps.LatLng(
     position.coords.latitude,
     position.coords.longitude
   );

   this.myMarker = new google.maps.Marker({
     map: this.map,
     animation: google.maps.Animation.cn,
     position: latLng,
     icon: { url: «assets/photo.jpg» }
   });

Y para borrar y pintar el marcador sería algo así: 

this.myMarker.setMap(null);
this.myMarker = null;

Poniendo todo junto podrás ver el siguiente repositorio con la implementación completa de un clon de Uber hecho en Angular usando la API de Google Maps para Javascript y Angular Maps.

 

Eso es todo, espero que este post te sea de utilidad y lo puedas aplicar a algún proyecto que tengas en mente y que simplemente te haya ayudado a entender la naturaleza de las aplicaciones “On Real Time” como Uber o Rappi. Déjame un comentario si lograste implementarlo, si quieres añadir alguna otra funcionalidad o si tienes alguna duda no dudes en dejarme un comentario en la parte de abajo, recuerda que si te gustó también puedes compartir usando los links a las redes sociales en la parte de abajo.

Sobre el Autor:
Sebastian Gomez
Google Developer Expert en Web Technologies
Twitter: @sebasgojs
Website: www.sebastian-gomez.com



Source: Google Dev

Deja un comentario