Visão geral de mapas e localização

O SDK de Aplicações Windows e o WinUI 3 fornecem APIs e controlos para mostrar mapas, detetar a localização do utilizador e configurar geofences. Use estas capacidades para construir aplicações que mostrem mapas interativos com pinos, rastreiem a posição do utilizador e desencadeem ações quando este entra ou sai de uma área geográfica.

Este artigo apresenta cada capacidade e inclui um exemplo completo que combina MapControl, Geolocator, e GeofenceMonitor numa única aplicação funcional.

Mostrar mapas com o MapControl

O MapControl apresenta um mapa interativo alimentado pelo Azure Maps. Pode adicionar marcadores, camadas e reagir às interações do utilizador, como deslocação, zoom e clique.

O MapControl requer uma conta no Azure Maps. Consulte Gerir a sua conta Azure Maps para criar uma conta e obter um token de serviço.

Para instruções de utilização detalhadas, consulte MapControl.

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

Note

O MapControl do UWP e as APIs Windows.Services.Maps estão obsoletos e poderão não estar disponíveis em versões futuras do Windows. As aplicações WinUI 3 devem usar o novo MapControl descrito acima. Para obter mais informações, consulte Recursos para funcionalidades descontinuadas.

Detetar a localização do utilizador

As APIs Windows.Devices.Geolocation permitem-lhe obter a posição geográfica do dispositivo. Estas APIs funcionam tanto em aplicações UWP como em SDK de Aplicações Windows (WinUI 3). É possível:

Para um guia passo a passo, consulte Obter a localização do utilizador.

Instalar geofences

Uma geocerca define uma fronteira geográfica. A sua aplicação recebe notificações quando o utilizador entra ou sai do limite. As geofences são úteis para lembretes baseados na localização, alertas ou entrega de conteúdos.

Para instruções sobre como criar e monitorizar geofences, veja Configurar uma geofence.

Capacidade de localização e privacidade

Todas as APIs de localização exigem a capacidade de Localização declarada no manifesto do pacote da sua aplicação. Também tem de chamar Geolocator.RequestAccessAsync em tempo de execução antes de aceder aos dados de localização.

O Windows dá aos utilizadores controlo sobre quais as aplicações que podem aceder à sua localização através das Definições > Localização de Privacidade e Segurança>. A sua aplicação deve lidar com o caso em que o utilizador nega ou revoga o acesso à localização.

Exemplo completo

O exemplo seguinte reúne MapControl, Geolocator, e GeofenceMonitor numa única janela do WinUI 3. Se não houver uma chave do Azure Maps configurada, o mapa degrada-se de forma natural enquanto a geolocalização e a geofencing continuam a funcionar.

Prerequisites

  • SDK de Aplicações Windows 2.2 ou posterior
  • Uma chave do Azure Maps — necessária para mostrar as peças do mapa. Sem uma chave válida, o MapControl renderiza mas mostra um mapa em branco.
  • A capacidade do dispositivo de localização declarada em Package.appxmanifest:
<DeviceCapability Name="location" />

Defina a sua chave Azure Maps como variável de ambiente antes de executar a aplicação:

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

MainWindow.xaml

Um painel de controlo de 300 píxeis à esquerda com botões e texto de estado, e um MapControl à direita. Uma sobreposição é apresentada quando a chave do Azure Maps estiver em falta.

<?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}");
}

Padrões-chave

  • MapControlestá integrado no SDK de Aplicações Windows 1.6 e versões posteriores. Define MapServiceToken para a tua chave Azure Maps.
  • TryConfigureMap verifica a AZURE_MAPS_KEY variável ambiente no arranque. Se a variável estiver vazia, o mapa colapsa e uma sobreposição explica como corrigi-la — sem crash, sem mapa vazio.
  • DeviceCapability Name="location" em Package.appxmanifest é obrigatório ou Geolocator.RequestAccessAsync devolve Denied.
  • GeofenceMonitor.GeofenceStateChanged é acionado numa thread em segundo plano, por isso use DispatcherQueue.TryEnqueue para atualizar a IU.