Índice

La siguiente aplicación es parte de una práctica de la asignatura “Nuevos Paradigmas de la Interacción” de la facultad de Ingeniería Informática de Granada (ETSIIT) Otras aplicaciones de la práctica son:

Si te interesa android, puedes echar un vistazo a los cursos disponibles en el blog, Android1, Android2

GPSQR

Enunciado: se facilitarán las coordenadas de una serie de puntos GPS (latitud/longitud) mediante códigos QR a la appGPSQR que iniciará automáticamente la navegación GPS hacia dicho punto, debe guardar el recorrido realizado y mostrarlo en un mapa al finalizar el mismo. En el anexo I tenéis ejemplos de los códigos QR que se deben detectar, en la defensa serán distintos.

En esta aplicación se lee un destino mediante códigos QR, tras esto, se puede iniciar la navegación con Google Maps (Usando la librería Android-GoogleDirectionLibrary). En la aplicación se muestran dos mapas. En el de abajo aparece el destino al que debemos llegar, además, se va dibujando un camino por el que el usuario va pasando. En el mapa de arriba se ve el mapa desde el punto de vista StreetView. Veamos la aplicación:

GPSQR


El Floating Action Button de abajo a la izquierda lanza el lector de QRs, que usa una simplificación de la librería Zxing. Cuando se escanea una localización, veremos lo siguiente:

Codigo QR leido con el destino

Una vez leido el QR, solo resta pulsar el marcador rojo para iniciar la navegación con Google Maps. La ruta calculada por la API de Google es la azul, mientras que la ruta real tomada por el usuario aparecerá en rojo.

Implementación

Esta aplicación tiene dos clases, una para la primera y única pantalla y otra es un servicio que se ejecuta en segundo plano, encargado de obtener la localicación del usuario a un intervalo regular. Echemos primero un vistazo al Servicio.

Clase LocationUpdaterService.java

Extiende de Service e implementa las siguientes interfaces:

public class LocationUpdaterService extends Service implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {
          // ....
        }

Con nombres bastantes descriptivos. Al iniciar el servicio, en su método onCreate se construye el cliente para la API de google del siguiente modo:

/**
 * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
 * LocationServices API.
 */
protected synchronized void buildGoogleApiClient() {
    if (BuildConfig.DEBUG) {
        Log.d(TAG, "Building GoogleApiClient");
    }
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
    createLocationRequest();
}

También se inicializa el BroadcastManager que usaremos para enviar las actualizaciones de la posición a la pantalla principal.

mBroadcaster = LocalBroadcastManager.getInstance(this);

El método onCreate se llama una única vez, al crear el servicio. Los comandos a ejecutar se colocan en el método onStartCommand, este metodo lo crearemos como Sticky, de modo que si el sistema finaliza el proceso del servicio, se volverá a ejecutar, volviendo así a obtener actualizaciones de localización:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
    if (BuildConfig.DEBUG) {
        Log.d(TAG, "OnStartCommand");
    }

    if (mGoogleApiClient.isConnected()) {
      LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
              mLocationRequest, this);
    }

    return START_STICKY;
}

Una vez hecho esto, se llamarán a las interfaces implementadas con los datos necesarios para obtener la localización actual, en este caso la interfaz que nos interesa es onLocationChanged:

@Override
public void onLocationChanged(Location location) {
    mCurrentLocation = location;
    mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
    sendResult(new LatLng(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude()));

    if (BuildConfig.DEBUG) {
        Log.d(TAG, mCurrentLocation.getLatitude() + ", " + mCurrentLocation.getLongitude());
    }
}

El método sendResult se usa para emitir un Broadcast al sistema y que la aplicación sea capaz de recibir el mensaje, incluso si no está en primer plano:

/**
 * This method send the current location to the activity who called the service, This way the
 * location can be used in the UI.
 *
 * @param message The location
 */
private void sendResult(LatLng message) {
    Intent intent = new Intent(COPA_RESULT);
    if (message != null)
        intent.putExtra(COPA_MESSAGE, message);
    mBroadcaster.sendBroadcast(intent);
}

Por último para que el Servicio funcione debemos registrarlo en el Manifest añadiendo la siguiente etiqueta:

<application>
<!--//....-->
<service android:name=".LocationUpdaterService">
<!--//....-->
</service></application>

Clase MapsActivity.java

Esta clase es la interfaz gráfica de la aplicación y donde se muestran los dos mapas, para poder trabajar con ellos se implementan las siguientes interfaces:

public class MapsActivity extends FragmentActivity implements
        OnMapReadyCallback,
        StreetViewPanorama.OnStreetViewPanoramaChangeListener,
        OnStreetViewPanoramaReadyCallback {

      private GoogleMap mMap;
      private StreetViewPanorama mStreetViewPanorama;

      // ...
}

Antes de poder trabajar con cualquiera de los mapas hemos de esperar a que estén inicializados, el momento de hacer esta inicialización es cuando el sistema llama a OnMapReadyCallback, OnStreetViewPanoramaReadyCallback:

/**
 * Manipulates the map once available.
 * This callback is triggered when the map is ready to be used.
 * This is where we can add markers or lines, add listeners or move the camera.
 * If Google Play services is not installed on the device, the user will be prompted to install
 * it inside the SupportMapFragment. This method will only be triggered once the user has
 * installed Google Play services and returned to the app.
 */
@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
    UiSettings uiSettings = mMap.getUiSettings();
    uiSettings.setMapToolbarEnabled(true);
    uiSettings.setZoomControlsEnabled(true);
}

@Override
public void onStreetViewPanoramaReady(StreetViewPanorama panorama) {
    mStreetViewPanorama = panorama;
    mStreetViewPanorama.setOnStreetViewPanoramaChangeListener(this);
    mStreetViewPanorama.setStreetNamesEnabled(true);
}

Entre otras cosas, en el método onCreate inicializamos el BroadcastReceiver que nos permitirá recibir actualizaciones desde el servicio, e iniciamos el servicio:

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_maps);
//  ...
    mLocationReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mPreviousLocation = mCurrentLocation;
            mCurrentLocation = intent.getParcelableExtra(LocationUpdaterService.COPA_MESSAGE);
            updateMap();
            mLocationsList.add(mCurrentLocation);

            if (BuildConfig.DEBUG) {
                Log.d(TAG, "LocationList size: " + mLocationsList.size());
            }
        }
    };

    mRequestLocationIntent = new Intent(this, LocationUpdaterService.class);
    startService(mRequestLocationIntent);
// ...
}

La función updateMap es la encargada de dibujar las líneas del camino seguido por el usuario.

Iniciar la navegación hasta destino

Cuando se lanza el lector QR y se obtiene el destino, se crea una ruta a seguir, en esta ruta es posible hacerla siguiendo el camino mostrado en el mapa, o lanzando la navegación con Google Maps. Para lo último, es neceario pulsar sobre el marcador de destino y posteriormente pulsar el icono de Google Maps:

LatLng firstLocation = new LatLng(mCoord[0], mCoord[1]);
mMap.addMarker(new MarkerOptions().position(firstLocation).title("Dest"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(firstLocation));
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(firstLocation, 21.0f));

GoogleDirection.withServerKey(getString(R.string.google_maps_server_key))
        .from(mCurrentLocation)
        .to(new LatLng(mCoord[0], mCoord[1]))
        .transportMode(TransportMode.WALKING)
        .execute(new DirectionCallback() {
            @Override
            public void onDirectionSuccess(Direction direction) {
                if (direction.isOK()) {
                    Toast.makeText(getApplicationContext(), "DIRECTION KOK", Toast.LENGTH_LONG).show();
                    ArrayList<latlng> directionPositionList = direction.getRouteList().get(0).getLegList().get(0).getDirectionPoint();
                    PolylineOptions polylineOptions = DirectionConverter.createPolyline(getApplicationContext(), directionPositionList, 5, Color.BLUE);
                    mMap.addPolyline(polylineOptions);
                } else {
                    Toast.makeText(getApplicationContext(), "NOT OK" + direction.getStatus(), Toast.LENGTH_LONG).show();
                }
            }

            @Override
            public void onDirectionFailure(Throwable t) {
                Toast.makeText(getApplicationContext(), "Failure", Toast.LENGTH_LONG).show();
            }
        });
Permisos requeridos para el AndroidManifest
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION">

Referencias y agradecimientos

</uses-permission></latlng>