diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java index b4ac93865e0..7b297db56cf 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java @@ -1,7 +1,10 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.Set; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; @@ -13,6 +16,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { private static final long serialVersionUID = -1900955198751393727L; private final Course course; private final LinkedHashMap competitorsById; + private final Set competitors; private final BoatClass boatClass; private final Serializable id; @@ -23,11 +27,12 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { public RaceDefinitionImpl(String name, Course course, BoatClass boatClass, Iterable competitors, Serializable id) { super(name); assert name != null; - this.course = course; + this.competitors = new HashSet<>(); this.competitorsById = new LinkedHashMap<>(); for (Competitor competitor : competitors) { Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor); + this.competitors.add(competitor); if (competitorWithEqualID != null && competitorWithEqualID != competitor) { throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name); } @@ -48,7 +53,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { @Override public Iterable getCompetitors() { - return competitorsById.values(); + return Collections.unmodifiableSet(competitors); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java index 1fd99932582..3182d990e1a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java @@ -99,6 +99,14 @@ public interface ScoreCorrection extends Serializable { */ boolean hasCorrectionFor(RaceColumn raceInLeaderboard); + /** + * Similar to {@link #hasCorrectionFor(RaceColumn)}, but returns true only if score corrections are found + * for any competitor who in raceInLeaderboard is not in a tracked race and hence the fleet assignment + * cannot be determined. This is helpful, e.g., for progress detection. If score corrections are present for such + * untracked competitors then all untracked fleets need to be assumed as finished. + */ + boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard); + /** * @return all race columns for which this score corrections object has at least one correction; note that this * object may hold corrections also for competitors that are not currently returned by diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java index b58f433eb1d..a0891127129 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java @@ -472,19 +472,30 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public boolean hasCorrectionFor(RaceColumn raceInLeaderboard) { + return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ false); + } + + private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard, boolean considerOnlyUntrackedRaces) { for (com.sap.sse.common.Util.Pair correctedScoresKey : correctedScores.keySet()) { - if (correctedScoresKey.getB() == raceInLeaderboard) { + if (correctedScoresKey.getB() == raceInLeaderboard && + (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(correctedScoresKey.getA()) == null)) { return true; } } for (com.sap.sse.common.Util.Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { - if (maxPointsReasonsKey.getB() == raceInLeaderboard) { + if (maxPointsReasonsKey.getB() == raceInLeaderboard && + (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(maxPointsReasonsKey.getA()) == null)) { return true; } } return false; } + @Override + public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard) { + return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ true); + } + @Override public TimePoint getTimePointOfLastCorrectionsValidity() { return timePointOfLastCorrectionsValidity; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java index 79b5eea0e3f..109ab688acc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java @@ -41,7 +41,7 @@ import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; +import com.sap.sailing.domain.leaderboard.ScoreCorrection; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; @@ -517,8 +517,8 @@ public class RaceContext { } } } - SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection(); - if(!hasTrackingForRaceColumn() && scoreCorrection != null && scoreCorrection.hasCorrectionFor(raceColumn)) { + ScoreCorrection scoreCorrection = leaderboard.getScoreCorrection(); + if(trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) { return RaceViewState.FINISHED; } if(startTime != null) { @@ -527,15 +527,6 @@ public class RaceContext { return RaceViewState.PLANNED; } - private boolean hasTrackingForRaceColumn() { - for(Fleet fleetOfRaceColumn : raceColumn.getFleets()) { - if(raceColumn.getTrackedRace(fleetOfRaceColumn) != null) { - return true; - } - } - return false; - } - private Wind checkForWindFixesFromRaceLog() { WindFixesFinder windFixesFinder = new WindFixesFinder(raceLog); List windList = windFixesFinder.analyze(); diff --git a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java index 86e78215568..60fe96a9e8b 100644 --- a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java +++ b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java @@ -34,8 +34,11 @@ import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; @@ -46,6 +49,8 @@ import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindPositionMode; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.server.RacingEventService; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; @@ -97,7 +102,7 @@ public class LeaderboardData extends ExportAction { * * */ - private Element createLeaderboardXML(Leaderboard leaderboard, List competitors, List races, Util.Pair> leaderboardConfidenceAndErrorMessages) { + private Element createLeaderboardXML(Leaderboard leaderboard, List windData, List competitors, List races, Util.Pair> leaderboardConfidenceAndErrorMessages) { Element leaderboardElement = new Element("leaderboard"); addNamedElementWithValue(leaderboardElement, "name", leaderboard.getName()); addNamedElementWithValue(leaderboardElement, "display_name", leaderboard.getDisplayName()); @@ -106,6 +111,9 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(leaderboardElement, "boat_class", getBoatClassName(leaderboard)); leaderboardElement.addContent(createTimedXML("last_modification_", leaderboard.getTimePointOfLatestModification())); leaderboardElement.addContent(createDataConfidenceXML(leaderboardConfidenceAndErrorMessages)); + Element windContainer = new Element("wind"); + windContainer.addContent(windData); + leaderboardElement.addContent(windContainer); leaderboardElement.addContent(competitors); leaderboardElement.addContent(races); return leaderboardElement; @@ -202,6 +210,35 @@ public class LeaderboardData extends ExportAction { return windElements; } + private Element createRaceXMLWithoutRaceData(final Fleet fleet, final RaceColumn column, final Leaderboard leaderboard, int sameDayGroupIndex, int raceCounter, Util.Pair> raceConfidenceAndErrorMessages) throws NoWindException, IOException, ServletException { + Element raceElement = new Element("race"); + addNamedElementWithValue(raceElement, "name", column.getName()+'-'+fleet.getName()); + addNamedElementWithValue(raceElement, "race_index_in_leaderboard", raceCounter); + addNamedElementWithValue(raceElement, "fleet_name", fleet.getName()); + addNamedElementWithValue(raceElement, "same_day_index", sameDayGroupIndex); + addNamedElementWithValue(raceElement, "is_live", "false"); + TimePoint timepointToBeUsed = MillisecondsTimePoint.now(); + List competitors = leaderboard.getCompetitorsFromBestToWorst(column, timepointToBeUsed); + for (Competitor competitorInLeaderboard : competitors) { + MaxPointsReason mpr = leaderboard.getScoreCorrection().getMaxPointsReason(competitorInLeaderboard, column, timepointToBeUsed); + if (mpr != null && !mpr.equals(MaxPointsReason.NONE)) { + // add this competitor to the list to have him evaluated + Element competitorElement = createCompetitorXML(competitorInLeaderboard, leaderboard, /*shortVersion*/ true, null); + Element competitorRaceDataElement = new Element("competitor_race_data"); + MaxPointsReason maxPointsReason = leaderboard.getMaxPointsReason(competitorInLeaderboard, column, timepointToBeUsed); + addNamedElementWithValue(competitorRaceDataElement, "max_points_reason", maxPointsReason.toString()); + boolean isDiscardedForCompetitor = leaderboard.isDiscarded(competitorInLeaderboard, column, timepointToBeUsed); + addNamedElementWithValue(competitorRaceDataElement, "is_discarded", isDiscardedForCompetitor == true ? "true" : "false"); + Double finalRaceScore = leaderboard.getTotalPoints(competitorInLeaderboard, column, timepointToBeUsed); + addNamedElementWithValue(competitorRaceDataElement, "final_race_score", finalRaceScore); + competitorElement.addContent(competitorRaceDataElement); + raceElement.addContent(competitorElement); + raceConfidenceAndErrorMessages = updateConfidence("Competitor " + competitorInLeaderboard.getName() + " has no valid data for this race!", 0.2, raceConfidenceAndErrorMessages); + } + } + return raceElement; + } + /** * Creates elements containing information about a race */ @@ -233,7 +270,7 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(raceElement, "start_of_tracking_time_as_millis", handleValue(race.getStartOfTracking())); addNamedElementWithValue(raceElement, "end_of_tracking_time_as_millis", handleValue(race.getEndOfTracking())); - LineDetails start = race.getStartLine(race.getStartOfTracking()); + LineDetails start = race.getStartLine(race.getStartOfRace()); if (start != null) { if (start.getLength() != null) { addNamedElementWithValue(raceElement, "start_line_length_in_meters", start.getLength().getMeters()); @@ -568,13 +605,16 @@ public class LeaderboardData extends ExportAction { // will kick in } } - if (endOfRaceForLastTrackedRaceInLeaderboard != null) { - timePointOfLatestModification = endOfRaceForLastTrackedRaceInLeaderboard; + // first look for latest modification of leaderboard + // if there is no latest modification then take the last races time + if (leaderboard.getTimePointOfLatestModification() != null) { + timePointOfLatestModification = leaderboard.getTimePointOfLatestModification(); } else { - if (leaderboard.getTimePointOfLatestModification() != null) { - timePointOfLatestModification = leaderboard.getTimePointOfLatestModification(); + if (endOfRaceForLastTrackedRaceInLeaderboard != null) { + timePointOfLatestModification = endOfRaceForLastTrackedRaceInLeaderboard; } else { - timePointOfLatestModification = MillisecondsTimePoint.now(); + // make sure that the timepoint is far in the future - could be that the export is called too early + timePointOfLatestModification = MillisecondsTimePoint.now().plus(Duration.ONE_YEAR); } } Duration totalTimeSailed = leaderboard.getTotalTimeSailed(competitor, timePointOfLatestModification); @@ -708,6 +748,36 @@ public class LeaderboardData extends ExportAction { return legElement; } + private List createFullWindDataXML(TrackedRace race, Fleet fleet, int sameDayIndex) { + List windElements = new ArrayList<>(); + List windSourcesToDeliver = new ArrayList(); + WindSourceImpl windSource = new WindSourceImpl(WindSourceType.COMBINED); + windSourcesToDeliver.add(windSource); + TimePoint fromTimePoint = race.getStartOfRace(); + TimePoint toTimePoint = race.getEndOfRace(); + int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis()) / /*resolutionInMillis*/ 1000*60*5); + WindTrack windTrack = race.getOrCreateWindTrack(windSource); + TimePoint timePoint = fromTimePoint; + for (int i = 0; i < numberOfFixes && toTimePoint != null && timePoint.compareTo(toTimePoint) < 0; i++) { + WindWithConfidence> averagedWindWithConfidence = windTrack + .getAveragedWindWithConfidence(null, timePoint); + if (averagedWindWithConfidence != null) { + Element windElement = new Element("windFix"); + addNamedElementWithValue(windElement, "same_day_index", sameDayIndex); + addNamedElementWithValue(windElement, "race_name", race.getRace().getName()); + addNamedElementWithValue(windElement, "fleet_name", fleet.getName()); + windElement.addContent(createTimedXML("", averagedWindWithConfidence.getObject().getTimePoint())); + addNamedElementWithValue(windElement, "bearingInDegrees", averagedWindWithConfidence.getObject().getBearing().getDegrees()); + addNamedElementWithValue(windElement, "directionFromInDegrees", averagedWindWithConfidence.getObject().getBearing().reverse().getDegrees()); + windElement.addContent(createWindXML("", new SpeedWithConfidenceImpl(new KnotSpeedImpl(averagedWindWithConfidence.getObject().getKnots()), + averagedWindWithConfidence.getConfidence(), toTimePoint))); + windElements.add(windElement); + } + timePoint = new MillisecondsTimePoint(timePoint.asMillis() + 1000*60*5); + } + return windElements; + } + /** * Creates xml elements that indicate confidence. */ @@ -784,24 +854,29 @@ public class LeaderboardData extends ExportAction { private Util.Pair> checkData(TrackedRace race) throws Exception { double simpleConfidence = 1.0; Vector messages = new Vector(); TimePoint dateAtYear2000 = new MillisecondsTimePoint(946681200000l); - if (race.getStartOfRace() == null || race.getStartOfRace().before(dateAtYear2000)) { - messages.add("Start time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); + if (race == null) { + messages.add("Tracked race is null - no race seems to be connected!"); simpleConfidence -= 1; - } - if (race.getEndOfRace() == null || race.getEndOfRace().before(dateAtYear2000)) { - messages.add("End time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); - simpleConfidence -= 1; - } - if (race.getStartOfTracking() == null || race.getStartOfTracking().before(dateAtYear2000)) { - messages.add("Start tracking time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); - simpleConfidence -= 0.2; - } - if (race.getEndOfTracking() == null || race.getEndOfTracking().before(dateAtYear2000)) { - messages.add("End tracking time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); - simpleConfidence -= 0.2; - } - if (race.isLive(MillisecondsTimePoint.now())) { - messages.add("This race is live - data for this race will not be available until the race has been finished!"); + } else { + if (race.getStartOfRace() == null || race.getStartOfRace().before(dateAtYear2000)) { + messages.add("Start time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); + simpleConfidence -= 1; + } + if (race.getEndOfRace() == null || race.getEndOfRace().before(dateAtYear2000)) { + messages.add("End time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); + simpleConfidence -= 1; + } + if (race.getStartOfTracking() == null || race.getStartOfTracking().before(dateAtYear2000)) { + messages.add("Start tracking time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); + simpleConfidence -= 0.2; + } + if (race.getEndOfTracking() == null || race.getEndOfTracking().before(dateAtYear2000)) { + messages.add("End tracking time of race " + race.getRaceIdentifier().getRaceName() + " is either null or not valid!"); + simpleConfidence -= 0.2; + } + if (race.isLive(MillisecondsTimePoint.now())) { + messages.add("This race is live - data for this race will not be available until the race has been finished!"); + } } return new Util.Pair>(simpleConfidence, messages); } @@ -886,24 +961,25 @@ public class LeaderboardData extends ExportAction { TimePoint timeSpent = MillisecondsTimePoint.now(); log.info("Starting XML export of " + leaderboard.getName()); Util.Pair> leaderboardConfidenceAndErrorMessages = checkData(leaderboard); - final List racesElements = new ArrayList(); final List competitorElements = new ArrayList(); - for (Competitor competitor : leaderboard.getAllCompetitors()) { Util.Pair> competitorConfidenceAndErrorMessages = checkData(competitor); competitorElements.add(createCompetitorXML(competitor, leaderboard, /*shortVersion*/ false, competitorConfidenceAndErrorMessages)); } - + List windData = new ArrayList(); TrackedRace raceBefore = null; int sameDayGroupIndex = 0; int raceCounter = 0; for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { for (Fleet fleet : raceColumn.getFleets()) { TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null && trackedRace.hasGPSData()) { + if (trackedRace != null) { sameDayGroupIndex += getSameDayGroupIndex(raceColumn.getTrackedRace(fleet), raceBefore); - TimePoint timeSpentForRace = MillisecondsTimePoint.now(); - Util.Pair> raceConfidenceAndErrorMessages = checkData(trackedRace); - final List legs = new ArrayList(); + } + TimePoint timeSpentForRace = MillisecondsTimePoint.now(); + Util.Pair> raceConfidenceAndErrorMessages = checkData(trackedRace); + final List legs = new ArrayList(); + if (trackedRace != null && trackedRace.hasGPSData()) { + windData.addAll(createFullWindDataXML(trackedRace, fleet, sameDayGroupIndex)); int legCounter = 0; for (TrackedLeg leg : trackedRace.getTrackedLegs()) { Util.Pair> legConfidenceAndErrorMessages = checkData(leg); @@ -914,12 +990,15 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(raceElement, "generation_time_in_milliseconds", elapsedTimeForRace.asMillis()); racesElements.add(raceElement); log.info("Exported complete race " + trackedRace.getRace().getName() + " in " + elapsedTimeForRace.asMillis() + " milliseconds!"); - raceBefore = trackedRace; + } else { + racesElements.add(createRaceXMLWithoutRaceData(fleet, raceColumn, leaderboard, sameDayGroupIndex, ++raceCounter, raceConfidenceAndErrorMessages)); + log.info("Exported empty race in race column " + raceColumn.getName() + " with fleet " + fleet.getName()); } + raceBefore = trackedRace; } } log.info("Finished XML export of leaderboard " + leaderboard.getName() + " in " + MillisecondsTimePoint.now().minus(timeSpent.asMillis()).asMillis() + " milliseconds"); - Element dataToExport = createLeaderboardXML(leaderboard, competitorElements, racesElements, leaderboardConfidenceAndErrorMessages); + Element dataToExport = createLeaderboardXML(leaderboard, windData, competitorElements, racesElements, leaderboardConfidenceAndErrorMessages); sendDocument(dataToExport, leaderboard.getName() + ".xml"); } }