地理位置

Browse sample. 浏览示例

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

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

开始使用

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

必须指定粗略精细的位置权限,或者同时指定这两种权限,并且应在 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(Manifest.Permission.AccessBackgroundLocation)]
    

    - 或 -

  • 更新 Android 清单:

    打开 Platforms/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 中,双击 Platforms/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 对象具有类型为 LocationLocation 属性,该属性表示已检测到的新位置。

注意

地理位置 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 对象具有类型为 GeolocationErrorError 属性,该属性指示侦听失败的原因。 引发 ListeningFailed 事件时,将停止侦听更多位置更改,且不会引发进一步的 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 方法不起作用。

准确性

以下部分列出了每个平台的位置准确度距离:

重要说明

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

最低

平台 距离(以米为单位)
Android 500
iOS 3000
Windows 1000 - 5000

平台 距离(以米为单位)
Android 500
iOS 1000
Windows 300 - 3000

中(默认)

平台 距离(以米为单位)
Android 100 - 500
iOS 100
Windows 30-500

平台 距离(以米为单位)
Android 0 - 100
iOS 10
Windows <= 10

最佳

平台 距离(以米为单位)
Android 0 - 100
iOS ~0
Windows <= 10

检测模拟位置

某些设备可能会返回提供商提供的模拟位置,或者返回提供模拟位置的应用程序提供的模拟位置。 可通过在任意 Location 上使用 IsFromMockProvider 来进行检测:

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) 构造函数分别接受纬度和经度参数。 正纬度值表示位于赤道以北,正经度值表示位于本初子午线以东。 使用 CalculateDistance 的最后一个参数指定单位为英里还是公里。 UnitConverters 类还定义了用于在两个单位之间进行转换的 KilometersToMilesMilesToKilometers 方法。

平台差异

本部分介绍地理位置 API 的特定于平台的差异。

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

在 Android 上,海拔高度(如果可用)返回高于 WGS 84 参考椭球的米数。 如果此位置没有海拔高度,则返回 0.0

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