Desenvolvendo Mapas em Universal Apps

O mapa é um recurso muito utilizado por aplicações Windows e Windows Phone. No caso de Universal Apps, temos a possibilidade de criar um código comum entre as duas plataformas, facilitando a manutenção e proporcionando maior agilidade no desenvolvimento do código.

Ao desenvolver um aplicativo de mapa num Universal App, é necessário incluir o SDK do Bing Map no Windows 8.1. Para incluir o SDK do Bing Map, deve-se usar a ferramenta “Extensions and Updates..” do menu “TOOLS” do Visual Studio:

Adicionando extensão do Bing Maos SDK
Adicionando extensão do Bing Maps SDK

Para o Windows Phone, ele já está embutido e portanto, não precisamos de nenhum outra intervenção.

Diferenças entre Windows 8.1 e Windows Phone 8.1

A API de mapas do Windows 8.1 difere daquela disponível para Windows Phone 8.1. Já a próxima versão do Windows aponta para uma convergência de modo que será mais fácil a implementação de aplicativos universais com mapas.
Mas atualmente, desenvolver aplicativos universais com suporte a mapas é uma tarefa que exige cuidados e atenção do desenvolvedor.

Por exemplo, para adicionar um Pin no mapa, temos, para Windows Phone, o elemento MapIcon. Porém este elemento não está disponível para Windows. Portanto, no Windows temos que incluir um UIElement no mapa.

Segue trecho de código que define a posição do usuário no mapa:

public void SetPosition(BasicGeoposition center, double zoom)
{
#if WINDOWS_APP
    map.SetView(new Location(center.Latitude, center.Longitude), zoom);
    RaisePropertyChanged("Center");
    RaisePropertyChanged("Zoom");
#elif WINDOWS_PHONE_APP
    map.Center = new Geopoint(center);
    map.ZoomLevel = zoom;
#endif
}

Nota: O uso dos símbolos de compilação WINDOWS_APP e WINDOWS_PHONE_APP ajudam a delimitar o código que é específico para Windows e Windows Phone. Existem ainda outras estratégias para trabalhar com Universal Apps. Para mais detalhes, acessar a referência “Strategies for sharing XAML code in Universal Apps”.

E durante o desenvolvimento, podemos usar o recurso do Visual Studio para chavear entre código Windows e Windows Phone:

Chaveando entre projetos Windows e Windows Phone.
Chaveando entre projetos Windows e Windows Phone.

Desempenho do Mapa

Um ponto chave no desenvolvimento de aplicativos é o controle sobre a taxa de redesenho da tela para maximizar o desempenho da aplicação.

Uma forma de detectar o redesenho é utilizando o recurso Settings.EnableRedrawRegions que altera a cor dos elementos visuais toda vez que eles são redesenhados:

/// <summary>
/// Invoked when the application is launched normally by the end user.  Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
    if (System.Diagnostics.Debugger.IsAttached)
    {
        this.DebugSettings.EnableRedrawRegions = true;
        this.DebugSettings.EnableFrameRateCounter = true;
    }
#endif
//...
}

Com isso, caso o aplicativo esteja com problemas de desempenho, podemos usar esse recurso para verificar o que está causando o problema:

Monitoramento de desempenho do aplicativo.
Monitoramento de desempenho do aplicativo.

No caso do mapa, especificamente, quando usamos o MapIcon no Windows Phone, a taxa de redesenho é menor quando movemos o mapa. Mas quando adicionamos um UIElement, a taxa de redesenho é maior
Por conta disso, é recomendado usar MapIcon para Windows Phone. E como Windows não tem ainda o MapIcon, nele ainda usamos o UIElement. Segue trecho de código que exibe o método que adiciona o Pin considerando essas diferenças:

public void AddPin(PushpinViewModel pushpinViewModel)
{
    pushpinViewModelList.Add(pushpinViewModel);
                        
#if WINDOWS_APP
            
    Pushpin pushpin = new Pushpin();
    pushpin.DataContext = pushpinViewModel;
    map.Children.Add(pushpin);
    MapLayer.SetPosition(pushpin, new Location(pushpinViewModel.Position.Latitude, pushpinViewModel.Position.Longitude));            
            
    // MapPolygon is not supported by Windows 8.1 Bing Map SDK
 
#elif WINDOWS_PHONE_APP
 
    MapIcon mapIcon = new MapIcon();
    mapIcon.Image = pushpinViewModel.PushpinImage;
    mapIcon.Location = new Geopoint(pushpinViewModel.Position);
    mapIcon.NormalizedAnchorPoint = new Point(0.5, 1.0);
    mapIcon.Title = pushpinViewModel.Title == null ? string.Empty : pushpinViewModel.Title;
    map.MapElements.Add(mapIcon);
 
    MapPolygon precision = null;
 
    if (pushpinViewModel.Accuracy > 0)
    {
        precision = GenerateMapAccuracyCircle(pushpinViewModel.Position, pushpinViewModel.Accuracy);
        map.MapElements.Add(precision);                
    }            
 
    //make a new source            
    pushpinViewModel.PropertyChanged += (sender, args) =>
    {
        if (args.PropertyName.Equals("Title"))
        {
            mapIcon.Title = ((PushpinViewModel)sender).Title;
        }
        else if (args.PropertyName.Equals("PushpinImage"))
        {
            mapIcon.Image = pushpinViewModel.PushpinImage;            
        }
        else if (args.PropertyName.Equals("Position"))
        {
            mapIcon.Location = new Geopoint(pushpinViewModel.Position);
        }
        else if (args.PropertyName.Equals("Accuracy") && precision != null)
        {
            GenerateMapAccuracyCircle(pushpinViewModel.Position, pushpinViewModel.Accuracy, precision);
        }
    };
 
#endif      
}

Localizando um Pin específico

Quando adicionamos o Pin, as vezes precisamos interagir com um pin específico para alterar sua localização os as informações nele contidas. Por exemplo, podemos alterar a localização do Pin que representa minha localização no mapa.

Para tanto, caso estejamos usando o UIElement, basta especificarmos a propriedade Tag ou definir outra propriedade que poderá ser usada posteriormente para localizar, na lista de elementos no mapa, aquele que contém a informação que identifica o Pin como sendo o da minha localização.
Mas no caso do MapIcon, não temos essa possibilidade já que nele podemos definir somente imagem, título e posição.
Uma forma de contornarmos isso é mantermos uma lista de objetos que estão relacionados com o Pin usando um ViewModel. No exemplo, usamos o PushpinViewModel. Daí, quando adicionamos um Pin, incluímos também o PushpinViewModel correspondente numa lista e então depois adicionamos o Pin no mapa.
E para esse PushpinViewModel, usamos os eventos de notificação de mudança de propriedade para alterar as informações dos Pins no mapa. Mas isso varia no caso de usarmos o UIElement ou o MapIcon:

  • UIElement: Definimos o PushpinViewModel como DataContext do elemento. E neste elemento fazemos o DataBinding da imagem e texto de acordo com a posição.
  • MapIcon: Adicionamos o handler para o evento de mudança de propriedade para alterarmos os valores do MapIcon quando o ViewModel for modificado:
    //make a new source            
    pushpinViewModel.PropertyChanged += (sender, args) =>
    {
        if (args.PropertyName.Equals("Title"))
        {
            mapIcon.Title = ((PushpinViewModel)sender).Title;
        }
        else if (args.PropertyName.Equals("PushpinImage"))
        {
            mapIcon.Image = pushpinViewModel.PushpinImage;            
        }
        else if (args.PropertyName.Equals("position"))
        {
            mapIcon.Location = new Geopoint(pushpinViewModel.Position);
        }
    };
    

Depois disso, para manipularmos um Pin no mapa, basta referenciarmos o PushpinViewModel e fazer as operações sobre ele.

Definindo Círculo de Precisão – Somente Windows Phone

Quando obtemos a posição do usuário usando GPS, dentre as informações que a API nos retorna, temos a propriedade Accuracy que fornece a precisão do local em metros.
Para este valor, precisamos fazer cálculos para gerar um círculo de precisão. Com resultado obtemos um conjunto de pontos no mapa que irão compor o MapPolygon. Contudo, este MapPolygon é específico para Windows Phone. Segue método que gera o MapPolygon:


/// <summary>
/// The MapPolygon is only supported on Windows Phone.
/// So, this method is only implemented for Windows Phone
/// </summary>
/// <param name="position">The position on map</param>
/// <param name="accuracy">The accuracy obtained from position on map</param>
/// <param name="precision">Specify the polygon if it already exist</param>
/// <returns>Updated MapPolygon</returns>
public MapPolygon GenerateMapAccuracyCircle(BasicGeoposition position, double accuracy, MapPolygon precision = null)
{
    if (precision == null)
    { 
        precision = new MapPolygon();
        precision.StrokeThickness = 1;
        precision.FillColor = Windows.UI.Color.FromArgb(80, 255,0,0);
    }
 
    var earthRadius = 6371;
    var lat = position.Latitude * Math.PI / 180.0; //radians
    var lon = position.Longitude * Math.PI / 180.0; //radians
    var d = accuracy / 1000 / earthRadius; // d = angular distance covered on earths surface
 
    List<BasicGeoposition> precisionPath = new List<BasicGeoposition>();
    for (int x = 0; x <= 360; x++)
    {
        var brng = x * Math.PI / 180.0; //radians
        var latRadians = Math.Asin(Math.Sin(lat) * Math.Cos(d) + Math.Cos(lat) * Math.Sin(d) * Math.Cos(brng));
        var lngRadians = lon + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(lat), Math.Cos(d) - Math.Sin(lat) * Math.Sin(latRadians));
 
        var pt = new BasicGeoposition()
        {
            Latitude = 180.0 * (latRadians / Math.PI),
            Longitude = 180.0 * (lngRadians / Math.PI)
        };
 
        precisionPath.Add(pt);
    }
 
    precision.Path = new Geopath(precisionPath);
 
    return precision;
}

Esse MapPolygon deve então ser adicionado no mapa como um MapElement:

MapPolygon precision = null;
 
if (pushpinViewModel.Accuracy > 0)
{
    precision = GenerateMapAccuracyCircle(pushpinViewModel.Position, pushpinViewModel.Accuracy);
    map.MapElements.Add(precision);                
}

Referências

Continue nos acompanhando no blog talkitbr.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s