Descripción general de mapas y ubicaciones

SDK de Aplicaciones para Windows y WinUI 3 proporcionan API y controles para mostrar mapas, detectar la ubicación del usuario y configurar geovallas. Use estas funcionalidades para crear aplicaciones que muestren mapas interactivos con patillas, realizar un seguimiento de la posición del usuario y desencadenar acciones cuando el usuario entra o sale de un área geográfica.

En este artículo se presenta cada funcionalidad e incluye un ejemplo completo que combina MapControl, Geolocatory GeofenceMonitor en una sola aplicación de trabajo.

Mostrar mapas con MapControl

MapControl muestra un mapa interactivo con tecnología de Azure Maps. Puede agregar marcadores, capas y responder a las interacciones del usuario, como desplazamiento, zoom y clic.

MapControl requiere una cuenta de Azure Maps. Consulte Administración de la cuenta de Azure Maps para crear una cuenta y obtener un token de servicio.

Para obtener instrucciones de uso detalladas, consulte MapControl.

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

Note

Las API de UWP MapControl y Windows.Services.Maps están en desuso y es posible que no estén disponibles en futuras versiones de Windows. Las aplicaciones winUI 3 deben usar el nuevo MapControl descrito anteriormente. Para obtener más información, consulte Recursos para características en desuso.

Detección de la ubicación del usuario

Las API de Windows.Devices.Geolocation permiten obtener la posición geográfica del dispositivo. Estas API funcionan en aplicaciones para UWP y SDK de Aplicaciones para Windows (WinUI 3). Ustedes pueden:

Para obtener una guía paso a paso, consulte Obtención de la ubicación del usuario.

Configurar geovallas

Una geovalla define un límite geográfico. La aplicación recibe notificaciones cuando el usuario entra o sale del límite. Las geocercas son útiles para recordatorios, alertas o la entrega de contenido basados en la ubicación.

Para obtener instrucciones sobre cómo crear y supervisar geovallas, consulte Configuración de una geovalla.

Funcionalidad y privacidad de la ubicación

Todas las API de ubicación requieren la funcionalidad Ubicación declarada en el manifiesto del paquete de la aplicación. También debe llamar a Geolocator.RequestAccessAsync en tiempo de ejecución antes de acceder a los datos de ubicación.

Windows proporciona a los usuarios control sobre qué aplicaciones pueden acceder a su ubicación a través de configuración > Privacidad y ubicación de seguridad>. La aplicación debe controlar el caso en el que el usuario deniega o revoca el acceso a la ubicación.

Ejemplo completo

En el ejemplo siguiente se reúne MapControl, Geolocatory GeofenceMonitor en una sola ventana de WinUI 3. Si no se ha configurado ninguna clave de Azure Maps, el mapa se degrada de forma controlada, mientras que la geolocalización y las geovallas siguen funcionando.

Prerequisites

  • SDK de Aplicaciones para Windows 2.2 o posterior
  • Clave de Azure Maps, necesaria para mostrar iconos de mapa. Sin una clave válida, MapControl se muestra pero presenta un mapa en blanco.
  • Funcionalidad del dispositivo ubicación declarada en Package.appxmanifest:
<DeviceCapability Name="location" />

Establezca la clave de Azure Maps como una variable de entorno antes de ejecutar la aplicación:

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

MainWindow.xaml

Un panel de control de 300 píxeles a la izquierda con botones y texto de estado, y un elemento MapControl a la derecha. Aparece una superposición cuando falta la clave de Azure Maps.

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

Patrones clave

  • MapControlestá integrado en SDK de Aplicaciones para Windows 1.6 y versiones posteriores. Asigne MapServiceToken a su clave de Azure Maps.
  • TryConfigureMap comprueba la variable de AZURE_MAPS_KEY entorno en el inicio. Si la variable está vacía, el mapa se pliega y un mensaje superpuesto explica cómo solucionarlo: sin que la aplicación se bloquee y sin que el mapa quede en blanco.
  • DeviceCapability Name="location" en Package.appxmanifest es obligatorio o Geolocator.RequestAccessAsync devuelve Denied.
  • GeofenceMonitor.GeofenceStateChanged se ejecuta en un subproceso en segundo plano, por lo que debe utilizar DispatcherQueue.TryEnqueue para actualizar la interfaz de usuario.