package tim.prune.data;
import tim.prune.config.Config;
/**
* Abstract class to hold static calculation functions
* for speed (and vertical speed)
*/
public abstract class SpeedCalculator
{
/**
* Calculate the horizontal speed value of the track at the specified index
* @param inTrack track object
* @param inIndex index of point to calculate speed for
* @param inValue object in which to place result of calculation
*/
public static void calculateSpeed(Track inTrack, int inIndex, SpeedValue inValue)
{
inValue.setInvalid();
if (inTrack == null || inIndex < 0 || inValue == null)
{
System.err.println("Cannot calculate speed for index " + inIndex);
return;
}
DataPoint point = inTrack.getPoint(inIndex);
if (point == null) {return;}
boolean pointHasSpeed = false;
double speedValue = 0.0;
// First, see if point has a speed value already
if (point.hasHSpeed()) {
speedValue = point.getHSpeed().getValue(Config.getUnitSet().getSpeedUnit());
pointHasSpeed = true;
}
// otherwise, see if we can calculate it from the timestamps
if (!pointHasSpeed && point.hasTimestamp() && !point.isWaypoint())
{
double totalRadians = 0.0;
int index = inIndex-1;
DataPoint p = null;
DataPoint q = point;
Timestamp earlyStamp = point.getTimestamp();
boolean stop = false;
// Count backwards until timestamp earlier than now; total distances back to this point
if (!point.getSegmentStart())
{
do
{
p = inTrack.getPoint(index);
boolean timeOk = p != null && p.hasTimestamp() && p.getTimestamp().isBefore(point.getTimestamp());
boolean pValid = timeOk && !p.isWaypoint();
if (pValid) {
totalRadians += DataPoint.calculateRadiansBetween(p, q);
earlyStamp = p.getTimestamp();
}
stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(p, point);
index--;
if (p != null && !p.isWaypoint()) {
q = p;
}
}
while (!stop);
}
// Count forwards until timestamp later than now; total distances forward to this point
Timestamp lateStamp = point.getTimestamp();
q = point;
index = inIndex+1;
do
{
p = inTrack.getPoint(index);
boolean timeOk = p != null && p.hasTimestamp() && !p.getTimestamp().isBefore(point.getTimestamp());
boolean pValid = timeOk && !p.isWaypoint() && !p.getSegmentStart();
if (pValid) {
totalRadians += DataPoint.calculateRadiansBetween(p, q);
lateStamp = p.getTimestamp();
}
stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point, p);
index++;
if (p != null && !p.isWaypoint()) {
q = p;
}
}
while (!stop);
// See if we've managed to get a time range of at least a second
long milliseconds = lateStamp.getMillisecondsSince(earlyStamp);
if (milliseconds >= 1000L)
{
double dist = Distance.convertRadiansToDistance(totalRadians);
// Store the value and maintain max and min values
speedValue = dist / milliseconds * 1000.0 * 60.0 * 60.0; // convert from per millisec to per hour
pointHasSpeed = true;
}
}
// Did we get a value?
if (pointHasSpeed)
{
inValue.setValue(speedValue);
}
// otherwise, just leave value as invalid
}
/**
* Calculate the vertical speed value of the track at the specified index
* @param inTrack track object
* @param inIndex index of point to calculate speed for
* @param inValue object in which to place the result of calculation
*/
public static void calculateVerticalSpeed(Track inTrack, int inIndex, SpeedValue inValue)
{
if (inTrack == null || inIndex < 0 || inValue == null) {
System.err.println("Cannot calculate vert speed for index " + inIndex);
return;
}
inValue.setInvalid();
DataPoint point = inTrack.getPoint(inIndex);
boolean pointHasSpeed = false;
double speedValue = 0.0;
// First, see if point has a speed value already
if (point != null && point.hasVSpeed())
{
speedValue = point.getVSpeed().getValue(Config.getUnitSet().getVerticalSpeedUnit());
pointHasSpeed = true;
}
// otherwise, see if we can calculate it from the heights and timestamps
if (!pointHasSpeed
&& point != null && point.hasTimestamp() && point.hasAltitude() && !point.isWaypoint())
{
int index = inIndex-1;
DataPoint p = null;
Timestamp earlyStamp = point.getTimestamp();
Altitude firstAlt = point.getAltitude();
boolean stop = false;
// Count backwards until timestamp earlier than now
if (!point.getSegmentStart())
{
do
{
p = inTrack.getPoint(index);
boolean timeOk = p != null && p.hasTimestamp() && p.getTimestamp().isBefore(point.getTimestamp());
boolean pValid = timeOk && !p.isWaypoint();
if (pValid) {
earlyStamp = p.getTimestamp();
if (p.hasAltitude()) firstAlt = p.getAltitude();
}
stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(p, point);
index--;
}
while (!stop);
}
// Count forwards until timestamp later than now
Timestamp lateStamp = point.getTimestamp();
Altitude lastAlt = point.getAltitude();
index = inIndex+1;
do
{
p = inTrack.getPoint(index);
boolean timeOk = p != null && p.hasTimestamp() && !p.getTimestamp().isBefore(point.getTimestamp());
boolean pValid = timeOk && !p.isWaypoint() && !p.getSegmentStart();
if (pValid) {
lateStamp = p.getTimestamp();
if (p.hasAltitude()) lastAlt = p.getAltitude();
}
stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point, p);
index++;
}
while (!stop);
// See if we've managed to get a non-zero time range
long milliseconds = lateStamp.getMillisecondsSince(earlyStamp);
if (milliseconds >= 1000L)
{
double altDiff = (lastAlt.getMetricValue() - firstAlt.getMetricValue())
* Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
speedValue = altDiff / milliseconds * 1000.0; // units are feet/sec or metres/sec
pointHasSpeed = true;
}
}
// Check whether we got a value from either method
if (pointHasSpeed)
{
inValue.setValue(speedValue);
}
}
/**
* Check whether the time difference between P1 and P2 is sufficiently large
* @param inP1 earlier point
* @param inP2 later point
* @return true if we can stop looking now, found a point early/late enough
*/
private static boolean hasSufficientTimeDifference(DataPoint inP1, DataPoint inP2)
{
if (inP1 == null || inP2 == null)
return true; // we have to give up now
if (!inP1.hasTimestamp() || !inP2.hasTimestamp())
return false; // keep looking
final long MIN_TIME_DIFFERENCE_MS = 1000L;
return inP2.getTimestamp().getMillisecondsSince(inP1.getTimestamp()) >= MIN_TIME_DIFFERENCE_MS;
}
}