Skip to content

Commit

Permalink
Working on track statistics, work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Didosa committed Dec 30, 2023
1 parent 9b4af6f commit 269cded
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 39 deletions.
7 changes: 2 additions & 5 deletions GPSDataCreator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,8 @@ private async static Task MoveAsync() {
position.MoveBy(speed, course);
utcTime = DateTimeHelper.ToJSTime(DateTime.UtcNow);
boatTime = utcTime + boatTimeOffset;
TrackPoint record = new TrackPoint();
TrackPoint record = new TrackPoint(position.Latitude, position.Longitude);
record.UTC = utcTime;
record.Latitude = position.Latitude;
record.Longitude = position.Longitude;
SendPositionAsync(record);
}
}
Expand Down Expand Up @@ -149,8 +147,7 @@ private async static Task<LatLon> LoadLatestPosistionAsync() {
ProxyResult<Track> proxyResult = await trackProxy.GetTrackAsync(start, utcNow, false);
if (proxyResult.Success) {
if (proxyResult.Data.HasTrackPoints) {
TrackPoint record = proxyResult.Data.LastTrackPoint;
result = new LatLon(record.Latitude.Value, record.Longitude.Value);
result = proxyResult.Data.LastTrackPoint;
}
} else {
Console.WriteLine(proxyResult.Message);
Expand Down
21 changes: 18 additions & 3 deletions PiLotAPICore/Controllers/TracksController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

using PiLot.API.ActionFilters;
using PiLot.API.Helpers;
using PiLot.Data.Files;
using PiLot.Model.Nav;
using PiLot.Utils.Logger;

namespace PiLot.API.Controllers {

Expand All @@ -24,11 +23,27 @@ public class TracksController : ControllerBase {
[Route(Program.APIROOT + "[controller]")]
[HttpGet]
[ServiceFilter(typeof(ReadAuthorizationFilter))]
public Track Get(Int64 startTime, Int64 endTime, Boolean isBoatTime) {
public Track GetTrack(Int64 startTime, Int64 endTime, Boolean isBoatTime) {
Track track = new TrackDataConnector().ReadTrack(startTime, endTime, isBoatTime);
return track;
}

/// <summary>
/// Gets the track segments for a track defined by a certain range
/// </summary>
/// <param name="startTime">Starttime in ms utc or boatTime</param>
/// <param name="endTime">Endtime in ms utc or boatTime</param>
/// <param name="isBoatTime">If true, start and end are BoatTime, else UTC</param>
[Route(Program.APIROOT + "[controller]/segments")]
[HttpGet]
[ServiceFilter(typeof(ReadAuthorizationFilter))]
public TrackSegment GetFastestMile(Int64 startTime, Int64 endTime, Boolean isBoatTime) {
Track track = new TrackDataConnector().ReadTrack(startTime, endTime, isBoatTime);
TrackSegmentType type = new TrackSegmentType(-1, 3600, null, null);
TrackSegment segment = new TrackAnalyzer(track).GetFastestTrackSegment(type);
return segment;
}

/// <summary>
/// Sends a track to the server
/// </summary>
Expand Down
4 changes: 1 addition & 3 deletions PiLotAPICore/Helpers/GpsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,7 @@ private void PersistLatest() {
TrackPoint lastTrackPoint = this.trackPoints[0];
if (this.previousSavedTrackPoint != null) {
deltaT = lastTrackPoint.UTC - this.previousSavedTrackPoint.UTC;
LatLon latestPosition = new LatLon(lastTrackPoint.Latitude.Value, lastTrackPoint.Longitude.Value);
LatLon previousPosition = new LatLon(this.previousSavedTrackPoint.Latitude.Value, this.previousSavedTrackPoint.Longitude.Value);
deltaX = latestPosition.DistanceTo(previousPosition);
deltaX = lastTrackPoint.DistanceTo(previousSavedTrackPoint);
doPersist = (deltaT >= MINDELTAT) && (deltaX > MINDISTANCE);
} else {
doPersist = true;
Expand Down
127 changes: 127 additions & 0 deletions PiLotAPICore/Helpers/TrackAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using PiLot.Model.Nav;

namespace PiLot.API.Helpers {

/// <summary>
/// This class helps analyzing a track, especially finding the fastest periods.
/// </summary>
public class TrackAnalyzer {

private Track track;
private List<EnhancedTrackPoint> trackPoints;

public TrackAnalyzer(Track pTrack) {
this.track = pTrack;
this.PreprocessTrack();
}

/// <summary>
/// Returns the fastest segment either for a minimal distance or a minimal duration.
/// The resulting segment can be slightly longer than required, because of course
/// the TrackPoints can have any distance between them. If the track is shorter than
/// the required distance or time, the result is null.
/// </summary>
/// <param name="pType">The type of track segment to find</param>
/// <returns>The fastest segment for the type, or null</returns>
public TrackSegment GetFastestTrackSegment(TrackSegmentType pType) {
if (pType.IsDistance) {
return this.GetFastestSegmentByDistance(pType);
} else {
return this.GetFastestSegmentByDuration(pType);
}
}

/// <summary>
/// This gets the fastest segment having a certain duration. E.g. the fastest hour.
/// </summary>
/// <param name="pType">The type defining the minimal duration</param>
/// <returns>The fastest segment or null</returns>
private TrackSegment GetFastestSegmentByDuration(TrackSegmentType pType) {
TrackSegment result = null;
Int32 startIndex = 0;
Int32 endIndex = 1;
Double duration; // seconds
Double distance; // meters
Double speed; // m/s
Double topSpeed = 0; // m/s
while (endIndex < this.trackPoints.Count) {
duration = (this.trackPoints[endIndex].UTC - this.trackPoints[startIndex].UTC) / 1000;
if (duration < pType.Duration) {
endIndex++;
} else {
distance = this.trackPoints[endIndex].TotalDistance - this.trackPoints[startIndex].TotalDistance;
speed = distance / duration;
if (speed > topSpeed) {
result = result ?? new TrackSegment(this.track, pType.ID);
result.StartUTC = this.trackPoints[startIndex].UTC;
result.StartBoatTime = this.trackPoints[startIndex].BoatTime ?? this.trackPoints[startIndex].UTC;
result.EndUTC = this.trackPoints[endIndex].UTC;
result.EndBoatTime = this.trackPoints[endIndex].BoatTime ?? this.trackPoints[endIndex].UTC;
result.Distance = distance;
topSpeed = speed;
}
startIndex++;
}
}
return result;
}

/// <summary>
/// This gets the fastest segment having a certain distance. E.g. the fastest mile.
/// </summary>
/// <param name="pType">The type defining the minimal distance</param>
/// <returns>The fastest segment or null</returns>
private TrackSegment GetFastestSegmentByDistance(TrackSegmentType pType) {
TrackSegment result = null;
Int32 startIndex = 0;
Int32 endIndex = 1;
Double distance; // meters
Double speed; // m/s
Double topSpeed = 0; // m/s
while (endIndex < this.trackPoints.Count) {
distance = this.trackPoints[endIndex].TotalDistance - this.trackPoints[startIndex].TotalDistance;
if (distance < pType.Distance) {
endIndex++;
} else {
speed = distance / ((this.trackPoints[endIndex].UTC - this.trackPoints[startIndex].UTC) / 1000);
if(speed > topSpeed) {
result = result ?? new TrackSegment(this.track, pType.ID);
result.StartUTC = this.trackPoints[startIndex].UTC;
result.StartBoatTime = this.trackPoints[startIndex].BoatTime ?? this.trackPoints[startIndex].UTC;
result.EndUTC = this.trackPoints[endIndex].UTC;
result.EndBoatTime = this.trackPoints[endIndex].BoatTime ?? this.trackPoints[endIndex].UTC;
result.Distance = distance;
topSpeed = speed;
}
startIndex++;
}
}
return result;
}

/// <summary>
/// Creates a list of enhanced track points, which not only contain the
/// positions and timestamps, but also the total distance from the first
/// position. This makes calculating the total distance between any two
/// track points much faster.
/// </summary>
private void PreprocessTrack() {
this.trackPoints = new List<EnhancedTrackPoint>();
if(track.TrackPoints.Count > 1) {
EnhancedTrackPoint currentTrackPoint = null, previousTrackPoint = null;
foreach (TrackPoint aTrackPoint in this.track.TrackPoints) {
if (currentTrackPoint != null) {
previousTrackPoint = currentTrackPoint;
}
currentTrackPoint = new EnhancedTrackPoint(aTrackPoint);
if (previousTrackPoint != null) {
currentTrackPoint.TotalDistance = previousTrackPoint.TotalDistance + currentTrackPoint.DistanceTo(previousTrackPoint);
}
this.trackPoints.Add(currentTrackPoint);
}
}
}
}
}
3 changes: 1 addition & 2 deletions PiLotAPICore/Workers/AnchorWatchWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,8 @@ private void StopObserveGps() {
/// <param name="pRecord">The latest GpsRecord, not null</param>
private void GpsDataRecieved(TrackPoint pRecord) {
try {
LatLon positionLatLon = new LatLon(pRecord.Latitude.Value, pRecord.Longitude.Value);
LatLon anchorLatLon = new LatLon(this.anchorWatch.Latitude, this.anchorWatch.Longitude);
Double distance = positionLatLon.DistanceTo(anchorLatLon);
Double distance = pRecord.DistanceTo(anchorLatLon);
Int32 newAlarmIndex = this.anchorWatch.Radius != 0 ? AnchorWatchWorker.ALARMS.FindIndex(a => a.Item1 < distance / this.anchorWatch.Radius) : 0;
if (newAlarmIndex != this.alarmIndex) {
this.StopAlarm();
Expand Down
8 changes: 4 additions & 4 deletions PiLotDemoDataSender/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,16 @@ private static void PrepareTracks() {
record2 = records[i + 1];
deltaT = record2.UTC - record1.UTC;
if(deltaT >= 2000) {
pos1 = new LatLon(record1.Latitude.Value, record1.Longitude.Value);
pos2 = new LatLon(record2.Latitude.Value, record2.Longitude.Value);
pos1 = new LatLon(record1.Latitude, record1.Longitude);
pos2 = new LatLon(record2.Latitude, record2.Longitude);
newPositionCount = (Int32)(deltaT / 1000) - 1;
Console.WriteLine($"Adding {newPositionCount} new positions");
stepMS = deltaT / (newPositionCount + 1);
for (Int32 j = 0; j < newPositionCount; j++) {
TrackPoint newRecord = new TrackPoint();
splitPoint = pos1.IntermediatePointTo(pos2, (Single)(j + 1) / (Single)(newPositionCount + 1));
TrackPoint newRecord = new TrackPoint(splitPoint.Latitude, splitPoint.Longitude);
newRecord.UTC = record1.UTC + (stepMS * (j + 1));
newRecord.BoatTime = record1.BoatTime + (stepMS * (j + 1));
splitPoint = pos1.IntermediatePointTo(pos2, (Single)(j + 1) / (Single)(newPositionCount + 1));
newRecord.Latitude = splitPoint.Latitude;
newRecord.Longitude = splitPoint.Longitude;
newTrack.AddTrackPoint(newRecord);
Expand Down
23 changes: 23 additions & 0 deletions PiLotModel/Nav/EnhancedTrackPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace PiLot.Model.Nav {

/// <summary>
/// This is a special type of TrackPoint, which additionally has information about
/// the total distance, which helps for track analysis.
/// </summary>
public class EnhancedTrackPoint: TrackPoint {

public EnhancedTrackPoint(TrackPoint pTrackPoint) : base(pTrackPoint.Latitude, pTrackPoint.Longitude) {
this.UTC = pTrackPoint.UTC;
this.BoatTime = pTrackPoint.BoatTime;
this.Altitude = pTrackPoint.Altitude;
this.LatitudeError = pTrackPoint.LatitudeError;
this.LongitudeError = pTrackPoint.LongitudeError;
this.AltitudeError = pTrackPoint.AltitudeError;
}

public Double TotalDistance { get; set; } = 0;

}
}
5 changes: 4 additions & 1 deletion PiLotModel/Nav/LatLon.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Text.Json.Serialization;

namespace PiLot.Model.Nav {

Expand All @@ -24,11 +25,13 @@ public LatLon(Double pLat, Double pLon) {
/// <summary>
/// Gets or sets the latitude in degrees. Allowed values are -90 to 90
/// </summary>
[JsonPropertyName("latitude")]
public Double Latitude { get; set; }

/// <summary>
/// The longitude in degrees. Any values are accepted, but will translate to -180 .. 180
/// </summary>
[JsonPropertyName("longitude")]
public Double Longitude {
get { return this.longitude; }
set {
Expand All @@ -46,7 +49,7 @@ public Double DistanceTo(LatLon pOther) {
Double φ1 = this.Latitude * Math.PI / 180;
Double φ2 = pOther.Latitude * Math.PI / 180;
Double Δφ = (pOther.Latitude - this.Latitude) * Math.PI / 180;
Double Δλ = (pOther.longitude - this.longitude) * Math.PI / 180;
Double Δλ = (pOther.Longitude - this.Longitude) * Math.PI / 180;
Double a = Math.Sin(Δφ / 2) * Math.Sin(Δφ / 2) +
Math.Cos(φ1) * Math.Cos(φ2) *
Math.Sin(Δλ / 2) * Math.Sin(Δλ / 2);
Expand Down
26 changes: 5 additions & 21 deletions PiLotModel/Nav/TrackPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ namespace PiLot.Model.Nav {
/// <summary>
/// Represents one point on a track. A TrackPoint has a position and a timestamp.
/// </summary>
public class TrackPoint : IComparable<TrackPoint> {
public class TrackPoint : LatLon, IComparable<TrackPoint> {

private const Char SEPARATOR = ';';

#region constructor

public TrackPoint() { }
public TrackPoint(Double pLatitude, Double pLongitude):base(pLatitude, pLongitude) { }

/// <summary>
/// Creates a TrackPoint from an Array with UTC, BoatTime, Lat, Lng. If any of them is
Expand All @@ -30,11 +30,9 @@ public static TrackPoint FromArray(Double?[] pData) {
&& (pData[2] != null)
&& (pData[3] != null)
) {
result = new TrackPoint {
result = new TrackPoint(pData[2].Value, pData[3].Value) {
UTC = Convert.ToInt64(pData[0]),
BoatTime = Convert.ToInt64(pData[1]),
Latitude = pData[2],
Longitude = pData[3]
BoatTime = Convert.ToInt64(pData[1])
};
}
return result;
Expand Down Expand Up @@ -78,11 +76,9 @@ public static TrackPoint FromStringArray(String[] pStringArray) {
&& Double.TryParse(pStringArray[2], out Double lat)
&& Double.TryParse(pStringArray[3], out Double lon)
) {
result = new TrackPoint();
result = new TrackPoint(lat, lon);
result.UTC = utc;
result.BoatTime = boatTime;
result.Latitude = lat;
result.Longitude = lon;
}
if ((pStringArray.Length >= 5) && Double.TryParse(pStringArray[4], out testDouble)) {
result.Altitude = testDouble;
Expand Down Expand Up @@ -116,18 +112,6 @@ public static TrackPoint FromStringArray(String[] pStringArray) {
[JsonPropertyName("boatTime")]
public Int64? BoatTime { get; set; }

/// <summary>
/// The latitude in degrees
/// </summary>
[JsonPropertyName("latitude")]
public Double? Latitude { get; set; }

/// <summary>
/// The longitude in degrees
/// </summary>
[JsonPropertyName("longitude")]
public Double? Longitude { get; set; }

/// <summary>
/// The asm in m as measured by the gps
/// </summary>
Expand Down
Loading

0 comments on commit 269cded

Please sign in to comment.