DNM+ Online
dotnetmania 2.0
Ubicándose con Windows Phone 7
Este artículo hace una introducción al servicio de ubicación en Windows Phone 7, centrándose primero en su uso, para adentrarse después en un par de consideraciones importantes: la simulación de datos en el emulador de Windows Phone 7 y los aspectos a considerar en cuanto a la precisión de los datos. También se incluye una pequeña introducción al uso del control para Bing Maps, y se finaliza haciendo hincapié en ciertas implicaciones que el uso del servicio de localización tiene a la hora de certificar nuestra aplicación para el Marketplace.

Introducción al servicio de ubicación

Una de las posibilidades que se abren en Windows Phone 7 para toda aplicación es la del uso del servicio de ubicación. El servicio de ubicación es un conjunto de API que ofrece datos de geo-localización a las aplicaciones, asentándose en tecnologías como Assisted-GPS (aGPS o A-GPS), el Sistema de Posicionamiento Wi-Fi (WPS) y la triangulación de señales provenientes de antenas y torres de comunicación.

Obtener acceso al servicio de ubicación y usarlo en nuestras aplicaciones es bastante fácil desde el punto de vista técnico, como se va a poder comprobar a continuación. El primer paso a dar es el de referenciar en nuestro proyecto el ensamblado System.Device.dll, e instanciar la clase GeoCoordinateWatcher, que reside dentro del espacio de nombres System.Device.Location:

GeoCoordinateWatcher watcher = new 
   GeoCoordinateWatcher(GeoPositionAccuracy.High);

La clase GeoCoordinateWatcher nos ofrece dos constructores, uno con un parámetro de tipo GeoPositionAccuracy, y otro sin ningún parámetro. El primero de ellos nos permite especificar la precisión deseada, mediante uno de dos posibles valores: GeoPositionAccuracy.Default o GeoPositionAccuracy.High. Las diferencias principales radican en el consumo de batería que realizará nuestra aplicación y la precisión en metros de los datos de ubicación recibidos. Si se usa el constructor sin parámetros, el valor usado será GeoPositionAccuracy.Default (menor consumo y menor precisión).

En relación con el consumo de batería, un aspecto a tener en cuenta es si se establece o no valor para la propiedad MovementThreshold, y en caso que se establezca, el valor elegido. MovementThreshold contiene la distancia relativa de movimiento, expresada en metros, desde la última ubicación conocida para que se genere un nuevo evento de cambio de ubicación. Por defecto, MovementThreshold tiene asignado un valor de cero, lo que significa que cualquier cambio que se detecte en la ubicación generará un evento, lo que llevará a un mayor consumo. Por lo tanto, determinar un valor adecuado para el uso de nuestra aplicación será importante tanto para el consumo de batería como para tener eventos y datos de posicionamiento adecuados al uso de nuestra aplicación: no se tienen las mismas necesidades si se está intentando localizar servicios (cines, restaurantes, etc.) cerca de nuestra ubicación, que si se está haciendo seguimiento de una ruta, que si se está guiando a un conductor hacia su destino. Una vez explicado todo esto, no ha de sorprender al lector que el segundo paso sea establecer el valor para MovementThreshold:

geoWatcher.MovementThreshold = 10; // 10 metros

Con las dos líneas de código vistas hasta ahora, estamos en disposición de saber la ubicación actual y de ver los cambios que se producen en ésta; pero para que ello sea posible, necesitamos registrar manejadores para los dos eventos que ofrece GeoCoordinateWatcher: PositionChanged y StatusChanged. Por lo tanto, el tercer paso a realizar es asociar los mismos:

watcher.PositionChanged += WatcherPositionChanged;
watcher.StatusChanged += WatcherStatusChanged;

Aunque a priori pueda únicamente interesar PositionChanged, StatusChanged es también importante. El evento StatusChanged se genera cada vez que el estado cambia: se adquirió una nueva ubicación, no hay datos disponibles, etc., y dos son los escenarios principales en los que tendríamos que fijarnos: el usuario ha deshabilitado el servicio de ubicación en la configuración del sistema (figura 1), o el servicio de ubicación está habilitado y funcionando pero no se reciben datos del mismo (por ejemplo, se está dentro de un túnel).

Figura 1. Servicio de ubicación desactivado por el usuario

En cuanto a los detalles del manejador, fijémonos en su prototipo:

watcher.PositionChanged += WatcherPositionChanged;
watcher.StatusChanged += WatcherStatusChanged;

El parámetro e, de tipo GeoPositionStatusChangedEventArgs, tiene básicamente una propiedad de nombre Status y de tipo GeoPositionStatus, tipo enumerado que puede tomar los siguientes valores:

  • Initializing. El servicio de ubicación se está inicializando.
  • Disabled. El servicio de ubicación está deshabilitado.
  • Ready. El servicio de ubicación está listo para proporcionar nuevos datos.
  • NoData. No hay datos disponibles del servicio de ubicación. Es importante notar que si se consulta la propiedad Status de GeoCoordinateWatcher, el valor será NoData antes de ser iniciado el servicio y después de ser parado.

Una vez visto el evento StatusChanged, pasemos ahora a ver el evento PositionChanged. En él se nos informa que ha habido cambios en la ubicación actual. La definición del gestor del evento es: Lo más importante a destacar aquí, al igual que en el caso de StatusChanged, es el segundo parámetro del método, de tipo GeoPositionChangedEventArgs, que es donde se nos informará de la ubicación actual a través de su única propiedad Position, en este caso de tipo GeoPosition, que tiene las siguientes propiedades:

  • Timestamp. Nos indica la fecha y hora de obtención de la ubicación.
  • Location. Es la ubicación actual vía una instancia de tipo GeoCoordinate, que nos ofrece propiedades para valores como la latitud, longitud y altura actuales.

Llegados a este punto, nos faltan un par de detalles para hacer uso de los servicios de ubicación. El primero de ellos es que debemos iniciar los mismos, lo que básicamente implica llamar al método Start:

watcher.Start();

El segundo detalle radica en que dentro del archivo WMAppManifest.xml, en su sección Capabilities, debemos incluir IDCAPLOCATION:


   

Desde un punto de vista puramente técnico, con todo lo visto hasta ahora ya se tienen todas las piezas necesarias para hacer usar el servicio de ubicación en cualquier aplicación; a modo de ejemplo, para implementar una aplicación como la que se puede ver en la figura 2, el código es tan simple como el mostrado en el listado 1.

Figura 2. Aplicación demo de uso de los servicios de ubicación
public partial class MainPage : PhoneApplicationPage
{
    GeoCoordinateWatcher watcher;

    public MainPage()
    {
        InitializeComponent();
        this.Loaded += PageLoaded;
    }

    void PageLoaded(object sender, RoutedEventArgs e)
    {
        watcher = new GeoCoordinateWatcher();
        watcher.MovementThreshold = 10; // 10 metros
        watcher.StatusChanged += WatcherStatusChanged;
        watcher.PositionChanged += WatcherPositionChanged;
        watcher.Start();
    }

    void WatcherStatusChanged(object sender, 
        GeoPositionStatusChangedEventArgs e)
    {
        string estado = string.Empty;
        switch (e.Status)
        {
            case GeoPositionStatus.Disabled:
                estado = "Deshabilitado";
                break;
            case GeoPositionStatus.Initializing:
                estado = "Inicializando";
                break;
            case GeoPositionStatus.NoData:
                estado = "Sin datos";
                break;
            case GeoPositionStatus.Ready:
                estado = "Listo";
                break;
            default:
                break;
        }

        estadoTextBlock.Text = estado;
    }

    void WatcherPositionChanged(object sender,
        GeoPositionChangedEventArgs<GeoCoordinate> e)
    {
        horaTextBlock.Text = e.Position.Timestamp.ToString("HH:mm:ss");
        latitudTextBlock.Text = e.Position.Location.Latitude.ToString();
        longitudTextBlock.Text = e.Position.Location.Longitude.ToString();
        precisionHorizontalTextBlock.Text =
            e.Position.Location.HorizontalAccuracy.ToString() + "m";
        alturaTextBlock.Text = 
            e.Position.Location.Altitude.ToString() + "m";
        precisionVerticalTextBlock.Text =
            e.Position.Location.VerticalAccuracy.ToString() + "m";
        velocidadTextBlock.Text = e.Position.Location.Speed.ToString() + "m/s";
        direccionTextBlock.Text = e.Position.Location.Course.ToString() + "º";
    }
}
Listado 1. Código de la página de la aplicación demo de la figura 2

Simulación del servicio de ubicación

Por muy simple que sea el uso del servicio de ubicación desde el punto de vista técnico, si el lector intenta ejecutar el código del listado 1 u otro parecido en el emulador de WP7 se llevará la sorpresa, esperada o no, de que no se producirá ningún evento PositionChanged.

El trabajo con el emulador de WP7 presenta muchas ventajas, pero al ser un emulador no dispone de ningún proveedor para el servicio de ubicación, por lo que si nos queremos evitar el tener que desplazarnos por las calles de nuestra ciudad con un portátil al lado y un dispositivo real conectado vía USB para poder probar el funcionamiento de nuestra aplicación y, más importante todavía, depurar, entonces tendremos que recurrir a algún tipo de simulador.

La respuesta oficial por parte de Microsoft a esta necesidad se produjo con fecha 28 de enero de este año en forma de la "receta" Windows Phone GPS Emulator 1. En tal receta se incluye un documento que nos explica cómo hacer uso del emulador (incluyendo algún detalle de su implementación), así como el ensamblado para uso del emulador y el propio emulador (en ambos casos con código fuente disponible).

Sin pretender repetir lo que en el documento del artículo se nos explica, la idea es bastante simple: la aplicación emuladora (figura 3) implementa un servicio WCF en el que irá informando de cambios en la ubicación en función de la que hagamos en la misma aplicación (establecer ubicación actual, iniciar una ruta, etc.).

Figura 3. Aplicación para simular eventos GPS

En la receta, además de la aplicación se nos ofrece el ensamblado GPSEmulatorClient.dll, que tenemos que referenciar en nuestra aplicación. Si modificamos la línea de código que instanciaba GeoCoordinateWatcher, y en su lugar instanciamos GpsEmulatorClient.GeoCoordinateWatcher, nuestra aplicación pasará a recibir datos del emulador de GPS.

En el listado 2 se puede observar un posible método para creación de la instancia adecuada de GeoCoordinateWatcher en función de si nos encontramos en configuración de Debug o Release, o si la aplicación se está ejecutando en el emulador o en un dispositivo real.

public static IGeoPositionWatcher<GeoCoordinate> CreateWatcher()
{
    IGeoPositionWatcher<GeoCoordinate> watcher = null;

#if DEBUG
    if (Microsoft.Devices.Environment.DeviceType ==
        Microsoft.Devices.DeviceType.Emulator)
    {
        watcher = new GpsEmulatorClient.GeoCoordinateWatcher();
    }
    else
    {
        GeoCoordinateWatcher geoWatcher = new GeoCoordinateWatcher();
       geoWatcher.MovementThreshold = 10; // 10 metros
       watcher = geoWatcher;
    }
#else
    GeoCoordinateWatcher geoWatcher = new GeoCoordinateWatcher();
    geoWatcher.MovementThreshold = 10; // 10 metros
    watcher = geoWatcher;
#endif
    return watcher;

}
Listado 2. Código para creación de la instancia adecuada de GeoCoordinateWatcher

De los pocos cambios que el código del listado 2 representa respecto a la instanciación en el código del listado 1, es importante destacar que la variable watcher deja de ser de tipo GeoCoordinateWatcher y pasa a ser de tipo IGeoPositionWatcher.

La clase System.Device.Location.GeoCoordinateWatcher implementa la interfaz IGeoPositionWatcher, por lo que si modificamos nuestro código para usar la interfaz en vez de la clase directamente, nuestro código funcionará igual que con anterioridad y nos permitirá reemplazar la instancia real que se usa por otra(s) que simulen el comportamiento de GeoCoordinateWatcher. Éste es precisamente el factor que aprovecha el ensamblado GpsEmulatorClient.dll.GpsEmulatorClient.GeoCoordinateWatcher es simplemente una clase que implementa la interfaz IGeoPositionWatcher, y que en su implementación accede al servicio WCF de la aplicación emuladora proporcionada en la receta de Microsoft.

Un aspecto muy importante a considerar es que aunque el lector siga las instrucciones al pie de la letra, escriba un código libre de errores y esté todo en perfecta sintonía y preparado para la ejecución con el simulador, lo más probable es que cuando ejecute su aplicación no obtenga ningún dato del simulador de GPS y el estado del GeoCoordinateWatcher no varíe nunca de NoData. Antes de que malgaste su tiempo intentando averiguar qué está pasando, sepa el lector que el simulador de GPS y el ensamblado GpsEmulatorClient.dll no están nada preparados para su ejecución en entornos donde el separador decimal es la coma, como es el caso de configuraciones en español.

Dado que se dispone del código fuente de la aplicación WPF correspondiente al emulador, una manera rápida y sencilla de solucionar el problema es modificando el código en App.xaml.cs, para que quede como el mostrado en el listado 3.

if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
{
    // Aquí dejar el código original
}
else
{
    Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}
Listado 3.

Con lo proporcionado en la receta y lo explicado aquí, podemos simplificar el trabajo de desarrollo y depuración de nuestra aplicación, pero en opinión del autor de este artículo sería mucho más interesante si la aplicación emuladora de GPS pudiera leer ficheros GPX o KML y pasar a simular los datos de ubicación a partir de la información de los mismos.

Antes de la respuesta oficial por parte de Microsoft, otros desarrolladores habían llegado a soluciones alternativas, también implementando IGeoPositionWatcher, y una búsqueda en su buscador preferido puede llevarle a ellas; en [2] tiene la referencia a una de ellas.

Sobre datos de ubicación y precisión

En la primera sección de este artículo, se comentaba que la propiedad Location de GeoPosition nos daba la ubicación actual, vía las siguientes propiedades, que se pueden ver todas reflejadas en la aplicación de ejemplo de la figura 2:

  • Latitude. Indica la latitud actual.
  • Longitude. Indica la longitud actual.
  • Altitude. Altura actual, expresada en metros.
  • Course. Dirección en la que se está produciendo el movimiento, que toma valores entre 0º y 360º (0º sería el norte, 90º –el este–, 180º –el sur– y 270º –el oeste–).
  • Speed. Velocidad del movimiento respecto a la ubicación previa, en metros por segundo.
  • HorizontalAccuracy. Precisión horizontal (latitud, longitud), expresada en metros.
  • VerticalAccuracy. Precisión vertical (altura), expresada en metros.

Los datos de la figura 2 provienen de la ejecución de la aplicación de ejemplo en el emulador con el simulador de GPS proporcionado por Microsoft, pero sirven para reflejar un aspecto a tener muy en cuenta en función del tipo de aplicación que estemos desarrollando: todos y cada uno de los valores de la lista anterior (latitud, longitud, altitud, etc.) son de tipo double, y todos (incluso en situaciones con datos reales) podrían tener el valor NaN, lo que significa que ese dato o valor en concreto no está disponible. El código de nuestra aplicación debe estar listo para tales situaciones si queremos evitarnos problemas o sorpresas.

Un caso habitual que se puede producir tiene que ver con el valor de Altitude, pues si la ubicación actual ha sido obtenida vía triangulación de señales Wi-Fi o de antenas, en vez de a través de GPS, obtendremos valores de Latitude y Longitude más o menos exactos, pero la altura no será conocida (solo se obtiene vía GPS) y su valor será double.NaN. Por lo tanto, si nuestra aplicación está mostrando un perfil de alturas, o requiere la altura para algún otro menester, es posible que haya que descartar tales datos.

Igualmente, en función de la naturaleza de nuestra aplicación, los valores de HorizontalAccuracy y VerticalAccuracy deberán ser inspeccionados, pues el margen de error de los datos podría ser inaceptable para el propósito de nuestra aplicación y podría, de nuevo, ser mejor descartar datos en función de la precisión. Un posible ejemplo para acabar de comprender sería el de la figura 4, en la que se muestra la ruta obtenida durante un paseo por la montaña con una aplicación de seguimiento para WP7, donde se ve claramente que un único valor de ubicación altera totalmente la ruta y los datos de la misma (distancia total recorrida, velocidad media, etc.).

Figura 4. Posibles problemas en función de la precisión de los datos de ubicación

Mostrando la ubicación sobre un mapa

Un artículo sobre posicionamiento en Windows Phone 7 no estaría completo si no se incluyese una introducción, aunque sea breve, de cómo conseguir mostrar la ubicación en un mapa. La opción más sencilla será, en nuestro caso, mediante la utilización del control para Bing Maps en el ensamblado Microsoft.Phone.Controls.Maps.dll.

Los pasos para su utilización son bastante simples. Lo primero que tenemos que hacer es acceder al portal de Bing Maps en http://www.bingmapsportal.com, registrarnos usando nuestro Windows Live ID y obtener una clave para nuestra aplicación (figura 5).

Figura 5. Clave para aplicación en el portal de Bing Maps

Una vez tenemos la clave, referenciamos el ensamblado Microsoft.Phone.Con­trols.Maps.dll en nuestro proyecto, y podremos inmediatamente añadir un control para Bing Maps en nuestra página, que deberemos personalizar especificando la clave obtenida en el paso anterior, tal como se muestra en el listado 4.

<my:Map x:Name="mapa">
    <my:Map.CredentialsProvider>
        <my:ApplicationIdCredentialsProvider ApplicationId="(AQUI SU CLAVE)" />
    </my:Map.CredentialsProvider>
    <my:Pushpin x:Name="pin" PositionOrigin="0.5,0.5"/>
</my:Map>
Listado 4. Código XAML para incluir un mapa en nuestra página

En el código del listado 4 se puede observar que además de definir el control y las credenciales a usar, se ha incluido un Pushpin de nombre pin, que es el elemento que se usará en el código del listado 5 para indicar la ubicación actual en el mapa, tal como puede observarse en la figura 6.

void WatcherPositionChanged(object sender,
    GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    pin.Location = e.Position.Location;
    mapa.Center = pin.Location;
}
Listado 5.
Figura 6. Aplicación demo incluyendo mapa de la ubicación actual

Muchas son las características y funcionalidades que se podrían implementar con la combinación de los servicios de ubicación y el uso de Bing Maps, pero con lo presentado en este artículo se tienen las bases para empezar con buen pie y buena dirección, y será el propio lector quien tendrá que encontrar maneras creativas e imaginativas para el uso de tales tecnologías.

Consideraciones de certificación

Hasta ahora, el artículo ha cubierto aspectos técnicos relacionados con los servicios de ubicación, pero no me gustaría cerrar el artículo dejando sin comentar tres de los aspectos a tener en cuenta si se desea que su aplicación pase con éxito el proceso de certificación para el MarketPlace (no deje de consultar la sección 2.10 del documento "Windows Phone 7 Application Certification Requirements" [3]):

  • Se tiene que facilitar al usuario una opción de configuración que le permita habilitar o deshabilitar el uso del servicio de ubicación por parte de la aplicación.
  • Si se envían o publican los datos obtenidos a través del servicio de ubicación en cualquier servicio, sitio web, etc., tal funcionalidad tiene que ser explicada y detallada, el usuario debe aceptarla de manera explícita, y es necesario ofrecerle al usuario la posibilidad de rechazarla o deshabilitarla.
  • Se debe incluir en algún lugar de la aplicación una descripción de la política de privacidad de la aplicación, que informe al usuario sobre cómo se usan y almacenan los datos obtenidos del servicio de ubicación, y qué mecanismos de control tiene el usuario sobre los mismos.

Teniendo en cuenta que cada envío de una aplicación para certificación puede fácilmente demorarse una semana o más hasta estar aprobada y publicada, es aconsejable no ignorar estos tres puntos (ni el resto de los descritos en el punto 2.10 en [3]).

Finalmente, un detalle que no por obvio deja de ser importante: independientemente de si hemos usado uno u otro simulador para facilitar el trabajo de desarrollo y depuración, tarde o temprano tendremos que salir al exterior con el dispositivo en la mano y asegurarnos de que el funcionamiento de la aplicación es el deseado en situaciones reales; aunque probablemente ésta pueda ser la fase más entretenida de todas.

blog comments powered by Disqus