Documente Academic
Documente Profesional
Documente Cultură
Tema 16
Mapas y Localización
TEMA 16. MAPAS Y LOCALIZACIÓN
Introducción
La API Google Maps para Android ha sido recientemente actualizada a su versión 2 y se incluye
en el paquete com.google.android.gms.maps que es parte de la API Google Location
Services. Las clases contenidas en esta librería gestionan automáticamente descargas,
visualizaciones y cacheado de mapas, además de proporcionar diversos controles y opciones
de visualización, por lo que será el medio más óptimo para integrar mapas y servicios de
localización en una aplicación.
La clase principal de la API de Google Maps para Android (v2) es MapFragment, que muestra
mapas gracias a los datos obtenidos del servicio de Google Maps. Cuando dicha clase obtiene
el foco, capturará pulsaciones y gestos sobre la pantalla (como, por ejemplo, pinch-to-zoom) y
gestionará las peticiones de nuevos mapas cuando sea necesario. Además, proporciona todos
los elementos gráficos necesarios para que el usuario pueda controlar el mapa de forma visual
así como la posibilidad de realizar dichas acciones de forma programática.
Esta librería no está incluida por defecto en la plataforma Android. Para integrar Google Maps
en una aplicación será necesario instalar las librerías de Google Play Services, las cuales están a
disposición de los desarrolladores como add-on de la SDK 1.
Para poder utilizar los mapas de Google, el desarrollador deberá registrarse en el servicio de
Google Maps para obtener una clave para la API Maps (v2), ya que el acceso a datos de mapas
Google está controlado.
La obtención de esta clave implica la instalación previa de la SDK de Google Play Services para
desarrolladores, a través del SDK Manager. También se deberá instalar una versión compatible
de las APIs de Google (para Android 4.2.2 o superior) así como crear un AVD cuyo target sea
una versión de Android 4.2.2 o superior, que incluya dichas APIs de Google.
En el registro (en Google APIs Console) se deberá proveer la huella digital del certificado SHA-1
que se use para firmar la aplicación 2. Este servicio de registro de Google Maps devolverá una
referencia de la Clave para la API Maps (v2) que deberá incluirse en todas las solicitudes de
mapas que realice la aplicación.
Una vez obtenida la clave para la API, se deberán especificar unos ajustes concretos en el
manifiesto, para poder usar la API Maps en la aplicación.
1
Estas librerías se podrán descargar a través del SDK Manager de Android.
2
Para poder publicar una aplicación en Google Play será necesario firmarla digitalmente.
Requisitos de uso
El uso de la API de Google Maps en una aplicación implica la inclusión del texto de atribución
de Google Play Services en la sección “Avisos Legales” que deberá incluir la aplicación. Esta
sección deberá incluirse como un ítem independiente del menú o como parte de la sección
“Sobre” (“About”).
GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo(Context);
<android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/
En el manifiesto de la aplicación que utilice esta librería, deberán declararse todos y cada uno
de los elementos referenciados directamente de esta librería (actividades, servicios,
receptores de broadcast y proveedores de contenido) así como todos los permisos adicionales
que sean necesarios, tal y como se verá más adelante.
La actualización de Google Play Services se ha lanzado a todos los dispositivos con Android 2.2
o superior, a través de Google Play. No obstante, esto no garantiza que todos estos
dispositivos tengan instalada una versión de la APK de Google Play Services compatible, por lo
que será necesario comprobar si está disponible. El proyecto-librería del cliente de Google Play
Services proporciona métodos para comprobar si la APK Google Play Services es
suficientemente moderna como para soportar dicha librería cliente y, en caso de no serlo,
enviará al usuario a Google Play Store para actualizar dicha APK de Google Play Services.
En principio, las aplicaciones que usan la API de Google Maps deberán ser probadas en
dispositivos reales ya que los AVDs no incluyen el APK de Google Play Services, ni otros APKs
necesarios (como Google Play Store). No obstante, se pueden instalar en el AVD, a través del
comando “adb install <nombre APK>”, la última versión de los APKs de Google Play Services y
de Google Play Store. Estos APK (com.google.android.gms.apk, com.android.vending.apk)
pueden ser obtenidos en Internet o pueden ser extraídos de un dispositivo real, con acceso
root, por ejemplo usando la aplicación Titanium Backup Pro 3.
3
El AVD deberá configurarse para la plataforma Android 2.3 o superior (se evitarán AVDs con las APIs de Google
preinstaladas) Se puede obtener más información de este procedimiento en estos enlaces:
http://stackoverflow.com/questions/14040185/running-google-maps-v2-on-android-
emulator/14271933#14271933
http://stackoverflow.com/questions/13691943/this-app-wont-run-unless-you-update-google-play-
services-via-bazaar/13869332#13869332
Para poder obtener una clave de uso de la API de Google Maps v2, es necesario registrarse
previamente en Google APIs Console 4 y conseguir un certificado para firmar la aplicación.
Todas las aplicaciones Android deben ser firmadas con un certificado digital del cual el
desarrollador posee le clave privada. Como todos los certificados digitales son únicos,
proporcionan una forma de identificar la aplicación en los sistemas de Google, así como
identificar el uso que hace de los recursos de Google Maps.
La clave de uso de la API de Google Maps v2 se genera para cada certificado y nombre de
paquete 5 realizando los siguientes cuatro pasos:
La clave de la API Maps se basa en la huella digital SHA-1 del certificado digital de la aplicación.
Dicha huella es una cadena de texto, única, generada por un algoritmo hash, y dependerá del
tipo de certificado que se posea (certificado debug y certificado release).
Durante el desarrollo de una aplicación, el desarrollador podrá obtener una clave provisional
para el uso de la API de Maps, generada a través del certificado debug. Este certificado es
generado por las tools de la SDK de Android y permite el uso temporal de la API Maps. No
obstante, cuando la aplicación sea publicada en Google Play, deberá estar firmada con un
certificado real (release), lo cual obligará a sustituir la clave debug por la clave asociada a dicho
certificado.
4
https://code.google.com/apis/console/
5
Es decir, el par “certificado” + “nombre de paquete de la aplicación” genera una clave de Google Maps única.
poder mostrar mapas durante este periodo, serán necesario obtener la clave temporal para el
uso de la API Maps, registrando la aplicación a través del certificado de debug. Por lo tanto, es
necesario obtener inicialmente la huella digital del certificado de debug. Para generar dicha
huella, primero es necesario localizar el almacén de claves debug (archivo llamado
debug.keystore). Por defecto, las herramientas de compilación crean dicho almacén de claves
en el directorio donde se almacena la configuración de los AVDs. La localización por defecto de
dicho directorio varía en función del sistema operativo utilizado:
Si se utiliza Eclipse con su plugin ADT, la localización del almacén de claves debug podrá ser
consultada accediendo al menú Window > Preferences > Android > Build.
Una vez localizado el almacén de claves debug se utilizará la herramienta Keytool incluida en la
instalación de la JDK para obtener la huella SHA-1 del certificado debug.
El uso de esta herramienta se realiza a través de la consola de comandos del sistema operativo
(accesible en Windows si se escribe “cmd” en Menú Inicio > Ejecutar… y se pulsa el botón
Aceptar). En Windows, por lo tanto, será muy útil hacer accesible dicho comando desde
cualquier ubicación para lo cual se deberá añadir el directorio “bin” de la JDK que se haya
instalado, en la variable de entorno Path. Para modificar dicha variable de entorno, se
accederá a Menú Inicio > Ejecutar (accesible también a través de la combinación de teclas
“Win+E”) y se escribirá sysdm.cpl.
Este comando abrirá el cuadro de diálogo “Propiedades del sistema”. En la pestaña “Opciones
avanzadas”, se pulsará el botón “Variables de entorno”, que abrirá el cuadro de diálogo donde
se podrá editar la variable de entorno (de sistema) Path.
Al pulsar el botón Editar se abrirá un último cuadro de diálogo en el cual habrá que añadir, al
final de la cadena de texto Valor de la variable, la ubicación del directorio “bin“ de la
instalación de la JDK. Para JDK 7u7 la ubicación por defecto es C:\Progamr
Files\jdk1.7.0_07\bin.
Una vez añadida la ubicación de Keytool a la variable de entorno Path, se podrá obtener la
huella digital SHA-1 del certificado debug escribiendo, en la consola de comandos, lo siguiente
(sin el símbolo $):
El procedimiento para la obtención de una clave para la API Maps en modo release es similar,
debiéndose sustituir la referencia al almacén de claves debug por el almacén de claves release.
La generación de paquetes APK firmados en modo release, desde Eclipse, se explica en el tema
19.
6
El símbolo “$” no será escrito. El comando se escribirá sin saltos de línea. La ruta C:\Users\<user>\ variará en
función del nombre de usuario Windows.
Una vez obtenida la huella digital SHA-1, se deberá crear un proyecto para la aplicación en
Google APIs Console y se activará el servicio de Google Maps para Android.
Al pulsar Create Project, Google APIs Console creará un nuevo proyecto llamado API Project
(cuyo nombre podrá ser posteriormente modificado). Una vez creado el proyecto, se podrá
acceder a Services desde el panel izquierdo de navegación, para activar el servicio Google
Maps Android API v2. Una vez aceptados los términos y condiciones, el servicio quedará
activado:
Una vez creado el proyecto y activado el servicio Maps para Android, se podrá solicitar la clave
para el uso de dicha API accediendo, desde el panel izquierdo de navegación, a API Access >
Create New Android Key... Se mostrará un cuadro de diálogo donde se deberá copiar la huella
digital SHA-1 del certificado (debug o release) y, a continuación, un punto y coma (“;”) y el
nombre del paquete de la aplicación.
Por ejemplo:
F0:34: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:C1:27:73;com.cursoandroid.mymaps
Al pulsar Create, se creará la clave para el uso de la API Maps en dispositivos Android, en la
sección Key for Android apps (with certificates):
Una vez se haya finalizado el registro en el servicio de Google Maps, habiendo obtenido la
clave de la API Maps (sea tipo debug o tipo release), se deberá añadir dicha clave en el
manifiesto de la aplicación. Esta clave será enviada a través de la API Maps a los servidores de
Google Maps, cuando la aplicación solicite la descarga de mapas.
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXhRaVlk" />
Este elemento hace que la clave para la API Maps sea visible para cualquier MapFragment que
sea utilizado en la aplicación.
<permission
android:name="com.cursoandroid.mymaps.permission.MAPS_RECEIVE"
android:protectionLevel="signature" />
<uses-permission
android:name="com.cursoandroid.mymaps.permission.MAPS_RECEIVE" />
El uso de la API Maps requiere además especificar otros permisos en el manifiesto, para que la
aplicación pueda acceder a funcionalidades del sistema Android, así como para que pueda
acceder a los servidores de Google Maps.
Los dos últimos permisos son recomendables, aunque podrán ser ignorados en caso de que la
aplicación no necesite acceder a la ubicación actual del usuario. Además, en caso de no añadir
estos permisos, la aplicación aún podrá ubicar al usuario si activa programáticamente la capa
My Location.
La última versión de la API Maps (v2) requiere OpenGL ES versión 2, por lo que se deberá
añadir el elemento <uses-feature> como elemento hijo de <manifest>:
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
Este requisito permite a Google Play no mostrar la aplicación a dispositivos que no soporten
OpenGL ES 2.0 (aquellos con versiones de Android inferiores a la 2.2 – Nivel de API 8).
La clase principal de la API de Google Maps para Android (v2) es MapFragment, que muestra
mapas gracias a los datos obtenidos del servicio de Google Maps. Cuando dicha clase obtiene
el foco, capturará pulsaciones y gestos sobre la pantalla (como, por ejemplo, pinch-to-zoom) y
gestionará las peticiones de nuevos mapas cuando sea necesario. Además, proporciona todos
los elementos gráficos necesarios para que el usuario pueda controlar el mapa de forma visual
así como la posibilidad de realizar dichas acciones de forma programática.
El modo más sencillo de incluir mapas en una aplicación es crear un layout que incluya un
fragmento MapFragment:
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
Como todos los fragmentos, también se puede añadir en tiempo de ejecución un MapFragment
(o un SupportMapFragment) obteniendo una instancia del mismo a través de su método
newInstance(). Esta instancia será añadida a un contenedor a través de una transacción. Por
ejemplo, en el método onCreate() de la actividad que extienda de FragmentActivity, se
podría añadir el fragmento así:
El uso de la librería de Google Maps (v2) implica, como ya se ha mencionado, que el dispositivo
tenga instalado el APK de Google Play Services. En caso de que no se encuentre su APK en el
dispositivo, o esté desactualizado, o deshabilitado, MapFragment, SupportMapFragment o
MapView mostrarán automáticamente un mensaje al usuario ofreciendo la posibilidad de
instalar, actualizar o activar Google Play Services a través de la Google Play Store.
El objeto mapa es una instancia de la clase GoogleMaps que deberá ser inicializada para que
incluya listeners, marcadores, polígonos, o incluso una configuración inicial de la perspectiva o
del punto de vista. Esta inicialización deberá realizarse una sola vez en el ciclo de vida del
7
Para usar esta clase se deberá incluir la última versión de la librería Android Support v4 en build path del proyecto
Eclipse, lo cual podrá hacerse automáticamente con la opción Source > Organize Imports (Ctrl+Mayúsculas+O), una
vez se haya referenciado SupportMapFragment en el código.
/**
* El mapa puede ser null si el APK de Google Play Services no está
* disponible.
*/
private GoogleMap mMyMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mapas_main);
inicializarMapaSoloSiEsNecesario();
}
@Override
protected void onResume() {
super.onResume();
inicializarMapaSoloSiEsNecesario();
}
/**
* Se añadirán los listeners, marcadores, polígonos, etc.
*
* Este método solo debe ser invocado una vez en el ciclo de vida, y
* solo en caso de que mMyMap NO sea nulo.
*/
private void inicializarMapa() { ... }
El estado inicial del mapa podrá ser inicializado en código, dentro del método
inicializarMapa(), y también a través de atributos XML personalizados.
• GoogleMap.MAP_TYPE_NORMAL
• GoogleMap.MAP_TYPE_HYBRID
• GoogleMap.MAP_TYPE_SATELLITE
• GoogleMap.MAP_TYPE_TERRAIN
• GoogleMap.MAP_TYPE_NONE (mostrará una cuadrícula vacía sin imágenes de mapas)
La API Maps define una serie de atributos XML personalizados que pueden ser utilizados en los
contenedores de mapas (MapFragment, SupportMapFragment y MapView). Para poder usar
estos atributos se deberá declarar un nuevo espacio de nombres en el archivo de layout:
xmlns:map="http://schemas.android.com/apk/res-auto"
Debido a un bug aún sin resolver, Eclipse no permite la definición de un espacio de nombres
custom, si dicho espacio de nombres se declara en un contenedor del elemento que utiliza
estos atributos. Por ejemplo, el siguiente layout no será considerado válido:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
.../>
<fragment
android:id="@+id/map"
...
map:mapType="normal" />
<RelativeLayout />
Eclipse mostrará el error “Unexpected namespace prefix ‘map’ found for tag fragment” , en la
línea map:mapType="normal". Mientras este bug es resuelto, la única forma de utilizar los
atributos custom es declarar el fragmento de forma independiente en un archivo XML de
layout, sin ningún contenedor:
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment"
map:mapType="normal" />
Por ejemplo:
opcionesIniciales.compassEnabled(false)
.rotateGesturesEnabled(false)
.mapType(GoogleMap.MAP_TYPE_TERRAIN);
SupportMapFragment myMapContainer =
SupportMapFragment.newInstance(opcionesIniciales);
FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction();
transaction.add(R.id.myMapContainer, myMapContainer);
transaction.commit();
El punto de vista inicial también puede ser establecido vía código, a través de una instancia de
la clase CameraPosition obtenida a través de su Builder. Este objeto será pasado al método
GoogleMapOptions.camera(CameraPosition):
opcionesIniciales.compassEnabled(false)
.rotateGesturesEnabled(false)
.mapType(GoogleMap.MAP_TYPE_TERRAIN)
.camera(puntoDeVistaInicial);
...
Tanto los controles que se muestran en el GoogleMap así como los gestos permitidos y los
eventos que lanzará el mapa, podrán ser establecidos a través de los métodos directos de
GoogleMapOptions.
Además, también se podrá usar el objeto de tipo UiSettings que devuelve el método
GoogleMap.getUiSettings(). Cualquier cambio que se realice en este objeto será
inmediatamente reflejado en el mapa:
Se ha de tener en cuenta que los gestos se habilitan o deshabilitan para el usuario. Se podrán
realizar animaciones y movimientos de cámara vía código aunque los gestos estén
deshabilitados.
La API Maps también permite que la aplicación escuche eventos que ocurren sobre el mapa:
8
También permite establecer la duración de la animación así como el listener CancellableCallback.
En ciertos casos es útil mover el punto de vista de la cámara de forma que el área de interés se
visualice con el mayor nivel de zoom posible. Para ello, se puede utilizar
CameraUpdateFactory.newLatLngBounds(LatLngBounds bounds, int padding) que
devolverá un objeto CameraUpdate para cambiar el punto de vista de la cámara de forma que
el objeto LatLngBounds proporcionado encaje completamente en el mapa. La clase
LatLngBounds incluye un constructor que requiere las coordenadas 9 (objetos LatLng) de los
puntos suroeste y noreste, respectivamente, del área que se desea visualizar. Además
contiene otro método, including(LatLng) que devolverá otro objeto LatLngBounds que
modificará las dimensiones del área de interés al incluir el nuevo punto. También se podrá
utilizar un builder para incluir más fácilmente conjuntos de coordenadas, que será obtenido a
través del método LatLngBounds.build(). Este método devuelve un objeto de tipo
LatLngBounds.Builder que incluye el método include(LatLng):
9
El formato por defecto de las coordenadas es grados decimales. Este es el formato que usa por defecto Google en
sus mapas. Para obtener las coordenadas de una localización concreta a través Google Maps bastará con pulsar con
el botón derecho del ratón sobre la localización y elegir la opción “¿Qué hay aquí?” Esta acción hará que aparezcan
las coordenadas (grados decimales) de dicha localización en la barra de búsqueda (search bar) de Google Maps.
// Se realiza la animación
mMyMap.animateCamera(zoomAreaInteres);
Marcadores
La API Maps proporciona marcadores que representan puntos concretos en el mapa y que
pueden ser personalizados. Los marcadores son instancias de la clase Marker y serán añadidos
sobre el mapa a través del método GoogleMap.addMarker(MarkerOptions).
Los marcadores responden a eventos on-click, mostrando, por ejemplo, ventanas con
información. Por defecto, los marcadores son visibles y no pueden ser arrastrados (al realizar
sobre ellos una pulsación larga), aunque se puede establecer a false la propiedad visible y a
true la propiedad draggable para marcadores concretos.
...
mMyMap.addMarker(new MarkerOptions()
.position(ESSAOUIRA)
.title(getString(R.string.essaouira))
.snippet(getString(R.string.ciudad_laberinto))
.icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_RED)));
Además de la posición y el título, cada marcador puede contener un snippet (texto extra
debajo del título) así como un icono (que no podrá ser cambiado una vez se haya creado el
marcador). El punto del icono que coincidirá con las coordenadas de la localización es, por
defecto, el punto medio de la línea base del icono. Este punto podrá ser modificado a través de
la propiedad anchor.
El icono por defecto de un marcador puede ser modificado a través de su método icon(),
usando BitmapDescriptorFactory. Invocando al método defaultMarker(float hue) se
podrá cambiar el color 10 del icono. Para sustituir la imagen del icono, se deberá pasar una
instancia de BitmapDescriptor al método icon(), la cual habrá sido obtenida a través de uno
de los métodos de BitmapDescriptorFactory:
Cuando el usuario toca un marcador que tiene un título asociado, se mostrará una ventana con
la información proporcionada (título en negrita y snippet). Este comportamiento puede ser
programado invocando a los métodos del marcador showInfoWindow() y hideInfoWindow().
Es muy importante tener en cuenta que esta ventana de información es dibujada en el mapa
como una imagen, a través del método View.draw(Canvas). Los listeners asociados a dicha
View (toques y gestos) serán ignorados y modificaciones posteriores de la misma no serán
reflejadas en el marcador hasta que se invoque al método showInfoWindow().
La API Maps permite escuchar y responder a eventos click (sobre marcadores o sobre sus
ventanas de información) y drag, asociando los listeners OnMarkerClickListener,
OnMarkerDragListener u OnInfoWindowsClickListener al objeto GoogleMap. Los métodos
que implementarán estas interfaces recibirán como parámetro el marcador sobre el que se
produce el evento.
10
HUE puede adoptar un valor entre 0 y 360, representando puntos en una rueda de colores.
Formas
La API Maps proporciona la posibilidad de añadir diversas formas sobre el mapa. Estas formas
serán instancias de:
La clase Polyline será definida a través de un conjunto ordenado de objetos LatLng que
serán proporcionados invocando consecutivamente al método PolilyneOptions.add():
...
mMyPolyline = mMyMap.addPolyline(puntosItinerario);
El uso de la clase Polygon es similar al uso de la clase Polyline. Un polígono es una polilínea
cuyo puntos inicial y final son el mismo. La zona delimitada por un polígono tendrá el fondo de
un color específico.
mMyPolygon = mMyMap.addPolygon(puntosPoligono);
Gracias a la clase Polygon se pueden crear figuras complejas que incluyan agujeros, los cuales
podrán ser definidos a través de una secuencia de puntos, pasada al método
PolygonOptions.addHole(LatLng...).
...
mMyCircle = mMyMap.addCircle(datosCirculo);
Como puede observarse en el código anterior, también se puede modificar el aspecto de las
diversas formas, así como su orden de superposición (z-index).
Una vez añadido el círculo, se podrá modificar el punto central, el radio y el resto de
propiedades, a través de los métodos set: Circle.setCenter(LatLng),
Circle.setRadius(int), etc. También se podrán modificar las propiedades de los polígonos
y polilíneas accediendo a sus correspondientes métodos set.
11
Los métodos add(), de PolylineOptions y PolygonOptions, polimórficos, también admiten una secuencia de n
puntos LatLng como parámetros de entrada.
Todas las formas son visibles por defecto. Sin embargo, pueden ser ocultadas temporalmente
invocando a su método setVisible(false). Para eliminar definitivamente una forma, se
deberá invocar a su método remove().
Por último, ya se ha mencionado que Circle es una proyección tipo Mercator de un círculo
sobre la superficie terrestre. Esto implica que el aspecto de estas formas no será exactamente
un círculo, sino que, en general, serán elípticas. De forma análoga, las polilíneas y los polígonos
pueden ser dibujadas de forma geodésica, como proyecciones tipo Mercator de líneas sobre la
superficie terrestre, de forma que tendrán un aspecto algo curvo. Se deberá usar el método
PolylineOptions.geodesic(true) o PolygonOptions.geodesic(true) ya que, por
defecto, las polilíneas y polígonos se dibujan de forma no geodésica.
Localización
Gracias a los servicios incluidos en la API de Google Maps, se pueden desarrollar aplicaciones
sensibles a la ubicación, dirección de movimiento y método de movimiento del dispositivo,
gracias a los datos obtenidos utilizando una combinación de tecnologías disponibles en el
dispositivo (GPS, triangulaciones entre antenas 3G, WiFi, sensores de movimiento, etc.).
Además, también se puede determinar si el dispositivo sale de una zona geográfica delimitada,
o geofence.
Para usar los datos de localización se podrá usar una capa llamada My Location, que muestra la
ubicación del dispositivo en el mapa sin proveer ningún dato de localización a la aplicación, o
bien se podrán usar los servicios de localización que provee la API Google Play Services
Location para solicitar programáticamente datos de localización, o la interfaz
LocationSource, que permite la creación de un proveedor de localización específico.
Capa My Location
La forma más sencilla de mostrar la ubicación del dispositivo es usar la capa y el botón My
Location. Cuando se pulse este botón, que aparecerá en el ángulo superior derecho del mapa,
la cámara centrará el mapa en la ubicación actual del dispositivo, en caso de que esté
disponible, mostrando un icono u otro en función de si el dispositivo está o no en movimiento.
Para activar esta capa, simplemente se incluirá la siguiente línea, al inicializar el objeto
GoogleMap:
mMyMap.setMyLocationEnabled(true);
<manifest … >
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
Solicitar el primero de los permisos (acceso a ubicación exacta) obliga a solicitar el segundo
permiso (acceso a ubicación aproximada, cuya precisión equivale a un edificio promedio de
una ciudad).
Antes de inicializar el cliente de los Servicios de Localización, se deberá comprobar que están
disponibles dispositivo los servicios de Google Play. En el método onCreate() de la actividad
principal de la aplicación, se podrá implementar un código similar al siguiente:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mapas_main);
if (isGoogleServicesAvailable()) {
// Se inicializa el LocationClient (aún no está conectado)
mMyLocationClient = new LocationClient(this, this, this);
}
}
12
Esta API ha sido diseñada para minimizar el consumo energético del dispositivo al usar servicios de localización.
13
Esta compleja y exclusiva funcionalidad puede ser consultada en este artículo:
http://developer.android.com/training/location/geofencing.html
En caso de que los servicios de Google Play no estén disponibles, o en caso de que el cliente,
por causas desconocidas, no pueda conectar con los Servicios de Localización (o estos no
puedan obtener información de los servidores de Google), se podrá ofrecer al usuario la
posibilidad de descargar los APK necesarios, o bien se podrán intentar resolver los problemas
de conexión que pudiesen existir, de forma automática.
int resultCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
se puede obtener un código de error contenido en resultCode. Con este código, se podrá
mostrar un diálogo al usuario, para que descargue, actualice o active los servicios de Google
Play:
Una vez realizada la acción que ofrezca el diálogo, se devolverá el control a la actividad
principal de la aplicación, invocándose al método onActivityResult(int requestCode,
int resultCode, Intent data). El requestCode que recibirá este método será
CONNECTION_FAILURE_RESOLUTION_REQUEST.
Por otro lado, también se podrán dar fallos de conexión que fuercen a los Servicios de
Localización a invocar al método callback onConnectionFailed(ConnectionResult). El
objeto ConnectionResult recibido como parámetro contiene el método hasResolution(). Si
este método devuelve true, se podrá intentar resolver el fallo invocando a:
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
showMessage(getString(R.string.ha_fallado_la_conexion_) +
connectionResult.getErrorCode());
if (connectionResult.hasResolution()) {
showMessage(
getString(R.string.intentando_resolver_el_fallo_de_conexion_) +
connectionResult.getErrorCode());
try {
// Se lanza una actividad que intentará resolver el fallo de
// conexión
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException e) {
// Esta excepción se lanza si los servicios de Google Play han
// cancelado el PendingIntent original
Log.e(TAG_ERROR, e.getMessage());
}
} else {
showMessage(
getString(R.string.fallo_de_conexion_sin_posible_resolucion_) +
connectionResult.getErrorCode());
}
}
Una vez se haya conectado el cliente de localización, se podrá obtener la ubicación del
dispositivo a través del método LocationClient.getLastLocation(). Este método
devolverá un objeto de tipo Location que contiene las coordenadas (latitud y longitud).
Para que la actividad pueda escuchar las actualizaciones de la ubicación, deberá implementar
el listener com.google.android.gms.location.LocationListener. Esta interfaz
proporciona el método callback onLocationChanged(Location), que recibirá la nueva
ubicación cada vez que esta cambie, contenida en el parámetro Location.
14
Este tiempo será respetado siempre y cuando no haya más aplicaciones solicitando actualizaciones de la
ubicación simultáneamente.
@Override
public void onConnected(Bundle arg0) {
// Ya se puede usar el LocationClient
showMessage(getString(R.string.cliente_conectado));
mMyLocationClient.requestLocationUpdates(locationRequest, this);
}
...
@Override
protected void onStop() {
// La desconexión del cliente lo invalida, de forma que habrá que
// hacer new LocationClient() de nuevo
if (mMyLocationClient != null && mMyLocationClient.isConnected()) {
mMyLocationClient.removeLocationUpdates(this);
mMyLocationClient.disconnect();
}
super.onStop();
}
El proceso para obtener actualizaciones sobre el reconocimiento del tipo de actividad que está
realizando el usuario (desplazarse en coche, andando, en bicicleta, parado, inclinando) es
similar al proceso de actualización de la ubicación actual, aunque requiere otro permiso
distinto 15:
<uses-permission
android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
15
El reconocimiento de la actividad del usuario no necesita los permisos ACCESS_COARSE_LOCATION ni
ACCESS_FINE_LOCATION.
Para poder recibir las actualizaciones de reconocimiento de actividad, se deberá declarar, por
lo tanto, un PendingIntent, así como un cliente de reconocimiento de actividad, que será una
instancia de la clase ActivityRecognitionClient. El PendingIntent será utilizado por los
Servicios de Localización para enviar actualizaciones de la actividad del usuario a la aplicación.
Además, se deberá indicar la frecuencia con la que se desea recibir las actualizaciones.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mapas_main);
if (isGoogleServicesAvailable()) {
// Se inicializa el cliente de la actividad del usuario
// (aún no está conectado)
mMyActivityRecognitionClient =
new ActivityRecognitionClient(this, this, this);
...
// (inicialización de PendingIntent)
}
}
Si el cliente se desconecta por alguna razón, los Servicios de Localización invocarán al método
callback onDisconnected(). Esta desconexión invalidará dicho cliente (y habrá que asignar a
null la instancia del cliente), por lo que será necesario inicializar uno nuevo si se necesita
Cuando el cliente haya conectado con los Servicios de Localización, estos invocarán al método
callback onConnected(). Será en este método donde se inicialice el PendingIntent y se
soliciten las actualizaciones de la actividad del usuario:
@Override
public void onConnected(Bundle arg0) {
// Ya se puede usar el ActivityRecognitionClient
showMessage(getString(R.string.cliente_conectado));
Los Servicios de Localización utilizan la información obtenida de las antenas y sensores del
dispositivo para deducir cuál es la actividad que está realizando el usuario. Por lo tanto,
proporcionan la probabilidad de que el usuario esté realizando cierta actividad en ese instante.
El objeto de tipo ActivityRecognitionResult que contiene el resultado devolverá, a través
de su método getMostProbableActivity(), un objeto de tipo DetectedActivity, que
devolverá, a su vez, la probabilidad de dicha actividad a través del método getConfidence() y
el tipo de actividad a través del método getType(). Los posibles tipos de actividad son:
• DetectedActivity.IN_VEHICLE
• DetectedActivity.ON_BICYCLE
• DetectedActivity.ON_FOOT
• DetectedActivity.STILL (parado)
• DetectedActivity.TILTING (inclinación)
• DetectedActivity.UNKNOWN
Los IntentService son la clase base para los servicios que manejan peticiones asíncronas, en
forma de Intent. Para ello, utilizan, internamente, AsyncTask, que encapsula un Handler. En
caso de querer acceder a la interfaz de usuario desde el IntentService, se deberá tener en
cuenta que existe un bug 16 en AsyncTask que hace dicho Handler no se inicialice siempre en el
hilo main, sino que será inicializado en aquel hilo donde la clase ejecute sus inicializadores
estáticos. Por lo tanto, en IntentService.onHandleIntent() se deberá utilizar un Handler
propio para poder acceder al hilo main:
public MyActivityRecognitionIntentService() {
super(MyActivityRecognitionIntentService.class.getName());
mMainThreadHandler = new Handler();
}
@Override
protected void onHandleIntent(Intent intent) {
16
Resolución bug AsyncTask:
http://stackoverflow.com/questions/4443278/toast-sending-message-to-a-handler-on-a-dead-thread
También se podría hacer Class.forName() en Application:
https://code.google.com/p/android/issues/detail?id=20915
getString(R.string._probabilidad_) +
confidence +
getString(R.string._simbolo_porcentaje),
Toast.LENGTH_SHORT).show();
}
});
<service
android:name="com.cursoandroid.mymaps.MyActivityRecognitionIntentService"
android:exported="false">
</service>
Simulación de ubicaciones
Durante el desarrollo de la aplicación se tendrán que hacer diversas pruebas para verificar el
modelo implementado para obtener la ubicación. Estas pruebas podrán realizarse en un AVD
al cual se envíen datos de ubicación GPS simulados desde Eclipse o vía Telnet, aunque para ello
será necesario instalar un APK de Google Play Services actualizado en el AVD, así como otros
paquetes.
En Eclipse, se abrirá la perspectiva DDMS accediendo al menú Window > Open Perspective >
Other… > DDMS. Dicha perspectiva contiene el panel Emulator Control el cual, en la zona
inferior, presenta los controles de ubicación (Location Controls).
Para poder enviar los datos simulados al AVD, será necesario seleccionarlo previamente en la
vista Devices.