响应基于距离的位置更新 (XAML)

[本文档是初步文档,以后可能更改。]

了解如何在用户位置移至指定距离之外时接收更新。 位置更新之间的距离称为移动阈值。本主题使用了一些代码示例,用于扩展地理位置示例跟踪位置部分。下载该示例并遵循其中的步骤,以查看如何使用移动阈值控制出现位置更新的位置。

路线图: 本主题与其他主题有何关联?请参阅:

步骤 1: 下载地理位置示例、添加 UI 并定义新属性

下载 Windows 10 Technical Preview 地理位置示例并打开 Scenario1_TrackPosition.xaml 文件。在此步骤中,你将添加用于指定移动阈值的 UI 元素。

首先,在 <Grid.RowDefinitions> 元素的末尾处针对移动阀值 UI 添加一个 RowDefinition 元素,如下所示。


<Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <!--Added for entering movement threshold-->
    <RowDefinition Height="Auto" />
</Grid.RowDefinitions>

然后,在 ScenarioOutput_Accuracy TextBlock 之后和 </Grid> 末尾之前添加以下 UI 元素。


<!--Added for entering movement threshold-->
<TextBlock TextWrapping="Wrap" Margin="0,0,10,0" Grid.Row="4" 
            Grid.Column="0" Style="{StaticResource BasicTextStyle}" 
            HorizontalAlignment="Left" VerticalAlignment="Center" 
            Text="Movement threshold (meters): " />
                
<TextBox x:Name="ScenarioIntput_MovementThreshold" 
            TextWrapping="Wrap" Grid.Row="4" Grid.Column="1" 
            HorizontalAlignment="Left" VerticalAlignment="Center" Text="3" />

接下来,打开 Scenario1_TrackPosition.xaml.cs 文件并将 prevLatitudeprevLongitudetotalDistance 私有类变量添加到 Scenario1:Page 分部类,如下所示。这些变量将用于帮助跟踪移动距离。


public sealed partial class Scenario1 : Page
{
    // Proides access to location data
    private Geolocator _geolocator = null;

    // A pointer to the main page
    private MainPage _rootPage = MainPage.Current;

    // Distance tracking variables
    private double prevLatitude = -1;
    private double prevLongitude = -1;
    private double totalDistance = 0;

...
...

步骤 2: 验证是否启用了位置

在你的应用可以访问位置之前,必须在设备上启用“位置”。在“设置”应用中,检查以下“位置隐私设置”是否已打开:

  • “位置”打开
  • “让 Windows 和你选择的应用使用你的位置和位置历史记录”设置已打开
  • 在“选择可以使用你的位置的应用”下,你的应用已设置为“打开”

注意  在某些情况下,“位置”和“选择可以使用你的位置的应用”设置可能不可用。

步骤 3: 启用位置功能

已在地理位置示例中启用了位置功能。此步骤对于新应用而言仅作提示之用。若要添加此功能,请在“解决方案资源管理器”中双击“package.appxmanifest”并选择“功能”选项卡。然后,选中“功能”列表中的“位置”。这将向程序包清单文件中添加 Location 设备功能。


  <Capabilities>
    <!-- DeviceCapability elements must follow Capability elements (if present) -->
    <DeviceCapability Name="location"/>
  </Capabilities>

步骤 4: 请求访问用户位置

地理位置示例中的 Scenario1_TrackPosition.xaml.cs 文件所示,使用 RequestAccessAsync 方法请求访问用户位置,这一点非常重要。

要点  在访问用户位置之前调用 RequestAccessAsync。此时,你的应用必须在前台运行,并且 RequestAccessAsync 必须从 UI 线程中调用。除非用户向你的应用授予访问其位置的权限,否则你的应用将无法访问位置数据。


using Windows.Devices.Geolocation;
...
var accessStatus = await Geolocator.RequestAccessAsync();

RequestAccessAsync 方法将提示用户获取访问其位置的权限。仅提示用户一次(每个应用)。在他们第一次授予或拒绝授予权限之后,此方法不会再进行获取权限的提示。若要在已提示用户之后帮助他们更改位置权限,我们建议提供一个指向位置设置的链接,如本主题中后面部分所示。

步骤 5: 定义移动阈值和注册位置更新

地理位置示例Scenario1_TrackPosition.xaml.cs 文件中,将用下面的这个示例中的代码来替换 StartTrackingStopTracking 方法。此新代码使用 MovementThreshold 属性指定必须更改为触发器 PositionChanged 事件的位置数。在此情况下,可从前面步骤中所添加的 ScenarioIntput_MovementThreshold 文本框获取移动阈值。

switch 语句将与 accessStatus 属性结合使用以便仅在允许对位置的访问时进行操作。如果允许,该代码将创建 Geolocator 对象、指定跟踪类型并注册位置更新。



 private async void StartTracking(object sender, RoutedEventArgs e)
{
    // Request permission to access location
    var accessStatus = await Geolocator.RequestAccessAsync();

    switch (accessStatus)
    {
        case GeolocationAccessStatus.Allowed:

            // Disable movement threshold textbox
            ScenarioIntput_MovementThreshold.IsEnabled = false;

            // Create Geolocator and define the movement threshold (in meters)
            _geolocator = new Geolocator();
            _geolocator.MovementThreshold = float.Parse(ScenarioIntput_MovementThreshold.Text);

            // Subscribe to PositionChanged event to get updated tracking positions
            _geolocator.PositionChanged += OnPositionChanged;

            // Subscribe to StatusChanged event to get updates of location status changes
            _geolocator.StatusChanged += OnStatusChanged;
                    
            _rootPage.NotifyUser("Waiting for update...", NotifyType.StatusMessage);
            LocationDisabledMessage.Visibility = Visibility.Collapsed;
            StartTrackingButton.IsEnabled = false;
            StopTrackingButton.IsEnabled = true;
            break;

        case GeolocationAccessStatus.Denied:
            _rootPage.NotifyUser("Access to location is denied.", NotifyType.ErrorMessage);
            LocationDisabledMessage.Visibility = Visibility.Visible;
            break;

        case GeolocationAccessStatus.Unspecified:
            _rootPage.NotifyUser("Unspecificed error!", NotifyType.ErrorMessage);
            LocationDisabledMessage.Visibility = Visibility.Collapsed;
            break;
    }
}


private void StopTracking(object sender, RoutedEventArgs e)
{
    _geolocator.PositionChanged -= OnPositionChanged;
    _geolocator.StatusChanged -= OnStatusChanged;
    _geolocator = null;

    StartTrackingButton.IsEnabled = true;
    StopTrackingButton.IsEnabled = false;

    // Enable movement threshold textbox
    ScenarioIntput_MovementThreshold.IsEnabled = true;

    // Clear status
    _rootPage.NotifyUser("Tracking stopped.", NotifyType.StatusMessage);
}

步骤 6: 处理位置更新

地理位置示例Scenario1_TrackPosition.xaml.cs 文件中,将用下面的这个示例中的代码来替换 OnPositionChanged 方法。此新代码使用当前位置来计算自上次更新后的距离 (updateDistance) 以及自开始跟踪后的总距离 (totalDistance)。

Geolocator 对象将触发 PositionChanged 事件以指示用户位置发生了更改。该事件通过参数的 Position 属性(属于类型 Geoposition)来传递相应的位置信息。 此示例中还显示了该方法不是从 UI 线程中调用的,并且 Dispatcher 对象实际调用了 UI 更改。


async private void OnPositionChanged(Geolocator sender, PositionChangedEventArgs e)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        double updateDistance = 0;

        // Calculate distance;
        if ((prevLatitude == -1) || (prevLongitude == -1))
        {
            updateDistance = 0;
        }
        else
        {
            updateDistance = CalculateDistance(prevLatitude, prevLongitude,
               e.Position.Coordinate.Point.Position.Latitude, 
               e.Position.Coordinate.Point.Position.Longitude);
        }

        // Update tracking
        prevLatitude = e.Position.Coordinate.Point.Position.Latitude;
        prevLongitude = e.Position.Coordinate.Point.Position.Longitude;
        totalDistance += updateDistance;

        // Update UI
        _rootPage.NotifyUser("Latest distance: " + 
            updateDistance.ToString("F2") +
            " meters,  Total distance: " +
            totalDistance.ToString("F2") +
            " meters", 
            NotifyType.StatusMessage);

        UpdateLocationData(e.Position);
    });
}

步骤 7: 计算距离

地理位置示例Scenario1_TrackPosition.xaml.cs 文件中,将添加 CalculateDistance 方法,如下面的这个示例所示。此方法使用半正矢公式来计算两个地理位置之间的距离。


private double CalculateDistance(double prevLat, double prevLong, double currLat, double currLong)
{
    const double degreesToRadians = (Math.PI / 180.0);
    const double earthRadius = 6371; // kilometers

    // convert latitude and longitude values to radians
    var prevRadLat = prevLat * degreesToRadians;
    var prevRadLong = prevLong * degreesToRadians;
    var currRadLat = currLat * degreesToRadians;
    var currRadLong = currLong * degreesToRadians;

    // calculate radian delta between each position.
    var radDeltaLat = currRadLat - prevRadLat;
    var radDeltaLong = currRadLong - prevRadLong;

    // calculate distance
    var expr1 = (Math.Sin(radDeltaLat / 2.0) *
                    Math.Sin(radDeltaLat / 2.0)) +

                (Math.Cos(prevRadLat) *
                    Math.Cos(currRadLat) *
                    Math.Sin(radDeltaLong / 2.0) *
                    Math.Sin(radDeltaLong / 2.0));

    var expr2 = 2.0 * Math.Atan2(Math.Sqrt(expr1),
                                    Math.Sqrt(1 - expr1));

    var distance = (earthRadius * expr2);
    return distance * 1000;  // return results as meters
}

步骤 8: 处理位置权限的更改

地理位置示例中所示,PositionStatus 可用于检查位置权限的当前状态。在用户位置设置更改(同时应用正在前台中运行)时进行检查,这一点尤为重要。同样地,请再次注意此方法不是从 UI 线程中调用的,并且 Dispatcher 对象实际调用了 UI 更改。



using Windows.UI.Core;
...
async private void OnStatusChanged(Geolocator sender, StatusChangedEventArgs e)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Show the location setting message only if status is disabled.
        LocationDisabledMessage.Visibility = Visibility.Collapsed;

        switch (e.Status)
        {
            case PositionStatus.Ready:
                // Location platform is providing valid data.
                ScenarioOutput_Status.Text = "Ready";
                _rootPage.NotifyUser("Location platform is ready.", NotifyType.StatusMessage);
                break;

            case PositionStatus.Initializing:
                // Location platform is attempting to acquire a fix. 
                ScenarioOutput_Status.Text = "Initializing";
                _rootPage.NotifyUser("Location platform is attempting to obtain a position.", NotifyType.StatusMessage);
                break;

            case PositionStatus.NoData:
                // Location platform could not obtain location data.
                ScenarioOutput_Status.Text = "No data";
                _rootPage.NotifyUser("Not able to determine the location.", NotifyType.ErrorMessage);
                break;

            case PositionStatus.Disabled:
                // The permission to access location data is denied by the user or other policies.
                ScenarioOutput_Status.Text = "Disabled";
                _rootPage.NotifyUser("Access to location is denied.", NotifyType.ErrorMessage);

                // Show message to the user to go to location settings
                LocationDisabledMessage.Visibility = Visibility.Visible;

                // Clear cached location data if any
                UpdateLocationData(null);
                break;

            case PositionStatus.NotInitialized:
                // The location platform is not initialized. This indicates that the application 
                // has not made a request for location data.
                ScenarioOutput_Status.Text = "Not initialized";
                _rootPage.NotifyUser("No request for location is made yet.", NotifyType.StatusMessage);
                break;

            case PositionStatus.NotAvailable:
                // The location platform is not available on this version of the OS.
                ScenarioOutput_Status.Text = "Not available";
                _rootPage.NotifyUser("Location is not available on this version of the OS.", NotifyType.ErrorMessage);
                break;

            default:
                ScenarioOutput_Status.Text = "Unknown";
                _rootPage.NotifyUser(string.Empty, NotifyType.StatusMessage);
                break;
        }
    });
}

步骤 9: 帮助用户更改位置设置

如果位置设置不允许你的应用访问用户位置,我们建议提供一个指向“设置”应用中的“位置隐私设置”的便捷链接。地理位置示例使用了一个用于导航到 ms-settings://privacy/location URI 的超链接控件,如下所示。


        <TextBlock x:Name="LocationDisabledMessage" FontStyle="Italic" 
                   Visibility="Collapsed" Margin="0,15,0,0" TextWrapping="Wrap" >
            <Run Text="This app is not able to get location data. Go to" />
                <Hyperlink NavigateUri="ms-settings://privacy/location">
                    <Run Text="Settings" />
                </Hyperlink>
            <Run Text="to check the location privacy settings."/>

或者,你的应用还可以调用 LaunchUriAsync 方法以从代码中启动“设置”应用。有关详细信息,请参阅如何使用 MS 设置协议显示内置设置页面


using Windows.System;
...
bool result = await Launcher.LaunchUriAsync(new Uri("ms-settings://privacy/location"));

步骤 10: 测试新代码

在开始测试应用之前,请记住:

  • 使用移动阈值触发位置更新时需要在物理上移动你的设备或使用模拟器模拟新位置。有关模拟位置的详细信息,请参阅设置设备的模拟地理位置
  • 你的位置的失真度可能会远远超过指定的移动阈值,具体取决于位置数据的源。例如,对于大约 30 米到 500 米的距离,使用 Wi-Fi 接收的位置可能会有所不同。有关位置源的详细信息,请参阅 Windows.Devices.Geolocation

若要开始测试新的代码:

  1. 在“调试”菜单上,单击“启动调试”测试该解决方案。
  2. 选择“跟踪位置”以查看已修改的 Scenario1_TrackPosition.xaml.cs 页面。
  3. 按“启动跟踪”以开始基于移动的跟踪。
  4. 首次运行该示例时,你会收到一个提示,询问是否可以让应用使用你的位置数据。选择“允许”选项。
  5. 将你的设备移至指定的移动阈值之外(鉴于位置精度),并查看 UI 中 updateDistancetotalDistance 值的变化。

相关主题

Windows 10 Technical Preview 地理位置示例
Windows 8.1 地理位置示例
必应地图 SDK 样例
Windows.Devices.Geolocation
敏感设备使用指南

 

 

显示:
© 2015 Microsoft