地理位置
本文介绍如何使用 .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(Android.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 或指示海拔高度(以米为单位)的正值。 其他可能不存在的值包括 Speed 和 Course 属性。
获取当前位置
虽然检查设备的上一个已知位置速度会更快,但结果可能不准确。 使用 GetLocationAsync 方法查询设备的当前位置。 可以配置查询的准确性和超时时间。 使用 GeolocationRequest 和 CancellationToken 参数的方法重载是最合适的,因为获取设备的位置可能需要一些时间。
注意
地理位置 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 或指示海拔高度(以米为单位)的正值。 可能不存在的其他值包括 Speed 和 Course。
警告
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 对象具有类型为 GeolocationError 的 Error 属性,该属性指示侦听失败的原因。 引发 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 类还定义了用于在两个单位之间进行转换的 KilometersToMiles 和 MilesToKilometers 方法。
平台差异
本部分介绍地理位置 API 的特定于平台的差异。
每个平台上计算海拔高度的方式都不同。
在 Android 上,海拔高度(如果可用)返回高于 WGS 84 参考椭球的米数。 如果此位置没有海拔高度,则返回 0.0
。
Location.ReducedAccuracy 属性仅由 iOS 使用,在所有其他平台上则会返回 false
。