PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/ReactiveUI.Mobile/Geolocation.cs

https://github.com/bsiegel/ReactiveUI
C# | 183 lines | 150 code | 29 blank | 4 comment | 20 complexity | cdcd30deaf50c1e2c0317452efd7b903 MD5 | raw file
Possible License(s): Apache-2.0, CC-BY-SA-3.0, LGPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reactive.Concurrency;
  5. using System.Reactive.Disposables;
  6. using System.Reactive.Linq;
  7. using System.Reactive.Subjects;
  8. using System.Reactive.Threading.Tasks;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using Xamarin.Geolocation;
  13. namespace ReactiveUI.Mobile
  14. {
  15. public interface IReactiveGeolocator
  16. {
  17. IObservable<Position> Listen(int minUpdateTime, double minUpdateDist, bool includeHeading = false);
  18. IObservable<Position> GetPosition(bool includeHeading = false);
  19. }
  20. public static class ReactiveGeolocator
  21. {
  22. [ThreadStatic] static IReactiveGeolocator _UnitTestImplementation;
  23. static IReactiveGeolocator _Implementation;
  24. internal static IReactiveGeolocator Implementation {
  25. get { return _UnitTestImplementation ?? _Implementation; }
  26. set {
  27. if (RxApp.InUnitTestRunner()) {
  28. _UnitTestImplementation = value;
  29. _Implementation = _Implementation ?? value;
  30. } else {
  31. _Implementation = value;
  32. }
  33. }
  34. }
  35. public static IObservable<Position> Listen(int minUpdateTime, double minUpdateDist, bool includeHeading = false)
  36. {
  37. if (Implementation != null) {
  38. return Implementation.Listen(minUpdateTime, minUpdateDist, includeHeading);
  39. }
  40. var ret = Observable.Create<Position>(subj => {
  41. var geo = new Geolocator();
  42. var disp = new CompositeDisposable();
  43. bool isDead = false;
  44. if (!geo.IsGeolocationAvailable || !geo.IsGeolocationEnabled) {
  45. return Observable.Throw<Position>(new Exception("Geolocation isn't available")).Subscribe(subj);
  46. }
  47. // NB: This isn't very Functional, but I'm lazy.
  48. disp.Add(Observable.FromEventPattern<PositionEventArgs>(x => geo.PositionChanged += x, x => geo.PositionChanged -= x).Subscribe(x => {
  49. if (isDead) return;
  50. subj.OnNext(x.EventArgs.Position);
  51. }));
  52. disp.Add(Observable.FromEventPattern<PositionErrorEventArgs>(x => geo.PositionError += x, x => geo.PositionError -= x).Subscribe(ex => {
  53. isDead = true;
  54. var toDisp = Interlocked.Exchange(ref disp, null);
  55. if (toDisp != null) toDisp.Dispose();
  56. subj.OnError(new GeolocationException(ex.EventArgs.Error));
  57. }));
  58. return disp;
  59. });
  60. return ret.Multicast(new Subject<Position>()).RefCount();
  61. }
  62. public static IObservable<Position> GetPosition(bool includeHeading = false)
  63. {
  64. if (Implementation != null) {
  65. return Implementation.GetPosition(includeHeading);
  66. }
  67. #if !WP7 && !WP8
  68. var ret = Observable.Create<Position>(subj => {
  69. var geo = new Geolocator();
  70. var cts = new CancellationTokenSource();
  71. var disp = new CompositeDisposable();
  72. if (!geo.IsGeolocationAvailable || !geo.IsGeolocationEnabled) {
  73. return Observable.Throw<Position>(new Exception("Geolocation isn't available")).Subscribe(subj);
  74. }
  75. disp.Add(new CancellationDisposable(cts));
  76. disp.Add(geo.GetPositionAsync(cts.Token, includeHeading).ToObservable().Subscribe(subj));
  77. return disp;
  78. }).Multicast(new AsyncSubject<Position>());
  79. #else
  80. // NB: Xamarin.Mobile.dll references CancellationTokenSource, but
  81. // the one we have comes from the BCL Async library - if we try to
  82. // pass in our type into the Xamarin library, it gets confused.
  83. var ret = Observable.Create<Position>(subj => {
  84. var geo = new Geolocator();
  85. var disp = new CompositeDisposable();
  86. if (!geo.IsGeolocationAvailable || !geo.IsGeolocationEnabled) {
  87. return Observable.Throw<Position>(new Exception("Geolocation isn't available")).Subscribe(subj);
  88. }
  89. disp.Add(geo.GetPositionAsync(Int32.MaxValue, includeHeading).ToObservable().Subscribe(subj));
  90. return disp;
  91. }).Multicast(new AsyncSubject<Position>());
  92. #endif
  93. return ret.RefCount();
  94. }
  95. public static IDisposable WithGeolocator(IReactiveGeolocator implementation)
  96. {
  97. var orig = Implementation;
  98. Implementation = implementation;
  99. return Disposable.Create(() => Implementation = orig);
  100. }
  101. public static IDisposable UseLocationData(IObservable<Position> positionStream, IScheduler scheduler = null)
  102. {
  103. return WithGeolocator(new TestGeolocator(positionStream, scheduler));
  104. }
  105. }
  106. public class TestGeolocator : IReactiveGeolocator
  107. {
  108. readonly BehaviorSubject<Position> _latestPosition = new BehaviorSubject<Position>(null);
  109. readonly IScheduler _scheduler;
  110. public TestGeolocator(IObservable<Position> positionStream, IScheduler scheduler = null)
  111. {
  112. positionStream.Multicast(_latestPosition).Connect();
  113. _scheduler = scheduler ?? RxApp.DeferredScheduler;
  114. }
  115. public IObservable<Position> Listen(int minUpdateTime, double minUpdateDist, bool includeHeading = false)
  116. {
  117. DateTimeOffset? lastStamp = null;
  118. Position lastPos = null;
  119. return _latestPosition.Skip(1)
  120. .Timestamp(_scheduler)
  121. .Where(x => {
  122. var ret = lastStamp == null || x.Timestamp - lastStamp > TimeSpan.FromMilliseconds(minUpdateTime);
  123. lastStamp = x.Timestamp;
  124. return ret;
  125. })
  126. .Where(x => {
  127. var ret = lastPos == null || distForPos(lastPos, x.Value) > minUpdateDist;
  128. lastPos = x.Value;
  129. return ret;
  130. })
  131. .Select(x => x.Value);
  132. }
  133. public IObservable<Position> GetPosition(bool includeHeading = false)
  134. {
  135. var ret = _latestPosition.Take(1).Multicast(new AsyncSubject<Position>());
  136. ret.Connect();
  137. return ret;
  138. }
  139. double distForPos(Position lhs, Position rhs)
  140. {
  141. return Math.Sqrt(
  142. Math.Pow(rhs.Latitude - lhs.Latitude, 2.0) +
  143. Math.Pow(rhs.Longitude - lhs.Longitude, 2.0));
  144. }
  145. }
  146. public class GeolocationException : Exception
  147. {
  148. public GeolocationException(GeolocationError error)
  149. {
  150. Info = error;
  151. }
  152. public GeolocationError Info { get; protected set; }
  153. }
  154. }