Mapy i lokalizacja — omówienie

Zestaw SDK do aplikacji systemu Windows i WinUI 3 udostępniają interfejsy API i kontrolki do wyświetlania map, wykrywania lokalizacji użytkownika i konfigurowania geofencingów. Te możliwości umożliwiają tworzenie aplikacji, które wyświetlają interaktywne mapy z przypinaniami, śledzą położenie użytkownika i wyzwalają akcje po wejściu użytkownika lub opuszczeniu obszaru geograficznego.

W tym artykule omówiono poszczególne funkcje i zamieszczono kompletny przykład, który łączy MapControl, Geolocator i GeofenceMonitor w jednej działającej aplikacji.

Wyświetlanie map za pomocą kontrolki MapControl

Kontrolka MapControl wyświetla interaktywną mapę obsługiwaną przez Azure Maps. Możesz dodawać znaczniki i warstwy oraz obsługiwać interakcje użytkownika, takie jak przesuwanie mapy, powiększanie i klikanie.

MapControl wymaga konta Azure Maps. Zobacz Zarządzanie kontem Azure Maps, aby utworzyć konto i uzyskać token usługi.

Aby uzyskać szczegółowe instrukcje dotyczące użycia, zobacz MapControl.

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

Note

Interfejsy API UWP MapControl i Windows.Services.Maps są przestarzałe i mogą nie być dostępne w przyszłych wersjach systemu Windows. Aplikacje WinUI 3 powinny używać nowej kontrolki MapControl opisanej powyżej. Aby uzyskać więcej informacji, zobacz Zasoby dotyczące przestarzałych funkcji.

Wykrywanie lokalizacji użytkownika

Interfejsy API Windows.Devices.Geolocation umożliwiają uzyskanie położenia geograficznego urządzenia. Te interfejsy API działają zarówno w aplikacjach platformy UWP, jak i Zestaw SDK do aplikacji systemu Windows (WinUI 3). Masz następujące możliwości:

Aby zapoznać się z przewodnikiem krok po kroku, zobacz Pobieranie lokalizacji użytkownika.

Konfigurowanie geofencingów

Geofence definiuje wirtualną granicę geograficzną. Aplikacja otrzymuje powiadomienia, gdy użytkownik wchodzi lub zamyka granicę. Geofencingi są przydatne w przypadku przypomnień opartych na lokalizacji, alertów lub dostarczania zawartości.

Aby uzyskać instrukcje dotyczące tworzenia i monitorowania geofencingów, zobacz Konfigurowanie geofencingu.

Możliwość lokalizacji i prywatność

Wszystkie interfejsy API lokalizacji wymagają możliwości lokalizacji zadeklarowanej w manifeście pakietu aplikacji. Przed uzyskaniem dostępu do danych lokalizacji należy również wywołać narzędzie Geolocator.RequestAccessAsync w czasie wykonywania.

Windows zapewnia użytkownikom kontrolę nad tym, które aplikacje mogą uzyskiwać dostęp do ich lokalizacji za pośrednictwem ustawień > Prywatność i lokalizacja zabezpieczeń>. Aplikacja powinna obsługiwać przypadek, w którym użytkownik odmawia lub odwołuje dostęp do lokalizacji.

Kompletny przykład

Poniższy przykład łączy elementy MapControl, Geolocatori GeofenceMonitor w jednym oknie WinUI 3. Jeśli nie skonfigurowano klucza Azure Maps, mapa przechodzi w tryb ograniczonego działania, a geolokalizacja i geostrefy nadal działają.

Prerequisites

  • Zestaw SDK do aplikacji systemu Windows 2.2 lub nowszy
  • Klucz Azure Maps — wymagany do wyświetlania kafelków mapy. Bez prawidłowego klucza kontrolka MapControl renderuje, ale wyświetla pustą mapę.
  • Funkcja urządzenia Lokalizacja zadeklarowana w elemecie Package.appxmanifest:
<DeviceCapability Name="location" />

Przed uruchomieniem aplikacji ustaw klucz Azure Maps jako zmienną środowiskową:

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

MainWindow.xaml

Panel sterowania o szerokości 300 pikseli po lewej stronie z przyciskami i tekstem stanu oraz MapControl po prawej stronie. Gdy brakuje klucza Azure Maps, pojawia się nakładka.

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

Kluczowe wzorce

  • MapControl jest wbudowany w pakiet Zestaw SDK do aplikacji systemu Windows 1.6 i nowsze wersje. Ustaw MapServiceToken na swój klucz Azure Maps.
  • TryConfigureMap sprawdza zmienną środowiskową podczas uruchamiania AZURE_MAPS_KEY . Jeśli zmienna jest pusta, mapa zwinie się, a nakładka wyjaśnia, jak ją naprawić — bez awarii, bez pustej mapy.
  • DeviceCapability Name="location" w Package.appxmanifest jest wymagane, w przeciwnym razie Geolocator.RequestAccessAsync zwróci Denied.
  • GeofenceMonitor.GeofenceStateChanged uruchamia się w wątku w tle, więc użyj polecenia DispatcherQueue.TryEnqueue , aby zaktualizować interfejs użytkownika.