Общие сведения о картах и расположении

Windows App SDK и WinUI 3 предоставляют API и элементы управления для отображения карт, обнаружения расположения пользователя и настройки геозон. Используйте эти возможности для создания приложений, которые отображают интерактивные карты с метками, отслеживают местоположение пользователя и запускают действия, когда пользователь входит в географическую область или покидает её.

В этой статье приведены все возможности и полный пример, который объединяет MapControlи GeolocatorGeofenceMonitor в одном рабочем приложении.

Отображение карт с помощью MapControl

MapControl отображает интерактивную карту на базе Azure Maps. Вы можете добавлять метки, слои и реагировать на действия пользователя, такие как перемещение карты, масштабирование и щелчок мышью.

Для MapControl требуется учетная запись Azure Maps. См. статью "Управление учетной записью Azure Maps", чтобы создать учетную запись и получить маркер службы.

Подробные инструкции по использованию см. в разделе MapControl.

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

Note

API UWP MapControl и API Windows.Services.Maps устарели и могут быть недоступны в будущих версиях Windows. Приложения WinUI 3 должны использовать новый MapControl, описанный выше. Дополнительные сведения см. в разделе Ресурсы для устаревших функций.

Обнаружение расположения пользователя

API Windows.Devices.Geolocation позволяют определить географическое местоположение устройства. Эти API работают как в приложениях UWP, так и в Windows App SDK (WinUI 3). Можно сделать следующее:

  • Получите одноразовое местоположение с помощью Geolocator.GetGeopositionAsync.
  • Отслеживайте изменения положения с течением времени с помощью события Geolocator.PositionChanged .
  • Отслеживайте изменения статуса посещения с помощью GeovisitMonitor для энергоэффективного определения местоположения.

Пошаговое руководство см. в разделе "Получение расположения пользователя".

Настройте геозоны

Геозона определяет географическую границу. Приложение получает уведомления при входе или выходе из границы. Геозоны используются для напоминаний с учётом местоположения, оповещений или предоставления контента.

Инструкции по созданию и мониторингу геозон см. в разделе "Настройка геозоны".

Возможности расположения и конфиденциальность

Для всех API расположений требуется возможность расположения , объявленная в манифесте пакета приложения. Перед доступом к данным расположения необходимо также вызвать Geolocator.RequestAccessAsync во время выполнения.

Windows предоставляет пользователям контроль над тем, какие приложения могут получить доступ к их расположению с помощью параметров > конфиденциальности и безопасности>. Приложение должно обрабатывать случай, когда пользователь запрещает доступ к местоположению или отзывает разрешение на него.

Полный пример

В следующем примере объединяются MapControlи GeolocatorGeofenceMonitor в одном окне WinUI 3. Если ключ Azure Maps не настроен, карта продолжает отображаться с ограниченной функциональностью, при этом геолокация и геозонирование продолжают работать.

Необходимые условия

  • Windows App SDK 2.2 или более поздней версии
  • Ключ Azure Maps , необходимый для отображения плиток карты. Без допустимого ключа объект MapControl отображает пустую карту.
  • Возможность устройства Местоположение, объявленная в Package.appxmanifest:
<DeviceCapability Name="location" />

Задайте ключ Azure Maps в качестве переменной среды перед запуском приложения:

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

MainWindow.xaml

Слева — панель управления шириной 300 пикселей с кнопками и текстом состояния, а справа — MapControl. При отсутствии ключа 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}");
}

Основные шаблоны

  • MapControlвстроен в Windows App SDK 1.6 и более поздних версий. Задайте MapServiceToken для ключа Azure Maps.
  • TryConfigureMap AZURE_MAPS_KEY проверяет переменную среды при запуске. Если переменная пуста, карта сворачивается, а поверх нее появляется оверлей с пояснением, как это исправить, — никакого сбоя и никакой пустой карты.
  • Требуется DeviceCapability Name="location" в Package.appxmanifest, или Geolocator.RequestAccessAsync возвращает Denied.
  • GeofenceMonitor.GeofenceStateChanged запускается в фоновом потоке, поэтому используется DispatcherQueue.TryEnqueue для обновления пользовательского интерфейса.