using System;
using System.Device.Location;
using System.Windows.Threading;
using System.Collections.Generic;
namespace SharpGIS.WinPhone.Gps
{
///
/// Simulates the GeoCoordinateWatcher
///
public sealed class GpsDriveSimulator: IGeoPositionWatcher, IDisposable
{
private const double MaxMovement = 0.0004; //Degrees
#region Private Fields
private volatile bool disposed;
private DispatcherTimer timer;
private GeoPositionStatus status = GeoPositionStatus.NoData;
private GeoPosition position;
private Random random = new Random();
private double lastlatitude = double.NaN;
private double lastlongitude = double.NaN;
private DateTime lastTime;
private IList drivePath;
private double speed;
#endregion
///
/// Initializes a new instance of the class.
///
/// The start latitude where the simulation should begin.
/// The start longitude where the simulation should begin.
public GpsDriveSimulator(IList path, double speed)
{
drivePath = path;
lastlongitude = path[0].Longitude;
lastlatitude = path[0].Latitude;
this.speed = speed;
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += timer_Tick;
}
double totalDistance;
///
/// Fired every time a simulated GPS measurement needs to occur
///
///
///
private void timer_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
TimeSpan time = now - lastTime;
double deltaDistance = time.TotalSeconds * speed;
double course;
double[] p = PointAlongLine(totalDistance + deltaDistance, out course);
if (p == null)
{
totalDistance = 0;
return;
}
totalDistance += deltaDistance;
double lon = p[0];
double lat = p[1];
double altitude = p[2];
while (lat < -90) lat += 180;
while (lat > 90) lat -= 180;
while (lon < -180) lon += 360;
while (lon > 180) lon -= 360;
while (course < 0) course += 360;
while (course > 360) course -= 360;
lastTime = now;
Position = new GeoPosition()
{
Location = new GeoCoordinate(
lat, lon, altitude, //Location
0, 0, //Accuracy
speed, course), //Movement
Timestamp = now
};
Status = GeoPositionStatus.Ready;
lastlatitude = lat;
lastlongitude = lon;
lastTime = now;
}
private double[] PointAlongLine(double dist, out double course)
{
double accDist = 0;
course = double.NaN;
for (int i = 0; i < drivePath.Count - 1; i++)
{
var p1 = drivePath[i];
var p2 = drivePath[i + 1];
double distToWaypoint = GetDistanceGeodesic(p1.Longitude, p1.Latitude, p2.Longitude, p2.Latitude);
if (dist < accDist + distToWaypoint)
{
var distAlongSegment = dist - accDist;
double fraction = distAlongSegment / distToWaypoint;
double altitude = (p2.Altitude - p1.Altitude) * fraction + p1.Latitude;
course = GetTrueBearingGeodesic(p1.Longitude, p1.Latitude, p2.Longitude, p2.Latitude);
return GetPointFromHeadingGeodesic(new double[] { p1.Longitude, p1.Latitude, altitude }, distAlongSegment, course);
}
accDist += distToWaypoint;
}
return null;
}
#region Geographic calculations
///
/// Calculates the distance between two points in meters
///
///
///
///
///
///
private static double GetDistanceGeodesic(double lon1, double lat1, double lon2, double lat2)
{
lon1 = lon1 / 180 * Math.PI;
lon2 = lon2 / 180 * Math.PI;
lat1 = lat1 / 180 * Math.PI;
lat2 = lat2 / 180 * Math.PI;
return 2 * Math.Asin(Math.Sqrt(Math.Pow((Math.Sin((lat1 - lat2) / 2)), 2) +
Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin((lon1 - lon2) / 2), 2))) * 6378137;
}
///
/// Calculates the bearing between two points.
///
///
///
///
///
///
///
private static double GetTrueBearingGeodesic(double lon1, double lat1, double lon2, double lat2)
{
lat1 = lat1 / 180 * Math.PI;
lon1 = lon1 / 180 * Math.PI;
lat2 = lat2 / 180 * Math.PI;
lon2 = lon2 / 180 * Math.PI;
double tc1 = Math.Atan2(Math.Sin(lon1 - lon2) * Math.Cos(lat2),
Math.Cos(lat1) * Math.Sin(lat2) - Math.Sin(lat1) * Math.Cos(lat2) * Math.Cos(lon1 - lon2)) % (2 * Math.PI);
return 360 - (tc1 / Math.PI * 180);
}
public static double[] GetPointFromHeadingGeodesic(double[] start, double distance, double heading)
{
double brng = heading / 180 * Math.PI;
double lon1 = start[0] / 180 * Math.PI;
double lat1 = start[1] / 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 double[] { lon, lat, start[2] };
}
#endregion
#region Properties
///
/// Gets the position.
///
/// The position.
public GeoPosition Position
{
get
{
return position;
}
private set
{
if (value != position)
{
position = value;
if(value != null)
RaisePositionChanged(new GeoPositionChangedEventArgs(position));
}
}
}
///
/// The status of the location service.
///
public GeoPositionStatus Status
{
get
{
return status;
}
private set
{
if (value != status)
{
status = value;
RaiseStatusChanged(new GeoPositionStatusChangedEventArgs(value));
}
}
}
#endregion
#region Public Methods
///
/// Starts the acquisition of data from the location service.
///
/// This parameter is not used..
public void Start(bool suppressPermissionPrompt)
{
Start();
}
///
/// Starts the acquisition of data from the location service.
///
public void Start()
{
this.DisposeCheck();
timer.Start();
lastTime = DateTime.Now;
Status = GeoPositionStatus.Initializing;
}
///
/// Stops the acquisition of data from the location service.
///
public void Stop()
{
this.DisposeCheck();
timer.Stop();
this.Position = null;
Status = GeoPositionStatus.Disabled;
}
///
/// Attempts to start the acquisition of data from the location service. If the
/// provided timeout interval is exceeded before the location service responds,
/// the request for location is stopped and the method returns false.
///
/// This parameter is not used.
/// A TimeSpan object specifying the amount of time to wait for location data
/// acquisition to begin.
/// true if the location service responds within the
/// timeout window. Otherwise, false.
public bool TryStart(bool suppressPermissionPrompt, TimeSpan timeout)
{
Start();
return true;
}
#endregion
#region Events
private void RaisePositionChanged(GeoPositionChangedEventArgs args)
{
if (PositionChanged != null)
{
PositionChanged(this, args);
}
}
///
/// Occurs when the location service detects a change in position.
///
public event EventHandler> PositionChanged;
private void RaiseStatusChanged(GeoPositionStatusChangedEventArgs args)
{
if (StatusChanged != null)
{
StatusChanged(this, args);
}
}
///
/// Occurs when the status of the location service changes.
///
public event EventHandler StatusChanged;
#endregion
#region IDisposable
///
/// Releases managed and unmanaged resources used by the
/// and stops the acquisition of data from the location service.
///
public void Dispose()
{
this.Dispose(true);
}
private void Dispose(bool disposing)
{
lock (this)
{
if (!this.disposed)
{
this.Stop();
this.disposed = true;
}
}
}
private void DisposeCheck()
{
if (this.disposed)
{
throw new ObjectDisposedException("GeoCoordinateSimulator");
}
}
~GpsDriveSimulator()
{
this.Dispose(false);
}
#endregion
}
}