mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-15 06:49:56 +00:00
Merge branch 'master' into bug4629
Change-Id: Ie185ba5fc28e7dfc3ec5d8000ff4f872459c7023
This commit is contained in:
commit
09ec0ce804
31 files changed
+1556
-784
No files matched your search
+5
-2
@@ -11,8 +11,9 @@ import com.sap.sailing.datamining.impl.data.CompleteManeuverCurveWithEstimationD
|
||||
import com.sap.sailing.datamining.shared.ManeuverSettings;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
|
||||
@@ -45,7 +46,9 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends
|
||||
List<HasCompleteManeuverCurveWithEstimationDataContext> result = new ArrayList<>();
|
||||
TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace();
|
||||
Competitor competitor = element.getCompetitor();
|
||||
ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
|
||||
new ManeuverDetectorImpl(trackedRace, competitor),
|
||||
element.getTrackedRaceContext().getLeaderboardContext().getPolarDataService());
|
||||
Iterable<Maneuver> maneuvers = trackedRace.getManeuvers(competitor, false);
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers);
|
||||
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetector
|
||||
|
||||
+19
-12
@@ -259,22 +259,29 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
|
||||
}
|
||||
|
||||
private int getNumberOf(ManeuverType maneuverType) {
|
||||
TrackedRace trackedRace = getTrackedRace();
|
||||
int number = 0;
|
||||
if (trackedRace != null && trackedRace.getStartOfRace() != null) {
|
||||
final TimePoint end;
|
||||
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
|
||||
if (trackedRace.getEndOfRace() != null) {
|
||||
end = trackedRace.getEndOfRace();
|
||||
TrackedRace trackedRace = getTrackedRace();
|
||||
if (trackedRace != null) {
|
||||
Course course = trackedRace.getRace().getCourse();
|
||||
Waypoint startWaypoint = course.getFirstWaypoint();
|
||||
MarkPassing startPassing = trackedRace.getMarkPassing(getCompetitor(), startWaypoint);
|
||||
TimePoint start = startPassing != null ? startPassing.getTimePoint() : trackedRace.getStartOfRace();
|
||||
|
||||
Waypoint finishWaypoint = course.getLastWaypoint();
|
||||
MarkPassing finishPassing = trackedRace.getMarkPassing(getCompetitor(), finishWaypoint);
|
||||
TimePoint end;
|
||||
if (finishPassing != null) {
|
||||
end = finishPassing.getTimePoint();
|
||||
} else {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
if (endOfTracking != null && endOfTracking.before(now)) {
|
||||
end = endOfTracking;
|
||||
} else {
|
||||
end = now;
|
||||
end = trackedRace.getEndOfRace();
|
||||
if (end == null) {
|
||||
TimePoint endOfTracking = trackedRace.getEndOfTracking();
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
end = endOfTracking != null && endOfTracking.before(now) ? endOfTracking : now;
|
||||
}
|
||||
}
|
||||
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), end, false)) {
|
||||
|
||||
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), start, end, false)) {
|
||||
if (maneuver.getType() == maneuverType) {
|
||||
number++;
|
||||
}
|
||||
|
||||
+7
@@ -5,6 +5,7 @@ import com.sap.sailing.domain.common.Positioned;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Timed;
|
||||
import com.sap.sse.datamining.annotations.Connector;
|
||||
@@ -96,6 +97,12 @@ public interface CompleteManeuverCurveWithEstimationData extends Timed, Position
|
||||
* boundaries, the boundaries of the curve with unstable course and speed are used.
|
||||
*/
|
||||
Bearing getRelativeBearingToNextMarkAfterManeuver();
|
||||
|
||||
Distance getDistanceToClosestMark();
|
||||
|
||||
Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees();
|
||||
|
||||
Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees();
|
||||
|
||||
/**
|
||||
* Gets whether a mark was crossed within the maneuver curve.
|
||||
|
||||
-46
@@ -36,50 +36,4 @@ public interface ManeuverDetector {
|
||||
*/
|
||||
List<Maneuver> detectManeuvers();
|
||||
|
||||
/**
|
||||
* Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already
|
||||
* include the calculated boundaries of each complete maneuvering spot, this method operates in a very
|
||||
* performance-efficient manner.
|
||||
*
|
||||
* @param maneuverCurves
|
||||
* The maneuver curves from which the maneuvers shall be derived
|
||||
* @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver
|
||||
* curves list is also empty.
|
||||
*/
|
||||
List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves);
|
||||
|
||||
/**
|
||||
* Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this
|
||||
* {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the
|
||||
* complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and
|
||||
* "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy.
|
||||
*
|
||||
* @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves.
|
||||
* @see CompleteManeuverCurve
|
||||
* @see ManeuverDetector
|
||||
*/
|
||||
List<CompleteManeuverCurve> detectCompleteManeuverCurves();
|
||||
|
||||
/**
|
||||
* Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs
|
||||
* significantly faster than {@link #detectCompleteManeuverCurves()}.
|
||||
*
|
||||
* @param maneuvers
|
||||
* The maneuvers to parse into complete maneuver curves
|
||||
* @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves
|
||||
* derived from provided maneuvers.
|
||||
* @see CompleteManeuverCurve
|
||||
* @see Maneuver
|
||||
*/
|
||||
List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers);
|
||||
|
||||
/**
|
||||
* Converts provided {@link CompleteManeuverCurve}-instances into
|
||||
* {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to
|
||||
* {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within
|
||||
* {@link #detectManeuvers()}.
|
||||
*/
|
||||
List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves);
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.sap.sailing.domain.maneuverdetection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
|
||||
/**
|
||||
* A maneuver detector which additional support for management of estimation data for wind estimation.
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
* @see ManeuverDetector
|
||||
*
|
||||
*/
|
||||
public interface ManeuverDetectorWithEstimationDataSupport extends ManeuverDetector {
|
||||
/**
|
||||
* Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already
|
||||
* include the calculated boundaries of each complete maneuvering spot, this method operates in a very
|
||||
* performance-efficient manner.
|
||||
*
|
||||
* @param maneuverCurves
|
||||
* The maneuver curves from which the maneuvers shall be derived
|
||||
* @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver
|
||||
* curves list is also empty.
|
||||
*/
|
||||
List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves);
|
||||
|
||||
/**
|
||||
* Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this
|
||||
* {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the
|
||||
* complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and
|
||||
* "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy.
|
||||
*
|
||||
* @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves.
|
||||
* @see CompleteManeuverCurve
|
||||
* @see ManeuverDetector
|
||||
*/
|
||||
List<CompleteManeuverCurve> detectCompleteManeuverCurves();
|
||||
|
||||
/**
|
||||
* Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs
|
||||
* significantly faster than {@link #detectCompleteManeuverCurves()}.
|
||||
*
|
||||
* @param maneuvers
|
||||
* The maneuvers to parse into complete maneuver curves
|
||||
* @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves
|
||||
* derived from provided maneuvers.
|
||||
* @see CompleteManeuverCurve
|
||||
* @see Maneuver
|
||||
*/
|
||||
List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers);
|
||||
|
||||
/**
|
||||
* Converts provided {@link CompleteManeuverCurve}-instances into
|
||||
* {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to
|
||||
* {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within
|
||||
* {@link #detectManeuvers()}.
|
||||
*/
|
||||
List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves);
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package com.sap.sailing.domain.maneuverdetection.impl;
|
||||
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.BearingChangeAnalyzer;
|
||||
import com.sap.sailing.domain.common.NauticalSide;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public abstract class AbstractManeuverDetectorImpl implements ManeuverDetector {
|
||||
|
||||
/**
|
||||
* Tracked race whose tracks are being processed for maneuver detection.
|
||||
*/
|
||||
protected final TrackedRace trackedRace;
|
||||
|
||||
/**
|
||||
* The competitor, whose maneuvers are being discovered
|
||||
*/
|
||||
protected final Competitor competitor;
|
||||
|
||||
/**
|
||||
* The track of competitor
|
||||
*/
|
||||
protected final GPSFixTrack<Competitor, GPSFixMoving> track;
|
||||
|
||||
/**
|
||||
* Constructs maneuver detector which is supposed to be used for maneuver detection within the provided tracked race
|
||||
* for provided competitor.
|
||||
*
|
||||
* @param trackedRace
|
||||
* The tracked race whose maneuvers are supposed to be detected
|
||||
* @param competitor
|
||||
* The competitor, whose maneuvers shall be discovered
|
||||
*/
|
||||
public AbstractManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
|
||||
this.trackedRace = trackedRace;
|
||||
this.competitor = competitor;
|
||||
this.track = trackedRace != null ? trackedRace.getTrack(competitor) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets track's start time point, end time point and the time point of last raw fix.
|
||||
*
|
||||
* @return {@code null} when there are no appropriate fixes contained within the analyzed track
|
||||
*/
|
||||
public TrackTimeInfo getTrackTimeInfo() {
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
TimePoint earliestTrackRecord = null;
|
||||
TimePoint latestRawFixTimePoint = null;
|
||||
MarkPassing crossedFinishLine = null;
|
||||
// getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid
|
||||
// deadlocks
|
||||
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
|
||||
if (lastWaypoint != null) {
|
||||
trackedRace.lockForRead(markPassings);
|
||||
try {
|
||||
if (markPassings != null && !markPassings.isEmpty()) {
|
||||
earliestTrackRecord = markPassings.iterator().next().getTimePoint();
|
||||
crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint);
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(markPassings);
|
||||
}
|
||||
}
|
||||
if (earliestTrackRecord == null) {
|
||||
GPSFixMoving firstRawFix = track.getFirstRawFix();
|
||||
if (firstRawFix != null) {
|
||||
earliestTrackRecord = firstRawFix.getTimePoint();
|
||||
}
|
||||
}
|
||||
if (earliestTrackRecord != null) {
|
||||
TimePoint latestTrackRecord;
|
||||
if (crossedFinishLine != null) {
|
||||
latestTrackRecord = crossedFinishLine.getTimePoint();
|
||||
} else {
|
||||
final GPSFixMoving lastRawFix = track.getLastRawFix();
|
||||
if (lastRawFix != null) {
|
||||
latestTrackRecord = lastRawFix.getTimePoint();
|
||||
latestRawFixTimePoint = latestTrackRecord;
|
||||
} else {
|
||||
latestTrackRecord = null;
|
||||
}
|
||||
}
|
||||
if (latestTrackRecord != null) {
|
||||
if (latestRawFixTimePoint == null) {
|
||||
final GPSFixMoving lastRawFix = track.getLastRawFix();
|
||||
if (lastRawFix != null) {
|
||||
latestRawFixTimePoint = lastRawFix.getTimePoint();
|
||||
}
|
||||
}
|
||||
if (latestRawFixTimePoint != null) {
|
||||
if (!earliestTrackRecord.equals(latestTrackRecord)) {
|
||||
return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint);
|
||||
}
|
||||
GPSFixMoving firstRawFix = track.getFirstRawFix();
|
||||
if (firstRawFix != null) {
|
||||
return new TrackTimeInfo(firstRawFix.getTimePoint(), latestRawFixTimePoint,
|
||||
latestRawFixTimePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cases, when the boats bow was headed through the wind coming from behind.
|
||||
*/
|
||||
protected int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
int numberOfJibes = wind == null ? 0
|
||||
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
|
||||
maneuverBoundaries.getDirectionChangeInDegrees(),
|
||||
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing());
|
||||
return numberOfJibes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cases, when the boats bow was headed through the wind coming from the front.
|
||||
*/
|
||||
protected int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
int numberOfTacks = wind == null ? 0
|
||||
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
|
||||
maneuverBoundaries.getDirectionChangeInDegrees(),
|
||||
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom());
|
||||
return numberOfTacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}.
|
||||
*/
|
||||
protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) {
|
||||
return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the approximated duration of the maneuver main curve considering the boat class of the competitor.
|
||||
*/
|
||||
protected Duration getApproximateManeuverDuration() {
|
||||
return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration();
|
||||
}
|
||||
|
||||
}
|
||||
+24
-2
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimat
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -24,13 +25,16 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
|
||||
private final Bearing relativeBearingToNextMarkBeforeManeuver;
|
||||
private final Bearing relativeBearingToNextMarkAfterManeuver;
|
||||
private final boolean markPassing;
|
||||
private Position position;
|
||||
private final Position position;
|
||||
private final Distance distanceToClosestMark;
|
||||
private final Double deviationOfManeuverAngleFromTargetTackAngleInDegrees;
|
||||
private final Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
|
||||
|
||||
public CompleteManeuverCurveWithEstimationDataImpl(Position position, ManeuverMainCurveWithEstimationData mainCurve,
|
||||
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed, Wind wind,
|
||||
int tackingCount, int jibingCount, boolean maneuverStartsByRunningAwayFromWind,
|
||||
Bearing relativeBearingToNextMarkBeforeManeuver, Bearing relativeBearingToNextMarkAfterManeuver,
|
||||
boolean markPassing) {
|
||||
boolean markPassing, Distance distanceToClosestMark, Double deviationOfManeuverAngleFromTargetTackAngleInDegrees, Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees) {
|
||||
this.position = position;
|
||||
this.mainCurve = mainCurve;
|
||||
this.curveWithUnstableCourseAndSpeed = curveWithUnstableCourseAndSpeed;
|
||||
@@ -41,6 +45,9 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
|
||||
this.relativeBearingToNextMarkBeforeManeuver = relativeBearingToNextMarkBeforeManeuver;
|
||||
this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver;
|
||||
this.markPassing = markPassing;
|
||||
this.distanceToClosestMark = distanceToClosestMark;
|
||||
this.deviationOfManeuverAngleFromTargetTackAngleInDegrees = deviationOfManeuverAngleFromTargetTackAngleInDegrees;
|
||||
this.deviationOfManeuverAngleFromTargetJibeAngleInDegrees = deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,5 +99,20 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getDistanceToClosestMark() {
|
||||
return distanceToClosestMark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees() {
|
||||
return deviationOfManeuverAngleFromTargetTackAngleInDegrees;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees() {
|
||||
return deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
|
||||
}
|
||||
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.sap.sailing.domain.maneuverdetection.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.CourseChange;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.ManeuverWithCoarseGrainedBoundariesImpl;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
/**
|
||||
* Maneuver detector implementation for GPS tracks with extremely low sampling rate such as 1 fix per 30 seconds.
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
*
|
||||
*/
|
||||
public class LowGPSSamplingRateManeuverDetectorImpl extends AbstractManeuverDetectorImpl implements ManeuverDetector {
|
||||
|
||||
public LowGPSSamplingRateManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
|
||||
super(trackedRace, competitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Maneuver> detectManeuvers() {
|
||||
List<Maneuver> result = new ArrayList<>();
|
||||
TrackTimeInfo startAndEndTimePoints = getTrackTimeInfo();
|
||||
if (startAndEndTimePoints != null) {
|
||||
ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace,
|
||||
competitor);
|
||||
Iterable<GPSFixMoving> approximatedFixes = approximatedFixesCalculator.approximate(
|
||||
startAndEndTimePoints.getTrackStartTimePoint(), startAndEndTimePoints.getTrackEndTimePoint());
|
||||
if (Util.size(approximatedFixes) > 2) {
|
||||
Iterator<GPSFixMoving> approximationPointsIter = approximatedFixes.iterator();
|
||||
GPSFixMoving previous = approximationPointsIter.next();
|
||||
GPSFixMoving current = approximationPointsIter.next();
|
||||
// the bearings in these variables are between approximation points
|
||||
do {
|
||||
GPSFixMoving next = approximationPointsIter.next();
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous
|
||||
.getSpeedAndBearingRequiredToReach(current);
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext = current
|
||||
.getSpeedAndBearingRequiredToReach(next);
|
||||
CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent
|
||||
.getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext);
|
||||
speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext;
|
||||
Maneuver maneuver = createManeuverFromGroupOfCourseChanges(competitor,
|
||||
speedWithBearingOnApproximationFromPreviousToCurrent, current,
|
||||
speedWithBearingOnApproximationFromCurrentToNext, courseChange.getCourseChangeInDegrees());
|
||||
result.add(maneuver);
|
||||
previous = current;
|
||||
current = next;
|
||||
} while (approximationPointsIter.hasNext());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Maneuver createManeuverFromGroupOfCourseChanges(Competitor competitor,
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, GPSFixMoving currentFix,
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees) {
|
||||
TimePoint maneuverTimePoint = currentFix.getTimePoint();
|
||||
Position maneuverPosition = currentFix.getPosition();
|
||||
final Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
|
||||
Tack tackAfterManeuver = null;
|
||||
try {
|
||||
tackAfterManeuver = wind == null ? null
|
||||
: trackedRace.getTack(maneuverPosition, maneuverTimePoint,
|
||||
speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
} catch (NoWindException e) {
|
||||
}
|
||||
ManeuverType maneuverType;
|
||||
ManeuverCurveBoundaries maneuverCurve = new ManeuverCurveBoundariesImpl(
|
||||
maneuverTimePoint.minus(getApproximateManeuverDuration().divide(2)),
|
||||
maneuverTimePoint.plus(getApproximateManeuverDuration().times(3.0)),
|
||||
speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd,
|
||||
totalCourseChangeInDegrees,
|
||||
speedWithBearingOnApproximationAtBeginning.compareTo(speedWithBearingOnApproximationAtBeginning) < 0
|
||||
? speedWithBearingOnApproximationAtBeginning : speedWithBearingOnApproximationAtEnd);
|
||||
|
||||
if (wind != null) {
|
||||
if (getNumberOfTacks(maneuverCurve, wind) > 0) {
|
||||
maneuverType = ManeuverType.TACK;
|
||||
} else if (getNumberOfJibes(maneuverCurve, wind) > 0) {
|
||||
maneuverType = ManeuverType.JIBE;
|
||||
} else {
|
||||
// heading up or bearing away
|
||||
Bearing windBearing = wind.getBearing();
|
||||
Bearing toWindBeforeManeuver = windBearing
|
||||
.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Bearing toWindAfterManeuver = windBearing
|
||||
.getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees())
|
||||
? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
|
||||
}
|
||||
} else {
|
||||
// no wind information; marking as UNKNOWN
|
||||
maneuverType = ManeuverType.UNKNOWN;
|
||||
}
|
||||
Maneuver maneuver = new ManeuverWithCoarseGrainedBoundariesImpl(maneuverType, tackAfterManeuver,
|
||||
maneuverPosition, maneuverTimePoint, maneuverCurve);
|
||||
return maneuver;
|
||||
}
|
||||
|
||||
}
|
||||
+77
-522
@@ -5,10 +5,8 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
@@ -21,12 +19,9 @@ import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
@@ -56,7 +51,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
* @see ManeuverDetector
|
||||
*
|
||||
*/
|
||||
public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ManeuverDetectorImpl.class.getName());
|
||||
|
||||
@@ -64,7 +59,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* Defines the maximal absolute course change velocity in degrees per second that shall be regarded as a stable
|
||||
* course.
|
||||
*/
|
||||
private static final double MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS = 2;
|
||||
private static final double MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS = 1;
|
||||
|
||||
/**
|
||||
* Defines the absolute course change in degrees between bearing steps to ignore in order to shorten the
|
||||
@@ -72,35 +67,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
*/
|
||||
private static final double MIN_ANGULAR_VELOCITY_FOR_MAIN_CURVE_BOUNDARIES_IN_DEGREES_PER_SECOND = 0.2;
|
||||
|
||||
/**
|
||||
* Defines the course change limit toward opposite direction related to the direction of maneuver main curve. If
|
||||
* speed maxima or stable bearing analysis produce a curve extension which exceeds this limit, the extension gets
|
||||
* rejected.
|
||||
*/
|
||||
private static final double MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES = 15.0;
|
||||
|
||||
/**
|
||||
* Tracked race whose tracks are being processed for maneuver detection.
|
||||
*/
|
||||
protected final TrackedRace trackedRace;
|
||||
|
||||
/**
|
||||
* The competitor, whose maneuvers are being discovered
|
||||
*/
|
||||
protected final Competitor competitor;
|
||||
|
||||
/**
|
||||
* The track of competitor
|
||||
*/
|
||||
protected final GPSFixTrack<Competitor, GPSFixMoving> track;
|
||||
|
||||
/**
|
||||
* Constructor for unit tests only.
|
||||
*/
|
||||
public ManeuverDetectorImpl() {
|
||||
trackedRace = null;
|
||||
competitor = null;
|
||||
track = null;
|
||||
super(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,9 +84,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* The competitor, whose maneuvers shall be discovered
|
||||
*/
|
||||
public ManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
|
||||
this.trackedRace = trackedRace;
|
||||
this.competitor = competitor;
|
||||
this.track = trackedRace.getTrack(competitor);
|
||||
super(trackedRace, competitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,355 +92,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
return getAllManeuversFromManeuverSpots(detectManeuverSpots());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves) {
|
||||
List<Maneuver> maneuvers = new ArrayList<>();
|
||||
for (CompleteManeuverCurve maneuverCurve : maneuverCurves) {
|
||||
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
|
||||
Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
|
||||
Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
|
||||
maneuvers.addAll(determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind,
|
||||
maneuverCurve.getMarkPassing()));
|
||||
}
|
||||
return maneuvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurve> detectCompleteManeuverCurves() {
|
||||
List<ManeuverSpot> maneuverSpots = detectManeuverSpots();
|
||||
return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null)
|
||||
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers) {
|
||||
List<CompleteManeuverCurve> result = new ArrayList<>();
|
||||
CompleteManeuverCurve curveToAdd = null;
|
||||
boolean previousManeuverCouldBelongToSameCurve = false;
|
||||
Maneuver previousManeuver = null;
|
||||
for (Maneuver maneuver : maneuvers) {
|
||||
boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE
|
||||
|| maneuver.isMarkPassing()
|
||||
&& (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE);
|
||||
if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve
|
||||
&& previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
|
||||
.equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())
|
||||
&& previousManeuver.getToSide() == maneuver.getToSide()) {
|
||||
curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver);
|
||||
} else {
|
||||
if (curveToAdd != null) {
|
||||
result.add(curveToAdd);
|
||||
}
|
||||
curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver);
|
||||
}
|
||||
previousManeuver = maneuver;
|
||||
previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve;
|
||||
}
|
||||
if (curveToAdd != null) {
|
||||
result.add(curveToAdd);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused
|
||||
* for the resulting complete maneuver curve.
|
||||
*
|
||||
* @see CompleteManeuverCurve
|
||||
* @see Maneuver
|
||||
*/
|
||||
private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) {
|
||||
ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps(
|
||||
maneuver.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(),
|
||||
maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(),
|
||||
getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter()));
|
||||
return new CompleteManeuverCurveImpl(mainCurveBoundaries,
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with
|
||||
* unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely
|
||||
* from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly.
|
||||
*/
|
||||
private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve,
|
||||
Maneuver maneuver) {
|
||||
ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = computeManeuverMainCurveDetails(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide());
|
||||
if (mainCurveDetails == null) {
|
||||
return maneuverCurve;
|
||||
}
|
||||
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees()
|
||||
+ maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
|
||||
.compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0
|
||||
? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed());
|
||||
return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries,
|
||||
maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves) {
|
||||
List<CompleteManeuverCurveWithEstimationData> result = new ArrayList<>();
|
||||
|
||||
CompleteManeuverCurve previousManeuverCurve = null;
|
||||
CompleteManeuverCurve currentManeuverCurve = null;
|
||||
for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) {
|
||||
if (currentManeuverCurve != null) {
|
||||
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
|
||||
currentManeuverCurve, previousManeuverCurve, nextManeuverCurve);
|
||||
result.add(maneuverCurveWithEstimationData);
|
||||
}
|
||||
previousManeuverCurve = currentManeuverCurve;
|
||||
currentManeuverCurve = nextManeuverCurve;
|
||||
}
|
||||
if (currentManeuverCurve != null) {
|
||||
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
|
||||
currentManeuverCurve, previousManeuverCurve, null);
|
||||
result.add(maneuverCurveWithEstimationData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The
|
||||
* computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as
|
||||
* computationally-intensive.
|
||||
*/
|
||||
private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData(
|
||||
CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve,
|
||||
CompleteManeuverCurve nextManeuverCurve) {
|
||||
Bearing courseAtMaxTurningRate = null;
|
||||
SpeedWithBearingStep stepWithLowestSpeed = null;
|
||||
SpeedWithBearingStep stepWithHighestSpeed = null;
|
||||
SpeedWithBearingStep stepWithMaxTurningRate = null;
|
||||
SpeedWithBearingStep previousStep = null;
|
||||
for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) {
|
||||
if (stepWithLowestSpeed == null
|
||||
|| stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) {
|
||||
stepWithLowestSpeed = step;
|
||||
}
|
||||
if (stepWithHighestSpeed == null
|
||||
|| stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) {
|
||||
stepWithHighestSpeed = step;
|
||||
}
|
||||
if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate
|
||||
.getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) {
|
||||
stepWithMaxTurningRate = step;
|
||||
courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing()
|
||||
.add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2));
|
||||
}
|
||||
previousStep = step;
|
||||
}
|
||||
int gpsFixCountWithinMainCurve = 0;
|
||||
int gpsFixCountWithinWholeCurve = 0;
|
||||
int gpsFixesCountFromPreviousManeuver = 0;
|
||||
int gpsFixesCountToNextManeuver = 0;
|
||||
try {
|
||||
track.lockForRead();
|
||||
boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
|
||||
.before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()
|
||||
.after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
for (GPSFixMoving fix : track.getFixes(
|
||||
considerPreviousManeuver
|
||||
? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointAfter()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
!considerPreviousManeuver,
|
||||
considerNextManeuver
|
||||
? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointBefore()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
!considerNextManeuver)) {
|
||||
if (fix.getTimePoint().before(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) {
|
||||
++gpsFixesCountFromPreviousManeuver;
|
||||
} else if (fix.getTimePoint().after(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) {
|
||||
++gpsFixesCountToNextManeuver;
|
||||
} else {
|
||||
if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore())
|
||||
&& !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) {
|
||||
++gpsFixCountWithinMainCurve;
|
||||
}
|
||||
++gpsFixCountWithinWholeCurve;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
|
||||
ManeuverLoss projectedManeuverLoss = getManeuverLoss(maneuverCurve.getMainCurveBoundaries());
|
||||
Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore()
|
||||
.travel(maneuverCurve.getMainCurveBoundaries().getDuration());
|
||||
Distance distanceSailedWithinManeuver = track.getDistanceTraveled(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
|
||||
Duration longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
|
||||
ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(),
|
||||
stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(),
|
||||
stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePoint(),
|
||||
maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate,
|
||||
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
|
||||
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(),
|
||||
Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees())
|
||||
/ maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(),
|
||||
gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes);
|
||||
projectedManeuverLoss = getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries());
|
||||
distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getSpeedWithBearingBefore()
|
||||
.travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration());
|
||||
distanceSailedWithinManeuver = track.getDistanceTraveled(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null ? getTrackTimeInfo()
|
||||
: null;
|
||||
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
|
||||
previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint()
|
||||
: previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint()
|
||||
: nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL;
|
||||
GPSFixMoving lastManeuverFix = track.getLastFixAtOrBefore(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
if (lastManeuverFix != null) {
|
||||
GPSFixMoving firstFixAfterLastManeuverFix = track.getFirstFixAfter(lastManeuverFix.getTimePoint());
|
||||
if (firstFixAfterLastManeuverFix != null) {
|
||||
intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint()
|
||||
.until(firstFixAfterLastManeuverFix.getTimePoint());
|
||||
}
|
||||
}
|
||||
Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL;
|
||||
GPSFixMoving firstManeuverFix = track.getFirstFixAtOrAfter(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
if (firstManeuverFix != null) {
|
||||
GPSFixMoving lastFixBeforeFirstManeuverFix = track.getLastFixBefore(firstManeuverFix.getTimePoint());
|
||||
if (lastFixBeforeFirstManeuverFix != null) {
|
||||
intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint()
|
||||
.until(firstManeuverFix.getTimePoint());
|
||||
}
|
||||
}
|
||||
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(),
|
||||
durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(),
|
||||
gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(),
|
||||
durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver,
|
||||
projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
|
||||
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve,
|
||||
longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
|
||||
intervalBetweenFirstFixOfCurveAndPreviousFix);
|
||||
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
|
||||
Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
|
||||
Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
|
||||
int numberOfJibes = getNumberOfJibes(mainCurve, wind);
|
||||
int numberOfTacks = getNumberOfTacks(mainCurve, wind);
|
||||
boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees()
|
||||
- 180) * mainCurve.getDirectionChangeInDegrees() < 0;
|
||||
Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing());
|
||||
Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing());
|
||||
return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve,
|
||||
curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes,
|
||||
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver,
|
||||
relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the duration and avg speed with avg course based on the competitor's track within the provided time
|
||||
* range.
|
||||
*/
|
||||
private Pair<Duration, SpeedWithBearing> calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from,
|
||||
TimePoint to) {
|
||||
Duration duration = from.until(to);
|
||||
Position fromPosition = track.getEstimatedPosition(from, false);
|
||||
Position toPosition = track.getEstimatedPosition(to, false);
|
||||
Distance distance = fromPosition.getDistance(toPosition);
|
||||
Bearing bearing = fromPosition.getBearingGreatCircle(toPosition);
|
||||
Speed speed = distance.inTime(Math.abs(duration.asMillis()));
|
||||
SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
|
||||
return new Pair<>(duration, avgSpeedWithBearing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative
|
||||
* bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course.
|
||||
*/
|
||||
private Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) {
|
||||
Bearing result = null;
|
||||
TrackedLegOfCompetitor legAfter = trackedRace.getTrackedLeg(competitor, timePoint);
|
||||
if (legAfter != null && legAfter.getLeg().getTo() != null) {
|
||||
Position nextMarkPosition = trackedRace.getApproximatePosition(legAfter.getLeg().getTo(), timePoint);
|
||||
Position maneuverEndPosition = track.getEstimatedPosition(timePoint, false);
|
||||
Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition);
|
||||
result = absoluteBearing.getDifferenceTo(boatCourse);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cases, when the boats bow was headed through the wind coming from behind.
|
||||
*/
|
||||
private int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
int numberOfJibes = wind == null ? 0
|
||||
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
|
||||
maneuverBoundaries.getDirectionChangeInDegrees(),
|
||||
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing());
|
||||
return numberOfJibes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cases, when the boats bow was headed through the wind coming from the front.
|
||||
*/
|
||||
private int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
int numberOfTacks = wind == null ? 0
|
||||
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
|
||||
maneuverBoundaries.getDirectionChangeInDegrees(),
|
||||
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom());
|
||||
return numberOfTacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects maneuver spots performed within a GPS-track of the competitor associated with this
|
||||
* {@link ManeuverDetector}-instance. See {@link ManeuverDetector} description for more info regarding the detection
|
||||
@@ -490,64 +110,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets track's start time point, end time point and the time point of last raw fix.
|
||||
*
|
||||
* @return {@code null} when there are no appropriate fixes contained within the analyzed track
|
||||
*/
|
||||
public TrackTimeInfo getTrackTimeInfo() {
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
TimePoint earliestTrackRecord = null;
|
||||
TimePoint latestRawFixTimePoint = null;
|
||||
MarkPassing crossedFinishLine = null;
|
||||
// getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid
|
||||
// deadlocks
|
||||
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
|
||||
if (lastWaypoint != null) {
|
||||
trackedRace.lockForRead(markPassings);
|
||||
try {
|
||||
if (markPassings != null && !markPassings.isEmpty()) {
|
||||
earliestTrackRecord = markPassings.iterator().next().getTimePoint();
|
||||
crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint);
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(markPassings);
|
||||
}
|
||||
}
|
||||
if (earliestTrackRecord == null) {
|
||||
GPSFixMoving firstRawFix = track.getFirstRawFix();
|
||||
if (firstRawFix != null) {
|
||||
earliestTrackRecord = firstRawFix.getTimePoint();
|
||||
}
|
||||
}
|
||||
if (earliestTrackRecord != null) {
|
||||
TimePoint latestTrackRecord;
|
||||
if (crossedFinishLine != null) {
|
||||
latestTrackRecord = crossedFinishLine.getTimePoint();
|
||||
} else {
|
||||
final GPSFixMoving lastRawFix = track.getLastRawFix();
|
||||
if (lastRawFix != null) {
|
||||
latestTrackRecord = lastRawFix.getTimePoint();
|
||||
latestRawFixTimePoint = latestTrackRecord;
|
||||
} else {
|
||||
latestTrackRecord = null;
|
||||
}
|
||||
}
|
||||
if (latestTrackRecord != null) {
|
||||
if (latestRawFixTimePoint == null) {
|
||||
final GPSFixMoving lastRawFix = track.getLastRawFix();
|
||||
if (lastRawFix != null) {
|
||||
latestRawFixTimePoint = lastRawFix.getTimePoint();
|
||||
}
|
||||
}
|
||||
if (latestRawFixTimePoint != null) {
|
||||
return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the maneuver spots with corresponding maneuvers within provided time frame. See step 1ff. in
|
||||
* {@link ManeuverDetector} description.
|
||||
@@ -566,13 +128,12 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* @return an empty list if no maneuver spots are detected for <code>competitor</code> between <code>from</code> and
|
||||
* <code>to</code>, or else the list of maneuver spots with corresponding maneuvers detected.
|
||||
*/
|
||||
protected List<ManeuverSpot> detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) {
|
||||
return detectManeuvers(
|
||||
trackedRace.approximate(competitor,
|
||||
trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()
|
||||
.getMaximumDistanceForCourseApproximation(),
|
||||
earliestManeuverStart, latestManeuverEnd),
|
||||
earliestManeuverStart, latestManeuverEnd);
|
||||
public List<ManeuverSpot> detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) {
|
||||
ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace,
|
||||
competitor);
|
||||
Iterable<GPSFixMoving> approximatedFixes = approximatedFixesCalculator.approximate(earliestManeuverStart,
|
||||
latestManeuverEnd);
|
||||
return detectManeuvers(approximatedFixes, earliestManeuverStart, latestManeuverEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -622,13 +183,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}.
|
||||
*/
|
||||
protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) {
|
||||
return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether {@code currentFix} can be grouped together with the previous fixes in order to be regarded as a
|
||||
* single maneuver spot. For this, the {@code newCourseChangeDirection must match the direction of provided
|
||||
@@ -661,6 +215,16 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected List<Maneuver> getAllManeuversFromManeuverSpots(List<ManeuverSpot> maneuverSpots) {
|
||||
List<Maneuver> maneuvers = new ArrayList<>();
|
||||
for (ManeuverSpot maneuverSpot : maneuverSpots) {
|
||||
for (Maneuver maneuver : maneuverSpot.getManeuvers()) {
|
||||
maneuvers.add(maneuver);
|
||||
}
|
||||
}
|
||||
return maneuvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines course change direction around the provided {@code fix} by means of
|
||||
* {@link #getSpeedWithBearingSteps(TimePoint, TimePoint)}. The course change analysis considers fixes within
|
||||
@@ -895,14 +459,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
} else {
|
||||
Pair<ManeuverMainCurveDetailsWithBearingSteps, ManeuverMainCurveDetailsWithBearingSteps> mainCurves = splitManeuverMainCurveByTimePoint(
|
||||
maneuverMainCurveDetails, firstPenaltyCircleCompletedAt);
|
||||
if (mainCurves.getA() == null || mainCurves.getB() == null) {
|
||||
// This should really not happen!
|
||||
logger.warning(
|
||||
"Maneuver detection has failed to process penalty circle maneuver correctly, because refinedPenaltyMainCurveDetails computation returned null. Race-Id: "
|
||||
+ trackedRace.getRace().getId() + ", Competitor: " + competitor.getName()
|
||||
+ ", Time point before maneuver: "
|
||||
+ maneuverUnstableCourseAndSpeedBoundaries.getTimePointBefore());
|
||||
} else {
|
||||
if (mainCurves.getA() != null && mainCurves.getB() != null) {
|
||||
maneuversAlreadyAdded = true;
|
||||
Pair<ManeuverCurveBoundaries, ManeuverCurveBoundaries> maneuverUnstableCourseAndSpeedBoundariesPair = splitManeuverCurveWithStableSpeedAndCourseByTimePoint(
|
||||
maneuverUnstableCourseAndSpeedBoundaries, mainCurves.getA(), mainCurves.getB(),
|
||||
@@ -990,19 +547,50 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
SpeedWithBearingStepsIterable speedWithBearingSteps, TimePoint timePoint) {
|
||||
List<SpeedWithBearingStep> stepsBefore = new ArrayList<>();
|
||||
List<SpeedWithBearingStep> stepsAfter = new ArrayList<>();
|
||||
SpeedWithBearingStep lastEntry = null;
|
||||
for (SpeedWithBearingStep entry : speedWithBearingSteps) {
|
||||
if (!entry.getTimePoint().after(timePoint)) {
|
||||
if (stepsBefore.isEmpty()) {
|
||||
// First bearing step supposed to have 0 as course change as
|
||||
// it does not have any previous steps with bearings to compute bearing difference.
|
||||
// If the condition is not met, the existing code which uses ManeuverBearingStep class will break.
|
||||
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0);
|
||||
}
|
||||
stepsBefore.add(entry);
|
||||
}
|
||||
if (!entry.getTimePoint().before(timePoint)) {
|
||||
if (stepsAfter.isEmpty()) {
|
||||
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0);
|
||||
// First step supposed to have 0 as course change as it does not have any previous steps to compute
|
||||
// bearing difference. If the condition is not met, the existing code which uses
|
||||
// SpeedWithBearingStepsIterable class will break.
|
||||
if (lastEntry != null && lastEntry.getTimePoint().before(timePoint)) {
|
||||
// If there is not any step located at the splitting time point, we need to retrieve the
|
||||
// interpolated speed with bearing at time point in order to produce boundary step for both step
|
||||
// sets at time point. If we will not do it, the course change between the steps split by
|
||||
// splitting time point will be lost.
|
||||
SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(timePoint);
|
||||
if (speedWithBearing != null) {
|
||||
double courseChangeAngleInDegrees = lastEntry.getSpeedWithBearing().getBearing()
|
||||
.getDifferenceTo(speedWithBearing.getBearing(),
|
||||
new DegreeBearingImpl(lastEntry.getCourseChangeInDegrees()))
|
||||
.getDegrees();
|
||||
double turningRateInDegreesPerSecond = Math.abs(
|
||||
courseChangeAngleInDegrees / lastEntry.getTimePoint().until(timePoint).asSeconds());
|
||||
SpeedWithBearingStep lastStepBefore = new SpeedWithBearingStepImpl(timePoint,
|
||||
speedWithBearing, courseChangeAngleInDegrees, turningRateInDegreesPerSecond);
|
||||
stepsBefore.add(lastStepBefore);
|
||||
SpeedWithBearingStep firstStepAfter = new SpeedWithBearingStepImpl(timePoint,
|
||||
speedWithBearing, 0.0, 0.0);
|
||||
stepsAfter.add(firstStepAfter);
|
||||
courseChangeAngleInDegrees = firstStepAfter.getSpeedWithBearing().getBearing()
|
||||
.getDifferenceTo(speedWithBearing.getBearing(),
|
||||
new DegreeBearingImpl(firstStepAfter.getCourseChangeInDegrees()))
|
||||
.getDegrees();
|
||||
turningRateInDegreesPerSecond = Math.abs(
|
||||
courseChangeAngleInDegrees / timePoint.until(entry.getTimePoint()).asSeconds());
|
||||
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(),
|
||||
courseChangeAngleInDegrees, turningRateInDegreesPerSecond);
|
||||
}
|
||||
|
||||
}
|
||||
if (stepsAfter.isEmpty()) {
|
||||
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0,
|
||||
0.0);
|
||||
}
|
||||
}
|
||||
stepsAfter.add(entry);
|
||||
}
|
||||
@@ -1125,7 +713,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* distance" is compared to the competitor's actual position at that time. This distance is returned as the result
|
||||
* of this method.
|
||||
*/
|
||||
private ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) {
|
||||
protected ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) {
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
||||
SpeedWithBearing speedWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore();
|
||||
SpeedWithBearing speedAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter();
|
||||
@@ -1162,16 +750,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
return approximateManeuverDuration.divide(2);
|
||||
}
|
||||
|
||||
protected List<Maneuver> getAllManeuversFromManeuverSpots(List<ManeuverSpot> maneuverSpots) {
|
||||
List<Maneuver> maneuvers = new ArrayList<>();
|
||||
for (ManeuverSpot maneuverSpot : maneuverSpots) {
|
||||
for (Maneuver maneuver : maneuverSpot.getManeuvers()) {
|
||||
maneuvers.add(maneuver);
|
||||
}
|
||||
}
|
||||
return maneuvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting at <code>timePointBeforeManeuver</code>, and assuming that the group of
|
||||
* <code>approximatedFixesAndCourseChanges</code> contains at least a tack and a jibe, finds the approximated fix's
|
||||
@@ -1241,8 +819,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* The target course change direction for the main curve to determine
|
||||
* @return The details of the maneuver main curve
|
||||
*/
|
||||
private ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails(TimePoint timePointBeforeManeuver,
|
||||
TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) {
|
||||
protected ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails(
|
||||
TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) {
|
||||
SpeedWithBearingStepsIterable stepsToAnalyze = getSpeedWithBearingSteps(timePointBeforeManeuver,
|
||||
timePointAfterManeuver);
|
||||
ManeuverMainCurveDetailsWithBearingSteps maneuverMainCurveDetails = computeManeuverMainCurve(stepsToAnalyze,
|
||||
@@ -1255,7 +833,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* the steps, performance costy call to {@link GPSFixTrack#getSpeedWithBearingSteps(TimePoint, TimePoint, Duration)}
|
||||
* is made.
|
||||
*/
|
||||
private SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver,
|
||||
protected SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver,
|
||||
TimePoint timePointAfterManeuver) {
|
||||
SpeedWithBearingStepsIterable stepsToAnalyze = track.getSpeedWithBearingSteps(timePointBeforeManeuver,
|
||||
timePointAfterManeuver);
|
||||
@@ -1272,10 +850,10 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* approximate the beginning time point of the maneuver, the speed maximum is determined throughout forward in time
|
||||
* iteration of speed steps starting from time point of main curve beginning. From the determined speed maximum, the
|
||||
* iteration continues until the point, when the bearing changes occur only with a maximum of
|
||||
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
|
||||
* regarded as a stable course. The exiting time point of maneuver is approximated analogously by speed maximum
|
||||
* determination throughout backward in time iteration of speed steps starting from time of main curve end, followed
|
||||
* by a search for a point with stable course.
|
||||
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
|
||||
* a stable course. The exiting time point of maneuver is approximated analogously by speed maximum determination
|
||||
* throughout backward in time iteration of speed steps starting from time of main curve end, followed by a search
|
||||
* for a point with stable course.
|
||||
*
|
||||
* @param maneuverMainCurveDetails
|
||||
* The details of the main curve, ideally computed by
|
||||
@@ -1324,8 +902,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* determined, the course changes get analyzed starting from {@code t'} until {@code (t -}
|
||||
* {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration}{@code )} in order to
|
||||
* locate the point where the bearing starts to change with a rate of maximal
|
||||
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
|
||||
* regarded as a stable course.
|
||||
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
|
||||
* a stable course.
|
||||
*
|
||||
* @param maneuverMainCurveDetails
|
||||
* The details of the main curve, ideally computed by
|
||||
@@ -1356,25 +934,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
if (isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, maneuverStart)) {
|
||||
maneuverStart = null;
|
||||
}
|
||||
TimePoint stableBearingAnalysisUntil = maneuverStart == null ? maneuverMainCurveDetails.getTimePointBefore()
|
||||
: maneuverStart.getExtensionTimePoint();
|
||||
// Stable course analysis is considered as not necessary for preparation phase of maneuver because no
|
||||
// oversteering is usually performed before maneuver
|
||||
Speed lowestSpeed = maneuverStart == null ? null : maneuverStart.getLowestSpeedWithinExtensionArea();
|
||||
double courseChangeSinceManeuverMainCurveInDegrees = maneuverStart == null ? 0
|
||||
: maneuverStart.getCourseChangeInDegreesWithinExtensionArea();
|
||||
stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, earliestTimePointForSpeedTrendAnalysis,
|
||||
stableBearingAnalysisUntil);
|
||||
ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed(
|
||||
stepsToAnalyze, true, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS);
|
||||
if (stableBearingExtension != null
|
||||
&& !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) {
|
||||
maneuverStart = stableBearingExtension;
|
||||
courseChangeSinceManeuverMainCurveInDegrees += stableBearingExtension
|
||||
.getCourseChangeInDegreesWithinExtensionArea();
|
||||
if (lowestSpeed == null
|
||||
|| lowestSpeed.compareTo(stableBearingExtension.getLowestSpeedWithinExtensionArea()) > 0) {
|
||||
lowestSpeed = stableBearingExtension.getLowestSpeedWithinExtensionArea();
|
||||
}
|
||||
}
|
||||
return maneuverStart != null
|
||||
? new ManeuverCurveBoundaryExtension(maneuverStart.getExtensionTimePoint(),
|
||||
maneuverStart.getSpeedWithBearingAtExtensionTimePoint(),
|
||||
@@ -1391,10 +955,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
if (curveBoundaryExtension == null) {
|
||||
return false;
|
||||
}
|
||||
return curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()
|
||||
* maneuverMainCurveDetails.getDirectionChangeInDegrees() < 0
|
||||
&& Math.abs(curveBoundaryExtension
|
||||
.getCourseChangeInDegreesWithinExtensionArea()) > MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES;
|
||||
return Math.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) > Math
|
||||
.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 3.0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1409,8 +971,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
* determined, the course changes get analyzed starting from {@code t'} until {@code (t +}
|
||||
* {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration} {@code * 3)} in order
|
||||
* to locate the point where the bearing starts to change with a rate of maximal
|
||||
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
|
||||
* regarded as a stable course.
|
||||
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
|
||||
* a stable course.
|
||||
*
|
||||
* @param maneuverMainCurveDetails
|
||||
* The details of the main curve, ideally computed by
|
||||
@@ -1450,7 +1012,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, stableBearingAnalysisFrom,
|
||||
latestTimePointForSpeedTrendAnalysis);
|
||||
ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed(
|
||||
stepsToAnalyze, false, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS);
|
||||
stepsToAnalyze, false, MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS);
|
||||
if (stableBearingExtension != null
|
||||
&& !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) {
|
||||
maneuverEnd = stableBearingExtension;
|
||||
@@ -1762,11 +1324,4 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
|
||||
return new SpeedWithBearingStepsIterable(maneuverBearingSteps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the approximated duration of the maneuver main curve considering the boat class of the competitor.
|
||||
*/
|
||||
protected Duration getApproximateManeuverDuration() {
|
||||
return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration();
|
||||
}
|
||||
|
||||
}
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
package com.sap.sailing.domain.maneuverdetection.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.polars.PolarDataService;
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
|
||||
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
|
||||
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
|
||||
import com.sap.sailing.domain.tracking.impl.CompleteManeuverCurveImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.Speed;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.impl.DegreeBearingImpl;
|
||||
|
||||
/**
|
||||
* A decorator which adds support for management of estimation data for wind estimation to an existing maneuver detector
|
||||
* implementation.
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
* @see ManeuverDetector
|
||||
*
|
||||
*/
|
||||
public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl
|
||||
implements ManeuverDetectorWithEstimationDataSupport {
|
||||
|
||||
private final ManeuverDetectorImpl maneuverDetector;
|
||||
private final PolarDataService polarDataService;
|
||||
|
||||
public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector,
|
||||
PolarDataService polarDataService) {
|
||||
this.maneuverDetector = maneuverDetector;
|
||||
this.polarDataService = polarDataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Maneuver> detectManeuvers() {
|
||||
return maneuverDetector.detectManeuvers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves) {
|
||||
List<Maneuver> maneuvers = new ArrayList<>();
|
||||
for (CompleteManeuverCurve maneuverCurve : maneuverCurves) {
|
||||
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
|
||||
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint,
|
||||
/* extrapolate */false);
|
||||
Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint);
|
||||
maneuvers
|
||||
.addAll(maneuverDetector.determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind,
|
||||
maneuverCurve.getMarkPassing()));
|
||||
}
|
||||
return maneuvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurve> detectCompleteManeuverCurves() {
|
||||
List<ManeuverSpot> maneuverSpots = maneuverDetector.detectManeuverSpots();
|
||||
return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null)
|
||||
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers) {
|
||||
List<CompleteManeuverCurve> result = new ArrayList<>();
|
||||
CompleteManeuverCurve curveToAdd = null;
|
||||
boolean previousManeuverCouldBelongToSameCurve = false;
|
||||
Maneuver previousManeuver = null;
|
||||
for (Maneuver maneuver : maneuvers) {
|
||||
boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE
|
||||
|| maneuver.isMarkPassing()
|
||||
&& (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE);
|
||||
if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve
|
||||
&& previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
|
||||
.equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())
|
||||
&& previousManeuver.getToSide() == maneuver.getToSide()) {
|
||||
curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver);
|
||||
} else {
|
||||
if (curveToAdd != null) {
|
||||
result.add(curveToAdd);
|
||||
}
|
||||
curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver);
|
||||
}
|
||||
previousManeuver = maneuver;
|
||||
previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve;
|
||||
}
|
||||
if (curveToAdd != null) {
|
||||
result.add(curveToAdd);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused
|
||||
* for the resulting complete maneuver curve.
|
||||
*
|
||||
* @see CompleteManeuverCurve
|
||||
* @see Maneuver
|
||||
*/
|
||||
private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) {
|
||||
ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps(
|
||||
maneuver.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(),
|
||||
maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(),
|
||||
maneuverDetector.getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter()));
|
||||
return new CompleteManeuverCurveImpl(mainCurveBoundaries,
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with
|
||||
* unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely
|
||||
* from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly.
|
||||
*/
|
||||
private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve,
|
||||
Maneuver maneuver) {
|
||||
ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = maneuverDetector.computeManeuverMainCurveDetails(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide());
|
||||
if (mainCurveDetails == null) {
|
||||
return maneuverCurve;
|
||||
}
|
||||
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees()
|
||||
+ maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
|
||||
.compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0
|
||||
? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed());
|
||||
return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries,
|
||||
maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves) {
|
||||
List<CompleteManeuverCurveWithEstimationData> result = new ArrayList<>();
|
||||
|
||||
CompleteManeuverCurve previousManeuverCurve = null;
|
||||
CompleteManeuverCurve currentManeuverCurve = null;
|
||||
for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) {
|
||||
if (currentManeuverCurve != null) {
|
||||
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
|
||||
currentManeuverCurve, previousManeuverCurve, nextManeuverCurve);
|
||||
result.add(maneuverCurveWithEstimationData);
|
||||
}
|
||||
previousManeuverCurve = currentManeuverCurve;
|
||||
currentManeuverCurve = nextManeuverCurve;
|
||||
}
|
||||
if (currentManeuverCurve != null) {
|
||||
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
|
||||
currentManeuverCurve, previousManeuverCurve, null);
|
||||
result.add(maneuverCurveWithEstimationData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The
|
||||
* computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as
|
||||
* computationally-intensive.
|
||||
*/
|
||||
private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData(
|
||||
CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve,
|
||||
CompleteManeuverCurve nextManeuverCurve) {
|
||||
Bearing courseAtMaxTurningRate = null;
|
||||
SpeedWithBearingStep stepWithLowestSpeed = null;
|
||||
SpeedWithBearingStep stepWithHighestSpeed = null;
|
||||
SpeedWithBearingStep stepWithMaxTurningRate = null;
|
||||
SpeedWithBearingStep previousStep = null;
|
||||
for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) {
|
||||
if (stepWithLowestSpeed == null
|
||||
|| stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) {
|
||||
stepWithLowestSpeed = step;
|
||||
}
|
||||
if (stepWithHighestSpeed == null
|
||||
|| stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) {
|
||||
stepWithHighestSpeed = step;
|
||||
}
|
||||
if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate
|
||||
.getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) {
|
||||
stepWithMaxTurningRate = step;
|
||||
courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing()
|
||||
.add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2));
|
||||
}
|
||||
previousStep = step;
|
||||
}
|
||||
int gpsFixCountWithinMainCurve = 0;
|
||||
int gpsFixCountWithinWholeCurve = 0;
|
||||
int gpsFixesCountFromPreviousManeuver = 0;
|
||||
int gpsFixesCountToNextManeuver = 0;
|
||||
try {
|
||||
maneuverDetector.track.lockForRead();
|
||||
boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
|
||||
.before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()
|
||||
.after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
for (GPSFixMoving fix : maneuverDetector.track.getFixes(
|
||||
considerPreviousManeuver
|
||||
? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointAfter()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
!considerPreviousManeuver,
|
||||
considerNextManeuver
|
||||
? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointBefore()
|
||||
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
!considerNextManeuver)) {
|
||||
if (fix.getTimePoint().before(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) {
|
||||
++gpsFixesCountFromPreviousManeuver;
|
||||
} else if (fix.getTimePoint().after(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) {
|
||||
++gpsFixesCountToNextManeuver;
|
||||
} else {
|
||||
if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore())
|
||||
&& !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) {
|
||||
++gpsFixCountWithinMainCurve;
|
||||
}
|
||||
++gpsFixCountWithinWholeCurve;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
maneuverDetector.track.unlockAfterRead();
|
||||
}
|
||||
|
||||
ManeuverLoss projectedManeuverLoss = maneuverDetector.getManeuverLoss(maneuverCurve.getMainCurveBoundaries());
|
||||
Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore()
|
||||
.travel(maneuverCurve.getMainCurveBoundaries().getDuration());
|
||||
Distance distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
|
||||
Duration longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
|
||||
ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl(
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(),
|
||||
stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(),
|
||||
stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(),
|
||||
maneuverCurve.getMainCurveBoundaries().getTimePoint(),
|
||||
maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate,
|
||||
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
|
||||
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(),
|
||||
Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees())
|
||||
/ maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(),
|
||||
gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes);
|
||||
projectedManeuverLoss = maneuverDetector
|
||||
.getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries());
|
||||
distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getSpeedWithBearingBefore()
|
||||
.travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration());
|
||||
distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null
|
||||
? maneuverDetector.getTrackTimeInfo() : null;
|
||||
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
|
||||
previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint()
|
||||
: previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
|
||||
.getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint()
|
||||
: nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL;
|
||||
GPSFixMoving lastManeuverFix = maneuverDetector.track.getLastFixAtOrBefore(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
|
||||
if (lastManeuverFix != null) {
|
||||
GPSFixMoving firstFixAfterLastManeuverFix = maneuverDetector.track
|
||||
.getFirstFixAfter(lastManeuverFix.getTimePoint());
|
||||
if (firstFixAfterLastManeuverFix != null) {
|
||||
intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint()
|
||||
.until(firstFixAfterLastManeuverFix.getTimePoint());
|
||||
}
|
||||
}
|
||||
Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL;
|
||||
GPSFixMoving firstManeuverFix = maneuverDetector.track.getFirstFixAtOrAfter(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
|
||||
if (firstManeuverFix != null) {
|
||||
GPSFixMoving lastFixBeforeFirstManeuverFix = maneuverDetector.track
|
||||
.getLastFixBefore(firstManeuverFix.getTimePoint());
|
||||
if (lastFixBeforeFirstManeuverFix != null) {
|
||||
intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint()
|
||||
.until(firstManeuverFix.getTimePoint());
|
||||
}
|
||||
}
|
||||
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(),
|
||||
durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(),
|
||||
gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(),
|
||||
durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver,
|
||||
projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
|
||||
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve,
|
||||
longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
|
||||
intervalBetweenFirstFixOfCurveAndPreviousFix);
|
||||
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
|
||||
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint,
|
||||
/* extrapolate */false);
|
||||
Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint);
|
||||
int numberOfJibes = maneuverDetector.getNumberOfJibes(mainCurve, wind);
|
||||
int numberOfTacks = maneuverDetector.getNumberOfTacks(mainCurve, wind);
|
||||
boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees()
|
||||
- 180) * mainCurve.getDirectionChangeInDegrees() < 0;
|
||||
Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing());
|
||||
Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark(
|
||||
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve
|
||||
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing());
|
||||
BoatClass boatClass = maneuverDetector.trackedRace.getRace().getBoatOfCompetitor(maneuverDetector.competitor)
|
||||
.getBoatClass();
|
||||
Double deviationFromTackAngle = null;
|
||||
Double deviationFromJibeAngle = null;
|
||||
Speed boatSpeed = curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore()
|
||||
.compareTo(curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter()) < 0
|
||||
? curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore()
|
||||
: curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter();
|
||||
if (polarDataService.getAllBoatClassesWithPolarSheetsAvailable().contains(boatClass)) {
|
||||
SpeedWithBearingWithConfidence<Void> closestTackTwa = polarDataService.getClosestTwaTws(ManeuverType.TACK,
|
||||
boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass);
|
||||
SpeedWithBearingWithConfidence<Void> closestJibeTwa = polarDataService.getClosestTwaTws(ManeuverType.JIBE,
|
||||
boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass);
|
||||
if (closestTackTwa != null) {
|
||||
deviationFromTackAngle = polarDataService.getManeuverAngleInDegreesFromTwa(
|
||||
closestTackTwa.getObject().getBearing().getDegrees(), ManeuverType.TACK);
|
||||
}
|
||||
if (closestJibeTwa != null) {
|
||||
deviationFromJibeAngle = polarDataService.getManeuverAngleInDegreesFromTwa(
|
||||
closestJibeTwa.getObject().getBearing().getDegrees(), ManeuverType.JIBE);
|
||||
}
|
||||
}
|
||||
Distance closestDistanceToMark = getClosestDistanceToMark(mainCurve.getTimePointOfMaxTurningRate());
|
||||
|
||||
return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve,
|
||||
curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes,
|
||||
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver,
|
||||
relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing(), closestDistanceToMark,
|
||||
deviationFromTackAngle, deviationFromJibeAngle);
|
||||
}
|
||||
|
||||
public Distance getClosestDistanceToMark(TimePoint timePoint) {
|
||||
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache(
|
||||
maneuverDetector.trackedRace, timePoint);
|
||||
Distance result = null;
|
||||
TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor,
|
||||
timePoint);
|
||||
if (legAfter != null) {
|
||||
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false);
|
||||
if (legAfter.getLeg().getTo() != null) {
|
||||
result = getClosestDistanceToMarkInternal(markPositionAtTimePointCache, legAfter.getLeg().getTo(),
|
||||
maneuverPosition);
|
||||
}
|
||||
if (legAfter.getLeg().getFrom() != null) {
|
||||
Distance distance = getClosestDistanceToMarkInternal(markPositionAtTimePointCache,
|
||||
legAfter.getLeg().getFrom(), maneuverPosition);
|
||||
if (result == null || distance != null && distance.compareTo(result) < 0) {
|
||||
result = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Distance getClosestDistanceToMarkInternal(
|
||||
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache, Waypoint waypoint,
|
||||
Position maneuverPosition) {
|
||||
Distance result = null;
|
||||
for (Mark mark : waypoint.getMarks()) {
|
||||
Position markPosition = markPositionAtTimePointCache.getEstimatedPosition(mark);
|
||||
Distance distance = markPosition.getDistance(maneuverPosition);
|
||||
if (result == null || distance.compareTo(result) < 0) {
|
||||
result = distance;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the duration and avg speed with avg course based on the competitor's track within the provided time
|
||||
* range.
|
||||
*/
|
||||
private Pair<Duration, SpeedWithBearing> calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from,
|
||||
TimePoint to) {
|
||||
Duration duration = from.until(to);
|
||||
Position fromPosition = maneuverDetector.track.getEstimatedPosition(from, false);
|
||||
Position toPosition = maneuverDetector.track.getEstimatedPosition(to, false);
|
||||
Distance distance = fromPosition.getDistance(toPosition);
|
||||
Bearing bearing = fromPosition.getBearingGreatCircle(toPosition);
|
||||
Speed speed = distance.inTime(Math.abs(duration.asMillis()));
|
||||
SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
|
||||
return new Pair<>(duration, avgSpeedWithBearing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative
|
||||
* bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course.
|
||||
*/
|
||||
public Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) {
|
||||
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache(
|
||||
maneuverDetector.trackedRace, timePoint);
|
||||
Bearing result = null;
|
||||
TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor,
|
||||
timePoint);
|
||||
if (legAfter != null && legAfter.getLeg().getTo() != null) {
|
||||
Position maneuverEndPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false);
|
||||
Waypoint nextWaypoint = legAfter.getLeg().getTo();
|
||||
for (Mark mark : nextWaypoint.getMarks()) {
|
||||
Position nextMarkPosition = markPositionAtTimePointCache.getEstimatedPosition(mark);
|
||||
Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition);
|
||||
Bearing resultCandidate = absoluteBearing.getDifferenceTo(boatCourse);
|
||||
if (result == null) {
|
||||
result = resultCandidate;
|
||||
} else if (Math.signum(result.getDegrees()) != Math.signum(resultCandidate.getDegrees())) {
|
||||
result = new DegreeBearingImpl(0);
|
||||
break;
|
||||
} else if (Math.abs(result.getDegrees()) > Math.abs(resultCandidate.getDegrees())) {
|
||||
result = resultCandidate;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.polars;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -181,4 +182,11 @@ public interface PolarDataService {
|
||||
* with this service, then lets the {@code consumer} accept that domain factory.
|
||||
*/
|
||||
void runWithDomainFactory(Consumer<DomainFactory> consumer) throws InterruptedException;
|
||||
|
||||
Map<BoatClass, Long> getFixCountPerBoatClass();
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
|
||||
double courseChangeDeg, BoatClass boatClass);
|
||||
|
||||
double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType);
|
||||
}
|
||||
@@ -175,11 +175,13 @@ public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends MappedTra
|
||||
void resumeValidityCaching();
|
||||
|
||||
/**
|
||||
* Gets a list of bearings between the provided time range (inclusive the boundaries). The bearings are retrieved by
|
||||
* means of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The first and last bearing steps will be always
|
||||
* sampled at provided {@code fromTimePoint} and {@code toTimePoint}, whereas the steps between are sampled at time
|
||||
* points of non-raw GPS fixes. The idea of this concept is to produce at least two bearing steps as result, in
|
||||
* order to provide the caller at least a {@code totalCourseChangeAngleInDegrees > 0} between the given time range.
|
||||
* Gets a list of speed with bearing steps considering the provided time range. The bearings are retrieved by means
|
||||
* of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The steps are sampled at time points of non-raw GPS fixes.
|
||||
* When there is no non-raw fix contained at {@code fromTimePoint}, the time point of the first step will be the
|
||||
* time point of the first non-raw fix before {@code fromTimePoint}. Analogously, when there is no non-raw fix
|
||||
* contained at {@code toTimePoint}, the last step's time point will be the first non-raw fix after
|
||||
* {@code toTimePoint}. The idea of this concept is to produce at least two steps as part of the result, in order to
|
||||
* provide the caller at least a {@code |totalCourseChangeAngleInDegrees > 0|} between the given time range.
|
||||
*
|
||||
* @param fromTimePoint
|
||||
* The from time point (inclusive) for resulting bearing steps
|
||||
|
||||
@@ -174,5 +174,5 @@ public interface Maneuver extends GPSFix {
|
||||
*/
|
||||
@Statistic(messageKey = "AvgTurningRateInDegreesPerSecond", resultDecimals = 4)
|
||||
double getAvgTurningRateInDegreesPerSecond();
|
||||
|
||||
|
||||
}
|
||||
+14
-45
@@ -1171,20 +1171,13 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
|
||||
Bearing lastCourse = null;
|
||||
TimePoint lastTimePoint = null;
|
||||
double lastCourseChangeAngleInDegrees = 0;
|
||||
TimePoint fixTimePointAfterToTimePoint = null;
|
||||
try {
|
||||
lockForRead();
|
||||
TimePoint timePoint = fromTimePoint;
|
||||
for (Iterator<FixType> iterator = getFixesIterator(fromTimePoint, false); iterator
|
||||
.hasNext(); timePoint = iterator.next().getTimePoint()) {
|
||||
if (timePoint == null) {
|
||||
continue;
|
||||
}
|
||||
if (timePoint.after(toTimePoint)) {
|
||||
fixTimePointAfterToTimePoint = timePoint;
|
||||
timePoint = toTimePoint;
|
||||
}
|
||||
SpeedWithBearing estimatedSpeed = getEstimatedSpeed(timePoint);
|
||||
FixType firstFix = getLastFixAtOrBefore(fromTimePoint);
|
||||
TimePoint currentTimePoint = firstFix == null ? fromTimePoint : firstFix.getTimePoint();
|
||||
for (Iterator<FixType> iterator = getFixesIterator(currentTimePoint, false); iterator
|
||||
.hasNext(); currentTimePoint = iterator.next().getTimePoint()) {
|
||||
SpeedWithBearing estimatedSpeed = getEstimatedSpeed(currentTimePoint);
|
||||
if (estimatedSpeed != null) {
|
||||
Bearing course = estimatedSpeed.getBearing();
|
||||
/*
|
||||
@@ -1195,44 +1188,20 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
|
||||
double courseChangeAngleInDegrees = lastCourse == null ? 0
|
||||
: lastCourse.getDifferenceTo(course, new DegreeBearingImpl(lastCourseChangeAngleInDegrees))
|
||||
.getDegrees();
|
||||
double turningRateInDegreesPerSecond = lastTimePoint == null ? 0
|
||||
: Math.abs(courseChangeAngleInDegrees
|
||||
/ lastTimePoint.until(currentTimePoint).asSeconds());
|
||||
|
||||
// Fix distorted turning rate due to inappropriate interpolation of getEstimatedSpeed() at first
|
||||
// and last step
|
||||
double courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees;
|
||||
Duration durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null
|
||||
: lastTimePoint.until(timePoint);
|
||||
if (fromTimePoint.equals(lastTimePoint)) {
|
||||
FixType firstFix = getLastFixAtOrBefore(fromTimePoint);
|
||||
if (firstFix != null && !firstFix.getTimePoint().equals(fromTimePoint)) {
|
||||
SpeedWithBearing firstFixEstimatedSpeed = getEstimatedSpeed(firstFix.getTimePoint());
|
||||
if (firstFixEstimatedSpeed != null) {
|
||||
durationBetweenStepsForTurningRateCalculation = firstFix.getTimePoint()
|
||||
.until(timePoint);
|
||||
courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees
|
||||
+ firstFixEstimatedSpeed.getBearing().getDifferenceTo(lastCourse).getDegrees();
|
||||
}
|
||||
}
|
||||
} else if (fixTimePointAfterToTimePoint != null && lastCourse != null) {
|
||||
SpeedWithBearing lastFixEstimatedSpeed = getEstimatedSpeed(fixTimePointAfterToTimePoint);
|
||||
if (lastFixEstimatedSpeed != null) {
|
||||
durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null
|
||||
: lastTimePoint.until(fixTimePointAfterToTimePoint);
|
||||
courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees + estimatedSpeed
|
||||
.getBearing().getDifferenceTo(lastFixEstimatedSpeed.getBearing()).getDegrees();
|
||||
}
|
||||
}
|
||||
|
||||
double turningRateInDegreesPerSecond = durationBetweenStepsForTurningRateCalculation == null ? 0
|
||||
: Math.abs(courseChangeInDegreesForTurningRateCalculation
|
||||
/ durationBetweenStepsForTurningRateCalculation.asSeconds());
|
||||
|
||||
speedWithBearingSteps.add(new SpeedWithBearingStepImpl(timePoint, estimatedSpeed,
|
||||
speedWithBearingSteps.add(new SpeedWithBearingStepImpl(currentTimePoint, estimatedSpeed,
|
||||
courseChangeAngleInDegrees, turningRateInDegreesPerSecond));
|
||||
if (currentTimePoint.after(toTimePoint)) {
|
||||
break;
|
||||
}
|
||||
lastCourse = course;
|
||||
lastCourseChangeAngleInDegrees = courseChangeAngleInDegrees;
|
||||
lastTimePoint = timePoint;
|
||||
lastTimePoint = currentTimePoint;
|
||||
}
|
||||
if (!timePoint.before(toTimePoint)) {
|
||||
if (!currentTimePoint.before(toTimePoint)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* Maneuver implementation which were detected on tracks with extremely low GPS-sampling rate. The implementation
|
||||
* suggests to ignore the following attributes:
|
||||
* <ul>
|
||||
* <li>Maneuver loss</li>
|
||||
* <li>Max. and Avg. Turning rate</li>
|
||||
* <li>Time point before and after of all maneuver boundaries</li>
|
||||
* <li>Lowest speed within all maneuver boundaries</li>
|
||||
* </ul>
|
||||
* However, to provide capability with existing code, the attributes are filled with values.
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
*
|
||||
*/
|
||||
public class ManeuverWithCoarseGrainedBoundariesImpl extends ManeuverImpl {
|
||||
|
||||
private static final long serialVersionUID = -381990329349665889L;
|
||||
|
||||
public ManeuverWithCoarseGrainedBoundariesImpl(ManeuverType type, Tack newTack, Position position,
|
||||
TimePoint timePoint, ManeuverCurveBoundaries maneuverBoundaries) {
|
||||
super(type, newTack, position, null, timePoint, maneuverBoundaries, maneuverBoundaries, Math.abs(maneuverBoundaries.getDirectionChangeInDegrees()), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManeuverCurveBoundaries getManeuverBoundaries() {
|
||||
return getMainCurveBoundaries();
|
||||
}
|
||||
|
||||
}
|
||||
+16
-4
@@ -113,6 +113,7 @@ import com.sap.sailing.domain.maneuverdetection.IncrementalManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ShortTimeAfterLastHitCache;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.IncrementalManeuverDetectorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.LowGPSSamplingRateManeuverDetectorImpl;
|
||||
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
|
||||
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sailing.domain.polars.PolarDataService;
|
||||
@@ -701,10 +702,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
@Override
|
||||
public List<Maneuver> computeCacheUpdate(Competitor competitor, EmptyUpdateInterval updateInterval)
|
||||
throws NoWindException {
|
||||
IncrementalManeuverDetector maneuverDetector = maneuverDetectorPerCompetitorCache
|
||||
.getValue(competitor);
|
||||
List<Maneuver> maneuvers = computeManeuvers(competitor, maneuverDetector);
|
||||
return maneuvers;
|
||||
Duration averageIntervalBetweenRawFixes = getTrack(competitor)
|
||||
.getAverageIntervalBetweenRawFixes();
|
||||
if (averageIntervalBetweenRawFixes != null) {
|
||||
ManeuverDetector maneuverDetector;
|
||||
if (averageIntervalBetweenRawFixes.asSeconds() >= 30) {
|
||||
maneuverDetector = new LowGPSSamplingRateManeuverDetectorImpl(TrackedRaceImpl.this,
|
||||
competitor);
|
||||
} else {
|
||||
maneuverDetector = maneuverDetectorPerCompetitorCache.getValue(competitor);
|
||||
}
|
||||
List<Maneuver> maneuvers = computeManeuvers(competitor, maneuverDetector);
|
||||
return maneuvers;
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}, /* nameForLocks */"Maneuver cache for race " + getRace().getName());
|
||||
}
|
||||
|
||||
+14
-78
@@ -9,11 +9,9 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.gwt.cell.client.AbstractCell;
|
||||
import com.google.gwt.cell.client.Cell.Context;
|
||||
import com.google.gwt.cell.client.DateCell;
|
||||
import com.google.gwt.cell.client.TextCell;
|
||||
import com.google.gwt.core.shared.GWT;
|
||||
import com.google.gwt.i18n.client.NumberFormat;
|
||||
import com.google.gwt.i18n.shared.DateTimeFormat;
|
||||
import com.google.gwt.i18n.shared.DateTimeFormat.PredefinedFormat;
|
||||
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
|
||||
@@ -42,14 +40,11 @@ import com.sap.sailing.gwt.ui.actions.GetManeuversForCompetitorsAction;
|
||||
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
|
||||
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
|
||||
import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter;
|
||||
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax;
|
||||
import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.HasStringAndDoubleValue;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel.LeaderBoardStyle;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.MinMaxRenderer;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.SortedCellTableWithStylableHeaders;
|
||||
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
|
||||
import com.sap.sse.common.TimeRange;
|
||||
@@ -82,8 +77,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
|
||||
private final StringMessages stringMessages;
|
||||
private final CompetitorSelectionProvider competitorSelectionModel;
|
||||
|
||||
private final NumberFormat towDigitAccuracy = NumberFormatterFactory.getDecimalFormat(2);
|
||||
|
||||
private final SimplePanel contentPanel = new SimplePanel();
|
||||
private final Label importantMessageLabel = new Label();
|
||||
private final SortedCellTableWithStylableHeaders<ManeuverTableData> maneuverCellTable;
|
||||
@@ -162,84 +155,31 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
|
||||
this.stringMessages.avgTurningRate(), this.stringMessages.degreesPerSecondUnit()));
|
||||
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getManeuverLoss,
|
||||
this.stringMessages.maneuverLoss(), stringMessages.metersUnit()));
|
||||
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getDirectionChange,
|
||||
this.maneuverCellTable.addColumn(createSortableAbsMinMaxColumn(ManeuverTableData::getDirectionChange,
|
||||
stringMessages.directionChange(), this.stringMessages.degreesShort()));
|
||||
initWidget(rootPanel);
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sortable column with the absolute value. Whereas {@link #createSortableMinMaxColumn()} creates a
|
||||
* sortable column with signed values.
|
||||
*/
|
||||
private SortableColumn<ManeuverTableData, String> createSortableAbsMinMaxColumn(
|
||||
Function<ManeuverTableData, Double> extractor, String title, String unit) {
|
||||
return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sortable column with signed values.
|
||||
*/
|
||||
private SortableColumn<ManeuverTableData, String> createSortableMinMaxColumn(
|
||||
Function<ManeuverTableData, Double> extractor, String title, String unit) {
|
||||
final SortableColumn<ManeuverTableData, String> col = new AbstractSortableColumnWithMinMax<ManeuverTableData, String>(
|
||||
new TextCell(), SortingOrder.ASCENDING) {
|
||||
final InvertibleComparator<ManeuverTableData> comparatorWithAbs = new InvertibleComparatorAdapter<ManeuverTableData>() {
|
||||
@Override
|
||||
public int compare(ManeuverTableData o1, ManeuverTableData o2) {
|
||||
Double o1v = extractor.apply(o1);
|
||||
Double o2v = extractor.apply(o2);
|
||||
if (o1v == null && o2v == null) {
|
||||
return 0;
|
||||
}
|
||||
if (o1v == null && o2v != null) {
|
||||
return -1;
|
||||
}
|
||||
if (o1v != null && o2v == null) {
|
||||
return 1;
|
||||
}
|
||||
return Double.compare(Math.abs(o1v), Math.abs(o2v));
|
||||
}
|
||||
};
|
||||
final HasStringAndDoubleValue<ManeuverTableData> dataProvider = new HasStringAndDoubleValue<ManeuverTableData>() {
|
||||
@Override
|
||||
public String getStringValueToRender(ManeuverTableData row) {
|
||||
Double value = extractor.apply(row);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return towDigitAccuracy.format(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDoubleValue(ManeuverTableData row) {
|
||||
Double value = extractor.apply(row);
|
||||
return value == null ? null : Math.abs(value);
|
||||
}
|
||||
};
|
||||
|
||||
final MinMaxRenderer<ManeuverTableData> renderer = new MinMaxRenderer<ManeuverTableData>(dataProvider, comparatorWithAbs);
|
||||
|
||||
@Override
|
||||
public InvertibleComparator<ManeuverTableData> getComparator() {
|
||||
return comparatorWithAbs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) {
|
||||
renderer.render(context, object, title, sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header<?> getHeader() {
|
||||
return new TextHeader(title + " [" + unit + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ManeuverTableData object) {
|
||||
return dataProvider.getStringValueToRender(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMinMax() {
|
||||
renderer.updateMinMax(maneuverCellTable.getDataProvider().getList());
|
||||
}
|
||||
};
|
||||
col.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
|
||||
return col;
|
||||
return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ false);
|
||||
}
|
||||
|
||||
private SortableColumn<ManeuverTableData, String> createManeuverTypeColumn() {
|
||||
return new SortableColumn<ManeuverTableData, String>(new TextCell(), SortingOrder.ASCENDING) {
|
||||
|
||||
@Override
|
||||
public InvertibleComparator<ManeuverTableData> getComparator() {
|
||||
return new InvertibleComparatorAdapter<ManeuverTableData>() {
|
||||
@@ -269,7 +209,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
|
||||
return o1.getTimePoint().compareTo(o2.getTimePoint());
|
||||
}
|
||||
};
|
||||
|
||||
final SortableColumn<ManeuverTableData, Date> col = new SortableColumn<ManeuverTableData, Date>(
|
||||
new DateCell(DateTimeFormat.getFormat(PredefinedFormat.TIME_LONG)), SortingOrder.ASCENDING) {
|
||||
@Override
|
||||
@@ -297,7 +236,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
|
||||
return -Boolean.compare(o1.isMarkPassing(), o2.isMarkPassing());
|
||||
}
|
||||
};
|
||||
|
||||
final SortableColumn<ManeuverTableData, Boolean> column = new SortableColumn<ManeuverTableData, Boolean>(
|
||||
new AbstractCell<Boolean>() {
|
||||
@Override
|
||||
@@ -332,9 +270,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
|
||||
return o1.getCompetitorName().compareTo(o2.getCompetitorName());
|
||||
}
|
||||
};
|
||||
|
||||
return new SortableColumn<ManeuverTableData, String>(new TextCell(), SortingOrder.ASCENDING) {
|
||||
|
||||
@Override
|
||||
public InvertibleComparator<ManeuverTableData> getComparator() {
|
||||
return comparator;
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.sap.sailing.gwt.ui.client.shared.racemap.maneuver;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gwt.cell.client.Cell.Context;
|
||||
import com.google.gwt.cell.client.TextCell;
|
||||
import com.google.gwt.i18n.client.NumberFormat;
|
||||
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
|
||||
import com.google.gwt.user.cellview.client.Header;
|
||||
import com.google.gwt.user.cellview.client.TextHeader;
|
||||
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.domain.common.InvertibleComparator;
|
||||
import com.sap.sailing.domain.common.SortingOrder;
|
||||
import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter;
|
||||
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
|
||||
import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.HasStringAndDoubleValue;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.MinMaxRenderer;
|
||||
|
||||
public class SortableMinMaxColumn extends AbstractSortableColumnWithMinMax<ManeuverTableData, String> {
|
||||
private final static NumberFormat TWO_DIGIT_ACCURACY = NumberFormatterFactory.getDecimalFormat(2);
|
||||
private final String title;
|
||||
private final String unit;
|
||||
|
||||
final InvertibleComparator<ManeuverTableData> comparator;
|
||||
|
||||
final HasStringAndDoubleValue<ManeuverTableData> dataProvider;
|
||||
|
||||
final MinMaxRenderer<ManeuverTableData> renderer;
|
||||
|
||||
final ListDataProvider<ManeuverTableData> maneuverTableListDataProvider;
|
||||
|
||||
public SortableMinMaxColumn(final Function<ManeuverTableData, Double> extractor, String title, String unit,
|
||||
ListDataProvider<ManeuverTableData> maneuverTableListDataProvider, boolean absolute) {
|
||||
super(new TextCell(), SortingOrder.ASCENDING);
|
||||
this.title = title;
|
||||
this.unit = unit;
|
||||
this.maneuverTableListDataProvider = maneuverTableListDataProvider;
|
||||
this.comparator = new InvertibleComparatorAdapter<ManeuverTableData>() {
|
||||
@Override
|
||||
public int compare(ManeuverTableData o1, ManeuverTableData o2) {
|
||||
Double o1v = extractor.apply(o1);
|
||||
Double o2v = extractor.apply(o2);
|
||||
return Comparator.nullsFirst((Double v1, Double v2)->Double.compare(absolute?Math.abs(v1):v1, absolute?Math.abs(v2):v2)).compare(o1v, o2v);
|
||||
}
|
||||
};
|
||||
this.dataProvider = new HasStringAndDoubleValue<ManeuverTableData>() {
|
||||
@Override
|
||||
public String getStringValueToRender(ManeuverTableData row) {
|
||||
Double value = extractor.apply(row);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return TWO_DIGIT_ACCURACY.format(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDoubleValue(ManeuverTableData row) {
|
||||
Double value = extractor.apply(row);
|
||||
return value == null ? null : absolute ? Math.abs(value) : value;
|
||||
}
|
||||
};
|
||||
|
||||
this.renderer = new MinMaxRenderer<ManeuverTableData>(dataProvider, comparator);
|
||||
this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvertibleComparator<ManeuverTableData> getComparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) {
|
||||
renderer.render(context, object, title, sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header<?> getHeader() {
|
||||
return new TextHeader(title + " [" + unit + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ManeuverTableData object) {
|
||||
return dataProvider.getStringValueToRender(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMinMax() {
|
||||
renderer.updateMinMax(maneuverTableListDataProvider.getList());
|
||||
}
|
||||
}
|
||||
+16
-15
@@ -26,6 +26,7 @@ public class MinMaxRenderer<T> {
|
||||
protected static final String BACKGROUND_BAR_STYLE_GOOD = "minMaxBackgroundBarGood";
|
||||
|
||||
private final HasStringAndDoubleValue<T> valueProvider;
|
||||
/** used to determine minimum and maximum values for the rendered bars.*/
|
||||
private final Comparator<T> comparator;
|
||||
private Double minimumValue;
|
||||
private Double maximumValue;
|
||||
@@ -119,7 +120,8 @@ public class MinMaxRenderer<T> {
|
||||
* @param row
|
||||
* The row to get the percentage for.
|
||||
*/
|
||||
protected int getPercentage(T row) {
|
||||
|
||||
protected int getPercentage(T row) {
|
||||
int percentage = 0;
|
||||
Double value = valueProvider.getDoubleValue(row);
|
||||
if (value != null) {
|
||||
@@ -128,10 +130,8 @@ public class MinMaxRenderer<T> {
|
||||
percentage = (int) (minBarLength + (100. - minBarLength) * (value - getMinimumDouble())
|
||||
/ (getMaximumDouble() - getMinimumDouble()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return percentage;
|
||||
|
||||
}
|
||||
|
||||
private Double getMinimumDouble() {
|
||||
@@ -149,23 +149,24 @@ public class MinMaxRenderer<T> {
|
||||
* The values of {@link LeaderboardRowDTO}s to determine the minimum and maximum values for.
|
||||
*/
|
||||
public void updateMinMax(Iterable<T> displayedLeaderboardRowsProvider) {
|
||||
T minimumRow = null;
|
||||
T maximumRow = null;
|
||||
T minimumOrderRow = null;
|
||||
T maximumOrderRow = null;
|
||||
for (T row : displayedLeaderboardRowsProvider) {
|
||||
if (valueProvider.getDoubleValue(row) != null
|
||||
&& (minimumRow == null || comparator.compare(minimumRow, row) > 0)) {
|
||||
minimumRow = row;
|
||||
&& (minimumOrderRow == null || comparator.compare(minimumOrderRow, row) > 0)) {
|
||||
minimumOrderRow = row;
|
||||
}
|
||||
if (valueProvider.getDoubleValue(row) != null
|
||||
&& (maximumRow == null || comparator.compare(maximumRow, row) < 0)) {
|
||||
maximumRow = row;
|
||||
&& (maximumOrderRow == null || comparator.compare(maximumOrderRow, row) < 0)) {
|
||||
maximumOrderRow = row;
|
||||
}
|
||||
}
|
||||
if (minimumOrderRow != null) {
|
||||
minimumValue = valueProvider.getDoubleValue(minimumOrderRow);
|
||||
|
||||
}
|
||||
if (minimumRow != null) {
|
||||
minimumValue = valueProvider.getDoubleValue(minimumRow);
|
||||
}
|
||||
if (maximumRow != null) {
|
||||
maximumValue = valueProvider.getDoubleValue(maximumRow);
|
||||
if (maximumOrderRow != null) {
|
||||
maximumValue = valueProvider.getDoubleValue(maximumOrderRow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -5062,7 +5062,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
final MasterDataImporter importer = new MasterDataImporter(baseDomainFactory, getService());
|
||||
importer.importFromStream(inputStream, importOperationId, override);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
// do not assume that RuntimeException is logged properly
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
getService()
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public class PolarDataResourceTest {
|
||||
|
||||
assertThat(polarService.getSpeedRegressionsPerAngle().size(), is(68));
|
||||
assertThat(polarService.getCubicRegressionsPerCourse().size(), is(4));
|
||||
assertThat(polarService.getFixCointPerBoatClass().get(boatClass), is(9330L));
|
||||
assertThat(polarService.getFixCountPerBoatClass().get(boatClass), is(9330L));
|
||||
// presuming that if downwind functions & regression collections' size are correct then any other thing is
|
||||
// imported correctly
|
||||
assertThat(polarService.getAngleRegressionFunction(boatClass, LegType.DOWNWIND), is(angleDownwindFunction));
|
||||
|
||||
+18
-5
@@ -177,7 +177,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
|
||||
return result;
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
|
||||
double courseChangeDeg, BoatClass boatClass) {
|
||||
assert type == ManeuverType.TACK || type == ManeuverType.JIBE;
|
||||
double minDiff = Double.MAX_VALUE;
|
||||
@@ -186,8 +187,9 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
|
||||
boatClass, speedAtManeuverStart, type == ManeuverType.TACK ? LegType.UPWIND : LegType.DOWNWIND,
|
||||
type == ManeuverType.TACK ? courseChangeDeg >= 0 ? Tack.PORT : Tack.STARBOARD
|
||||
: courseChangeDeg >= 0 ? Tack.STARBOARD : Tack.PORT)) {
|
||||
double diff = Math.abs(trueWindSpeedAndAngle.getObject().getBearing().getDegrees() * 2)
|
||||
- Math.abs(courseChangeDeg);
|
||||
double targetManeuverAngle = getManeuverAngleInDegreesFromTwa(
|
||||
trueWindSpeedAndAngle.getObject().getBearing().getDegrees(), type);
|
||||
double diff = Math.abs(targetManeuverAngle) - Math.abs(courseChangeDeg);
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestTwsTwa = trueWindSpeedAndAngle;
|
||||
@@ -235,11 +237,21 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
|
||||
}
|
||||
SpeedWithBearingWithConfidence<Void> speed = polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass,
|
||||
windSpeed, legType);
|
||||
Bearing bearing = new DegreeBearingImpl(speed.getObject().getBearing().getDegrees() * 2);
|
||||
Bearing bearing = new DegreeBearingImpl(getManeuverAngleInDegreesFromTwa(speed.getObject().getBearing().getDegrees(), maneuverType));
|
||||
BearingWithConfidence<Void> bearingWithConfidence = new BearingWithConfidenceImpl<Void>(bearing,
|
||||
speed.getConfidence(), null);
|
||||
return bearingWithConfidence;
|
||||
}
|
||||
|
||||
public double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType) {
|
||||
if (maneuverType == ManeuverType.TACK) {
|
||||
return Math.abs(twa) * 2;
|
||||
}
|
||||
if (maneuverType == ManeuverType.JIBE) {
|
||||
return (180 - Math.abs(twa)) * 2;
|
||||
}
|
||||
throw new IllegalArgumentException("ManeuverType needs to be tack or jibe.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertExistingFixes(TrackedRace trackedRace) {
|
||||
@@ -388,7 +400,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
|
||||
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getRegressionsImpl();
|
||||
}
|
||||
|
||||
public Map<BoatClass, Long> getFixCointPerBoatClass() {
|
||||
@Override
|
||||
public Map<BoatClass, Long> getFixCountPerBoatClass() {
|
||||
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getFixCountPerBoatClass();
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -107,8 +107,7 @@ public class AngleAndSpeedRegression implements Serializable {
|
||||
boolean angleFound;
|
||||
try {
|
||||
angle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedCandidateInKnots);
|
||||
if ((tack == Tack.PORT && legType == LegType.UPWIND)
|
||||
|| (tack == Tack.STARBOARD && legType == LegType.DOWNWIND)) {
|
||||
if (tack == Tack.PORT) {
|
||||
angle = -angle;
|
||||
}
|
||||
angleFound = true;
|
||||
|
||||
+29
-14
@@ -28,14 +28,14 @@ import com.sap.sailing.server.gateway.deserialization.impl.CompleteManeuverCurve
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.DetailedBoatClassJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverMainCurveWithEstimationDataJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverWindJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.PositionJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.WindJsonSerializer;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.Duration;
|
||||
@@ -120,34 +120,40 @@ public class EstimationDataSerializationDeserializationTest {
|
||||
longestIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
|
||||
intervalBetweenFirstFixOfCurveAndPreviousFix);
|
||||
|
||||
MillisecondsTimePoint windTimePoint = new MillisecondsTimePoint(dateFormat.parse("06/23/2011-15:28:25"));
|
||||
SpeedWithBearing windSpeedWithBearing = new KnotSpeedWithBearingImpl(2, new DegreeBearingImpl(340));
|
||||
DegreePosition windPosition = new DegreePosition(54.325246, 10.148556);
|
||||
Wind wind = new WindImpl(windPosition, windTimePoint, windSpeedWithBearing);
|
||||
|
||||
DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556);
|
||||
Wind wind = new WindImpl(maneuverPosition, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing);
|
||||
int jibingCount = 203;
|
||||
int tackingCount = 12345;
|
||||
boolean maneuverStartsByRunningAwayFromWind = true;
|
||||
Bearing relativeBearingToNextMarkBeforeManeuver = new DegreeBearingImpl(202.23);
|
||||
Bearing relativeBearingToNextMarkAfterManeuver = new DegreeBearingImpl(10.01);
|
||||
boolean markPassing = true;
|
||||
Distance closestDistanceToMark = new MeterDistance(3.0);
|
||||
Double deviationFromTargetTackAngle = 23.30;
|
||||
Double deviationFromTargetJibeAngle = 22.30;
|
||||
|
||||
CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl(
|
||||
maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount,
|
||||
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver,
|
||||
relativeBearingToNextMarkAfterManeuver, markPassing);
|
||||
relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark,
|
||||
deviationFromTargetTackAngle, deviationFromTargetJibeAngle);
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
|
||||
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer());
|
||||
new ManeuverWindJsonSerializer(), new PositionJsonSerializer());
|
||||
JSONObject json = serializer.serialize(toSerialize);
|
||||
CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonDeserializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(),
|
||||
new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer());
|
||||
new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer());
|
||||
CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json);
|
||||
|
||||
assertEquals(deviationFromTargetJibeAngle,
|
||||
deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
|
||||
assertEquals(deviationFromTargetTackAngle,
|
||||
deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
|
||||
assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark());
|
||||
assertEquals(maneuverPosition, deserialized.getPosition());
|
||||
assertEquals(tackingCount, deserialized.getTackingCount());
|
||||
assertEquals(jibingCount, deserialized.getJibingCount());
|
||||
@@ -156,8 +162,8 @@ public class EstimationDataSerializationDeserializationTest {
|
||||
assertEquals(relativeBearingToNextMarkBeforeManeuver,
|
||||
deserialized.getRelativeBearingToNextMarkBeforeManeuver());
|
||||
assertEquals(relativeBearingToNextMarkAfterManeuver, deserialized.getRelativeBearingToNextMarkAfterManeuver());
|
||||
assertEquals(windTimePoint, deserialized.getWind().getTimePoint());
|
||||
assertEquals(windPosition, deserialized.getWind().getPosition());
|
||||
assertEquals(wind.getTimePoint(), deserialized.getWind().getTimePoint());
|
||||
assertEquals(wind.getPosition(), deserialized.getWind().getPosition());
|
||||
assertEquals(windSpeedWithBearing.getBearing(), deserialized.getWind().getBearing());
|
||||
assertEquals(windSpeedWithBearing.getMetersPerSecond(), deserialized.getWind().getMetersPerSecond(), DELTA);
|
||||
|
||||
@@ -303,22 +309,31 @@ public class EstimationDataSerializationDeserializationTest {
|
||||
Bearing relativeBearingToNextMarkAfterManeuver = null;
|
||||
boolean markPassing = false;
|
||||
DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556);
|
||||
Distance closestDistanceToMark = null;
|
||||
Double deviationFromTargetTackAngle = null;
|
||||
Double deviationFromTargetJibeAngle = null;
|
||||
|
||||
CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl(
|
||||
maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount,
|
||||
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver,
|
||||
relativeBearingToNextMarkAfterManeuver, markPassing);
|
||||
relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark,
|
||||
deviationFromTargetTackAngle, deviationFromTargetJibeAngle);
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
|
||||
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer());
|
||||
new ManeuverWindJsonSerializer(), new PositionJsonSerializer());
|
||||
JSONObject json = serializer.serialize(toSerialize);
|
||||
CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonDeserializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(),
|
||||
new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer());
|
||||
new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer());
|
||||
CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json);
|
||||
|
||||
assertEquals(deviationFromTargetJibeAngle,
|
||||
deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
|
||||
assertEquals(deviationFromTargetTackAngle,
|
||||
deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
|
||||
assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark());
|
||||
assertEquals(maneuverPosition, deserialized.getPosition());
|
||||
assertEquals(tackingCount, deserialized.getTackingCount());
|
||||
assertEquals(jibingCount, deserialized.getJibingCount());
|
||||
|
||||
+22
-4
@@ -3,7 +3,10 @@ package com.sap.sailing.server.gateway.deserialization.impl;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
|
||||
@@ -12,6 +15,7 @@ import com.sap.sailing.server.gateway.deserialization.JsonDeserializationExcepti
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.impl.DegreeBearingImpl;
|
||||
|
||||
/**
|
||||
@@ -24,13 +28,13 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
|
||||
|
||||
private final ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer;
|
||||
private final ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer;
|
||||
private final WindJsonDeserializer windDeserializer;
|
||||
private final ManeuverWindJsonDeserializer windDeserializer;
|
||||
private final PositionJsonDeserializer positionDeserializer;
|
||||
|
||||
public CompleteManeuverCurveWithEstimationDataJsonDeserializer(
|
||||
ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer,
|
||||
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer,
|
||||
WindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) {
|
||||
ManeuverWindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) {
|
||||
this.mainCurveDeserializer = mainCurveDeserializer;
|
||||
this.curveWithUnstableCourseAndSpeedDeserializer = curveWithUnstableCourseAndSpeedDeserializer;
|
||||
this.windDeserializer = windDeserializer;
|
||||
@@ -48,7 +52,9 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
|
||||
.deserialize((JSONObject) object.get(
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer.CURVE_WITH_UNSTABLE_COURSE_AND_SPEED));
|
||||
JSONObject windJson = (JSONObject) object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.WIND);
|
||||
Wind wind = windJson == null ? null : windDeserializer.deserialize(windJson);
|
||||
SpeedWithBearing windSpeedWithBearing = windJson == null ? null : windDeserializer.deserialize(windJson);
|
||||
Wind wind = windSpeedWithBearing == null ? null
|
||||
: new WindImpl(position, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing);
|
||||
Integer tackingCount = getInteger(
|
||||
object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.TACKING_COUNT));
|
||||
Integer jibingCount = getInteger(
|
||||
@@ -59,16 +65,28 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER);
|
||||
Double relativeBearingToNextMarkAfterManeuver = (Double) object.get(
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER);
|
||||
Double closestDistanceToMarkInMeters = (Double) object
|
||||
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.CLOSEST_DISTANCE_TO_MARK);
|
||||
Double deviationFromTargetTackAngle = (Double) object
|
||||
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_TACK_ANGLE);
|
||||
Double deviationFromTargetJibeAngle = (Double) object
|
||||
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_JIBE_ANGLE);
|
||||
return new CompleteManeuverCurveWithEstimationDataImpl(position, mainCurve, curveWithUnstableCourseAndSpeed,
|
||||
wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind,
|
||||
convertBearing(relativeBearingToNextMarkBeforeManeuver),
|
||||
convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing);
|
||||
convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing,
|
||||
convertDistance(closestDistanceToMarkInMeters), deviationFromTargetTackAngle,
|
||||
deviationFromTargetJibeAngle);
|
||||
}
|
||||
|
||||
private Bearing convertBearing(Double degrees) {
|
||||
return degrees == null ? null : new DegreeBearingImpl(degrees);
|
||||
}
|
||||
|
||||
private Distance convertDistance(Double meters) {
|
||||
return meters == null ? null : new MeterDistance(meters);
|
||||
}
|
||||
|
||||
public static Integer getInteger(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.sap.sailing.server.gateway.deserialization.impl;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.impl.DegreeBearingImpl;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
*
|
||||
*/
|
||||
public class ManeuverWindJsonDeserializer implements JsonDeserializer<SpeedWithBearing> {
|
||||
|
||||
public SpeedWithBearing deserialize(JSONObject object) throws JsonDeserializationException {
|
||||
|
||||
Double directionInTrueDegrees = (Double) object.get(ManeuverWindJsonSerializer.DIRECTION_IN_TRUE_DEGREES);
|
||||
Double speedInKnots = (Double) object.get(ManeuverWindJsonSerializer.SPEED_IN_KNOTS);
|
||||
Bearing degreeBearing = new DegreeBearingImpl(directionInTrueDegrees);
|
||||
SpeedWithBearing speedBearing = new KnotSpeedWithBearingImpl(speedInKnots, degreeBearing);
|
||||
return speedBearing;
|
||||
}
|
||||
|
||||
}
|
||||
+11
-2
@@ -23,16 +23,19 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer
|
||||
public static final String MANEUVER_STARTS_BY_RUNNING_AWAY_FROM_WIND = "maneuverStartsByRunningAwayFromWind";
|
||||
public static final String RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER = "relativeBearingToNextMarkBeforeManeuver";
|
||||
public static final String RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER = "relativeBearingToNextMarkAfterManeuver";
|
||||
public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters";
|
||||
public static final String DEVIATION_FROM_TARGET_TACK_ANGLE = "deviationFromTargetTackAngleInDegrees";
|
||||
public static final String DEVIATION_FROM_TARGET_JIBE_ANGLE = "deviationFromTargetJibeAngleInDegrees";
|
||||
|
||||
private final ManeuverCurveBoundariesJsonSerializer mainCurveSerializer;
|
||||
private final ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer;
|
||||
private final WindJsonSerializer windSerializer;
|
||||
private final ManeuverWindJsonSerializer windSerializer;
|
||||
private final PositionJsonSerializer positionSerializer;
|
||||
|
||||
public CompleteManeuverCurveWithEstimationDataJsonSerializer(
|
||||
ManeuverCurveBoundariesJsonSerializer mainCurveSerializer,
|
||||
ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer,
|
||||
WindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) {
|
||||
ManeuverWindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) {
|
||||
this.mainCurveSerializer = mainCurveSerializer;
|
||||
this.curveWithUnstableCourseAndSpeedSerializer = curveWithUnstableCourseAndSpeedSerializer;
|
||||
this.windSerializer = windSerializer;
|
||||
@@ -59,6 +62,12 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer
|
||||
result.put(RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER,
|
||||
maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver() == null ? null
|
||||
: maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver().getDegrees());
|
||||
result.put(CLOSEST_DISTANCE_TO_MARK, maneuverWithEstimationData.getDistanceToClosestMark() == null ? null
|
||||
: maneuverWithEstimationData.getDistanceToClosestMark().getMeters());
|
||||
result.put(DEVIATION_FROM_TARGET_TACK_ANGLE,
|
||||
maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
|
||||
result.put(DEVIATION_FROM_TARGET_JIBE_ANGLE,
|
||||
maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+83
-15
@@ -1,19 +1,28 @@
|
||||
package com.sap.sailing.server.gateway.serialization.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
|
||||
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverSpot;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
|
||||
import com.sap.sailing.domain.polars.PolarDataService;
|
||||
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,14 +37,28 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst
|
||||
public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters";
|
||||
public static final String START_TIME_POINT = "startUnixTime";
|
||||
public static final String END_TIME_POINT = "endUnixTime";
|
||||
public static final String FIXES_COUNT_FOR_POLARS = "fixesCountForPolars";
|
||||
|
||||
private final BoatClassJsonSerializer boatClassJsonSerializer;
|
||||
private final CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer;
|
||||
private final PolarDataService polarDataService;
|
||||
private final Integer startBeforeStartLineInSeconds;
|
||||
private final Integer endBeforeStartLineInSeconds;
|
||||
private final Integer startAfterFinishLineInSeconds;
|
||||
private final Integer endAfterFinishLineInSeconds;
|
||||
|
||||
public CompleteManeuverCurvesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer,
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer) {
|
||||
public CompleteManeuverCurvesWithEstimationDataJsonSerializer(PolarDataService polarDataService,
|
||||
BoatClassJsonSerializer boatClassJsonSerializer,
|
||||
CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer,
|
||||
Integer startBeforeStartLineInSeconds, Integer endBeforeStartLineInSeconds,
|
||||
Integer startAfterFinishLineInSeconds, Integer endAfterFinishLineInSeconds) {
|
||||
this.polarDataService = polarDataService;
|
||||
this.boatClassJsonSerializer = boatClassJsonSerializer;
|
||||
this.maneuverWithEstimationDataJsonSerializer = maneuverWithEstimationDataJsonSerializer;
|
||||
this.startBeforeStartLineInSeconds = startBeforeStartLineInSeconds;
|
||||
this.endBeforeStartLineInSeconds = endBeforeStartLineInSeconds;
|
||||
this.startAfterFinishLineInSeconds = startAfterFinishLineInSeconds;
|
||||
this.endAfterFinishLineInSeconds = endAfterFinishLineInSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,18 +69,45 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo();
|
||||
TimePoint from = null;
|
||||
TimePoint to = null;
|
||||
if (startBeforeStartLineInSeconds != Integer.MIN_VALUE) {
|
||||
from = trackTimeInfo.getTrackStartTimePoint()
|
||||
.minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L));
|
||||
} else if (startAfterFinishLineInSeconds != Integer.MIN_VALUE) {
|
||||
from = trackTimeInfo.getTrackEndTimePoint()
|
||||
.plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L));
|
||||
}
|
||||
if (endAfterFinishLineInSeconds != Integer.MIN_VALUE) {
|
||||
to = trackTimeInfo.getTrackEndTimePoint()
|
||||
.plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L));
|
||||
} else if (endBeforeStartLineInSeconds != Integer.MIN_VALUE) {
|
||||
to = trackTimeInfo.getTrackStartTimePoint()
|
||||
.minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L));
|
||||
}
|
||||
if (trackTimeInfo != null) {
|
||||
if (from != null ^ to != null) {
|
||||
if (from == null) {
|
||||
from = trackTimeInfo.getTrackStartTimePoint();
|
||||
}
|
||||
if (to == null) {
|
||||
to = trackTimeInfo.getTrackEndTimePoint();
|
||||
}
|
||||
}
|
||||
final JSONObject forCompetitorJson = new JSONObject();
|
||||
byCompetitorJson.add(forCompetitorJson);
|
||||
forCompetitorJson.put(COMPETITOR_NAME, competitor.getName());
|
||||
forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer
|
||||
.serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()));
|
||||
final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray();
|
||||
for (CompleteManeuverCurveWithEstimationData maneuver : getCompleteManeuverCurvesWithEstimationData(
|
||||
trackedRace, competitor)) {
|
||||
Iterable<CompleteManeuverCurveWithEstimationData> completeManeuvers = from != null
|
||||
? getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor, from, to)
|
||||
: getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor);
|
||||
for (CompleteManeuverCurveWithEstimationData maneuver : completeManeuvers) {
|
||||
completeManeuverCurvesWithEstimationData
|
||||
.add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver));
|
||||
}
|
||||
forCompetitorJson.put(FIXES_COUNT_FOR_POLARS, getFixesCountForPolars(trackedRace, competitor));
|
||||
forCompetitorJson.put(MANEUVER_CURVES, completeManeuverCurvesWithEstimationData);
|
||||
Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor)
|
||||
.getAverageIntervalBetweenFixes();
|
||||
@@ -80,17 +130,35 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst
|
||||
}
|
||||
|
||||
private Iterable<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
TrackedRace trackedRace, Competitor competitor) {
|
||||
Iterable<Maneuver> maneuvers = trackedRace.getManeuvers(competitor, false);
|
||||
ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = null;
|
||||
try {
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers);
|
||||
maneuversWithEstimationData = maneuverDetector.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to) {
|
||||
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
List<ManeuverSpot> maneuverSpots = maneuverDetector.detectManeuvers(from, to);
|
||||
List<CompleteManeuverCurve> maneuverCurves = maneuverSpots.stream()
|
||||
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
|
||||
ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
|
||||
maneuverDetector, polarDataService);
|
||||
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetectorWithEstimationData
|
||||
.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
|
||||
return maneuversWithEstimationData;
|
||||
}
|
||||
|
||||
private Iterable<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
|
||||
TrackedRace trackedRace, Competitor competitor) {
|
||||
Iterable<Maneuver> maneuvers = trackedRace.getManeuvers(competitor, false);
|
||||
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
|
||||
maneuverDetector, polarDataService);
|
||||
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetectorWithEstimationData
|
||||
.getCompleteManeuverCurves(maneuvers);
|
||||
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetectorWithEstimationData
|
||||
.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
|
||||
return maneuversWithEstimationData;
|
||||
}
|
||||
|
||||
private long getFixesCountForPolars(TrackedRace trackedRace, Competitor competitor) {
|
||||
BoatClass boatClass = trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass();
|
||||
Long fixesCountForBoatPolars = polarDataService.getFixCountPerBoatClass().get(boatClass);
|
||||
return fixesCountForBoatPolars == null ? 0L : fixesCountForBoatPolars;
|
||||
}
|
||||
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package com.sap.sailing.server.gateway.serialization.impl;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
|
||||
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vladislav Chumak (D069712)
|
||||
*
|
||||
*/
|
||||
public class GpsFixesWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer {
|
||||
public static final String GPS_FIXES = "gpsFixes";
|
||||
public static final String BOAT_CLASS = "boatClass";
|
||||
public static final String COMPETITOR_NAME = "competitorName";
|
||||
public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds";
|
||||
public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters";
|
||||
public static final String START_TIME_POINT = "startUnixTime";
|
||||
public static final String END_TIME_POINT = "endUnixTime";
|
||||
public static final String WIND = "wind";
|
||||
public static final String RELATIVE_BEARING_TO_NEXT_MARK = "relativeBearingToNextMark";
|
||||
public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters";
|
||||
|
||||
private final BoatClassJsonSerializer boatClassJsonSerializer;
|
||||
private final GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer;
|
||||
private final boolean addWind;
|
||||
private final boolean addNextWaypoint;
|
||||
private final ManeuverWindJsonSerializer windJsonSerializer;
|
||||
private final Boolean smoothFixes;
|
||||
private Integer startBeforeStartLineInSeconds;
|
||||
private Integer endBeforeStartLineInSeconds;
|
||||
private Integer startAfterFinishLineInSeconds;
|
||||
private Integer endAfterFinishLineInSeconds;
|
||||
|
||||
public GpsFixesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer,
|
||||
GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer, ManeuverWindJsonSerializer windJsonSerializer,
|
||||
boolean addWind, boolean addNextWaypoint, Boolean smoothFixes, Integer startBeforeStartLineInSeconds,
|
||||
Integer endBeforeStartLineInSeconds, Integer startAfterFinishLineInSeconds,
|
||||
Integer endAfterFinishLineInSeconds) {
|
||||
this.boatClassJsonSerializer = boatClassJsonSerializer;
|
||||
this.gpsFixMovingJsonSerializer = gpsFixMovingJsonSerializer;
|
||||
this.windJsonSerializer = windJsonSerializer;
|
||||
this.addWind = addWind;
|
||||
this.addNextWaypoint = addNextWaypoint;
|
||||
this.smoothFixes = smoothFixes;
|
||||
this.startBeforeStartLineInSeconds = startBeforeStartLineInSeconds;
|
||||
this.endBeforeStartLineInSeconds = endBeforeStartLineInSeconds;
|
||||
this.startAfterFinishLineInSeconds = startAfterFinishLineInSeconds;
|
||||
this.endAfterFinishLineInSeconds = endAfterFinishLineInSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject serialize(TrackedRace trackedRace) {
|
||||
final JSONObject result = new JSONObject();
|
||||
JSONArray byCompetitorJson = new JSONArray();
|
||||
result.put(BYCOMPETITOR, byCompetitorJson);
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
|
||||
ManeuverDetectorWithEstimationDataSupportDecoratorImpl estimationDataSupportDecoratorImpl = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
|
||||
maneuverDetector, null);
|
||||
TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo();
|
||||
TimePoint from = null;
|
||||
TimePoint to = null;
|
||||
if (startBeforeStartLineInSeconds != Integer.MIN_VALUE) {
|
||||
from = trackTimeInfo.getTrackStartTimePoint()
|
||||
.minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L));
|
||||
} else if (startAfterFinishLineInSeconds != Integer.MIN_VALUE) {
|
||||
from = trackTimeInfo.getTrackEndTimePoint()
|
||||
.plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L));
|
||||
} else {
|
||||
from = trackTimeInfo.getTrackStartTimePoint();
|
||||
}
|
||||
if (endAfterFinishLineInSeconds != Integer.MIN_VALUE) {
|
||||
to = trackTimeInfo.getTrackEndTimePoint()
|
||||
.plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L));
|
||||
} else if (endBeforeStartLineInSeconds != Integer.MIN_VALUE) {
|
||||
to = trackTimeInfo.getTrackStartTimePoint()
|
||||
.minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L));
|
||||
} else {
|
||||
to = trackTimeInfo.getTrackEndTimePoint();
|
||||
}
|
||||
if (trackTimeInfo != null) {
|
||||
final JSONObject forCompetitorJson = new JSONObject();
|
||||
byCompetitorJson.add(forCompetitorJson);
|
||||
forCompetitorJson.put(COMPETITOR_NAME, competitor.getName());
|
||||
forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer
|
||||
.serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()));
|
||||
final JSONArray gpsFixesWithEstimationData = new JSONArray();
|
||||
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
for (GPSFixMoving gpsFix : track.getFixes(from, true, to, true)) {
|
||||
JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix);
|
||||
if (addWind) {
|
||||
Wind wind = trackedRace.getWind(gpsFix.getPosition(), gpsFix.getTimePoint());
|
||||
JSONObject serializedWind = wind == null ? null : windJsonSerializer.serialize(wind);
|
||||
serializedGpsFix.put(WIND, serializedWind);
|
||||
}
|
||||
if (addNextWaypoint) {
|
||||
Distance closestDistanceToMark = estimationDataSupportDecoratorImpl
|
||||
.getClosestDistanceToMark(gpsFix.getTimePoint());
|
||||
SpeedWithBearing speedWithBearing = smoothFixes
|
||||
? track.getEstimatedSpeed(gpsFix.getTimePoint()) : gpsFix.getSpeed();
|
||||
Bearing relativeBearingToNextMark = speedWithBearing == null ? null
|
||||
: estimationDataSupportDecoratorImpl.getRelativeBearingToNextMark(
|
||||
gpsFix.getTimePoint(), speedWithBearing.getBearing());
|
||||
serializedGpsFix.put(CLOSEST_DISTANCE_TO_MARK,
|
||||
closestDistanceToMark == null ? null : closestDistanceToMark.getMeters());
|
||||
serializedGpsFix.put(RELATIVE_BEARING_TO_NEXT_MARK,
|
||||
relativeBearingToNextMark == null ? null : relativeBearingToNextMark.getDegrees());
|
||||
}
|
||||
gpsFixesWithEstimationData.add(serializedGpsFix);
|
||||
}
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
forCompetitorJson.put(GPS_FIXES, gpsFixesWithEstimationData);
|
||||
Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor)
|
||||
.getAverageIntervalBetweenFixes();
|
||||
forCompetitorJson.put(AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS,
|
||||
averageIntervalBetweenFixes == null ? 0 : averageIntervalBetweenFixes.asSeconds());
|
||||
Double distanceTravelledInMeters = null;
|
||||
if (trackTimeInfo.getTrackStartTimePoint() != null && trackTimeInfo.getTrackEndTimePoint() != null) {
|
||||
distanceTravelledInMeters = track.getDistanceTraveled(trackTimeInfo.getTrackStartTimePoint(),
|
||||
trackTimeInfo.getTrackEndTimePoint()).getMeters();
|
||||
}
|
||||
forCompetitorJson.put(DISTANCE_TRAVELLED_IN_METERS, distanceTravelledInMeters);
|
||||
forCompetitorJson.put(START_TIME_POINT, trackTimeInfo.getTrackStartTimePoint() == null ? null
|
||||
: trackTimeInfo.getTrackStartTimePoint().asMillis());
|
||||
forCompetitorJson.put(END_TIME_POINT, trackTimeInfo.getTrackEndTimePoint() == null ? null
|
||||
: trackTimeInfo.getTrackEndTimePoint().asMillis());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+58
-3
@@ -85,9 +85,12 @@ import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSe
|
||||
import com.sap.sailing.server.gateway.serialization.impl.DistanceJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.FleetJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.GPSFixJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.GpsFixesWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.ManeuversJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.MarkPassingsJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.NationalityJsonSerializer;
|
||||
@@ -1027,7 +1030,15 @@ public class RegattasResource extends AbstractSailingServerResource {
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
@Path("{regattaname}/races/{racename}/completeManeuverCurvesWithEstimationData")
|
||||
public Response getCompleteManeuverCurvesWithEstimationData(@PathParam("regattaname") String regattaName,
|
||||
@PathParam("racename") String raceName) {
|
||||
@PathParam("racename") String raceName,
|
||||
@QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer startBeforeStartLineInSeconds,
|
||||
@QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer endBeforeStartLineInSeconds,
|
||||
@QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer startAfterFinishLineInSeconds,
|
||||
@QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer endAfterFinishLineInSeconds) {
|
||||
Response response;
|
||||
Regatta regatta = findRegattaByName(regattaName);
|
||||
if (regatta == null) {
|
||||
@@ -1043,11 +1054,55 @@ public class RegattasResource extends AbstractSailingServerResource {
|
||||
} else {
|
||||
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
|
||||
CompleteManeuverCurvesWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurvesWithEstimationDataJsonSerializer(
|
||||
new DetailedBoatClassJsonSerializer(),
|
||||
getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(),
|
||||
new CompleteManeuverCurveWithEstimationDataJsonSerializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
|
||||
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer()));
|
||||
new ManeuverWindJsonSerializer(), new PositionJsonSerializer()),
|
||||
startBeforeStartLineInSeconds, endBeforeStartLineInSeconds, startAfterFinishLineInSeconds,
|
||||
endAfterFinishLineInSeconds);
|
||||
JSONObject jsonMarkPassings = serializer.serialize(trackedRace);
|
||||
String json = jsonMarkPassings.toJSONString();
|
||||
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
@Path("{regattaname}/races/{racename}/gpsFixesWithEstimationData")
|
||||
public Response getGpsFixesWithEstimationData(@PathParam("regattaname") String regattaName,
|
||||
@PathParam("racename") String raceName, @QueryParam("addWind") @DefaultValue("true") Boolean addWind,
|
||||
@QueryParam("addNextWaypoint") @DefaultValue("true") Boolean addNextWaypoint,
|
||||
@QueryParam("smoothFixes") @DefaultValue("true") Boolean smoothFixes,
|
||||
@QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer startBeforeStartLineInSeconds,
|
||||
@QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer endBeforeStartLineInSeconds,
|
||||
@QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer startAfterFinishLineInSeconds,
|
||||
@QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
|
||||
+ "") Integer endAfterFinishLineInSeconds) {
|
||||
Response response;
|
||||
Regatta regatta = findRegattaByName(regattaName);
|
||||
if (regatta == null) {
|
||||
response = Response.status(Status.NOT_FOUND)
|
||||
.entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
|
||||
.type(MediaType.TEXT_PLAIN).build();
|
||||
} else {
|
||||
RaceDefinition race = findRaceByName(regatta, raceName);
|
||||
if (race == null) {
|
||||
response = Response.status(Status.NOT_FOUND)
|
||||
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.")
|
||||
.type(MediaType.TEXT_PLAIN).build();
|
||||
} else {
|
||||
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
|
||||
GpsFixesWithEstimationDataJsonSerializer serializer = new GpsFixesWithEstimationDataJsonSerializer(
|
||||
new DetailedBoatClassJsonSerializer(), new GPSFixMovingJsonSerializer(),
|
||||
new ManeuverWindJsonSerializer(), addWind, addNextWaypoint, smoothFixes,
|
||||
startBeforeStartLineInSeconds, endBeforeStartLineInSeconds, startAfterFinishLineInSeconds,
|
||||
endAfterFinishLineInSeconds);
|
||||
JSONObject jsonMarkPassings = serializer.serialize(trackedRace);
|
||||
String json = jsonMarkPassings.toJSONString();
|
||||
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
|
||||
|
||||
+5
-5
@@ -5,19 +5,19 @@ import com.google.gwt.resources.client.ImageResource;
|
||||
|
||||
public interface DataMiningResources extends ClientBundle {
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/close.png")
|
||||
@Source("com/sap/sse/datamining/ui/images/close.png")
|
||||
ImageResource closeIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/arrow_left.png")
|
||||
@Source("com/sap/sse/datamining/ui/images/arrow_left.png")
|
||||
ImageResource arrowLeftIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/arrow_right.png")
|
||||
@Source("com/sap/sse/datamining/ui/images/arrow_right.png")
|
||||
ImageResource arrowRightIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/plusicon_small.png")
|
||||
@Source("com/sap/sse/datamining/ui/images/plusicon_small.png")
|
||||
ImageResource plusIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/magnifier_small.png")
|
||||
@Source("com/sap/sse/datamining/ui/images/magnifier_small.png")
|
||||
ImageResource searchIcon();
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user