diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index d01e92bc82e..adad160a109 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -87,62 +87,66 @@ public class MarkPassingCalculator { private class Listen implements Runnable { @Override public void run() { - logger.fine("MarkPassingCalculator is listening"); - boolean finished = false; - Map> competitorFixes = new HashMap<>(); - Map> markFixes = new HashMap<>(); - List addedWaypoints = new ArrayList<>(); - List removedWaypoints = new ArrayList<>(); - Integer smallestChangedWaypointIndex = null; - List> fixedMarkPassings = new ArrayList<>(); - List> removedFixedMarkPassings = new ArrayList<>(); - List> suppressedMarkPassings = new ArrayList<>(); - List unsuppressedMarkPassings = new ArrayList<>(); - while (!finished) { - logger.finer("MPC is checking the queue"); - List allNewFixInsertions = new ArrayList<>(); - try { - allNewFixInsertions.add(listener.getQueue().take()); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "MarkPassingCalculator threw exception " + e.getMessage() - + " while waiting for new GPSFixes"); - } - listener.getQueue().drainTo(allNewFixInsertions); - logger.finer("MPC recieved "+ allNewFixInsertions.size()+" new updates."); - for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { - if (listener.isEndMarker(fixInsertion)) { - logger.info("Stopping "+MarkPassingCalculator.this+"'s listener"); - finished = true; - } else { - fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, - smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, - suppressedMarkPassings, unsuppressedMarkPassings); + try { + logger.fine("MarkPassingCalculator is listening"); + boolean finished = false; + Map> competitorFixes = new HashMap<>(); + Map> markFixes = new HashMap<>(); + List addedWaypoints = new ArrayList<>(); + List removedWaypoints = new ArrayList<>(); + Integer smallestChangedWaypointIndex = null; + List> fixedMarkPassings = new ArrayList<>(); + List> removedFixedMarkPassings = new ArrayList<>(); + List> suppressedMarkPassings = new ArrayList<>(); + List unsuppressedMarkPassings = new ArrayList<>(); + while (!finished) { + logger.finer("MPC is checking the queue"); + List allNewFixInsertions = new ArrayList<>(); + try { + allNewFixInsertions.add(listener.getQueue().take()); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "MarkPassingCalculator threw exception " + e.getMessage() + + " while waiting for new GPSFixes"); } - } - if (!suspended) { - if (smallestChangedWaypointIndex != null) { - Map, List>> candidateDeltas = finder - .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex); - chooser.removeWaypoints(removedWaypoints); - chooser.addWaypoints(addedWaypoints); - for (Entry, List>> entry : candidateDeltas - .entrySet()) { - Util.Pair, List> pair = entry.getValue(); - chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + listener.getQueue().drainTo(allNewFixInsertions); + logger.finer("MPC recieved "+ allNewFixInsertions.size()+" new updates."); + for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { + if (listener.isEndMarker(fixInsertion)) { + logger.info("Stopping "+MarkPassingCalculator.this+"'s listener"); + finished = true; + } else { + fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, + smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, + suppressedMarkPassings, unsuppressedMarkPassings); } } - updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, - unsuppressedMarkPassings); - computeMarkPasses(competitorFixes, markFixes); - competitorFixes.clear(); - markFixes.clear(); - addedWaypoints.clear(); - removedWaypoints.clear(); - fixedMarkPassings.clear(); - removedFixedMarkPassings.clear(); - suppressedMarkPassings.clear(); - unsuppressedMarkPassings.clear(); + if (!suspended) { + if (smallestChangedWaypointIndex != null) { + Map, List>> candidateDeltas = finder + .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex); + chooser.removeWaypoints(removedWaypoints); + chooser.addWaypoints(addedWaypoints); + for (Entry, List>> entry : candidateDeltas + .entrySet()) { + Util.Pair, List> pair = entry.getValue(); + chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + } + } + updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, + unsuppressedMarkPassings); + computeMarkPasses(competitorFixes, markFixes); + competitorFixes.clear(); + markFixes.clear(); + addedWaypoints.clear(); + removedWaypoints.clear(); + fixedMarkPassings.clear(); + removedFixedMarkPassings.clear(); + suppressedMarkPassings.clear(); + unsuppressedMarkPassings.clear(); + } } + } finally { + logger.info("MarkPassingCalculator Listen thread terminating"); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java index ce99464f99a..e5f86a6a9ba 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.markpassingcalculation.impl; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -76,7 +77,7 @@ public class CandidateChooserImpl implements CandidateChooser { candidates = new HashMap<>(); List startAndEnd = Arrays.asList(start, end); for (Competitor c : race.getRace().getCompetitors()) { - candidates.put(c, new TreeSet()); + candidates.put(c, Collections.synchronizedSet(new TreeSet())); currentMarkPasses.put(c, new HashMap()); TreeSet fixedPasses = new TreeSet(new Comparator() { @Override @@ -172,69 +173,72 @@ public class CandidateChooserImpl implements CandidateChooser { final Boolean isGateStart = race.isGateStart(); Map> edges = allEdges.get(c); for (Candidate newCan : newCandidates) { - for (Candidate oldCan : candidates.get(c)) { - final Candidate early; - final Candidate late; - if (oldCan.getOneBasedIndexOfWaypoint() < newCan.getOneBasedIndexOfWaypoint()) { - early = oldCan; - late = newCan; - } else if (oldCan.getOneBasedIndexOfWaypoint() > newCan.getOneBasedIndexOfWaypoint()) { - late = oldCan; - early = newCan; - } else { - continue; // don't create edge from/to same waypoint - } - - final double estimatedDistanceProbability; - final double startTimingProbability; - if (early == start) { - // An edge starting at the start proxy node. If the late candidate is for a start mark passing, - // determine a probability not based on distance traveled but based on the - // time difference between scheduled start time and candidate's time point. If the "late" candidate - // is not for the start mark/line, meaning that mark passings including the actual start are - // skipped, as usual use getDistanceEstimationBasedProbability assuming a start mark passing at - // the race's start time. - if (isGateStart == Boolean.TRUE || start.getTimePoint() == null) { // TODO for gate start read gate timing and scale probability accordingly - startTimingProbability = 1; // no start time point known; all candidate time points equally likely - estimatedDistanceProbability = 1; // can't tell distance sailed either because we don't know the start time + final Set competitorCandidates = candidates.get(c); + synchronized (competitorCandidates) { + for (Candidate oldCan : competitorCandidates) { + final Candidate early; + final Candidate late; + if (oldCan.getOneBasedIndexOfWaypoint() < newCan.getOneBasedIndexOfWaypoint()) { + early = oldCan; + late = newCan; + } else if (oldCan.getOneBasedIndexOfWaypoint() > newCan.getOneBasedIndexOfWaypoint()) { + late = oldCan; + early = newCan; } else { - // no gate start and we know the race start time - if (late.getWaypoint() == race.getRace().getCourse().getFirstWaypoint()) { - // no skips; going from the start proxy node to a candidate for the start mark passing; - // calculate the probability for the start being the start given its timing and multiply - // with the estimation for the distance-based probability: - final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint() - .plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint()); - // Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time - // off means 1/3, and so on - startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME - / (double) (MILLISECONDS_BEFORE_STARTTIME + Math - .abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis())); + continue; // don't create edge from/to same waypoint + } + + final double estimatedDistanceProbability; + final double startTimingProbability; + if (early == start) { + // An edge starting at the start proxy node. If the late candidate is for a start mark passing, + // determine a probability not based on distance traveled but based on the + // time difference between scheduled start time and candidate's time point. If the "late" candidate + // is not for the start mark/line, meaning that mark passings including the actual start are + // skipped, as usual use getDistanceEstimationBasedProbability assuming a start mark passing at + // the race's start time. + if (isGateStart == Boolean.TRUE || start.getTimePoint() == null) { // TODO for gate start read gate timing and scale probability accordingly + startTimingProbability = 1; // no start time point known; all candidate time points equally likely + estimatedDistanceProbability = 1; // can't tell distance sailed either because we don't know the start time + } else { + // no gate start and we know the race start time + if (late.getWaypoint() == race.getRace().getCourse().getFirstWaypoint()) { + // no skips; going from the start proxy node to a candidate for the start mark passing; + // calculate the probability for the start being the start given its timing and multiply + // with the estimation for the distance-based probability: + final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint() + .plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint()); + // Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time + // off means 1/3, and so on + startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME + / (double) (MILLISECONDS_BEFORE_STARTTIME + Math + .abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis())); + estimatedDistanceProbability = 1; + } else { + startTimingProbability = 0.1; // can't really tell how well the start time was matched when + // we don't have a start candidate + estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late); + } + } + } else { + startTimingProbability = 1; // no penalty for any start time difference because this edge doesn't cover a start + if (late == end) { + // final edge; we don't know anything about distances for the end proxy node estimatedDistanceProbability = 1; } else { - startTimingProbability = 0.1; // can't really tell how well the start time was matched when - // we don't have a start candidate - estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late); + estimatedDistanceProbability = getDistanceEstimationBasedProbability(c, early, late); } } - } else { - startTimingProbability = 1; // no penalty for any start time difference because this edge doesn't cover a start - if (late == end) { - // final edge; we don't know anything about distances for the end proxy node - estimatedDistanceProbability = 1; - } else { - estimatedDistanceProbability = getDistanceEstimationBasedProbability(c, early, late); + // If one of the candidates is fixed, the edge is always created unless they travel backwards in time. + // Otherwise the edge is only created if the distance estimation, which can be calculated as long as the + // candidates are not the proxy and or start is close enough to the actual distance sailed. + final NavigableSet fixed = fixedPassings.get(c); + // TODO this comparison does not exactly implement the condition "if distance is more likely than skipping" + if (travelingForwardInTimeOrUnknown(early, late) && + (fixed.contains(early) || fixed.contains(late) || estimatedDistanceProbability > MINIMUM_PROBABILITY)) { + addEdge(edges, new Edge(early, late, startTimingProbability * estimatedDistanceProbability, race.getRace().getCourse().getNumberOfWaypoints())); } } - // If one of the candidates is fixed, the edge is always created unless they travel backwards in time. - // Otherwise the edge is only created if the distance estimation, which can be calculated as long as the - // candidates are not the proxy and or start is close enough to the actual distance sailed. - final NavigableSet fixed = fixedPassings.get(c); - // TODO this comparison does not exactly implement the condition "if distance is more likely than skipping" - if (travelingForwardInTimeOrUnknown(early, late) && - (fixed.contains(early) || fixed.contains(late) || estimatedDistanceProbability > MINIMUM_PROBABILITY)) { - addEdge(edges, new Edge(early, late, startTimingProbability * estimatedDistanceProbability, race.getRace().getCourse().getNumberOfWaypoints())); - } } } } @@ -306,13 +310,15 @@ public class CandidateChooserImpl implements CandidateChooser { // currentCheapestEdge.getEnd(), summing up their cost with the cost required // to reach currentCheapestEdge.getEnd() Set edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd()); - for (Edge e : edgesForNewCandidate) { - int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint(); - // only add edge if it stays within the current segment, not exceeding - // the next fixed mark passing - if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval - && (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) { - currentEdgesCheapestFirst.add(new Util.Pair(e, currentCheapestCost + e.getCost())); + if (edgesForNewCandidate != null) { + for (Edge e : edgesForNewCandidate) { + int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint(); + // only add edge if it stays within the current segment, not exceeding + // the next fixed mark passing + if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval + && (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) { + currentEdgesCheapestFirst.add(new Util.Pair(e, currentCheapestCost + e.getCost())); + } } } } 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 13552f9dfbf..0e9a7c88a03 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 @@ -2935,12 +2935,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { resumeAllCachesNotUpdatingWhileLoading(); } - if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { - // no more new data can be expected; stop mark passing calculator if one is being used - if (isUsingMarkPassingCalculator()) { - markPassingCalculator.stop(); - } - } } private void suspendAllCachesNotUpdatingWhileLoading() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java index f8a8da0d4b6..7db2ca0723f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java @@ -73,6 +73,10 @@ public class BoatClassImageResolver { } public static ImageResource getBoatClassIconResource(String displayName) { - return boatClassIconsMap.get(displayName); + ImageResource imageResource = boatClassIconsMap.get(displayName); + if(imageResource == null) { + imageResource = BoatClassImageResources.INSTANCE.genericBoatClass(); + } + return imageResource; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.java index 2a80440f7ff..1321be68557 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.java @@ -106,8 +106,8 @@ public class OldLeaderboard extends Composite { lastScoringUpdateTimeDiv.setInnerText(lastUpdate); lastScoringUpdateTextDiv.setInnerText(TextMessages.INSTANCE.eventRegattaLeaderboardLastScoreUpdate()); } else { - lastScoringUpdateTimeDiv.setInnerText(" "); - lastScoringUpdateTextDiv.setInnerText(" "); + lastScoringUpdateTimeDiv.setInnerHTML(" "); + lastScoringUpdateTextDiv.setInnerHTML(" "); } setVisible(hasLiveRaceDiv, hasLiveRace);