Skip to content

Commit

Permalink
Calculate segment speed in the model. Prepare for top segments statis…
Browse files Browse the repository at this point in the history
…tics
  • Loading branch information
Didosa committed Sep 20, 2024
1 parent 8e62c95 commit 1da307a
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 33 deletions.
1 change: 0 additions & 1 deletion PiLotAPICore/Controllers/TrackSegmentTypesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using PiLot.API.ActionFilters;
using PiLot.API.Helpers;
using PiLot.Data.Nav;
using PiLot.Data.Postgres.Nav;
using PiLot.Model.Nav;

namespace PiLot.API.Controllers {
Expand Down
40 changes: 40 additions & 0 deletions PiLotAPICore/Controllers/TrackSegmentsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

using PiLot.API.ActionFilters;
using PiLot.API.Helpers;
using PiLot.Data.Nav;
using PiLot.Model.Nav;

namespace PiLot.API.Controllers {

/// <summary>
/// Offers access to read Track segments (for the segments a one specific track,
/// please use the Tracks controller)
/// </summary>
[ApiController]
public class TrackSegmentsController : ControllerBase {

/// <summary>
/// Finds track segments based on the type, the boats and optionally a timeframe.
/// </summary>
/// <param name="typeId">The id of the segment type to load</param>
/// <param name="start">Optional start time in milliseconds</param>
/// <param name="end">Optional end time in milliseconds</param>
/// <param name="isBoatTime">Whether start and end are BoatTime or UTC</param>
/// <param name="boats">A list of boat names</param>
/// <param name="pageSize">The maximal number of results to return</param>
[Route(Program.APIROOT + "[controller]")]
[HttpGet]
[ServiceFilter(typeof(ReadAuthorizationFilter))]
public List<TrackSegment> FindTrackSegments(Int32 typeId, Int64? start, Int64? end, Boolean isBoatTime, String boats, Int32 pageSize) {
ITrackDataConnector dataConnector = DataConnectionHelper.TrackDataConnector;
if (dataConnector.SupportsStatistics) {
return dataConnector.FindTrackSegments(typeId, start, end, isBoatTime, boats.Split(','), pageSize);
} else {
return new List<TrackSegment>(0);
}
}
}
}
15 changes: 15 additions & 0 deletions PiLotData/Nav/ITrackDataConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ public interface ITrackDataConnector {
/// <returns>A list of TrackSegment, can be empty but not null</returns>
List<TrackSegment> ReadTrackSegments(Int32 pTrackId);

/// <summary>
/// Returns a list of TrackSegments for a certain time, optionally filtered by time
/// and boats. If start and end are passed, all segments that overlap with the
/// period start-end are returned. If any of the values is null, there will be no
/// limitation in that direction.
/// </summary>
/// <param name="pTypeId">The track type ID</param>
/// <param name="pStart">Start of the search period in ms</param>
/// <param name="pEnd">End of the search period in ms</param>
/// <param name="pIsBoatTime">Whether to search based on UTC (false) or BoatTime</param>
/// <param name="pBoats">A list of boats to filter by, can be null</param>
/// <param name="pPageSize">The maximum number of results to return</param>
/// <returns>a list of TrackSegment, can be empty but not null</returns>
List<TrackSegment> FindTrackSegments(Int32 pTypeId, Int64? pStart, Int64? pEnd, Boolean pIsBoatTime, String[] pBoats, Int32 pPageSize);

/// <summary>
/// Saves a track segment. Any existing segment for the same track and type
/// will be replaced. Will only be implemented if SupportStatistics=true
Expand Down
9 changes: 9 additions & 0 deletions PiLotDataFiles/Nav/TrackDataConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ public List<TrackSegment> ReadTrackSegments(Int32 pTrackId) {
throw new NotImplementedException("Files based DataConnector does not support statistics. Check SupportsStatistics before calling ReadTrackSegments()");
}

/// <summary>
/// This should not be called as SupportsStatistics is false. Will throw an exception.
/// Only there in order to implement ITrackDataConnector
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public List<TrackSegment> FindTrackSegments(Int32 pTypeId, Int64? pStart, Int64? pEnd, Boolean pIsBoatTime, String[] pBoats, Int32 pPageSize) {
throw new NotImplementedException("Files based DataConnector does not support statistics. Check SupportsStatistics before calling FindTrackSegments()");
}

/// <summary>
/// This should not be called as SupportsStatistics is false. Will throw an exception.
/// Only there in order to implement ITrackDataConnector
Expand Down
10 changes: 10 additions & 0 deletions PiLotDataFiles/Nav/TrackDataConnector2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ public List<TrackSegment> ReadTrackSegments(Int32 pTrackId) {
throw new NotImplementedException("Files based DataConnector does not support statistics. Check SupportsStatistics before calling ReadTrackSegments()");
}

/// <summary>
/// This should not be called as SupportsStatistics is false. Will throw an exception.
/// Only there in order to implement ITrackDataConnector
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public List<TrackSegment> FindTrackSegments(Int32 pTypeId, Int64? pStart, Int64? pEnd, Boolean pIsBoatTime, String[] pBoats, Int32 pPageSize) {
throw new NotImplementedException("Files based DataConnector does not support statistics. Check SupportsStatistics before calling FindTrackSegments()");
}


/// <summary>
/// This should not be called as SupportsStatistics is false. Will throw an exception.
/// Only there in order to implement ITrackDataConnector
Expand Down
29 changes: 28 additions & 1 deletion PiLotDataPostgres/Nav/TrackDataConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,31 @@ public List<TrackSegment> ReadTrackSegments(Int32 pTrackId) {
return this.dbHelper.ReadData<TrackSegment>(query, new Func<NpgsqlDataReader, TrackSegment>(this.ReadTrackSegment), pars);
}

/// <summary>
/// Returns a list of TrackSegments for a certain time, optionally filtered by time
/// and boats. If start and end are passed, all segments that overlap with the
/// period start-end are returned. If any of the values is null, there will be no
/// limitation in that direction.
/// </summary>
/// <param name="pTypeId">The track type ID</param>
/// <param name="pStart">Start of the search period in ms</param>
/// <param name="pEnd">End of the search period in ms</param>
/// <param name="pIsBoatTime">Whether to search based on UTC (false) or BoatTime</param>
/// <param name="pBoats">A list of boats to filter by, can be null</param>
/// <param name="pPageSize">The maximum number of results to return</param>
/// <returns>a list of TrackSegment, can be empty but not null</returns>
public List<TrackSegment> FindTrackSegments(Int32 pTypeId, Int64? pStart, Int64? pEnd, Boolean pIsBoatTime, String[] pBoats, Int32 pPageSize) {
String query = "SELECT * FROM find_track_segments(@p_type_id, @p_start, @p_end, @p_is_boattime, @p_boats, @p_page_size)";
List<(String, Object)> pars = new List<(String, Object)>();
pars.Add(("@p_type_id", pTypeId));
pars.Add(("@p_start", this.dbHelper.GetNullableParameterValue(pStart)));
pars.Add(("@p_end", this.dbHelper.GetNullableParameterValue(pEnd)));
pars.Add(("@p_is_boattime", pIsBoatTime));
pars.Add(("@p_boats", pBoats));
pars.Add(("@p_page_size", pPageSize));
return this.dbHelper.ReadData<TrackSegment>(query, new Func<NpgsqlDataReader, TrackSegment>(this.ReadTrackSegment), pars);
}

/// <summary>
/// Saves a track segment to the database. Any existing segment for the same track and type
/// will be replaced.
Expand Down Expand Up @@ -416,7 +441,9 @@ private TrackSegment ReadTrackSegment(NpgsqlDataReader pReader) {
EndUTC = pReader.GetInt64("end_utc"),
StartBoatTime = pReader.GetInt64("start_boattime"),
EndBoatTime = pReader.GetInt64("end_boattime"),
Distance_mm = pReader.GetInt32("distance_mm")
Distance_mm = pReader.GetInt32("distance_mm"),
Speed = pReader.GetDouble("speed"),
Boat = pReader.GetString("boat")
};
return result;
}
Expand Down
19 changes: 9 additions & 10 deletions PiLotModel/Nav/TrackSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,20 @@ public Double Distance {

/// <summary>
/// The total distance of the segment in millimeters. This is used in the db,
/// and for precise comparison of Segments
/// and for precise calculation of the speed
/// </summary>
[JsonIgnore]
public Int32 Distance_mm {
get; set;
}
public Int32 Distance_mm { get; set; }

/// <summary>
/// Returns the average speed of the segment in m/s
/// </summary>
[JsonIgnore]
public Double Speed {
get {
return this.Distance_mm / (Double)(this.EndUTC - this.StartUTC);
}
}
[JsonPropertyName("speed")]
public Double Speed { get;set; }

/// <summary>
/// The boat name
/// </summary>
public String Boat { get; set; }
}
}
18 changes: 15 additions & 3 deletions PiLotWeb/js/Model/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -1778,7 +1778,7 @@ PiLot.Model.Nav = (function () {

/**
* Creates a track object based on a serialized track object. Returns null, if the
* pData is invalid. If start/end date and distance will only be set explicitly, if
* pData is invalid. Start/end date and distance will only be set explicitly, if
* there are is no trackPointsArray delivered.
* @param {Object} pData - an object with id, boat, distance, startUtc, endUtc, startBoatTime, endBoatTime, trackPointsArray
*/
Expand Down Expand Up @@ -1913,14 +1913,16 @@ PiLot.Model.Nav = (function () {
* @param {DateTime} pEndBoatTime - the end of the segment in BoatTime
* @param {Number} pDistance - the distance covered in meters
*/
var TrackSegment = function (pTrackId, pType, pStartUtc, pEndUtc, pStartBoatTime, pEndBoatTime, pDistance) {
var TrackSegment = function (pTrackId, pType, pStartUtc, pEndUtc, pStartBoatTime, pEndBoatTime, pDistance, pSpeed, pBoat) {
this.trackId = pTrackId;
this.type = pType;
this.startUtc = pStartUtc;
this.endUtc = pEndUtc;
this.startBoatTime = pStartBoatTime;
this.endBoatTime = pEndBoatTime;
this.distance = pDistance;
this.speed = pSpeed;
this.boat = pBoat;
this.initialize();
};

Expand Down Expand Up @@ -1961,10 +1963,20 @@ PiLot.Model.Nav = (function () {
/**
* @returns {Number} - the total distance in meters. This can be more
* than the distance defined by the TrackSegmentType, because it's based
* on acutal TrackPoints.
* on actual TrackPoints.
* */
getDistance: function() {
return this.distance;
},

/** @returns the average speed for the segment in m/s */
getSpeed: function () {
return this.speed;
},

/** @returns the name of the boat */
getBoat: function () {
return this.boat;
}
};

Expand Down
62 changes: 51 additions & 11 deletions PiLotWeb/js/Service/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,24 +427,64 @@ PiLot.Service.Nav = (function () {
if (Array.isArray(json)) {
result = [];
for (anItem of json) {
const trackSegment = new PiLot.Model.Nav.TrackSegment(
anItem.trackId,
this.trackSegmentTypes.get(anItem.typeId),
RC.Date.DateHelper.millisToLuxon(anItem.startUtc, language),
RC.Date.DateHelper.millisToLuxon(anItem.endUtc, language),
RC.Date.DateHelper.millisToLuxon(anItem.startBoatTime, language),
RC.Date.DateHelper.millisToLuxon(anItem.endBoatTime, language),
anItem.distance
);
result.push(trackSegment);
result.push(this.trackSegmentFromData(anItem, language));
}
} else {
PiLot.log('Did not get an array from TrackSegmentTypes endpoint.', 0);
PiLot.log('Did not get an array from TrackSegments endpoint.', 0);
}
}
return result;
},

/**
* Finds all track segments of a certain type, optionally limited by a timeframe. If start
* and end is passed, all segments that overlap with the interval start-end are returned,
* so be aware that the resulting segments potentially have start/end outside the interval.
* @param {Number} pType - The track segment type id, mandatory
* @param {Number} pStart - Start time in milliseconds, can be null
* @param {Number} pEnd - timeframe end time in milliseconds, can be null
* @param {Boolean} pIsBoatTime - whether start and end is boat time or utc
* @param {Number} pPageSize - the number of records to return
* */
findTrackSegmentsAsync: async function (pType, pStart, pEnd, pIsBoatTime, pBoats, pPageSize) {
let result = null;
await this.ensureTrackSegmentTypesLoadedAsync();
const json = await PiLot.Utils.Common.getFromServerAsync(`/Tracks/${pTrackId}/Segments`);
const language = PiLot.Utils.Language.getLanguage();
if (json !== null) {
if (Array.isArray(json)) {
result = [];
for (anItem of json) {
result.push(this.trackSegmentFromData(anItem, language));
}
} else {
PiLot.log('Did not get an array from TrackSegments endpoint.', 0);
}
}
return result;
},

/**
* Converts a data object as delivered by the backend to a TrackSegment object.
* Please make sure to call this.ensureTrackSegmentTypesLoadedAsync before calling
* this (we don't want to call it here as this is usually called within a loop).
* @param {Object} pData - the data object
* @param {String} pLanguage - the language key (DE, EN etc.) used for the luxon object
*/
trackSegmentFromData: function (pData, pLanguage) {
return new PiLot.Model.Nav.TrackSegment(
anItem.trackId,
this.trackSegmentTypes.get(pData.typeId),
RC.Date.DateHelper.millisToLuxon(pData.startUtc, pLanguage),
RC.Date.DateHelper.millisToLuxon(pData.endUtc, pLanguage),
RC.Date.DateHelper.millisToLuxon(pData.startBoatTime, pLanguage),
RC.Date.DateHelper.millisToLuxon(pData.endBoatTime, pLanguage),
pData.distance,
pData.speed,
pData.boat
);
},

/**
* Gets the cached map of all trackSegmentTypes
* @returns {Map} key: id, value: trackSegmentType
Expand Down
3 changes: 1 addition & 2 deletions PiLotWeb/js/View/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -1415,8 +1415,7 @@ PiLot.View.Nav = (function () {
const segmentType = pSegment.getType();
const label = segmentType.getLabel(pLanguage);
control.querySelector('.lblLabel').innerHTML = label;
const effectiveDurationMS = pSegment.getEndUtc().toMillis() - pSegment.getStartUtc().toMillis();
const speed = pSegment.getDistance() / (effectiveDurationMS / 1000); // speed in m/s
const speed = pSegment.getSpeed(); // speed in m/s
if (isDistance) {
let duration = luxon.Duration.fromObject({ seconds: segmentType.getDistance() / speed });
control.querySelector('.lblDuration').innerHTML = this.durationToHHMMSS(duration);
Expand Down
Loading

0 comments on commit 1da307a

Please sign in to comment.