地図と位置情報の概要

Windows アプリ SDKと WinUI 3 には、マップの表示、ユーザーの位置の検出、ジオフェンスの設定を行う API とコントロールが用意されています。 これらの機能を使用して、ピン付きの対話型マップを表示するアプリを構築し、ユーザーの位置を追跡し、ユーザーが地理的領域に出入りしたときにアクションをトリガーします。

この記事では、各機能について説明し、1 つの作業アプリでMapControlGeolocatorを組み合わせたGeofenceMonitorを示します。

MapControl を使用してマップを表示する

MapControl には、Azure Mapsを利用した対話型のマップが表示されます。 ピン、レイヤーを追加し、パン、ズーム、クリックなどのユーザー操作に応答できます。

MapControl には、Azure Maps アカウントが必要です。 アカウントを作成してサービス トークンを取得するには、Azure Maps アカウントの管理に関するページを参照してください。

詳細な使用方法については、 MapControl を参照してください。

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

Note

UWP MapControlWindows。Services.Maps API は非推奨となり、今後のバージョンのWindowsでは使用できない可能性があります。 WinUI 3 アプリでは、上記の新しい MapControl を使用する必要があります。 詳細については、「 非推奨の機能のリソース」を参照してください。

ユーザーの場所を検出する

Windows。Devices.Geolocation API を使用すると、デバイスの地理的位置を取得できます。 これらの API は、UWP アプリと Windows アプリ SDK (WinUI 3) アプリの両方で動作します。 次のようにすることができます。

詳細なガイドについては、「 ユーザーの場所を取得する」を参照してください。

ジオフェンスを設定する

ジオフェンスは地理的境界を定義します。 ユーザーが境界に出入りすると、アプリは通知を受け取ります。 ジオフェンスは、場所ベースのリマインダー、アラート、またはコンテンツ配信に役立ちます。

ジオフェンスの作成と監視の手順については、「ジオフェンス の設定」を参照してください。

場所の機能とプライバシー

すべての場所 API には、アプリのパッケージ マニフェストで宣言された Location 機能が必要です。 位置情報データにアクセスする前に、実行時に Geolocator.RequestAccessAsync を呼び出す必要もあります。

Windowsでは、[設定] > [プライバシーとセキュリティ] > [場所] を使用して、ユーザーが自分の場所にアクセスできるアプリ制御できます。 アプリは、ユーザーが場所へのアクセスを拒否または取り消すケースを処理する必要があります。

完全なコード例

次の例では、1 つの WinUI 3 ウィンドウに MapControlGeolocator、および GeofenceMonitor をまとめます。 Azure Maps キーが構成されていない場合、ジオロケーションとジオフェンシングが引き続き機能している間、マップは正常に低下します。

前提条件

  • Windows アプリ 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 アプリ SDK 1.6 以降に組み込まれています。 MapServiceTokenを Azure Maps キーに設定します。
  • TryConfigureMap は、起動時に AZURE_MAPS_KEY 環境変数をチェックします。 変数が空の場合、マップは折りたたまれて、オーバーレイで修正方法が説明されます。クラッシュなし、空のマップはありません。
  • DeviceCapability Name="location" in Package.appxmanifest は必須か、 Geolocator.RequestAccessAsyncDeniedを返します。
  • GeofenceMonitor.GeofenceStateChanged はバックグラウンド スレッドで発生するため、 DispatcherQueue.TryEnqueue を使用して UI を更新します。