Google Maps

Google Maps nos proporciona un servicio de cartografía online que podremos utilizar en nuestras aplicaciones Android. Entre las ventajas que aporta 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 debemos tener en cuenta:
 

  • -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 clave 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/. Tendrás que introducir un usuario de Google que realiza la solicitud.

2.    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).

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

4.    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.

5.    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.
 

6.    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.

7.    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.

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

Nota: Si aparece el mensaje “Task list not built…” pulsa sobre él y desactiva “Do not build Gradle task list duringGradle sunc”.

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.


 

5.   Accede a la consola de Google Cloud (https://cloud.google.com/console/) y selecciona el proyecto creado.

6.   En el menú de la izquierda selecciona APIs y servicios / credenciales. En la lista de Claves de API, pulsa en el botón de editar (con forma de lápiz):

7.  En Restricciones de clave, selecciona Aplicaciones de Android y pulsa en AGREGAR UN ELEMENTO. 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.

 

  8.  Pulsa en LISTO. Y selecciona Restringir clave. En el desplegable selecciona Maps SDK for Android. Finalmente pulsa el botón 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 21 Android 5.0 (Lollipop)

2.   Vamos a importa a nuestro proyectoel paquete de  la libreria de Google Play Services. Para ello añade en el graddle del módulo app la siguiente línea.

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

3.   AndroidManifest.xmlañade en siguiente permiso:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COURSE_LOCATION"/>

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.

4.  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" />

5.  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.
NOTA: Las claves de acceso no pueden ser publicadas. Si estás compartiendo el proyecto en Git, aseguraté de incluir este fichero en gitignore.
 

6.  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> 

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

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

8.   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)
} 

9.   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 mapa) {
   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(mapa: mapa) {
   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))
} 

10.   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