Last active
August 2, 2019 21:05
-
-
Save dotMorten/1c6532a1f3617b7c97af41c209aaca26 to your computer and use it in GitHub Desktop.
Wraps the WinRT Geolocation APIs in reflection-based calls so no dependency on Win10 is needed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
namespace Windows.Devices.Geolocation | |
{ | |
internal sealed class Geolocator | |
{ | |
private readonly object locatorInstance; | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.Geolocator, Windows, ContentType=WindowsRuntime"); | |
private static readonly EventInfo PositionChangedEventInfo = WinRTType.GetEvent(nameof(PositionChanged)); | |
private static MethodInfo GetGeopositionAsyncMethodInfo = WinRTType.GetTypeInfo().GetMethod(nameof(GetGeopositionAsync), new Type[] { typeof(TimeSpan), typeof(TimeSpan) }); | |
private Delegate positionChangedDelegate; | |
public Geolocator() | |
{ | |
if (!IsSupported) | |
throw new PlatformNotSupportedException(); | |
locatorInstance = Activator.CreateInstance(WinRTType, new object[] { }); | |
positionChangedDelegate = Delegate.CreateDelegate(PositionChangedEventInfo.EventHandlerType, this, typeof(Geolocator).GetMethod(nameof(OnPositionChanged), BindingFlags.NonPublic | BindingFlags.Instance)); | |
} | |
public static bool IsSupported => WinRTType != null && Geoposition.WinRTType != null && Geocoordinate.WinRTType != null && AsyncOperation.BaseWinRTType != null && PositionChangedEventInfo != null && GetGeopositionAsyncMethodInfo != null; | |
public uint ReportInterval | |
{ | |
get => (uint)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(ReportInterval)).GetValue(locatorInstance); | |
set => WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(ReportInterval)).SetValue(locatorInstance, value); | |
} | |
public double MovementThreshold | |
{ | |
get => (double)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(MovementThreshold)).GetValue(locatorInstance); | |
set => WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(MovementThreshold)).SetValue(locatorInstance, value); | |
} | |
public PositionAccuracy DesiredAccuracy | |
{ | |
get => (PositionAccuracy)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(DesiredAccuracy)).GetValue(locatorInstance); | |
set => WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(DesiredAccuracy)).SetValue(locatorInstance, (int)value); | |
} | |
internal async Task<Geoposition> GetGeopositionAsync(TimeSpan maximumAge, TimeSpan timeout) | |
{ | |
var asyncOp = GetGeopositionAsyncMethodInfo.Invoke(locatorInstance, new object[] { maximumAge, timeout }); | |
var operation = new AsyncOperation(Geoposition.WinRTType, asyncOp); | |
var result = await operation.Task.ConfigureAwait(false); | |
return result == null ? null : Geoposition.Create(result); | |
} | |
private event EventHandler<PositionChangedEventArgs> _positionChanged; | |
public event EventHandler<PositionChangedEventArgs> PositionChanged | |
{ | |
add | |
{ | |
if (_positionChanged == null) | |
{ | |
PositionChangedEventInfo.AddMethod.Invoke(locatorInstance, new object[] { positionChangedDelegate }); | |
} | |
_positionChanged += value; | |
} | |
remove | |
{ | |
_positionChanged -= value; | |
if (_positionChanged == null) | |
{ | |
PositionChangedEventInfo.RemoveMethod.Invoke(locatorInstance, new object[] { positionChangedDelegate }); | |
} | |
} | |
} | |
private void OnPositionChanged(object sender, object eventArgs) | |
{ | |
_positionChanged?.Invoke(this, new PositionChangedEventArgs(eventArgs)); | |
} | |
} | |
internal sealed class AsyncOperation | |
{ | |
internal static Type BaseWinRTType { get; } = Type.GetType("Windows.Foundation.IAsyncOperation`1, Windows, ContentType=WindowsRuntime"); | |
private Type WinRTType { get; } | |
private readonly object instance; | |
private readonly TaskCompletionSource<object> tcs; | |
public AsyncOperation(Type type, object instance) | |
{ | |
tcs = new TaskCompletionSource<object>(); | |
this.instance = instance; | |
var mi = typeof(AsyncOperation).GetMethod(nameof(CompletionHandler), BindingFlags.NonPublic | BindingFlags.Instance); | |
WinRTType = BaseWinRTType.MakeGenericType(type); | |
var CompletedProperty = WinRTType.GetProperty("Completed"); | |
var completedDelegate = Delegate.CreateDelegate(CompletedProperty.PropertyType, this, mi); | |
CompletedProperty.SetValue(instance, completedDelegate); | |
} | |
public Task<object> Task => tcs.Task; | |
private void CompletionHandler(object asyncInfo, AsyncStatus asyncStatus) | |
{ | |
if (asyncStatus == AsyncStatus.Canceled) | |
tcs.TrySetCanceled(); | |
else if (asyncStatus == AsyncStatus.Error) | |
tcs.TrySetException(WinRTType.GetProperty("ErrorCode").GetValue(instance) as Exception); | |
else | |
tcs.TrySetResult(WinRTType.GetMethod("GetResults").Invoke(instance, null)); | |
} | |
private enum AsyncStatus { Started, Completed, Canceled, Error } | |
} | |
internal sealed class PositionChangedEventArgs : EventArgs | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.PositionChangedEventArgs, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
private Geoposition _position; | |
internal PositionChangedEventArgs(object reference) | |
{ | |
instance = reference; | |
} | |
public Geoposition Position => _position ?? (_position = Geoposition.Create(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Position))?.GetValue(instance))); | |
} | |
internal sealed class Geoposition | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.Geoposition, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
internal static Geoposition Create(object instance) | |
{ | |
if (instance == null) | |
return null; | |
return new Geoposition(instance); | |
} | |
private Geoposition(object reference) | |
{ | |
instance = reference; | |
} | |
private Geocoordinate _coordinate; | |
public Geocoordinate Coordinate => _coordinate ?? (_coordinate = Geocoordinate.Create(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Coordinate))?.GetValue(instance))); | |
} | |
internal sealed class Geopoint | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.Geopoint, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
internal static Geopoint Create(object instance) | |
{ | |
if (WinRTType == null || instance == null) return null; | |
return new Geopoint(instance); | |
} | |
private Geopoint(object reference) | |
{ | |
instance = reference; | |
} | |
/// <summary> | |
/// The spatial reference identifier for the geographic point, corresponding to a spatial reference system based on the specific ellipsoid used for either flat-earth mapping or round-earth mapping. | |
/// </summary> | |
public uint SpatialReferenceId => (uint)(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(SpatialReferenceId))?.GetValue(instance) ?? 4326); | |
/// <summary> | |
/// The type of geographic shape. | |
/// </summary> | |
public GeoshapeType GeoshapeType => (GeoshapeType)(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(GeoshapeType))?.GetValue(instance) ?? GeoshapeType.Geopoint); | |
/// <summary> | |
/// The altitude reference system of the geographic point. GeoPoint will default to a value of unspecified when constructed without an altitude reference system. The behavior of an unspecified altitude reference system will depend on the API. A MapIcon will treat an unspecified reference system as Surface with an altitude value of 0 and the supplied value for altitude will be ignored. | |
/// </summary> | |
public AltitudeReferenceSystem AltitudeReferenceSystem => (AltitudeReferenceSystem)(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(AltitudeReferenceSystem))?.GetValue(instance) ?? AltitudeReferenceSystem.Unspecified); | |
private BasicGeoposition _position; | |
/// <summary> | |
/// The position of a geographic point. | |
/// </summary> | |
public BasicGeoposition Position => _position ?? (_position = BasicGeoposition.Create(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Position))?.GetValue(instance))); | |
} | |
internal sealed class Geocoordinate | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.Geocoordinate, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
internal static Geocoordinate Create(object instance) => instance == null ? null : new Geocoordinate(instance); | |
private Geocoordinate(object reference) | |
{ | |
instance = reference; | |
} | |
public double Accuracy => (double)(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Accuracy))?.GetValue(instance) ?? double.NaN); | |
public double? Altitude => (double?)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Altitude))?.GetValue(instance); | |
public double? AltitudeAccuracy => (double?)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(AltitudeAccuracy))?.GetValue(instance); | |
public double? Heading => (double?)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Heading))?.GetValue(instance); | |
public double Latitude => (double)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Latitude)).GetValue(instance); | |
public double Longitude => (double)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Longitude)).GetValue(instance); | |
public double? Speed => (double?)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Speed))?.GetValue(instance); | |
public DateTimeOffset Timestamp => (DateTimeOffset)(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Timestamp))?.GetValue(instance) ?? DateTimeOffset.MinValue); | |
public DateTimeOffset? PositionSourceTimestamp => (DateTimeOffset?)WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(PositionSourceTimestamp))?.GetValue(instance); | |
public PositionSource PositionSource => (PositionSource) (WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(PositionSource))?.GetValue(instance) ?? PositionSource.Unknown); | |
private Geopoint _point; | |
public Geopoint Point => _point ?? (_point = Geopoint.Create(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(Point))?.GetValue(instance))); | |
private GeocoordinateSatelliteData _satelliteData; | |
public GeocoordinateSatelliteData SatelliteData => _satelliteData ?? (_satelliteData = GeocoordinateSatelliteData.Create(WinRTType.GetTypeInfo().GetDeclaredProperty(nameof(SatelliteData))?.GetValue(instance))); | |
} | |
internal sealed class BasicGeoposition | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.BasicGeoposition, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
internal static BasicGeoposition Create(object instance) => WinRTType == null || instance == null ? null : new BasicGeoposition(instance); | |
private BasicGeoposition(object reference) | |
{ | |
instance = reference; | |
} | |
public double Altitude => (double)(WinRTType.GetTypeInfo().GetDeclaredField(nameof(Altitude))?.GetValue(instance) ?? double.NaN); | |
public double Latitude => (double)WinRTType.GetTypeInfo().GetDeclaredField(nameof(Latitude)).GetValue(instance); | |
public double Longitude => (double)WinRTType.GetTypeInfo().GetDeclaredField(nameof(Longitude)).GetValue(instance); | |
} | |
internal sealed class GeocoordinateSatelliteData | |
{ | |
internal static Type WinRTType { get; } = Type.GetType("Windows.Devices.Geolocation.GeocoordinateSatelliteData, Windows, ContentType=WindowsRuntime"); | |
private readonly object instance; | |
internal static GeocoordinateSatelliteData Create(object instance) => WinRTType == null || instance == null ? null : new GeocoordinateSatelliteData(instance); | |
private GeocoordinateSatelliteData(object reference) | |
{ | |
instance = reference; | |
} | |
public double? HorizontalDilutionOfPrecision => (double?)WinRTType.GetTypeInfo().GetDeclaredField(nameof(HorizontalDilutionOfPrecision))?.GetValue(instance); | |
public double? PositionDilutionOfPrecision => (double?)WinRTType.GetTypeInfo().GetDeclaredField(nameof(PositionDilutionOfPrecision))?.GetValue(instance); | |
public double? VerticalDilutionOfPrecision => (double?)WinRTType.GetTypeInfo().GetDeclaredField(nameof(VerticalDilutionOfPrecision))?.GetValue(instance); | |
} | |
internal enum GeoshapeType | |
{ | |
Geopoint = 0, Geocircle = 1, Geopath = 2, GeoboundingBox = 3 | |
} | |
internal enum AltitudeReferenceSystem | |
{ | |
Unspecified = 0, Terrain = 1, Ellipsoid = 2, Geoid = 3, Surface = 4 | |
} | |
internal enum PositionSource | |
{ | |
/// <summary> | |
/// The position was obtained from cellular network data. | |
/// </summary> | |
Cellular = 0, | |
/// <summary> | |
/// The position was obtained from satellite data. | |
/// </summary> | |
Satellite = 1, | |
/// <summary> | |
/// The position was obtained from Wi-Fi network data. | |
/// </summary> | |
WiFi = 2, | |
/// <summary> | |
/// (Starting with Windows 8.1.) The position was obtained from an IP address. | |
/// </summary> | |
IPAddress = 3, | |
/// <summary> | |
/// (Starting with Windows 8.1.) The position was obtained from an unknown source. | |
/// </summary> | |
Unknown = 4, | |
/// <summary> | |
/// (Starting with Windows 10, version 1607.) The position was obtained from the user's manually-set location. | |
/// </summary> | |
Default = 5, | |
/// <summary> | |
/// (Starting with Windows 10, version 1607.) The position was obtained via the coarse location feature and was therefore intentionally made inaccurate to a degree. | |
/// </summary> | |
Obfuscated = 6, | |
} | |
internal enum PositionAccuracy | |
{ | |
/// <summary> | |
/// Optimize for power, performance, and other cost considerations. | |
/// </summary> | |
Default = 0, | |
/// <summary> | |
/// Deliver the most accurate report possible. This includes using services that | |
/// might charge money, or consuming higher levels of battery power or connection | |
/// bandwidth. An accuracy level of **High** may degrade system performance and should | |
/// be used only when necessary. | |
/// </summary> | |
High = 1 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment