地理位置

浏览示例。 浏览示例

本文介绍如何使用 .NET Multi-platform App UI (.NET MAUI) 接口。 此接口提供用于检索设备的当前地理位置坐标的 API。

IGeolocation 接口的默认实现可通过 Geolocation.Default 属性获得。 IGeolocation 接口和 Geolocation 类都包含在 Microsoft.Maui.Devices.Sensors 命名空间中。

开始吧

若要访问 地理位置 功能,需要以下特定于平台的设置:

必须指定粗略 精细的位置权限,或者同时具有这两种权限,并且应在 Android 项目中进行配置。

此外,如果应用面向 Android 5.0(API 级别 21)或更高版本,则必须声明应用使用清单文件中的硬件功能。 可通过以下方式添加此项:

  • 添加基于程序集的权限:

    打开 Platforms/Android/MainApplication.cs 文件,并在 using 指令后添加以下程序集属性:

    [assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
    [assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
    [assembly: UsesFeature("android.hardware.___location", Required = false)]
    [assembly: UsesFeature("android.hardware.___location.gps", Required = false)]
    [assembly: UsesFeature("android.hardware.___location.network", Required = false)]
    

    如果应用程序面向 Android 10 - Q (API 级别 29 或更高版本),并且正在请求 LocationAlways,则还必须添加此权限请求:

    [assembly: UsesPermission(Android.Manifest.Permission.AccessBackgroundLocation)]
    

    - 或 -

  • 更新 Android Manifest:

    打开 平台/Android/AndroidManifest.xml 文件,并在 manifest 节点中添加以下内容:

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.___location" android:required="false" />
    <uses-feature android:name="android.hardware.___location.gps" android:required="false" />
    <uses-feature android:name="android.hardware.___location.network" android:required="false" />
    

    如果应用程序面向 Android 10 - Q (API 级别 29 或更高版本),并且正在请求 LocationAlways,则还必须添加此权限请求:

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    

    - 或 -

  • 在清单编辑器中更新 Android 清单:

    在 Visual Studio 中,双击 平台/Android/AndroidManifest.xml 文件以打开 Android 清单编辑器。 然后,在 “所需权限 ”下检查上面列出的权限。 这将自动更新 AndroidManifest.xml 文件。

小窍门

请务必阅读 有关后台位置更新的 Android 文档,因为需要考虑许多限制。

获取最后一个已知位置

设备可能已缓存设备的最新位置。 GetLastKnownLocationAsync()使用此方法访问缓存的位置(如果可用)。 这通常比执行完整位置查询更快,但可能不太准确。 如果不存在缓存位置,此方法将 null返回 。

注释

如有必要,地理位置 API 会提示用户获取权限。

下面的代码示例演示如何检查缓存的位置:

public async Task<string> GetCachedLocation()
{
    try
    {
        Location ___location = await Geolocation.Default.GetLastKnownLocationAsync();

        if (___location != null)
            return $"Latitude: {___location.Latitude}, Longitude: {___location.Longitude}, Altitude: {___location.Altitude}";
    }
    catch (FeatureNotSupportedException fnsEx)
    {
        // Handle not supported on device exception
    }
    catch (FeatureNotEnabledException fneEx)
    {
        // Handle not enabled on device exception
    }
    catch (PermissionException pEx)
    {
        // Handle permission exception
    }
    catch (Exception ex)
    {
        // Unable to get ___location
    }

    return "None";
}

根据设备,并非所有位置值都可用。 例如,该 Altitude 属性可以是 null,值为 0,或者有一个正值表示高于海平面的米数。 可能不存在的其他值包括 SpeedCourse 属性。

获取当前位置

虽然检查设备的最后已知位置可能更快,但它可能不准确。 GetLocationAsync使用该方法查询设备的当前位置。 可以配置查询的准确性和超时。 最好使用带有 GeolocationRequestCancellationToken 参数的方法重载,因为获取设备的位置可能需要一些时间。

注释

如有必要,地理位置 API 会提示用户获取权限。

下面的代码示例演示如何请求设备的位置,同时支持取消:

private CancellationTokenSource _cancelTokenSource;
private bool _isCheckingLocation;

public async Task GetCurrentLocation()
{
    try
    {
        _isCheckingLocation = true;

        GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));

        _cancelTokenSource = new CancellationTokenSource();

        Location ___location = await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token);

        if (___location != null)
            Console.WriteLine($"Latitude: {___location.Latitude}, Longitude: {___location.Longitude}, Altitude: {___location.Altitude}");
    }
    // Catch one of the following exceptions:
    //   FeatureNotSupportedException
    //   FeatureNotEnabledException
    //   PermissionException
    catch (Exception ex)
    {
        // Unable to get ___location
    }
    finally
    {
        _isCheckingLocation = false;
    }
}

public void CancelRequest()
{
    if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false)
        _cancelTokenSource.Cancel();
}

并非所有位置值都可用,具体取决于设备。 例如,该 Altitude 属性可以是 null,值为 0,或者有一个正值表示高于海平面的米数。 可能不存在的其他值包括 SpeedCourse

警告

GetLocationAsync 在某些情况下可以返回 null 。 这表示基础平台无法获取当前位置。

侦听位置更改

除了查询设备的当前位置外,还可以在应用处于前台时侦听位置更改。

若要查看应用当前是否正在侦听位置更改, IsListeningForeground 可以查询的属性。 准备好开始侦听位置更改后,应调用该方法 StartListeningForegroundAsync 。 此方法开始侦听位置更新,并在位置更改时引发 LocationChanged 事件,前提是应用位于前台。 GeolocationLocationChangedEventArgs此事件附带的对象具有一个Location类型属性Location,表示检测到的新位置。

注释

如有必要,地理位置 API 会提示用户获取权限。

下面的代码示例演示如何侦听位置更改,以及如何处理更改的位置:

async void OnStartListening()
{
    try
    {
        Geolocation.LocationChanged += Geolocation_LocationChanged;
        var request = new GeolocationListeningRequest((GeolocationAccuracy)Accuracy);
        var success = await Geolocation.StartListeningForegroundAsync(request);

        string status = success
            ? "Started listening for foreground ___location updates"
            : "Couldn't start listening";
    }
    catch (Exception ex)
    {
        // Unable to start listening for ___location changes
    }
}

void Geolocation_LocationChanged(object sender, GeolocationLocationChangedEventArgs e)
{
    // Process e.Location to get the new ___location
}

可以通过为事件注册事件处理程序 ListeningFailed 来实现错误处理。 此事件附带的对象具有一个指示侦听失败原因的GeolocationListeningFailedEventArgs属性,类型为ErrorListeningFailed引发事件时,对位置进一步更改的侦听将停止,并且不会引发更多LocationChanged事件。

若要停止侦听位置更改,请调用 StopListeningForeground 方法:

void OnStopListening()
{
    try
    {
        Geolocation.LocationChanged -= Geolocation_LocationChanged;
        Geolocation.StopListeningForeground();
        string status = "Stopped listening for foreground ___location updates";
    }
    catch (Exception ex)
    {
        // Unable to stop listening for ___location changes
    }
}

注释

当应用未侦听位置更改时,该方法 StopListeningForeground 不起作用。

检查位置服务是否已启用

Geolocation 类具有一个只读 IsEnabled 属性,可用于确定是否已在设备上启用位置服务。

准确性

以下部分概述了每个平台的位置准确性距离:

重要

iOS 对准确性有一些限制。 有关详细信息,请参阅 “平台差异 ”部分。

最低

平台 距离(以米为单位)
安卓 500
iOS 3000
Windows操作系统 1000 - 5000

平台 距离(以米为单位)
安卓 500
iOS 1000
Windows操作系统 300 - 3000

中(默认)

平台 距离(以米为单位)
安卓 100 - 500
iOS 100
Windows操作系统 30-500

平台 距离(以米为单位)
安卓 0 - 100
iOS 10
Windows操作系统 <= 10

最佳

平台 距离(以米为单位)
安卓 0 - 100
iOS ~0
Windows操作系统 <= 10

检测模拟位置

某些设备可能会从提供商或提供模拟位置的应用程序获取位置数据。 您可以通过在任意IsFromMockProvider上使用Location来检测这一情况:

public async Task CheckMock()
{
    GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Medium);
    Location ___location = await Geolocation.Default.GetLocationAsync(request);

    if (___location != null && ___location.IsFromMockProvider)
    {
        // ___location is from a mock provider
    }
}

两个位置之间的距离

该方法 CalculateDistance 计算两个地理位置之间的距离。 此计算距离不考虑道路或其他路径,只是地球表面两点之间的最短距离。 此计算称为 大圆距离 计算。

以下代码计算美国波士顿和旧金山城市之间的距离:

Location boston = new Location(42.358056, -71.063611);
Location sanFrancisco = new Location(37.783333, -122.416667);

double miles = Location.CalculateDistance(boston, sanFrancisco, DistanceUnits.Miles);

构造 Location(Double, Double, Double) 函数分别接受纬度和经度参数。 正纬度值位于赤道以北,正经度值位于 Prime Meridian 的东部。 使用最终参数指定 CalculateDistance 英里或公里。 该 UnitConverters 类还定义了用于在两个单位之间转换的 KilometersToMilesMilesToKilometers 方法。

平台差异

本部分描述了与地理位置 API 相关的各个平台特定差异。

在每个平台上以不同的方式计算海拔高度。

在 Android 系统中,如果可用,高度以米为单位返回,并测量为高于 WGS 84 参考椭球体的高度。 如果此位置没有海拔高度, 0.0 则返回。

Location.ReducedAccuracy 属性仅供 iOS 使用,并在所有其他平台上返回 false