Skip to content

Instantly share code, notes, and snippets.

@dotMorten
Last active August 2, 2019 21:05
Show Gist options
  • Save dotMorten/1c6532a1f3617b7c97af41c209aaca26 to your computer and use it in GitHub Desktop.
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
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