Google Maps

Google Maps nos proporciona un servicio de cartografía online que podremos utilizar en nuestras aplicaciones Android. Veamos las claves necesarias para utilizarlo. Estudiaremos la versión 2 de la API, que incorpora interesantes ventajas respecto a la versión anterior. Entre estas ventajas destaca el menor tráfico intercambiado con el servidor, la utilización de fragments y los gráficos en 3D. Como inconveniente cabe resaltar que la nueva versión solo funciona en el dispositivo con Google Play instalado.

Conviene destacar que, a diferencia de Android, Google Maps no es un software libre, por lo que está limitado a una serie de condiciones de servicio. Desde Julio de 2018 se ha introducido una serie de restricciones de uso[1] que debe
 

  • -Mientras la política de utilización anterior nos permitía centralizar 18 APIs de localización distintas, ahora se han reducido a 3: mapas, rutas y lugares.
  • -Google “regalará”  200 dólares mensuales de uso a cada desarrollador que utilice las nuevas APIs de Google Maps.
  • -El acceso a Google Maps se integra dentro de la plataforma Cloud de Google. Esto obliga a los desarrolladores a indicar un medio de pago para utilizar las APIs, aunque no vaya a exceder de los 200 dólares mensuales de crédito.
  • -Las llamadas gratuitas a las APIs se han limitado de 25.000 peticiones diarias a 28.000 por mes.
  • -Para poder utilizar el API de Google Maps el desarrollador deberá tener una llave válida, actualizada y su perfil de Google Cloud deberá incluir, como indicamos previamente, sus datos bancarios.

A cambio de lo anterior, podemos incluir propaganda en los mapas o incluso podemos usarlo en aplicaciones móviles de pago.

Obtención de una clave Google Maps

Para poder utilizar este servicio de Google, igual que como ocurre cuando se utiliza desde una página web, será necesario registrar la aplicación que lo utilizará. Tras registrar la aplicación se nos entregará una clave que tendremos que indicar en la aplicación.

Ejercicio: Obtención de una clave Google Maps

1.     Para obtener la clave Google Maps entra en la siguiente página webhttps://code.google.com/apis/console/

2.     Tendrás que introducir un usuario de Google que realiza la solicitud.

3.    Crea un nuevo proyecto en esta consola. Para ello abre el desplegable de la parte superior y en la ventana emergente selecciona NUEVO PROYECTO. Introduce como nombre Ejemplo Google Maps y pulsa Crear. (el proceso tardará algunos segundos y debes tener en cuenta que no podrás modificar el nombre asignado al proyecto).

4.    Una vez generado, selecciónalo en el desplegable superior y accede a la opción Ir a la visión general de las APIs, que encontrarás dentro de la tarjeta APIs.

5.    En la nueva pantalla podrás ver algunos accesos directos a las APIs más comunes, entre las que se suele encontrar la opción de Maps SDK for Android. Si no es así, selecciona la opción HABILITAR APIS Y SERVICIOS en la parte superior, donde podrás acceder a Maps SDK for Android.

6.    Una vez dentro de la opción de mapas podrás encontrar información relacionada con la documentación o el listado de precios[2]. Te recomendamos que leas detenidamente ambas informaciones. Una vez hecho, pulsa en Habilitar.
 

7.    En la nueva ventana podremos consultar información relevante sobre las cuotas de uso y métricas de nuestra aplicación, una vez esté operativa. De momento, selecciona la pestaña de Credenciales. En la ventana emergente selecciona Crear Credenciales. Selecciona Clave de API.

8.    En la ventana siguiente, copia al portapapeles la clave creada:

Ejercicio: Restringir uso de la clave Google Maps

La clave que acabamos de crear podría caer en malas manos y ser usada desde otra aplicación Android, iOS o Web. Esto podría ser perjudicial para nosotros, al verse reducida la cuenta asignada a esta clave. Una forma de evitar usos no autorizados de esta clave consiste en indicar a Google que solo permita el uso de esta clave desde nuestra aplicación. Para conseguirlo, lo primero que necesitamos es la huella digital SHA1 del certificado digital con el que se ha firmado nuestra aplicación. En esta fase de desarrollo estamos usando el certificado digital de depuración. En la fase de publicación, el certificado será diferente y tendremos que volver a obtener la huella SHA1. Los pasos 1 al 4 permiten obtener la huella digital SHA1 desde Android Studio. Los pasos 5 al 9 realizan lo mismo, pero desde la línea de comando. Puedes utilizar la alternativa que prefieras.

1.   Desde Android Studio, pulsa en el botón Gradle del panel de la derecha.

2.   En el desplegable abre la ruta <Nombre del proyecto>/Task/android.

3.   Haz doble clic en signingReport.

4.   En la ventana Run, aparecerá la firma digital SHA1. Cópiala al portapapeles y pasa al punto 10.


 

5.   El primer paso va a consistir en descubrir dónde está almacenado el certificado digital de depuración. Accede a la carpeta .android que encontrarás en la carpeta de tu usuario. Dentro se almacena el fichero debug.keystore con el certificado digital de depuración. En Windows, la ruta de este fichero podría ser C:\Users\&lt;Usuario&gt;\.android\debug.keystore. En Linux y Mac, la ruta es /.android/debug.keystore.

6.   Copia esta ruta en el portapapeles.

7  Ahora necesitamos extraer la huella digital SHA1 de este fichero. Para extraer la huella digital puedes utilizar el programa keytool. En Windows, este programa se encuentra en la carpeta C:\Program Files\Java\jre7\bin\ o en una similar. Abre un intérprete de comandos (símbolo del sistema) y sitúate en la carpeta anterior (o similar).

 cd C:\Archivos de programa\Java\jre7\bin

  8.  Ejecuta el siguiente comando reemplazando el nombre del fichero por el que acabas de copiar en el portapapeles.

keytool -v -list -keystore [ruta a debug.keystore]
 

En nuestro ejemplo:

 keytool -v -list -keystore C:\android-sdk\.android\debug.keystore
 

NOTA: Si la ruta del fichero tiene espacios, introdúcela entre comillas.

El programa te solicitará una contraseña para proteger el almacén de claves. Deja la contraseña en blanco. De toda la información mostrada, nos interesa la huella digital del certificado en codificación SHA1. Como puedes ver en la captura anterior, para nuestro ejemplo está formada por los siguientes bytes:
 

9E:80:89:80:E0:54:45:AA:61:FD:38:75:E3:F5:64:08:DB:9F:83:B9   

9.    Copia en el portapapeles esta secuencia de dígitos. En Windows pulsa con el botón derecho sobre la barra superior de la venta y selecciona Marcar. Selecciona el área a copiar. Luego, en este mismo menú, selecciona Editar/Copiar.

10.   Accede a la consola de administración de APIs de Google (https://code.google.com/apis/console/) y selecciona el proyecto creado.

11.   En el menú de la izquierda selecciona Credenciales. En la lista de Claves de API, pulsa en el botón de editar (con forma de lápiz):

12.    Selecciona Aplicación para Android y pulsa en Añadir nombre de paquete y huella digital. Aparecerán dos cuadros de entrada donde has de añadir el nombre de paquete de la aplicación y la huella digital obtenida al principio del ejercicio.


 

13.  Finalmente, pulsa en Guardar.
 

Ejercicio: Un ejemplo simple con Google Maps

Veamos un sencillo ejemplo que nos permite visualizar un mapa centrado en las coordenadas geográficas detectadas por el sistema de posicionamiento.

1.   Crea un nuevo proyecto con los siguientes datos:

Phone and Tablet / Empty Activity
Name: Ejemplo Google Maps
Package name: org.example.ejemplogooglemaps
Language: Java
Minimum API level: API 19 Android 4.4 (KitKat)

2.   Abre Android SDK Manager y asegúrate de que los paquetes Google Play Services y Google Repositiry están instalados. Si hay una versión más reciente actualizala:
 


 

3.   Vamos a importa a nuestro proyectoel paquete de  la libreria de Google Play Services. Para ello añade en el graddle la siguiente línea.

implementation 'com.google.android.gms:play-services-maps:16.0.0'

4.   AndroidManifest.xmlañade en siguiente permiso:

NOTA: Los permisos de localización no son necesarios para trabajar con Google Maps, pero sí los debemos especificar para trabajar con la funcionalidad MyLocation, la cual vamos a utilizar en nuestro ejemplo. NOTA: Este permiso incluye de forma implícita los permisos ACCESS_COARSE_LOCATION y NETWORK_PROVIDER.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 

5.  Añade también las siguientes líneas dentro de la sección <application>:

<meta-data android:name="com.google.android.geo.API_KEY"
           android:value="@string/google_maps_key" />

6.  Crea el siguiente fichero de recurso res/values/google_maps_api.xml:

<resources>
   <string name="google_maps_key" templateMergeStrategy="preserve" 
                                                    translatable="false">
      AIzaSyCfcwo2LEBJ11aiW3bxZ9tUujYULXI-GN8
   </string>
</resources>

Reemplaza los caracteres marcados (“AIza…”) por la API Key obtenida en el ejercicio anterior.

7.  Reemplaza el contenido del layout activity_main.xml por:

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">
   <fragment
      android:id="@+id/mapa"
      class="com.google.android.gms.maps.SupportMapFragment"/>
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> 

NOTA: La etiqueta <fragment> ha de escribirse en minúscula.

8.   Abre MainActivity.class y haz que esta clase herede de FragmentActivity y que implemente la interfaz OnMapReadyCallback:

public class MapaActivity extends FragmentActivity 
                          implements OnMapReadyCallback 
class MapaActivity: FragmentActivity(), OnMapReadyCallback { 

9.   Reemplaza el método onCreate por el siguiente:

@Override protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   // Obtenemos el mapa de forma asíncrona (notificará cuando esté listo)
   SupportMapFragment mapFragment = (SupportMapFragment)
                 getSupportFragmentManager().findFragmentById(R.id.mapa);
   mapFragment.getMapAsync(this);
} 
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
   // Obtenemos el mapa de forma asíncrona (notificará cuando esté listo)
   val mapFragment = supportFragmentManager.findFragmentById(R.id.mapa) as
                                                        SupportMapFragment
   mapFragment.getMapAsync(this)
} 

10.   Debemos implementar el método onMapReady que será llamado en el momento en que el mapa está disponible. Es en esta función donde podremos manipular el mapa. Una implementación mínima sería la siguiente:

@Override public void onMapReady(GoogleMap googleMap) {
   GoogleMap mapa = googleMap;
   LatLng UPV = new LatLng(39.481106, -0.340987); //Nos ubicamos en la UPV
   mapa.addMarker(new MarkerOptions().position(UPV).title("Marker UPV"));
   mapa.moveCamera(CameraUpdateFactory.newLatLng(UPV));
} 
override fun onMapReady(googleMap: GoogleMap) {
   val UPV = LatLng(39.481106, -0.340987) //Nos ubicamos en la UPV
   googleMap.addMarker(MarkerOptions().position(UPV).title("Marker UPV"))
   googleMap.moveCamera(CameraUpdateFactory.newLatLng(UPV))
} 

11.   Ejecuta la aplicación. A continuación, se muestra el resultado:

NOTA: Si utilizas un emulador, asegúrate que disponga de servicios de Google Play.

Ejercicio: Introduciendo código en Google Maps

En el ejercicio anterior hemos visto un ejemplo muy básico, donde solo se mostraba un mapa con las opciones predeterminadas. En este ejercicio aprenderemos a configurarlo y añadir marcadores desde el código.

1.  Abre el layout activity_main.xml y añade los siguientes tres botones dentro del <ConstraintLayout> (tras el <fragment …>):

<Button android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="moveCamera"
        android:text="ir a UPV"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button2"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />
<Button android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="animateCamera"
        android:text="animar a UPV"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button3"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/button1" />
<Button android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="addMarker"
        android:text="marcador"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/button2" /> 

2.  Sustituye el contenido de MainActivity.java por:

public class MainActivity extends FragmentActivity implements
                        OnMapReadyCallback, GoogleMap.OnMapClickListener {
   private GoogleMap mapa;
   private final LatLng UPV = new LatLng(39.481106, -0.340987);

   @Override protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      SupportMapFragment mapFragment = (SupportMapFragment) 
                  getSupportFragmentManager().findFragmentById(R.id.mapa);
      mapFragment.getMapAsync(this);
   }

   @Override public void onMapReady(GoogleMap googleMap) {
      mapa = googleMap;
      mapa.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
      mapa.getUiSettings().setZoomControlsEnabled(false);
      mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(UPV, 15));

      mapa.addMarker(new MarkerOptions()
                .position(UPV)
                .title("UPV")
                .snippet("Universidad Politécnica de Valencia")
                .icon(BitmapDescriptorFactory
                        .fromResource(android.R.drawable.ic_menu_compass))
                .anchor(0.5f, 0.5f));
      mapa.setOnMapClickListener(this);
      if (ActivityCompat.checkSelfPermission(this,
                android.Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED) {
         mapa.setMyLocationEnabled(true);
         mapa.getUiSettings().setCompassEnabled(true);
      } 
   }

   public void moveCamera(View view) {
      mapa.moveCamera(CameraUpdateFactory.newLatLng(UPV));
   }

   public void animateCamera(View view) {
      mapa.animateCamera(CameraUpdateFactory.newLatLng(UPV));
   }

   public void addMarker(View view) {
      mapa.addMarker(new MarkerOptions().position(
                                      mapa.getCameraPosition().target));
   }

   @Override public void onMapClick(LatLng puntoPulsado) {
      mapa.addMarker(new MarkerOptions().position(puntoPulsado)
                   .icon(BitmapDescriptorFactory
                   .defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)));
   }
} 
class MainActivity : FragmentActivity(), OnMapReadyCallback,
                                           GoogleMap.OnMapClickListener {
   lateinit var mapa: GoogleMap
   val UPV = LatLng(39.481106, -0.340987)

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val mapFragment = supportFragmentManager.findFragmentById(R.id.mapa)
                                                     as SupportMapFragment
      mapFragment.getMapAsync(this)
   }

   override fun onMapReady(googleMap: GoogleMap) {
      mapa = googleMap.apply {
         mapType = GoogleMap.MAP_TYPE_SATELLITE
         uiSettings.isZoomControlsEnabled = false
         moveCamera(CameraUpdateFactory.newLatLngZoom(UPV, 15f))
         addMarker(MarkerOptions().position(UPV).title("UPV")
               .snippet("Universidad Politécnica de Valencia")
               .icon(BitmapDescriptorFactory.fromResource(
                     android.R.drawable.ic_menu_compass))
               .anchor(0.5f, 0.5f))
      }
      if (ActivityCompat.checkSelfPermission(this,
            android.Manifest.permission.ACCESS_FINE_LOCATION)
                     == PackageManager.PERMISSION_GRANTED) {
         mapa.isMyLocationEnabled = true
         mapa.uiSettings.isCompassEnabled = true
      }
      mapa.setOnMapClickListener(this)
   }

   fun moveCamera(view: View) {
      mapa.moveCamera(CameraUpdateFactory.newLatLng(UPV))
   }

   fun animateCamera(view: View) {
      mapa.animateCamera(CameraUpdateFactory.newLatLng(UPV))
   }

   fun addMarker(view: View) {
      mapa.addMarker(MarkerOptions().position(mapa.cameraPosition.target))
   }

   override fun onMapClick(puntoPulsado: LatLng) {
      mapa.addMarker(MarkerOptions().position(puntoPulsado)
            .icon(BitmapDescriptorFactory.defaultMarker(
               BitmapDescriptorFactory.HUE_YELLOW)))
   }
} 

Comenzamos declarando dos objetos: UPV, que hace referencia a la posición geográfica de la Universidad Politécnica de Valencia, y mapa, que nos permitirá acceder al objeto GoogleMap que hemos insertado en un fragment de nuestro layout. El mapa es cargado asíncronamiente, por lo que es necesario implementar el método onMapReady de la interfaz OnMapReadyCallback, que sera llamado en el momento en que mapa esté listo. Será en este método cuándo podremos configurar el objeto GoogleMap que nos pasan como parámetro, para adaptarlo a nuestras necesidades. setMapType() permite seleccionar el tipo de mapa (normal, satélite, hibrido o relieve). Para averiguar las constantes correspondientes, te recomendamos que utilices la opción de autocompletar (escribe GoogleMap. y podrás seleccionar las constantes de esta clase). El método moveCamera() desplaza el área de visualización a una determinada posición (UPV), a la vez que define el nivel de zum (15). El nivel de zum ha de estar en un rango de 2 (continente) hasta 21 (calle). El método addMarker() permite añadir los típicos marcadores que habrás visto en muchos mapas. En este ejemplo se indica la posición (UPV), un título, una descripción, un icono y el punto del icono, que haremos coincidir con el punto exacto que queremos indicar en el mapa. Un valor de (0, 0) corresponde a la esquina superior izquierda del icono y (1, 1), a la esquina inferior derecha. Como nuestro icono tiene forma de círculo , hemos indicado el valor (0.5, 0.5) para que coincida con su centro. Finalmente, hemos registrado un escuchador de evento para detectar pulsaciones sobre la pantalla. El escuchador vamos a ser nosotros mismos (this), por lo que hemos implementado la interfaz OnMapClickListener y añadido el método onMapClick().
El método setMyLocationEnabled(true) activa la visualización de la posición del dispositivo por medio del típico círculo. Para dispositivos con versión 6 o superior hay que tener la precaución de verificar si tenemos permiso de localización antes de activar esta opción. Por defecto, al tratarse de un permiso catalogado como peligroso se encontrará desactivado. En este código no se solicita este permiso. Para activarlo manualmente debes usar en el dispositivo la opción Ajustes / Aplicaciones / Ejemplo Google Maps / Permisos. El método getUiSettings() permite configurar las acciones de la interfaz de usuario. En este ejemplo se han utilizado dos: desactivar los botones de zum y visualizar una brújula. Estos métodos solo están disponibles si la capa LocationLayer está activa, por lo que es recomendable iniciarlos posteriormente al método setMyLocationEnabled(true). Puedes usar autocompletar para descubrir otras posibles configuraciones. En caso de no tener permiso de localización, debemos deshabilitar el botón que nos lleva a nuestra posición.
A continuación, se incluyen los tres métodos que se ejecutarán al pulsar sobre los botones añadidos al layout. El primero, moveCamera(), desplaza el punto de visualización a la UPV. A diferencia del uso anterior, sin cambiar el nivel de zum que el usuario tenga seleccionado.
El segundo, animateCamera(), nos desplaza también a la UPV por medio de una animación (similar a la que a veces utilizan en el Telediario para mostrar un punto en conflicto).
El tercero, addMarker(), añade un nuevo marcador en el centro del mapa que estamos observando (getCameraPosition()). En este caso usaremos el marcador por defecto, sin información adicional.
Como hemos indicado, se llamará a onMapClick() cuando se pulse sobre el mapa. Se pasa como parámetro las coordenadas del punto donde se ha pulsado, que utilizaremos para añadir un marcador. Esta vez el marcador será de color amarillo.

3.    Ejecuta la aplicación. Se muestra el resultado.
 

[1] https://cloud.google.com/maps-platform/pricing/sheet/?hl=es

[2] https://developers.google.com/maps/billing/understanding-cost-of-use?hl=es#maps-product