Visão geral de mapas e localização

SDK do Aplicativo Windows e WinUI 3 fornecem APIs e controles para exibir mapas, detectar a localização do usuário e configurar cercas geográficas. Use esses recursos para criar aplicativos que mostram mapas interativos com pinos, acompanhar a posição do usuário e disparar ações quando o usuário entra ou sai de uma área geográfica.

Este artigo apresenta cada funcionalidade e inclui um exemplo completo que combina MapControl, Geolocatore GeofenceMonitor em um único aplicativo de trabalho.

Exibir mapas com MapControl

O MapControl exibe um mapa interativo alimentado por Azure Mapas. Você pode adicionar pinos, camadas e responder a interações do usuário, como arrastar, aplicar zoom e clicar.

MapControl requer uma conta de Azure Mapas. Consulte Gerenciar sua conta Azure Mapas para criar uma conta e obter um token de serviço.

Para obter instruções detalhadas de uso, consulte MapControl.

<MapControl x:Name="myMap"
            MapServiceToken="YOUR_AZURE_MAPS_TOKEN"
            Height="400" />

Note

As APIs UWP MapControl e Windows.Services.Maps estão obsoletas e podem não estar disponíveis em versões futuras do Windows. Os aplicativos WinUI 3 devem usar o novo MapControl descrito acima. Para obter mais informações, veja Recursos para funcionalidades preteridas.

Detectar a localização do usuário

As APIs Windows.Devices.Geolocation permitem obter a posição geográfica do dispositivo. Essas APIs funcionam em aplicativos UWP e SDK do Aplicativo Windows (WinUI 3). É possível:

Para obter um guia passo a passo, consulte Obter a localização do usuário.

Configurar cercas geográficas

Uma cerca geográfica define um limite geográfico. Seu aplicativo recebe notificações quando o usuário entra ou sai do limite. As geocercas são úteis para lembretes, alertas ou fornecimento de conteúdo com base na localização.

Para obter instruções sobre como criar e monitorar cercas geográficas, consulte Configurar uma cerca geográfica.

Capacidade de localização e privacidade

Todas as APIs de localização exigem a funcionalidade Local declarada no manifesto do pacote do aplicativo. Você também deve chamar Geolocator.RequestAccessAsync em runtime antes de acessar dados de localização.

Windows fornece aos usuários controle sobre quais aplicativos podem acessar sua localização por meio do Local de Privacidade e Segurança > de Configurações>. Seu aplicativo deve lidar com o caso em que o usuário nega ou revoga o acesso ao local.

Exemplo completo

O exemplo a seguir reúne MapControl, Geolocatore GeofenceMonitor em uma única janela do WinUI 3. Se nenhuma chave do Azure Mapas estiver configurada, o mapa se degrada normalmente, enquanto a geolocalização e o geofencing continuam funcionando.

Pré-requisitos

  • SDK do Aplicativo Windows 2.2 ou posterior
  • Uma chave Azure Mapas – necessária para exibir blocos de mapa. Sem uma chave válida, o MapControl é renderizado, mas mostra um mapa em branco.
  • A funcionalidade do dispositivo Location declarada em Package.appxmanifest:
<DeviceCapability Name="location" />

Defina sua chave Azure Mapas como uma variável de ambiente antes de executar o aplicativo:

$env:AZURE_MAPS_KEY = "your-key-here"

MainWindow.xaml

Um painel de controle de 300 pixels à esquerda com botões e texto de status e um MapControl à direita. Uma sobreposição é exibida quando a chave do Azure Mapas está ausente.

<?xml version="1.0" encoding="utf-8" ?>
<Window
    x:Class="MapLocationDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Map Location Demo">
    <Window.SystemBackdrop>
        <MicaBackdrop />
    </Window.SystemBackdrop>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TitleBar Title="Map Location Demo" />

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="300" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Grid Grid.Column="0" Margin="16" RowSpacing="12">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <TextBlock Text="Location Demo" FontSize="20" FontWeight="Bold"/>
                <StackPanel Grid.Row="1" Spacing="8">
                    <Button x:Name="FindMeButton" Content="Find My Location"
                            Click="FindMeButton_Click" HorizontalAlignment="Stretch"/>
                    <Button x:Name="AddGeofenceButton" Content="Add Geofence Here"
                            Click="AddGeofenceButton_Click" HorizontalAlignment="Stretch"/>
                </StackPanel>
                <TextBlock x:Name="StatusText" Grid.Row="2"
                           Text="Click 'Find My Location' to begin." TextWrapping="Wrap"/>
                <ListView x:Name="EventLog" Grid.Row="3" Header="Event Log"/>
            </Grid>

            <Grid Grid.Column="1">
                <MapControl x:Name="MyMap" />
                <StackPanel x:Name="MapKeyMissing" Visibility="Collapsed"
                            HorizontalAlignment="Center" VerticalAlignment="Center"
                            Spacing="8">
                    <FontIcon Glyph="&#xE783;" FontSize="48"
                              HorizontalAlignment="Center"
                              Foreground="{ThemeResource SystemFillColorCautionBrush}" />
                    <TextBlock Text="Azure Maps key not configured"
                               FontSize="18" FontWeight="SemiBold"
                               HorizontalAlignment="Center" />
                    <TextBlock x:Name="MapKeyHint" TextWrapping="Wrap" MaxWidth="400"
                               HorizontalAlignment="Center" TextAlignment="Center"
                               Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Devices.Geolocation;
using Windows.Devices.Geolocation.Geofencing;

namespace MapLocationDemo;

public sealed partial class MainWindow : Window
{
    private Geolocator? _geolocator;
    private BasicGeoposition _lastPosition;
    private bool _mapAvailable;

    public MainWindow()
    {
        InitializeComponent();
        _mapAvailable = TryConfigureMap();
        GeofenceMonitor.Current.GeofenceStateChanged += OnGeofenceStateChanged;
    }

    // Read the Azure Maps key from an environment variable.
    // If missing, collapse the map and show an informational overlay.
    private bool TryConfigureMap()
    {
        var key = Environment.GetEnvironmentVariable("AZURE_MAPS_KEY");
        if (string.IsNullOrWhiteSpace(key))
        {
            MyMap.Visibility = Visibility.Collapsed;
            MapKeyMissing.Visibility = Visibility.Visible;
            MapKeyHint.Text = "Set the AZURE_MAPS_KEY environment variable "
                + "and restart.\nGeolocation and geofencing still work "
                + "without the map.";
            Log("Azure Maps key not found — map disabled");
            return false;
        }
        MyMap.MapServiceToken = key;
        return true;
    }

    private async void FindMeButton_Click(object sender, RoutedEventArgs e)
    {
        FindMeButton.IsEnabled = false;
        StatusText.Text = "Requesting location access...";

        var access = await Geolocator.RequestAccessAsync();
        if (access != GeolocationAccessStatus.Allowed)
        {
            StatusText.Text =
                "Location access denied. Check Settings > Privacy > Location.";
            FindMeButton.IsEnabled = true;
            return;
        }

        _geolocator = new Geolocator { DesiredAccuracyInMeters = 100 };
        try
        {
            var pos = await _geolocator.GetGeopositionAsync();
            var lat = pos.Coordinate.Point.Position.Latitude;
            var lon = pos.Coordinate.Point.Position.Longitude;
            _lastPosition = new BasicGeoposition
            {
                Latitude = lat, Longitude = lon
            };

            StatusText.Text =
                $"Location: {lat:F5}, {lon:F5}  ({pos.Coordinate.Accuracy:F0} m)";
            Log($"Position: {lat:F5}, {lon:F5}");

            if (_mapAvailable)
            {
                var pt = new Geopoint(_lastPosition);
                MyMap.Center = pt;
                MyMap.ZoomLevel = 15;

                var layer = new MapElementsLayer();
                layer.MapElements = new List<MapElement>
                {
                    new MapIcon { Location = pt }
                };
                MyMap.Layers.Clear();
                MyMap.Layers.Add(layer);
            }
        }
        catch (Exception ex) { StatusText.Text = $"Error: {ex.Message}"; }
        finally { FindMeButton.IsEnabled = true; }
    }

    private void AddGeofenceButton_Click(object sender, RoutedEventArgs e)
    {
        if (_lastPosition.Latitude == 0 && _lastPosition.Longitude == 0)
        {
            StatusText.Text = "Get your location first.";
            return;
        }

        var fence = new Geofence("MyGeofence",
            new Geocircle(_lastPosition, 200),
            MonitoredGeofenceStates.Entered | MonitoredGeofenceStates.Exited,
            false, TimeSpan.FromSeconds(5));
        GeofenceMonitor.Current.Geofences.Add(fence);

        StatusText.Text = $"Geofence added at "
            + $"{_lastPosition.Latitude:F5}, {_lastPosition.Longitude:F5}";
        Log("Geofence registered");
    }

    private void OnGeofenceStateChanged(
        GeofenceMonitor sender, object args)
    {
        var reports = sender.ReadReports();
        DispatcherQueue.TryEnqueue(() =>
        {
            foreach (var r in reports)
            {
                var msg = r.NewState switch
                {
                    GeofenceState.Entered => $"Entered: {r.Geofence.Id}",
                    GeofenceState.Exited  => $"Exited: {r.Geofence.Id}",
                    GeofenceState.Removed => $"Removed: {r.Geofence.Id}",
                    _ => null
                };
                if (msg != null) { StatusText.Text = msg; Log(msg); }
            }
        });
    }

    private void Log(string msg) =>
        EventLog.Items.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {msg}");
}

Principais padrões

  • MapControlé integrado ao SDK do Aplicativo Windows 1.6 e posterior. Defina MapServiceToken como sua chave de Azure Mapas.
  • TryConfigureMap verifica a variável de AZURE_MAPS_KEY ambiente na inicialização. Se a variável estiver vazia, o mapa se recolhe e uma sobreposição explica como corrigir o problema — sem travamentos, sem mapa em branco.
  • DeviceCapability Name="location" em Package.appxmanifest é necessário ou Geolocator.RequestAccessAsync retorna Denied.
  • GeofenceMonitor.GeofenceStateChanged é executado em uma thread em segundo plano, portanto, use DispatcherQueue.TryEnqueue para atualizar a interface do usuário.