From d2d4abc272bbb87adfce12a49ae505cb998b6311 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Tue, 12 Jun 2018 21:55:02 +0200 Subject: [PATCH 01/19] Adjusted getSpeedWithBearingSteps() to return steps with time points which ALWAYS correspond to non-raw fixes --- .../sailing/domain/tracking/GPSFixTrack.java | 12 ++-- .../domain/tracking/impl/GPSFixTrackImpl.java | 59 +++++-------------- 2 files changed, 21 insertions(+), 50 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java index a91c230a693..4e2674fd8ac 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java @@ -175,11 +175,13 @@ public interface GPSFixTrack 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 diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index b50674511a1..ab0e6249971 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -1171,20 +1171,13 @@ public abstract class GPSFixTrackImpl extends Bearing lastCourse = null; TimePoint lastTimePoint = null; double lastCourseChangeAngleInDegrees = 0; - TimePoint fixTimePointAfterToTimePoint = null; try { lockForRead(); - TimePoint timePoint = fromTimePoint; - for (Iterator 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 iterator = getFixesIterator(currentTimePoint, true); 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 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; } } From 7507ec39e9df3ad2c7ad0acbabd8756bff118235 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Tue, 12 Jun 2018 23:26:31 +0200 Subject: [PATCH 02/19] Improved maneuver splitting at mark passing time points --- .../impl/ManeuverDetectorImpl.java | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 0a32506111d..a7080e68424 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -990,19 +990,50 @@ public class ManeuverDetectorImpl implements ManeuverDetector { SpeedWithBearingStepsIterable speedWithBearingSteps, TimePoint timePoint) { List stepsBefore = new ArrayList<>(); List 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); } From 176afbaf7e9af1c52cbdfa1d67e1ea45bb74da88 Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Fri, 15 Jun 2018 11:15:30 +0200 Subject: [PATCH 03/19] bug4614: created a sortable column that takes a signed value for speed change and maneuverloss in ManeuverTablePanel.java --- .../racemap/maneuver/ManeuverTablePanel.java | 90 +++++++++++++++++-- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index 35c94d9fa89..706446d3333 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -148,27 +148,30 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableMinMaxColumn( +/** + * Creates a sortable column with the absolute value. + * Whereas {@link #createSortableMinMaxColumn()} creates a sortable column with signed values. + */ + private SortableColumn createSortableAbsMinMaxColumn( Function extractor, String title, String unit) { final SortableColumn col = new AbstractSortableColumnWithMinMax( new TextCell(), SortingOrder.ASCENDING) { @@ -236,6 +239,77 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableMinMaxColumn( + Function extractor, String title, String unit) { + final SortableColumn col = new AbstractSortableColumnWithMinMax( + new TextCell(), SortingOrder.ASCENDING) { + final InvertibleComparator comparator = new InvertibleComparatorAdapter() { + @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(o1v, o2v); + } + }; + final HasStringAndDoubleValue dataProvider = new HasStringAndDoubleValue() { + @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 : value; + } + }; + + final MinMaxRenderer renderer = new MinMaxRenderer(dataProvider, comparator); + + @Override + public InvertibleComparator 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(maneuverCellTable.getDataProvider().getList()); + } + }; + col.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + return col; + } private SortableColumn createManeuverTypeColumn() { return new SortableColumn(new TextCell(), SortingOrder.ASCENDING) { From c08ab551ac01798a9d0f1d9089be2f323683f2a9 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Sun, 17 Jun 2018 13:50:31 +0200 Subject: [PATCH 04/19] Refined stable course analysis --- .../impl/ManeuverDetectorImpl.java | 31 +++---------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index a7080e68424..a05eae5ec19 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -72,13 +72,6 @@ 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. */ @@ -1387,25 +1380,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(), @@ -1422,10 +1401,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()) / 2.0; } /** From 8b3e7c40a2933dadebeea3d09ee41cc8a90564b6 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Mon, 18 Jun 2018 00:23:22 +0200 Subject: [PATCH 05/19] Fixed double occurence of the first speed with bearing step --- .../com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index ab0e6249971..f4266d5e422 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -1175,7 +1175,7 @@ public abstract class GPSFixTrackImpl extends lockForRead(); FixType firstFix = getLastFixAtOrBefore(fromTimePoint); TimePoint currentTimePoint = firstFix == null ? fromTimePoint : firstFix.getTimePoint(); - for (Iterator iterator = getFixesIterator(currentTimePoint, true); iterator + for (Iterator iterator = getFixesIterator(currentTimePoint, false); iterator .hasNext(); currentTimePoint = iterator.next().getTimePoint()) { SpeedWithBearing estimatedSpeed = getEstimatedSpeed(currentTimePoint); if (estimatedSpeed != null) { From c4f4d5e8d969f93e5540a4bc3476555780bdc542 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Mon, 18 Jun 2018 00:24:27 +0200 Subject: [PATCH 06/19] Added handling for mark-passing set with finish-line passing only --- .../maneuverdetection/impl/ManeuverDetectorImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index a05eae5ec19..2d5f7d4a5ae 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -534,7 +534,13 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } } if (latestRawFixTimePoint != null) { - return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint); + if(!earliestTrackRecord.equals(latestTrackRecord)) { + return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint); + } + GPSFixMoving firstRawFix = track.getFirstRawFix(); + if(firstRawFix != null) { + return new TrackTimeInfo(firstRawFix.getTimePoint(), latestRawFixTimePoint, latestRawFixTimePoint); + } } } } From c203cc53765ed617614e9a8ebede526170f30fc3 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Mon, 18 Jun 2018 16:30:57 +0200 Subject: [PATCH 07/19] Changed max turning rate threshold for stable course analysis to 1 --- .../maneuverdetection/impl/ManeuverDetectorImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 2d5f7d4a5ae..f248d9dbeae 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -64,7 +64,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 @@ -1302,7 +1302,7 @@ 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 + * {@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. @@ -1354,7 +1354,7 @@ 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 + * {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is * regarded as a stable course. * * @param maneuverMainCurveDetails @@ -1423,7 +1423,7 @@ 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 + * {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is * regarded as a stable course. * * @param maneuverMainCurveDetails @@ -1464,7 +1464,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; From 25244390df049ae5f850c0dbca6fa3617f4ee200 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Mon, 18 Jun 2018 17:56:22 +0200 Subject: [PATCH 08/19] Reduced course change limit for maneuver section extension --- .../domain/maneuverdetection/impl/ManeuverDetectorImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index f248d9dbeae..6572f25f378 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -1408,7 +1408,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return false; } return Math.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) > Math - .abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 2.0; + .abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 3.0; } /** From b4697f98e3e58858f2eb8943f55896b4fdbec967 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Jul 2018 12:05:22 +0200 Subject: [PATCH 09/19] code formatting Change-Id: If53e49429cbabba1c0a452b3cb409f1a34b48862 --- .../racemap/maneuver/ManeuverTablePanel.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index 706446d3333..ff330995cea 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -167,10 +167,11 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableAbsMinMaxColumn( Function extractor, String title, String unit) { final SortableColumn col = new AbstractSortableColumnWithMinMax( @@ -239,6 +240,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createManeuverTypeColumn() { return new SortableColumn(new TextCell(), SortingOrder.ASCENDING) { - @Override public InvertibleComparator getComparator() { return new InvertibleComparatorAdapter() { @@ -343,7 +344,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent col = new SortableColumn( new DateCell(DateTimeFormat.getFormat(PredefinedFormat.TIME_LONG)), SortingOrder.ASCENDING) { @Override @@ -371,7 +371,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent column = new SortableColumn( new AbstractCell() { @Override @@ -406,9 +405,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent(new TextCell(), SortingOrder.ASCENDING) { - @Override public InvertibleComparator getComparator() { return comparator; From 1d61ab61ed94600296125297d3f9e37179c8da5f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Jul 2018 12:35:36 +0200 Subject: [PATCH 10/19] bug4614: introduced SortableMinMaxColumn to factor out the commonalities of columns sorted by absolute and real value Change-Id: I10f0efbb1e63e694e4575b1322124cd8041e82db --- .../racemap/maneuver/ManeuverTablePanel.java | 139 +----------------- .../maneuver/SortableMinMaxColumn.java | 94 ++++++++++++ 2 files changed, 96 insertions(+), 137 deletions(-) create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index ff330995cea..2d233399dbf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -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 maneuverCellTable; @@ -174,71 +167,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableAbsMinMaxColumn( Function extractor, String title, String unit) { - final SortableColumn col = new AbstractSortableColumnWithMinMax( - new TextCell(), SortingOrder.ASCENDING) { - final InvertibleComparator comparatorWithAbs = new InvertibleComparatorAdapter() { - @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 dataProvider = new HasStringAndDoubleValue() { - @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 renderer = new MinMaxRenderer(dataProvider, comparatorWithAbs); - - @Override - public InvertibleComparator 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 */ true); } /** @@ -246,71 +175,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableMinMaxColumn( Function extractor, String title, String unit) { - final SortableColumn col = new AbstractSortableColumnWithMinMax( - new TextCell(), SortingOrder.ASCENDING) { - final InvertibleComparator comparator = new InvertibleComparatorAdapter() { - @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(o1v, o2v); - } - }; - final HasStringAndDoubleValue dataProvider = new HasStringAndDoubleValue() { - @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 : value; - } - }; - - final MinMaxRenderer renderer = new MinMaxRenderer(dataProvider, comparator); - - @Override - public InvertibleComparator 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(maneuverCellTable.getDataProvider().getList()); - } - }; - col.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); - return col; + return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ false); } private SortableColumn createManeuverTypeColumn() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java new file mode 100755 index 00000000000..b6c5d146cff --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java @@ -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 { + private final static NumberFormat TWO_DIGIT_ACCURACY = NumberFormatterFactory.getDecimalFormat(2); + private final String title; + private final String unit; + + final InvertibleComparator comparator; + + final HasStringAndDoubleValue dataProvider; + + final MinMaxRenderer renderer; + + final ListDataProvider maneuverTableListDataProvider; + + public SortableMinMaxColumn(final Function extractor, String title, String unit, + ListDataProvider maneuverTableListDataProvider, boolean absolute) { + super(new TextCell(), SortingOrder.ASCENDING); + this.title = title; + this.unit = unit; + this.maneuverTableListDataProvider = maneuverTableListDataProvider; + this.comparator = new InvertibleComparatorAdapter() { + @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() { + @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 : value; + } + }; + + this.renderer = new MinMaxRenderer(dataProvider, comparator); + this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + } + + @Override + public InvertibleComparator 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()); + } +} \ No newline at end of file From 70c3a037eaaf1f8502241996e364f02487f0c509 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 5 Jul 2018 06:45:30 +0200 Subject: [PATCH 11/19] Added maneuver detection implementation for extremely low GPS sampling rates --- ...eWithEstimationDataRetrievalProcessor.java | 6 +- .../maneuverdetection/ManeuverDetector.java | 46 -- ...uverDetectorWithEstimationDataSupport.java | 61 ++ .../impl/AbstractManeuverDetectorImpl.java | 155 ++++++ ...owGPSSamplingRateManeuverDetectorImpl.java | 156 ++++++ .../impl/ManeuverDetectorImpl.java | 526 ++---------------- ...ithEstimationDataSupportDecoratorImpl.java | 404 ++++++++++++++ .../sap/sailing/domain/tracking/Maneuver.java | 2 +- ...neuverWithCoarseGrainedBoundariesImpl.java | 37 ++ .../domain/tracking/impl/TrackedRaceImpl.java | 20 +- ...urvesWithEstimationDataJsonSerializer.java | 6 +- 11 files changed, 873 insertions(+), 546 deletions(-) create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java index f11191a86e7..f05bdf58be0 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java @@ -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,8 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends List 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)); Iterable maneuvers = trackedRace.getManeuvers(competitor, false); Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); Iterable maneuversWithEstimationData = maneuverDetector diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java index b31f723b038..dc7a9118502 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java @@ -36,50 +36,4 @@ public interface ManeuverDetector { */ List 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 detectManeuvers(Iterable 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 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 getCompleteManeuverCurves(Iterable 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 getCompleteManeuverCurvesWithEstimationData( - Iterable maneuverCurves); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java new file mode 100644 index 00000000000..3724d130b25 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java @@ -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 detectManeuvers(Iterable 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 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 getCompleteManeuverCurves(Iterable 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 getCompleteManeuverCurvesWithEstimationData( + Iterable maneuverCurves); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java new file mode 100644 index 00000000000..a5d63b7e908 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java @@ -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 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 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(); + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java new file mode 100644 index 00000000000..7396d284474 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java @@ -0,0 +1,156 @@ +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.BoatClass; +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); + } + + /** + * Tries to detect maneuvers on the competitor's track based on a number of approximating fixes. The + * fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the + * bearing the boat had at the time of the approximating fix which is taken from the original track. + *

+ * + * The time period assumed for a maneuver duration is taken from the + * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty + * list is returned. Maneuvers can only be expected to be detected if at least three fixes are provided in + * approximatedFixesToAnalyze. For the inner approximating fixes (all except the first and the last + * approximating fix), their course changes according to the approximated path (and not the underlying actual + * tracked fixes) are computed. Subsequent course changes to the same direction are then grouped. Those in closer + * timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes + * that have no surrounding other course changes to group) are grouped into one {@link Maneuver}. + * + * @param ignoreMarkPassings + * When true, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the + * fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful + * for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to + * identify for the time before and after the mark passing which maneuvers exist on which side of the + * passing. + * @param earliestManeuverStart + * maneuver start will not be before this time point; if a maneuver is found whose time point is at or + * after this time point, no matter how close it is, its start regarding speed and course into the + * maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the + * maneuver duration before the maneuver time point were before this time point. + * @param latestManeuverEnd + * maneuver end will not be after this time point; if a maneuver is found whose time point is at or + * before this time point, no matter how close it is, its end regarding speed and course out of the + * maneuver and the leg after the maneuver is not taken from a later time point, even if half the + * maneuver duration after the maneuver time point were after this time point. + * + * @return an empty list if no maneuver is detected for competitor between from and + * to, or else the list of maneuvers detected. + */ + @Override + public List detectManeuvers() { + List result = new ArrayList<>(); + TrackTimeInfo startAndEndTimePoints = getTrackTimeInfo(); + if (startAndEndTimePoints != null) { + ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace, + competitor); + Iterable approximatedFixes = approximatedFixesCalculator.approximate( + startAndEndTimePoints.getTrackStartTimePoint(), startAndEndTimePoints.getTrackEndTimePoint()); + if (Util.size(approximatedFixes) > 2) { + Iterator 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; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 6572f25f378..e7d32f9abb1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -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()); @@ -72,28 +67,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector { */ private static final double MIN_ANGULAR_VELOCITY_FOR_MAIN_CURVE_BOUNDARIES_IN_DEGREES_PER_SECOND = 0.2; - /** - * 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 track; - /** * Constructor for unit tests only. */ public ManeuverDetectorImpl() { - trackedRace = null; - competitor = null; - track = null; + super(null, null); } /** @@ -106,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 @@ -116,355 +92,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return getAllManeuversFromManeuverSpots(detectManeuverSpots()); } - @Override - public List detectManeuvers(Iterable maneuverCurves) { - List 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 detectCompleteManeuverCurves() { - List maneuverSpots = detectManeuverSpots(); - return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null) - .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); - } - - @Override - public List getCompleteManeuverCurves(Iterable maneuvers) { - List 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 getCompleteManeuverCurvesWithEstimationData( - Iterable maneuverCurves) { - List 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 durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( - previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint() - : previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() - .getTimePointAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); - Pair 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 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 @@ -483,70 +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 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; - } - /** * Detects the maneuver spots with corresponding maneuvers within provided time frame. See step 1ff. in * {@link ManeuverDetector} description. @@ -566,12 +129,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * to, or else the list of maneuver spots with corresponding maneuvers detected. */ protected List detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) { - return detectManeuvers( - trackedRace.approximate(competitor, - trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass() - .getMaximumDistanceForCourseApproximation(), - earliestManeuverStart, latestManeuverEnd), - earliestManeuverStart, latestManeuverEnd); + ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace, + competitor); + Iterable approximatedFixes = approximatedFixesCalculator.approximate(earliestManeuverStart, + latestManeuverEnd); + return detectManeuvers(approximatedFixes, earliestManeuverStart, latestManeuverEnd); } /** @@ -621,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 @@ -660,6 +215,16 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return true; } + protected List getAllManeuversFromManeuverSpots(List maneuverSpots) { + List 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 @@ -894,14 +459,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } else { Pair 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 maneuverUnstableCourseAndSpeedBoundariesPair = splitManeuverCurveWithStableSpeedAndCourseByTimePoint( maneuverUnstableCourseAndSpeedBoundaries, mainCurves.getA(), mainCurves.getB(), @@ -1155,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 track = trackedRace.getTrack(competitor); SpeedWithBearing speedWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore(); SpeedWithBearing speedAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter(); @@ -1192,16 +750,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return approximateManeuverDuration.divide(2); } - protected List getAllManeuversFromManeuverSpots(List maneuverSpots) { - List maneuvers = new ArrayList<>(); - for (ManeuverSpot maneuverSpot : maneuverSpots) { - for (Maneuver maneuver : maneuverSpot.getManeuvers()) { - maneuvers.add(maneuver); - } - } - return maneuvers; - } - /** * Starting at timePointBeforeManeuver, and assuming that the group of * approximatedFixesAndCourseChanges contains at least a tack and a jibe, finds the approximated fix's @@ -1252,6 +800,9 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } } } + if(result == null) { + System.out.println("a"); + } return result; } @@ -1271,8 +822,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, @@ -1285,7 +836,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); @@ -1302,10 +853,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_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. + * {@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 @@ -1354,8 +905,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_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_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 @@ -1423,8 +974,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_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_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 @@ -1776,11 +1327,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(); - } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java new file mode 100644 index 00000000000..689df4a4736 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -0,0 +1,404 @@ +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.Mark; +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.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 ManeuverDetectorImpl maneuverDetector; + + public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector) { + this.maneuverDetector = maneuverDetector; + } + + @Override + public List detectManeuvers() { + return maneuverDetector.detectManeuvers(); + } + + @Override + public List detectManeuvers(Iterable maneuverCurves) { + List 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 detectCompleteManeuverCurves() { + List maneuverSpots = maneuverDetector.detectManeuverSpots(); + return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null) + .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); + } + + @Override + public List getCompleteManeuverCurves(Iterable maneuvers) { + List 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 getCompleteManeuverCurvesWithEstimationData( + Iterable maneuverCurves) { + List 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 durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( + previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint() + : previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() + .getTimePointAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); + Pair 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()); + 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 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. + */ + private 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) { + Waypoint nextWaypoint = legAfter.getLeg().getTo(); + for (Mark mark : nextWaypoint.getMarks()) { + Position nextMarkPosition = markPositionAtTimePointCache.getEstimatedPosition(mark); + Position maneuverEndPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false); + 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; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java index 7011612e984..0bc4bdb5b2c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java @@ -174,5 +174,5 @@ public interface Maneuver extends GPSFix { */ @Statistic(messageKey = "AvgTurningRateInDegreesPerSecond", resultDecimals = 4) double getAvgTurningRateInDegreesPerSecond(); - + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java new file mode 100644 index 00000000000..328e073e972 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java @@ -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: + *

    + *
  • Maneuver loss
  • + *
  • Max. and Avg. Turning rate
  • + *
  • Time point before and after of all maneuver boundaries
  • + *
  • Lowest speed within all maneuver boundaries
  • + *
+ * 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(); + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index d80b0c45e6a..2760bf877ca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -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 computeCacheUpdate(Competitor competitor, EmptyUpdateInterval updateInterval) throws NoWindException { - IncrementalManeuverDetector maneuverDetector = maneuverDetectorPerCompetitorCache - .getValue(competitor); - List 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 maneuvers = computeManeuvers(competitor, maneuverDetector); + return maneuvers; + } else { + return Collections.emptyList(); + } } }, /* nameForLocks */"Maneuver cache for race " + getRace().getName()); } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java index 9a0def79beb..b155633069e 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java @@ -6,8 +6,9 @@ import org.json.simple.JSONObject; 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.TrackTimeInfo; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; import com.sap.sailing.domain.tracking.GPSFixTrack; @@ -82,7 +83,8 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst private Iterable getCompleteManeuverCurvesWithEstimationData( TrackedRace trackedRace, Competitor competitor) { Iterable maneuvers = trackedRace.getManeuvers(competitor, false); - ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + new ManeuverDetectorImpl(trackedRace, competitor)); Iterable maneuversWithEstimationData = null; try { Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); From 5da71b27b43f686acce8eecc2ee83ccb1382536f Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 5 Jul 2018 06:57:30 +0200 Subject: [PATCH 12/19] Removed outdated javadoc --- ...owGPSSamplingRateManeuverDetectorImpl.java | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java index 7396d284474..26de57c6b24 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.CourseChange; import com.sap.sailing.domain.common.ManeuverType; @@ -37,41 +36,6 @@ public class LowGPSSamplingRateManeuverDetectorImpl extends AbstractManeuverDete super(trackedRace, competitor); } - /** - * Tries to detect maneuvers on the competitor's track based on a number of approximating fixes. The - * fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the - * bearing the boat had at the time of the approximating fix which is taken from the original track. - *

- * - * The time period assumed for a maneuver duration is taken from the - * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty - * list is returned. Maneuvers can only be expected to be detected if at least three fixes are provided in - * approximatedFixesToAnalyze. For the inner approximating fixes (all except the first and the last - * approximating fix), their course changes according to the approximated path (and not the underlying actual - * tracked fixes) are computed. Subsequent course changes to the same direction are then grouped. Those in closer - * timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes - * that have no surrounding other course changes to group) are grouped into one {@link Maneuver}. - * - * @param ignoreMarkPassings - * When true, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the - * fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful - * for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to - * identify for the time before and after the mark passing which maneuvers exist on which side of the - * passing. - * @param earliestManeuverStart - * maneuver start will not be before this time point; if a maneuver is found whose time point is at or - * after this time point, no matter how close it is, its start regarding speed and course into the - * maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the - * maneuver duration before the maneuver time point were before this time point. - * @param latestManeuverEnd - * maneuver end will not be after this time point; if a maneuver is found whose time point is at or - * before this time point, no matter how close it is, its end regarding speed and course out of the - * maneuver and the leg after the maneuver is not taken from a later time point, even if half the - * maneuver duration after the maneuver time point were after this time point. - * - * @return an empty list if no maneuver is detected for competitor between from and - * to, or else the list of maneuvers detected. - */ @Override public List detectManeuvers() { List result = new ArrayList<>(); From 316ccc3b438136561905d74262a70f8c764927b1 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 5 Jul 2018 10:52:05 +0200 Subject: [PATCH 13/19] Added polar feature to exported estimation data + corrected polars bugs --- ...eWithEstimationDataRetrievalProcessor.java | 3 +- ...mpleteManeuverCurveWithEstimationData.java | 7 ++ ...teManeuverCurveWithEstimationDataImpl.java | 26 ++++++- ...ithEstimationDataSupportDecoratorImpl.java | 76 ++++++++++++++++++- .../domain/polars/PolarDataService.java | 8 ++ .../jaxrs/api/test/PolarDataResourceTest.java | 2 +- .../polars/impl/PolarDataServiceImpl.java | 23 ++++-- .../mining/AngleAndSpeedRegression.java | 3 +- ...nDataSerializationDeserializationTest.java | 22 +++++- ...rveWithEstimationDataJsonDeserializer.java | 16 +++- ...CurveWithEstimationDataJsonSerializer.java | 9 +++ ...urvesWithEstimationDataJsonSerializer.java | 17 ++++- .../gateway/jaxrs/api/RegattasResource.java | 2 +- 13 files changed, 193 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java index f05bdf58be0..eff334d61f5 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java @@ -47,7 +47,8 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace(); Competitor competitor = element.getCompetitor(); ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( - new ManeuverDetectorImpl(trackedRace, competitor)); + new ManeuverDetectorImpl(trackedRace, competitor), + element.getTrackedRaceContext().getLeaderboardContext().getPolarDataService()); Iterable maneuvers = trackedRace.getManeuvers(competitor, false); Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); Iterable maneuversWithEstimationData = maneuverDetector diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java index bd41ca3d81a..9738718cca4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java @@ -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. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java index 38ac9040bbc..89c43715903 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java @@ -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; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java index 689df4a4736..1281b2f14f3 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -4,7 +4,9 @@ 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; @@ -17,6 +19,7 @@ import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseA 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; @@ -44,10 +47,13 @@ import com.sap.sse.common.impl.DegreeBearingImpl; public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl implements ManeuverDetectorWithEstimationDataSupport { - private ManeuverDetectorImpl maneuverDetector; + private final ManeuverDetectorImpl maneuverDetector; + private final PolarDataService polarDataService; - public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector) { + public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector, + PolarDataService polarDataService) { this.maneuverDetector = maneuverDetector; + this.polarDataService = polarDataService; } @Override @@ -348,10 +354,72 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl 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 closestTackTwa = polarDataService.getClosestTwaTws(ManeuverType.TACK, + boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass); + SpeedWithBearingWithConfidence 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()); + relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing(), closestDistanceToMark, + deviationFromTackAngle, deviationFromJibeAngle); + } + + private 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; } /** @@ -381,10 +449,10 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl 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); - Position maneuverEndPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false); Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition); Bearing resultCandidate = absoluteBearing.getDifferenceTo(boatCourse); if (result == null) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java index 0e1c1ce4e70..231fd9b68ce 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java @@ -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 consumer) throws InterruptedException; + + Map getFixCountPerBoatClass(); + + SpeedWithBearingWithConfidence getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart, + double courseChangeDeg, BoatClass boatClass); + + double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType); } diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java index 0ebfeff56d2..5ec38c3b121 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java @@ -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)); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 56b91be5abc..a6b97c92076 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -177,7 +177,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT return result; } - private SpeedWithBearingWithConfidence getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart, + @Override + public SpeedWithBearingWithConfidence 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 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 bearingWithConfidence = new BearingWithConfidenceImpl(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 getFixCointPerBoatClass() { + @Override + public Map getFixCountPerBoatClass() { return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getFixCountPerBoatClass(); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index 4aec93bf628..258bc9a4ef9 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -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; diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java index 779585de9d7..b4c77ee8dd9 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java @@ -132,11 +132,15 @@ public class EstimationDataSerializationDeserializationTest { 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(), @@ -148,6 +152,11 @@ public class EstimationDataSerializationDeserializationTest { new WindJsonDeserializer(new PositionJsonDeserializer()), 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()); @@ -303,11 +312,15 @@ 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(), @@ -319,6 +332,11 @@ public class EstimationDataSerializationDeserializationTest { new WindJsonDeserializer(new PositionJsonDeserializer()), 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()); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java index 6c7dfe3c4b0..0408e37fd08 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java @@ -4,6 +4,7 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData; @@ -12,6 +13,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; /** @@ -59,16 +61,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; diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java index c904b298a51..5c3ae7b119d 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java @@ -23,6 +23,9 @@ 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; @@ -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; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java index b155633069e..6bb7a8fb7d6 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java @@ -3,6 +3,7 @@ package com.sap.sailing.server.gateway.serialization.impl; 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; @@ -10,6 +11,7 @@ import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDa 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.polars.PolarDataService; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Maneuver; @@ -29,12 +31,16 @@ 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; - public CompleteManeuverCurvesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer, + public CompleteManeuverCurvesWithEstimationDataJsonSerializer(PolarDataService polarDataService, + BoatClassJsonSerializer boatClassJsonSerializer, CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer) { + this.polarDataService = polarDataService; this.boatClassJsonSerializer = boatClassJsonSerializer; this.maneuverWithEstimationDataJsonSerializer = maneuverWithEstimationDataJsonSerializer; } @@ -59,6 +65,7 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst 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(); @@ -84,7 +91,7 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst TrackedRace trackedRace, Competitor competitor) { Iterable maneuvers = trackedRace.getManeuvers(competitor, false); ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( - new ManeuverDetectorImpl(trackedRace, competitor)); + new ManeuverDetectorImpl(trackedRace, competitor), polarDataService); Iterable maneuversWithEstimationData = null; try { Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); @@ -95,4 +102,10 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst 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; + } + } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 15c76506ba3..6ae0be5b38d 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -1043,7 +1043,7 @@ 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(), From e94ca51c1576788ebdd60b64a0b517843cf129d0 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 5 Jul 2018 12:37:32 +0200 Subject: [PATCH 14/19] Added export for gps fixes with estimation data --- .../impl/ManeuverDetectorImpl.java | 3 - ...ithEstimationDataSupportDecoratorImpl.java | 4 +- ...nDataSerializationDeserializationTest.java | 21 ++-- ...rveWithEstimationDataJsonDeserializer.java | 10 +- .../impl/ManeuverWindJsonDeserializer.java | 29 +++++ ...CurveWithEstimationDataJsonSerializer.java | 4 +- ...FixesWithEstimationDataJsonSerializer.java | 119 ++++++++++++++++++ .../gateway/jaxrs/api/RegattasResource.java | 37 +++++- 8 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java create mode 100644 java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index e7d32f9abb1..0d77148b553 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -800,9 +800,6 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { } } } - if(result == null) { - System.out.println("a"); - } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java index 1281b2f14f3..00d7e544997 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -385,7 +385,7 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl deviationFromTackAngle, deviationFromJibeAngle); } - private Distance getClosestDistanceToMark(TimePoint timePoint) { + public Distance getClosestDistanceToMark(TimePoint timePoint) { NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache( maneuverDetector.trackedRace, timePoint); Distance result = null; @@ -442,7 +442,7 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl * 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) { + public Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) { NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache( maneuverDetector.trackedRace, timePoint); Bearing result = null; diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java index b4c77ee8dd9..fa01070bb1b 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java @@ -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,12 +120,9 @@ 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; @@ -144,12 +141,12 @@ public class EstimationDataSerializationDeserializationTest { 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, @@ -165,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); @@ -324,12 +321,12 @@ public class EstimationDataSerializationDeserializationTest { 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, diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java index 0408e37fd08..886f0e3c745 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java @@ -3,8 +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; @@ -26,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; @@ -50,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( diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java new file mode 100644 index 00000000000..ab20cd8fee8 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java @@ -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 { + + 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; + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java index 5c3ae7b119d..e1b807cf388 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java @@ -29,13 +29,13 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer 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; diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java new file mode 100644 index 00000000000..4ad7c938008 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java @@ -0,0 +1,119 @@ +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; + +/** + * + * @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; + + public GpsFixesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer, + GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer, ManeuverWindJsonSerializer windJsonSerializer, + boolean addWind, boolean addNextWaypoint, Boolean smoothFixes) { + this.boatClassJsonSerializer = boatClassJsonSerializer; + this.gpsFixMovingJsonSerializer = gpsFixMovingJsonSerializer; + this.windJsonSerializer = windJsonSerializer; + this.addWind = addWind; + this.addNextWaypoint = addNextWaypoint; + this.smoothFixes = smoothFixes; + } + + @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(); + 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 track = trackedRace.getTrack(competitor); + track.lockForRead(); + try { + for (GPSFixMoving gpsFix : track.getFixes()) { + 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; + } + +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 6ae0be5b38d..c2e4743c7dd 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -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; @@ -1047,7 +1050,39 @@ public class RegattasResource extends AbstractSailingServerResource { new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), - new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer())); + new ManeuverWindJsonSerializer(), new PositionJsonSerializer())); + 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) { + 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); JSONObject jsonMarkPassings = serializer.serialize(trackedRace); String json = jsonMarkPassings.toJSONString(); return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); From 02a2ff00c9d1ddd7b023c571bac738e68255f5d5 Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Thu, 5 Jul 2018 13:06:39 +0200 Subject: [PATCH 15/19] bug4614: Changed getDoubleValue() in SortableMinMaxColumn to return the desired value according to the rendering order. --- .../racemap/maneuver/ManeuverTablePanel.java | 10 +++--- .../maneuver/SortableMinMaxColumn.java | 2 +- .../gwt/ui/leaderboard/MinMaxRenderer.java | 31 ++++++++++--------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java index 2d233399dbf..48a9d511e1e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTablePanel.java @@ -141,17 +141,17 @@ public class ManeuverTablePanel extends AbstractCompositeComponent { protected static final String BACKGROUND_BAR_STYLE_GOOD = "minMaxBackgroundBarGood"; private final HasStringAndDoubleValue valueProvider; + /** used to determine minimum and maximum values for the rendered bars.*/ private final Comparator comparator; private Double minimumValue; private Double maximumValue; @@ -119,7 +120,8 @@ public class MinMaxRenderer { * @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 { percentage = (int) (minBarLength + (100. - minBarLength) * (value - getMinimumDouble()) / (getMaximumDouble() - getMinimumDouble())); } - - } + } return percentage; - } private Double getMinimumDouble() { @@ -149,23 +149,24 @@ public class MinMaxRenderer { * The values of {@link LeaderboardRowDTO}s to determine the minimum and maximum values for. */ public void updateMinMax(Iterable 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); } } From 24f922bde2d3a0557c07be7dbe1077411c8027b6 Mon Sep 17 00:00:00 2001 From: Vladislav Chumak Date: Thu, 5 Jul 2018 14:05:23 +0200 Subject: [PATCH 16/19] Added parameters to get maneuvers and gps fixes before and after start/finish line --- .../impl/ManeuverDetectorImpl.java | 2 +- ...urvesWithEstimationDataJsonSerializer.java | 77 ++++++++++++++++--- ...FixesWithEstimationDataJsonSerializer.java | 36 ++++++++- .../gateway/jaxrs/api/RegattasResource.java | 28 ++++++- 4 files changed, 124 insertions(+), 19 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 0d77148b553..b4068b215f9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -128,7 +128,7 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { * @return an empty list if no maneuver spots are detected for competitor between from and * to, or else the list of maneuver spots with corresponding maneuvers detected. */ - protected List detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) { + public List detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) { ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace, competitor); Iterable approximatedFixes = approximatedFixesCalculator.approximate(earliestManeuverStart, diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java index 6bb7a8fb7d6..357d034b0d4 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java @@ -1,5 +1,8 @@ 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; @@ -10,6 +13,7 @@ import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimat 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; @@ -17,6 +21,8 @@ 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; /** * @@ -36,13 +42,23 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst 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(PolarDataService polarDataService, BoatClassJsonSerializer boatClassJsonSerializer, - CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer) { + 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 @@ -53,15 +69,41 @@ 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 completeManeuvers = from != null + ? getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor, from, to) + : getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor); + for (CompleteManeuverCurveWithEstimationData maneuver : completeManeuvers) { completeManeuverCurvesWithEstimationData .add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver)); } @@ -87,18 +129,29 @@ public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends Abst return result; } + private Iterable getCompleteManeuverCurvesWithEstimationData( + TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to) { + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + List maneuverSpots = maneuverDetector.detectManeuvers(from, to); + List maneuverCurves = maneuverSpots.stream() + .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); + ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + maneuverDetector, polarDataService); + Iterable maneuversWithEstimationData = maneuverDetectorWithEstimationData + .getCompleteManeuverCurvesWithEstimationData(maneuverCurves); + return maneuversWithEstimationData; + } + private Iterable getCompleteManeuverCurvesWithEstimationData( TrackedRace trackedRace, Competitor competitor) { Iterable maneuvers = trackedRace.getManeuvers(competitor, false); - ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( - new ManeuverDetectorImpl(trackedRace, competitor), polarDataService); - Iterable maneuversWithEstimationData = null; - try { - Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); - maneuversWithEstimationData = maneuverDetector.getCompleteManeuverCurvesWithEstimationData(maneuverCurves); - } catch (Exception e) { - e.printStackTrace(); - } + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + maneuverDetector, polarDataService); + Iterable maneuverCurves = maneuverDetectorWithEstimationData + .getCompleteManeuverCurves(maneuvers); + Iterable maneuversWithEstimationData = maneuverDetectorWithEstimationData + .getCompleteManeuverCurvesWithEstimationData(maneuverCurves); return maneuversWithEstimationData; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java index 4ad7c938008..06cddcbfdd3 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java @@ -15,6 +15,8 @@ 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; /** * @@ -39,16 +41,26 @@ public class GpsFixesWithEstimationDataJsonSerializer extends AbstractTrackedRac 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) { + 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 @@ -61,6 +73,26 @@ public class GpsFixesWithEstimationDataJsonSerializer extends AbstractTrackedRac 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); @@ -71,7 +103,7 @@ public class GpsFixesWithEstimationDataJsonSerializer extends AbstractTrackedRac GPSFixTrack track = trackedRace.getTrack(competitor); track.lockForRead(); try { - for (GPSFixMoving gpsFix : track.getFixes()) { + for (GPSFixMoving gpsFix : track.getFixes(from, true, to, true)) { JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix); if (addWind) { Wind wind = trackedRace.getWind(gpsFix.getPosition(), gpsFix.getTimePoint()); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index c2e4743c7dd..8215e8819d9 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -1030,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) { @@ -1050,7 +1058,9 @@ public class RegattasResource extends AbstractSailingServerResource { new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), - new ManeuverWindJsonSerializer(), 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(); @@ -1065,7 +1075,15 @@ public class RegattasResource extends AbstractSailingServerResource { 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("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) { @@ -1082,7 +1100,9 @@ public class RegattasResource extends AbstractSailingServerResource { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); GpsFixesWithEstimationDataJsonSerializer serializer = new GpsFixesWithEstimationDataJsonSerializer( new DetailedBoatClassJsonSerializer(), new GPSFixMovingJsonSerializer(), - new ManeuverWindJsonSerializer(), addWind, addNextWaypoint, smoothFixes); + 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(); From 008bd93fec706282153f39d6b8ac5b077f29b793 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 5 Jul 2018 18:04:34 +0200 Subject: [PATCH 17/19] First attempt to correct the maneuver count calculations of TrackedRaceOfCompetitor --- .../data/RaceOfCompetitorWithContext.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java index e1b5013233e..e3258e846e6 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java @@ -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++; } From cff922c2c0d8af7b4fbcecd898ae44ada4d89b9c Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Fri, 6 Jul 2018 12:07:42 +0200 Subject: [PATCH 18/19] Fixed the source paths of the DataMiningResources --- .../ui/client/resources/DataMiningResources.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java index a3bb8df0fb2..81122a16629 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java @@ -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(); } From b09437e1a7d516bce68d9b4a20339c2e5d475c2e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 8 Jul 2018 23:20:12 +0200 Subject: [PATCH 19/19] catch Throwable instead of Exception to also catch AssertionErrors --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 19180b1b143..3eeba3fab02 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -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()