APIs e Microsserviços

10 jul, 2014

Uso de GPS com mapa no Windows Phone 8.1

Publicidade

O objetivo deste artigo é mostrar o uso da API de GPS no Windows Phone 8.1, assim como mostrar a localização no controle de mapa, tudo é claro com a linguagem Visual C# 5. Usar GPS se tornou corriqueiro em diversas aplicações, pois praticamente todo dispositivo (celular e tablet) vem com essa funcionalidade, e integrá-la à aplicação é muito mais fácil do que você imagina. O código que mostrarei neste artigo pode ser usado nas versões 7.5, 8 e 8.1 do Windows Phone.

Se você quiser integrar a API do Bing, que é nativa, diretamente com a sua aplicação, leia este artigo, e este outro que acho muito importante para integrar uma base de dados SQL Server com o recurso de mapas. Com isso, você poderá integrar dados armazenados em uma base de dados ou requisitar dados de um serviço e mostrar no mapa os devidos pontos. Quando falo sobre dados, refiro-me à latitude e à longitude – é disso que um mapa necessita. Então, vamos ao projeto em si. Neste projeto, usarei o Visual Studio 2013 Ultimate com o Update 2 instalado.

Os pré-requisitos para este artigo são conhecimento básicos de C# e Visual Studio .NET 2013 Update 2.

Projeto Windows Phone

Já que faremos passo a passo, abra o Visual Studio 2013, selecione a opção File / New / Project ou CTRL + SHIFT + N ou, na janela inicial, clique em New Project. Conforme a figura 1, selecione Visual C# / Store Apps / Windows Phone Apps / Blank App (Windows Phone Silverlight). Em Name, digite GPS_Mapa_MSDN e, no Location, você pode gravar onde desejar.

Figura 1 – Novo projeto
Figura 1 – Novo projeto

 

Clique no botão OK, e o VS solicita o Target do OS do Windows Phone, conforme a figura 2. Selecione Windows Phone 8.1 – assim, o projeto já é criado com o que há de mais novo.

 

Figura 2 – Projeto no WPhone 8.1
Figura 2 – Projeto no WPhone 8.1

Clique no botão OK e deixe que o Visual Studio crie o projeto. A página MainPage.xaml é a inicial, que já aparece aberta. Todas as referências do XAML estão mostradas nas primeiras linhas, assim como a estrutura inicial sugerida pelo template. Como teremos um mapa nessa página, e é necessário espaço suficiente para exibi-lo. A primeira coisa a fazer é comentar este bloco de códigos a seguir, que já vem com o template.

<!--TitlePanel contains the name of the application and page title--><!--
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
    <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>-->

Pergunta: Renatão, posso deixar essas linhas para colocar o título para que o usuário veja onde está? Sim, pode, mas eu prefiro uma tela mais limpa quando exibo mapas, senão o usuário irá desistir de ter que navegar no mapa num tamanho muito pequeno de tela, isso desanima qualquer um.

A seguir, como vamo lidar com GPS, vamos criar a seguinte estrutura de controles para mostrar a latitude e a longitude. Só que o próximo controle disponível chamado ContentPanel é um Grid, então basta trocá-lo para um StackPanel e acrescentar uma orientação Vertical. Dentro deste, adicione um outro StackPanel Vertical com dois TextBlocks, conforme códigos a seguir. Veja que defini as propriedades Style e Foreground conforme os recursos (StaticResource) do Windows Phone. Claro que você pode alterar para qualquer outro que achar mais adequado.

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" 
        Orientation="Vertical">
    <StackPanel Orientation="Vertical">
        <TextBlock x:Name="StatusTextBlock" 
                   Text="serviço localização desligado" 
                   Style="{StaticResource PhoneTextTitle2Style}" 
                   Foreground="{StaticResource PhoneAccentBrush}" />
        <TextBlock x:Name="DadosTextBlock" 
                   Text="latitude/longitude"
                   Style="{StaticResource PhoneTextTitle2Style}" 
                   Foreground="{StaticResource PhoneAccentBrush}" /> 
    </StackPanel>
    
</StackPanel>

E o mapa? O mapa é um controle existente na Toolbox, então localize o controle Map na toolbox e arraste-o logo após o TextBlock chamado DadosTextBlock. Será adicionada a seguinte linha ao XAML, e aproveite e já configure as propriedades x:Name (nome do controle), ZoomLevel (Zoom default para exibir o mapa) e Height (altura).

<maps:Map x:Name="mapa" ZoomLevel="10" Height="602"/>

Por que foi adicionado o <maps:Map />? Essa notação faz referência ao namespace que é inserido automaticamente na lista no início da página, para que o controle possa ser inicializado e usado, sendo:

xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"

A seguir, precisamos criar a barra de menu da aplicação, que conterá 3 botões e 4 opções de menus. Insira o bloco de códigos XAML a seguir logo abaixo do fechamento da tag do bloco shell:SystemTray.IsVisible=”True”>. Veja que para os 3 ícones estou usando imagens (png) que você pode baixar da Internet ou criar as suas próprias. Observe que o ApplicationBar está configurado para mostrar tudo (IsVisible=True) e que contém menus ativos (IsMenuEnabled=True).

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton 
            IconUri="/Assets/low.png" 
            Text="Baixa"
            Click="Baixa_Click"/>

        <shell:ApplicationBarIconButton 
            IconUri="/Assets/high.png" 
            Text="Alta"
            Click="Alta_Click"/>

        <shell:ApplicationBarIconButton 
            IconUri="/Assets/stop.png" 
            Text="Parar"
            Click="Parar_Click"/>

        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="Rua" Click="Rua_Click"/>
            <shell:ApplicationBarMenuItem Text="Aerio" Click="Aerio_Click"/>
            <shell:ApplicationBarMenuItem Text="Híbrido" Click="Hibrido_Click"/>
            <shell:ApplicationBarMenuItem Text="Terra" Click="Terra_Click"/>
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

Note na figura 3 como está a tela com o layout definido até o momento.

Figura 3 – Tela da página com o mapa e menus
Figura 3 – Tela da página com o mapa e menus

 

Cabe ressaltar que cada opção do ApplicationBar, seja um ícone ou menu, contém o evento Click do objeto, ou seja, para cada uma dessas opções, você deverá assinar o evento. Pronto, salve o projeto e pressione F7 para exibir o código C#. A primeira coisa a fazer é referenciar os dois namespaces a seguir, os quais nos dão acesso à manipulação de serviços de localização e ao controle mapa.

using System.Device.Location;
using Microsoft.Phone.Maps.Controls;

A seguir, logo abaixo da declaração da classe, digite as duas linhas para referenciar a classe GeoCoordinateWatcher na variável watcher, que nos dá acesso a todo o serviço de localização, e crie a variável texto do tipo string.

private void Baixa_Click(object sender, EventArgs e)
{
    texto = "força mínima";
    IniciarLocalizacao(GeoPositionAccuracy.Default);
}

private void Alta_Click(object sender, EventArgs e)
{
    texto = "força total";
    IniciarLocalizacao(GeoPositionAccuracy.High);
}

Como esse evento IniciarLocalizacao ainda não existe, crie-o. Para isso, clique no nome do evento a ser criado e dê um CTRL + Ponto (.) + ENTER. O bloco de código a seguir recebe um argumento, que é o geoPositionAccuracy, mostra o texto “iniciando força mínima” ou “iniciando força total” no TextBlock StatusTextBlock. Depois, inicializa o objeto watcher de acordo com o parâmetro passado (Default ou High) e define a distância mínima de 20 metros conforme a posição é alterada (evento PositionChanged que veremos depois). Duas coisas importantes que você não pode deixar de setar são os eventos StatusChanged e PositionChanged, os quais pertencem ao objeto watcher. O primeiro controle é o status do GPS, e o segundo é o que ficará alerta a qualquer alteração de posição. Sendo assim, já insira o handler para ambos. A dica na digitação é que quando digitar o += já pressione o TAB (2 vezes) logo em seguida; assim, o respectivo evento é criado automaticamente. Ao final, o evento Start é disparado.

private void IniciarLocalizacao(GeoPositionAccuracy geoPositionAccuracy)
{
    StatusTextBlock.Text = "iniciando " + texto;
    watcher = new GeoCoordinateWatcher(geoPositionAccuracy);
    watcher.MovementThreshold = 20;

    watcher.StatusChanged += watcher_StatusChanged;
    watcher.PositionChanged += watcher_PositionChanged;
    watcher.Start();
}

Alteração do status do GPS

O status do GPS pode ser: desabilitado, inicializando, sem dados ou pronto. Para cada opção, é importante avisar o usuário, para que ele saiba o que está acontecendo. O código do evento StatusChanged recebe como argumento o objeto sender e os eventArgs, conforme o código a seguir. Sendo assim, vamos criar um delegate assíncrono para disparar o evento a ser criado chamado TrocarStatusChanged, contendo um swtich para cada estado do GPS. Dessa forma, como será um delegate assíncrono, ele fica o tempo todo em alerta verificando o GPS, e quando o status mudar, ele dispara o evento para exibir o respectivo texto ao usuário. Quando você criar o evento TrocarStatusChanged, veja que ele é um void.

void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
    Deployment.Current.Dispatcher.BeginInvoke(() => TrocarStatusChanged(e));
}


void TrocarStatusChanged(GeoPositionStatusChangedEventArgs e)
{
    switch (e.Status)
    {
        case GeoPositionStatus.Disabled:
            StatusTextBlock.Text = "localização não suportada neste dispositivo";
            break;
        case GeoPositionStatus.Initializing:
            StatusTextBlock.Text = "inicializando o serviço " + texto;
            break;
        case GeoPositionStatus.NoData:
            StatusTextBlock.Text = "dados indisponíveis " + texto;
            break;
        case GeoPositionStatus.Ready:
            StatusTextBlock.Text = "recebendo dados " + texto;
            break;
        default:
            break;
    }  
}

Alteração da posição do GPS

Quando se utilizam os serviços do GPS, presume-se que ele vai adquirir novas coordenadas a cada momento, portanto é preciso ficar atento a cada alteração. Para isso, crie o evento a seguir, que é um delegate assíncrono que irá chamar o evento TrocarPositionChanged para capturar a nova latitude e longitude, informar ao usuário e mostrar no mapa.

Aqui cabe uma observação importante quanto ao uso do mapa da Nokia. Enquanto estamos no ambiente de desenvolvimento, não é preciso ter um appID e um Token, o seu mapa funcionará sem problemas. Mas, no momento que for distribuir a aplicação na lojinha, aí sim, você precisa entrar no site da MS Store, registrar a sua aplicação, pedir para gerar um Token e, quando este estiver gerado, é o que você deverá adicionar às linhas comentadas que deixei de propósito no código a seguir.

void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    Deployment.Current.Dispatcher.BeginInvoke(() => TrocarPositionChanged(e));
}


void TrocarPositionChanged(GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    DadosTextBlock.Text = string.Format("lat: {0:0.000} / long: {1:0.000}",
                            e.Position.Location.Latitude, e.Position.Location.Longitude);

    //MapsSettings.ApplicationContext.ApplicationId = "appID";
    //MapsSettings.ApplicationContext.AuthenticationToken = "AuthenticationToken";

    mapa.Center = new GeoCoordinate(e.Position.Location.Latitude,
                                    e.Position.Location.Longitude);
}

Agora que já temos os delegates para ficar observando as alterações de Status e posição, digite o código para o botão Parar, conforme a seguir.

private void Parar_Click(object sender, EventArgs e)
{
    if (watcher != null)
        watcher.Stop();

    StatusTextBlock.Text = "sem serviço de localização";
    DadosTextBlock.Text = string.Empty;
}

Configuração do manifesto

Como esta é uma aplicação que usará o serviço de localização, é preciso configurar o manifesto. Portanto, abra o arquivo WMAppManifest.xml e, na guia Capabilities, marque o checkbox ID_CAP_LOCATION, conforme a figura 4. Em seguida, salve a aplicação.

Figura 4 – Configuração do manifesto
Figura 4 – Configuração do manifesto

 

Tipos de visões do mapa

A maioria dos mapas utiliza recurso de mostrar as imagens de diferentes visões, e como o nosso menu já está preparado para tal funcionalidade, resta-nos implementar o código a seguir. No geral, essa funcionalidade é tão simples que basta configurar o Enum da propriedade CartographicMode.

private void Rua_Click(object sender, EventArgs e)
{
    mapa.CartographicMode = MapCartographicMode.Road;
}

private void Aerio_Click(object sender, EventArgs e)
{
    mapa.CartographicMode = MapCartographicMode.Aerial;
}

private void Hibrido_Click(object sender, EventArgs e)
{
    mapa.CartographicMode = MapCartographicMode.Hybrid;
}

private void Terra_Click(object sender, EventArgs e)
{
    mapa.CartographicMode = MapCartographicMode.Terrain;
}

Lidar com problemas de memória em qualquer aplicação não é fácil, e deixar coisas pendentes não é uma boa prática. Como atribuímos o handler para dois eventos do watcher no IniciarLocalizacao, sobrescreva o evento OnNavigateFrom com o código a seguir para desmarcar esses dois handlers invocados anteriormente. Isso é uma boa prática e fundamental para não dar problemas de memory leak ou conflitos desnecessários. O OnNavigateFrom ocorre quando a aplicação é suspensa ou fechada.

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    watcher.StatusChanged -= watcher_StatusChanged;
    watcher.PositionChanged -= watcher_PositionChanged;
            
    base.OnNavigatedFrom(e);
}

Pronto, todos os códigos estão prontos. Compile a aplicação e certifique-se de que está compilado 100% com sucesso. Pressione F5 para executar a aplicação no emulador do Windows Phone 8.1. Se você estiver utilizando um proxy e não mostrar o mapa, é preciso configurar a rede para que a máquina virtual enxergue a sua rede, afinal o emulador é uma máquina virtual.

Assim que a aplicação abrir no emulador, clique no primeiro ou segundo ícones para ativar a localização. Uma excelente opção é clicar no ícone Tools (>>) para exibir as ferramentas adicionais. Na guia Location, digite o nome da cidade e marque um ponto com o mouse para que o mapa no celular seja mostrado, assim como a latitude e a longitude. Na figura 5, digitei a cidade Seattle e marquei um determinado ponto.

Figura 5 – Mapa de Seattle
Figura 5 – Mapa de Seattle

 

Agora, alterei para a cidade de Florianópolis e marquei um ponto. Na aplicação, selecione o menu com vista aérea, conforme a figura 6. Nos ícones do emulador, há um ícone com um indicador, que deve utilizar para dar o zoom no mapa do emulador.

 

Figura 6 – Vista aérea
Figura 6 – Vista aérea

Já que programamos os códigos, fique à vontade para selecionar os demais tipos de visões e identificar qual é a mais adequada para você, conforme a figura 7.

Figura 7 – Visões Híbrida e Terra do mapa
Figura 7 – Visões Híbrida e Terra do mapa

 

Você pode colocar breakpoints nos códigos para identificar exatamente o que se passa quando ocorre uma alteração na mudança de posição ou alteração do status do GPS.

Conclusão

O uso de mapas integrados à aplicação tem se tornado comum em diversas áreas. Imagine aplicações de rastreamento de encomendas ou ainda entregas de mercadorias, ou seja, o horizonte que podemos ter nessa área é muito amplo. E, do ponto de vista de implementação de códigos visto neste artigo, facilita muito para o desenvolvedor que deseja adicionar mapas para enriquecer a aplicação.

Agradeço a oportunidade de poder compartilhar o conhecimento com todos. Qualquer dúvida e preparação de times de desenvolvimento, por favor me contate.