Created
December 12, 2012 01:43
-
-
Save dotMorten/4264128 to your computer and use it in GitHub Desktop.
GeoPositionWatcher for more accurately simulating a GPS device, including change in accuracy, heading, and speed.
This code is compatible with WPF, Windows Phone 7 and Windows Phone 8
This file contains hidden or 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
// GpsCoordinateSimulator.cs: | |
// WPF: Requires reference to System.Device.dll | |
using System; | |
using System.Device.Location; | |
using System.Windows; | |
using System.Windows.Threading; | |
namespace GpsSimulator | |
{ | |
/// <summary> | |
/// GeoPositionWatcher for more accurately simulating a GPS, | |
/// including change in accuracy, heading, and speed. | |
/// </summary> | |
public class GeoCoordinateSimulator : IGeoPositionWatcher<GeoCoordinate> | |
{ | |
private static Random randomizer = new Random(); | |
private DispatcherTimer timer; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="GeoCoordinateSimulator" /> class. | |
/// </summary> | |
public GeoCoordinateSimulator() | |
{ | |
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) }; | |
timer.Tick += timer_Tick; | |
Status = GeoPositionStatus.NoData; | |
// Default start location | |
StartLatitude = 34.057104; | |
StartLongitude = -117.196816; | |
} | |
/// <summary> | |
/// Called when the position timer triggers, and calculates the next position based on current speed and heading, | |
/// and adds a little randomization to current heading, speed and accuracy. | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="e"></param> | |
private void timer_Tick(object sender, EventArgs e) | |
{ | |
Status = GeoPositionStatus.Ready; | |
var oldPosition = Position; | |
if (oldPosition == null) | |
{ | |
oldPosition = new GeoPosition<GeoCoordinate>( | |
new DateTimeOffset(DateTime.Now.AddSeconds(1)), new GeoCoordinate() | |
{ | |
Latitude = StartLatitude, | |
Longitude = StartLongitude, | |
Altitude = StartAltitude, | |
Speed = 0, | |
Course = 0, | |
HorizontalAccuracy = 20, | |
VerticalAccuracy = 0 | |
}); | |
} | |
var now = DateTime.Now; | |
TimeSpan timeParsed = oldPosition.Timestamp - now; | |
double acceleration = randomizer.NextDouble() * 5 - 2.5; | |
double deltaSpeed = acceleration * timeParsed.TotalSeconds; | |
double newSpeed = Math.Max(0, deltaSpeed + oldPosition.Location.Speed); | |
double deltaCourse = randomizer.NextDouble() * 30 - 15; | |
double newCourse = deltaCourse + oldPosition.Location.Course; | |
while (newCourse < 0) newCourse += 360; | |
while (newCourse >= 360) newCourse -= 360; | |
double distanceTravelled = (newSpeed + oldPosition.Location.Speed) * .5 * timeParsed.TotalSeconds; | |
double accuracy = Math.Min(500, Math.Max(20, oldPosition.Location.HorizontalAccuracy + (randomizer.NextDouble() * 100 - 50))); | |
var pos = GetPointFromHeadingGeodesic(new Point(oldPosition.Location.Longitude, oldPosition.Location.Latitude), distanceTravelled, newCourse-180); | |
var newPosition = new GeoPosition<GeoCoordinate>( | |
new DateTimeOffset(now), new GeoCoordinate() | |
{ | |
Latitude = pos.Y, | |
Longitude = pos.X, | |
Altitude = oldPosition.Location.Altitude + oldPosition.Location.Altitude+randomizer.NextDouble()*20, | |
Speed = newSpeed, | |
Course = newCourse, | |
HorizontalAccuracy = accuracy, | |
VerticalAccuracy = randomizer.NextDouble() * 300, | |
}); | |
Position = newPosition; | |
} | |
/// <summary> | |
/// Gets a point on the globe based on a location, a heading and a distance. | |
/// </summary> | |
/// <param name="start">The start.</param> | |
/// <param name="distance">The distance.</param> | |
/// <param name="heading">The heading.</param> | |
/// <returns></returns> | |
private static Point GetPointFromHeadingGeodesic(Point start, double distance, double heading) | |
{ | |
double brng = heading / 180 * Math.PI; | |
double lon1 = start.X / 180 * Math.PI; | |
double lat1 = start.Y / 180 * Math.PI; | |
double dR = distance / 6378137; //Angular distance in radians | |
double lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(dR) + Math.Cos(lat1) * Math.Sin(dR) * Math.Cos(brng)); | |
double lon2 = lon1 + Math.Atan2(Math.Sin(brng) * Math.Sin(dR) * Math.Cos(lat1), Math.Cos(dR) - Math.Sin(lat1) * Math.Sin(lat2)); | |
double lon = lon2 / Math.PI * 180; | |
double lat = lat2 / Math.PI * 180; | |
while (lon < -180) lon += 360; | |
while (lat < -90) lat += 180; | |
while (lon > 180) lon -= 360; | |
while (lat > 90) lat -= 180; | |
return new Point(lon, lat); | |
} | |
/// <summary> | |
/// Gets or sets the start latitude. | |
/// </summary> | |
/// <value> | |
/// The start latitude. | |
/// </value> | |
public double StartLatitude { get; set; } | |
/// <summary> | |
/// Gets or sets the start longitude. | |
/// </summary> | |
/// <value> | |
/// The start longitude. | |
/// </value> | |
public double StartLongitude { get; set; } | |
/// <summary> | |
/// Gets or sets the start altitude. | |
/// </summary> | |
/// <value> | |
/// The start altitude. | |
/// </value> | |
public double StartAltitude { get; set; } | |
#region IGeoPositionWatcher<GeoCoordinate> | |
/// <summary> | |
/// Initiate the acquisition of location data. | |
/// </summary> | |
public void Start() | |
{ | |
Start(true); | |
} | |
/// <summary> | |
/// Start acquiring location data, specifying whether or not to suppress prompting | |
/// for permissions. This method returns synchronously. | |
/// </summary> | |
/// <param name="suppressPermissionPrompt"> | |
/// If true, do not prompt the user to enable location providers and only start | |
/// if location data is already enabled. If false, a dialog box may be displayed | |
/// to prompt the user to enable location sensors that are disabled. | |
/// </param> | |
public void Start(bool suppressPermissionPrompt) | |
{ | |
TryStart(true, TimeSpan.MaxValue); | |
} | |
/// <summary> | |
/// Start acquiring location data, specifying an initialization timeout. This | |
/// method returns synchronously. | |
/// </summary> | |
/// <param name="suppressPermissionPrompt"> | |
/// If true, do not prompt the user to enable location providers and only start | |
/// if location data is already enabled. If false, a dialog box may be displayed | |
/// to prompt the user to enable location sensors that are disabled. | |
/// </param> | |
/// <param name="timeout">Time in milliseconds to wait for initialization to complete.</param> | |
/// <returns>true if succeeded, false if timed out.</returns> | |
public bool TryStart(bool suppressPermissionPrompt, TimeSpan timeout) | |
{ | |
timer.Start(); | |
Status = GeoPositionStatus.Initializing; | |
return true; | |
} | |
/// <summary> | |
/// Stop acquiring location data. | |
/// </summary> | |
public void Stop() | |
{ | |
timer.Stop(); | |
Status = GeoPositionStatus.NoData; | |
Position = null; | |
} | |
private GeoPosition<GeoCoordinate> m_Position; | |
public GeoPosition<GeoCoordinate> Position | |
{ | |
get { return m_Position; } | |
private set | |
{ | |
if (m_Position != value) | |
{ | |
m_Position = value; | |
if (PositionChanged != null) | |
PositionChanged(this, new GeoPositionChangedEventArgs<GeoCoordinate>(m_Position)); | |
} | |
} | |
} | |
public event EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>> PositionChanged; | |
private GeoPositionStatus m_Status; | |
public GeoPositionStatus Status | |
{ | |
get { return m_Status; } | |
set | |
{ | |
if (m_Status != value) | |
{ | |
m_Status = value; | |
if (StatusChanged != null) | |
StatusChanged(this, new GeoPositionStatusChangedEventArgs(value)); | |
} | |
} | |
} | |
public event EventHandler<GeoPositionStatusChangedEventArgs> StatusChanged; | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment