Merge branch 'master' into bug2822_boatcolors

Change-Id: I19333a21e5d79726b1409eb319798f55b49faf79
This commit is contained in:
Axel Uhl
2018-03-22 14:45:41 +01:00
55 changed files with 432 additions and 323 deletions
@@ -1,8 +1,6 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Tack;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.annotations.Connector;
@@ -13,12 +11,6 @@ public interface HasManeuverContext extends HasWindOnTrackedLeg, HasManeuver, Ha
@Connector(scanForStatistics = false)
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
@Dimension(messageKey = "ManeuverType", ordinal = 12)
ManeuverType getManeuverType();
@Dimension(messageKey = "ToSide", ordinal = 16)
NauticalSide getToSide();
@Dimension(messageKey = "TackBeforeManeuver")
Tack getTackBeforeManeuver();
@@ -7,10 +7,8 @@ import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.impl.data.MarkPassingWithContext;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor;
@@ -30,8 +28,8 @@ public class MarkPassingRetrievalProcessor extends AbstractRetrievalProcessor<Ha
try {
Iterable<Maneuver> maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false);
for (Maneuver maneuver : maneuvers) {
if (maneuver.getType() == ManeuverType.MARK_PASSING) {
maneuversWithContext.add(new MarkPassingWithContext(element, (MarkPassingManeuver) maneuver));
if (maneuver.isMarkPassing()) {
maneuversWithContext.add(new MarkPassingWithContext(element, maneuver));
}
}
} catch (NoWindException e) {
@@ -36,7 +36,7 @@ public class ManeuverSpeedDetailsWithContext implements HasManeuverSpeedDetailsC
@Override
public NauticalSide getToSide() {
return maneuverContext.getToSide();
return maneuverContext.getManeuver().getToSide();
}
@Override
@@ -45,7 +45,7 @@ public class ManeuverSpeedDetailsWithContext implements HasManeuverSpeedDetailsC
double lastSpeedValue = 0;
Function<Integer, Integer> twaIterationFunction = ManeuverSpeedDetailsUtils
.getTWAIterationFunctionForManeuverDirection(maneuverContext.getToSide());
.getTWAIterationFunctionForManeuverDirection(getToSide());
for (int twa = maneuverEnteringTWA, i = 0; i < 360; ++i, twa = twaIterationFunction.apply(twa)) {
if (maneuverSpeedPerTWA[twa] == 0 || lastSpeedValue == 0) {
@@ -64,7 +64,7 @@ public class ManeuverSpeedDetailsWithContext implements HasManeuverSpeedDetailsC
double firstSpeedValue = maneuverContext.getManeuverEnteringSpeed();
Function<Integer, Integer> twaIterationFunction = ManeuverSpeedDetailsUtils
.getTWAIterationFunctionForManeuverDirection(maneuverContext.getToSide());
.getTWAIterationFunctionForManeuverDirection(getToSide());
for (int twa = maneuverEnteringTWA, i = 0; i < 360; ++i, twa = twaIterationFunction.apply(twa)) {
if (firstSpeedValue == 0) {
@@ -85,7 +85,7 @@ public class ManeuverSpeedDetailsWithContext implements HasManeuverSpeedDetailsC
double lastSpeedValue = 0;
Function<Integer, Integer> twaIterationFunction = ManeuverSpeedDetailsUtils
.getTWAIterationFunctionForManeuverDirection(maneuverContext.getToSide());
.getTWAIterationFunctionForManeuverDirection(getToSide());
for (int twa = maneuverEnteringTWA, i = 0; i < 360; ++i, twa = twaIterationFunction.apply(twa)) {
if (maneuverSpeedPerTWA[twa] == 0 || lastSpeedValue == 0) {
@@ -4,8 +4,6 @@ import com.sap.sailing.datamining.data.HasManeuverContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
@@ -88,16 +86,6 @@ public class ManeuverWithContext implements HasManeuverContext {
return maneuver;
}
@Override
public ManeuverType getManeuverType() {
return getManeuver().getType();
}
@Override
public NauticalSide getToSide() {
return getDirectionChangeInDegreesForAnalysis() >= 0 ? NauticalSide.STARBOARD : NauticalSide.PORT;
}
@Override
public Double getAbsoluteDirectionChangeInDegrees() {
return Math.abs(getDirectionChangeInDegreesForAnalysis());
@@ -7,22 +7,22 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Util;
public class MarkPassingWithContext implements HasMarkPassingContext {
private static final long serialVersionUID = -337042113749307686L;
private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitor;
private final MarkPassingManeuver maneuver;
private final Maneuver maneuver;
private Double absoluteRank;
private boolean rankHasBeenInitialized;
private Wind wind;
public MarkPassingWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, MarkPassingManeuver maneuver) {
public MarkPassingWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, Maneuver markPassingManeuver) {
this.trackedLegOfCompetitor = trackedLegOfCompetitor;
this.maneuver = maneuver;
this.maneuver = markPassingManeuver;
}
@Override
@@ -31,18 +31,18 @@ public class MarkPassingWithContext implements HasMarkPassingContext {
}
@Override
public MarkPassingManeuver getManeuver() {
public Maneuver getManeuver() {
return maneuver;
}
@Override
public Waypoint getWaypoint() {
return getManeuver().getWaypointPassed();
return getManeuver().getMarkPassing().getWaypoint();
}
@Override
public NauticalSide getPassingSide() {
return getManeuver().getSide();
return getManeuver().getToSide();
}
@Override
@@ -1,5 +1,5 @@
package com.sap.sailing.domain.common;
public enum ManeuverType {
HEAD_UP, BEAR_AWAY, TACK, JIBE, PENALTY_CIRCLE, MARK_PASSING, UNKNOWN;
HEAD_UP, BEAR_AWAY, TACK, JIBE, PENALTY_CIRCLE, UNKNOWN;
}
@@ -11,6 +11,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import org.junit.After;
@@ -236,7 +237,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper {
public void raceAdded(TrackedRace trackedRace) {
}
};
raceHandle.getTrackedRegatta().addRaceListener(raceListener);
raceHandle.getTrackedRegatta().addRaceListener(raceListener, Optional.empty());
raceHandle.getTrackedRegatta().removeRaceListener(raceListener).get();
}
@@ -24,7 +24,6 @@ import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RegattaLogFixTrack
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tracking.TrackedRegattaListener;
import com.sap.sailing.server.MasterDataImportClassLoaderService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.GPSFixJsonDeserializer;
@@ -36,6 +35,7 @@ import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdent
import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.GPSFixJsonHandlerImpl;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.SmartphoneUUIDJsonHandler;
import com.sap.sse.MasterDataImportClassLoaderService;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.replication.Replicable;
import com.sap.sse.util.ServiceTrackerFactory;
@@ -1,6 +1,6 @@
package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.server.MasterDataImportClassLoaderService;
import com.sap.sse.MasterDataImportClassLoaderService;
public class MasterDataImportClassLoaderServiceImpl implements MasterDataImportClassLoaderService {
@@ -7,6 +7,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -305,7 +306,8 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
raceColumn.setRaceIdentifier(fleet, trackedRegatta.getRegatta().getRaceIdentifier(raceDef));
trackedRace = trackedRegatta.createTrackedRace(raceDef, sidelines, windStore,
params.getDelayToLiveInMillis(), WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND,
boatClass.getApproximateManeuverDurationInMilliseconds(), null, /*useMarkPassingCalculator*/ true, raceLogResolver);
boatClass.getApproximateManeuverDurationInMilliseconds(), null, /*useMarkPassingCalculator*/ true, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
notifyRaceCreationListeners();
logger.info(String.format("Started tracking race-log race (%s)", raceLog));
// this wakes up all waiting race handles
@@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -487,7 +488,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
// we already know our single RaceDefinition
assert SwissTimingRaceTrackerImpl.this.race == race;
}
}, useInternalMarkPassingAlgorithm, raceLogResolver);
}, useInternalMarkPassingAlgorithm, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
notifyRaceCreationListeners();
logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName());
}
@@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TimeZone;
import java.util.logging.Logger;
@@ -399,7 +400,8 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i
TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS,
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
/* raceDefinitionSetToUpdate */ null, useInternalMarkPassingAlgorithm, raceLogResolver);
/* raceDefinitionSetToUpdate */ null, useInternalMarkPassingAlgorithm, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
trackedRace.onStatusChanged(this, new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0));
TimePoint bestStartTimeKnownSoFar = bestStartTimePerRaceID.get(currentRaceID);
if (bestStartTimeKnownSoFar != null) {
@@ -6,6 +6,7 @@ import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Ignore;
import org.junit.Test;
@@ -75,7 +76,7 @@ public class FetchTracksAndStoreLocallyTest extends OnlineTracTracBasedTest {
@Override
public void raceRemoved(TrackedRace trackedRace) {
}
});
}, Optional.empty());
super.completeSetupLaunchingControllerAndWaitForRaceDefinition(ReceiverType.RACECOURSE,
ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS);
}
@@ -5,6 +5,7 @@ import static org.mockito.Mockito.mock;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@@ -78,7 +79,7 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
@Override
public void raceRemoved(TrackedRace trackedRace) {
}
});
}, Optional.empty());
for (Receiver receiver : domainFactory
.getUpdateReceivers(trackedRegatta, /* delayToLiveInMillis */0l,
/* simulator */null, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@@ -5,6 +5,7 @@ import static org.mockito.Mockito.when;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
@@ -33,6 +34,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.util.ThreadLocalTransporter;
public class TestDeadlockInRegattaListener {
@Rule
@@ -56,13 +58,14 @@ public class TestDeadlockInRegattaListener {
private static final long serialVersionUID = -3599667964201700780L;
@Override
protected void notifyListenersAboutTrackedRaceRemoved(TrackedRace trackedRace) {
protected void notifyListenersAboutTrackedRaceRemoved(TrackedRace trackedRace,
Optional<ThreadLocalTransporter> threadLocalTransporter) {
try {
latch.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
super.notifyListenersAboutTrackedRaceRemoved(trackedRace);
super.notifyListenersAboutTrackedRaceRemoved(trackedRace, Optional.empty());
}
};
RacingEventServiceImpl racingEventService = new RacingEventServiceImpl() {
@@ -125,10 +128,10 @@ public class TestDeadlockInRegattaListener {
throw new RuntimeException(e);
}
}).start();
trackedRegatta.addTrackedRace(trackedRace1);
trackedRegatta.addTrackedRace(trackedRace1, Optional.empty());
// the following runs into RacingEventService.getRaceTrackerByRegattaAndRaceIdentifier
// which waits for the latch based on the override above while in synchronized RegattaListener.raceAdded
new Thread(()->trackedRegatta.addTrackedRace(trackedRace2)).start();
new Thread(()->trackedRegatta.addTrackedRace(trackedRace2, Optional.empty())).start();
monitorOnRegattaListenerLatch.await();
// the following awaits the latch in TrackedRegattaImpl.notifyListenersAboutTrackedRaceRemoved
// after the write lock has been obtained but before the synchronized RegattaListener.raceRemoved method
@@ -4,6 +4,7 @@ import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Future;
@@ -90,6 +91,7 @@ import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.util.ThreadLocalTransporter;
public class MockedTrackedRace implements DynamicTrackedRace {
private static final long serialVersionUID = 5827912985564121181L;
@@ -597,15 +599,15 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void addTrackedRace(TrackedRace trackedRace) {
public void addTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
public void removeTrackedRace(TrackedRace trackedRace) {
public void removeTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
public void addRaceListener(RaceListener listener) {
public void addRaceListener(RaceListener listener, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
@@ -628,7 +630,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
WindStore windStore, long delayToLiveInMillis,
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingcalculator,
RaceLogResolver raceLogResolver) {
RaceLogResolver raceLogResolver, Optional<ThreadLocalTransporter> threadLocalTransporter) {
return null;
}
@@ -4,6 +4,7 @@ import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
@@ -74,11 +75,11 @@ public class TrackedRegattaTest {
throw new RuntimeException(e);
}
}
});
}, Optional.empty());
DynamicTrackedRace race1 = createRace("R1");
Thread thread1 = new Thread(() -> {
regatta.addTrackedRace(race1);
regatta.addTrackedRace(race1, Optional.empty());
});
thread1.start();
// This ensures, that the add event is being processed but is not finished because
@@ -88,7 +89,7 @@ public class TrackedRegattaTest {
addPhaser.arriveAndAwaitAdvance();
Thread thread2 = new Thread(() -> {
regatta.removeTrackedRace(race1);
regatta.removeTrackedRace(race1, Optional.empty());
});
thread2.start();
// If the implementation ensures that the events are fired in order,
@@ -14,6 +14,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
@@ -694,7 +695,7 @@ public class DomainFactoryImpl implements DomainFactory {
return trackedRegatta.createTrackedRace(race, sidelines,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate, useMarkPassingCalculator, raceLogResolver);
raceDefinitionSetToUpdate, useMarkPassingCalculator, raceLogResolver, Optional.empty());
}
/**
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -245,7 +246,8 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm, raceLogResolver);
raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm, raceLogResolver,
/* Not needed because the RaceTracker is not active on a replica */ Optional.empty());
if (runAfterCreatingTrackedRace != null) {
runAfterCreatingTrackedRace.accept(trackedRace);
}
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
@@ -70,7 +71,7 @@ public class Simulator {
stop(); // stop simulator when tracked race is removed from its regatta
}
}
});
}, /* No replication handling necessary */ Optional.empty());
startWindPlayer();
}
@@ -70,7 +70,6 @@ import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
@@ -970,19 +969,18 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
}
}
break;
case MARK_PASSING:
// analyze all mark passings, not only those after this leg's start, to catch the mark passing
// maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
}
break;
default:
/* Do nothing here.
* Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
*/
}
if(maneuver.isMarkPassing()) {
// analyze all mark passings, not only those after this leg's start, to catch the mark passing
// maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
if (maneuver.getMarkPassing().getWaypoint() == trackedLeg.getLeg().getFrom()) {
result.sideToWhichMarkAtLegStartWasRounded = maneuver.getToSide();
}
}
}
result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
@@ -35,7 +35,6 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.ManeuverWithMainCurveBoundariesImpl;
import com.sap.sailing.domain.tracking.impl.ManeuverWithStableSpeedAndCourseBoundariesImpl;
import com.sap.sailing.domain.tracking.impl.MarkPassingManeuverImpl;
import com.sap.sailing.domain.tracking.impl.SpeedWithBearingStepImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -416,30 +415,15 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
maneuverMainCurveDetails.getTimePointBefore());
TrackedLegOfCompetitor legAfterManeuver = trackedRace.getTrackedLeg(competitor,
maneuverMainCurveDetails.getTimePointAfter());
Waypoint waypointPassed = null; // set for MARK_PASSING maneuvers only
NauticalSide sideToWhichWaypointWasPassed = null; // set for MARK_PASSING maneuvers only
// check for mask passing first; a tacking / jibe-setting mark rounding thus takes precedence over being
// detected as a penalty circle
final TimePoint markPassingTimePoint;
MarkPassing markPassing = null; // will remain null if no mark passing has been recorded within maneuver
// boundaries
// check whether a waypoint has been passed within maneuver
if (legBeforeManeuver != legAfterManeuver
// a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack
&& legAfterManeuver != null
&& legAfterManeuver.getLeg().getFrom() != trackedRace.getRace().getCourse().getFirstWaypoint()) {
waypointPassed = legAfterManeuver.getLeg().getFrom();
MarkPassing markPassing = trackedRace.getMarkPassing(competitor, waypointPassed);
markPassingTimePoint = markPassing != null ? markPassing.getTimePoint() : maneuverDetails.getTimePoint();
Position markPassingPosition = markPassing != null
? competitorTrack.getEstimatedPosition(markPassingTimePoint, /* extrapolate */false)
: maneuverPosition;
sideToWhichWaypointWasPassed = maneuverDirection;
// produce an additional mark passing maneuver; continue to analyze to catch jibe sets and kiwi drops
maneuvers.add(new MarkPassingManeuverImpl(ManeuverType.MARK_PASSING, tackAfterManeuver, markPassingPosition,
maneuverLoss, markPassingTimePoint, maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond(), waypointPassed,
sideToWhichWaypointWasPassed));
} else {
markPassingTimePoint = null;
Waypoint waypointPassed = legAfterManeuver.getLeg().getFrom();
markPassing = trackedRace.getMarkPassing(competitor, waypointPassed);
}
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
final Bearing courseBeforeManeuver = maneuverMainCurveDetails.getSpeedWithBearingBefore().getBearing();
@@ -451,121 +435,162 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
int numberOfTacks = wind == null ? 0
: bearingChangeAnalyzer.didPass(courseBeforeManeuver, mainCurveTotalCourseChangeInDegrees,
courseAfterManeuver, wind.getFrom());
if (markPassingTimePoint != null && (numberOfTacks + numberOfJibes > 0)) {
// In case of a mark passing we need to split the maneuver analysis into the phase before and after
// the mark passing. First of all, this is important to identify the correct maneuver time point for
// each tack and jibe, second it is essential to call a penalty which is only the case if the tack and
// the jibe are on the same side of the mark passing; otherwise this may have been a jibe set or a
// kiwi drop.
// Therefore, we recursively detect the maneuvers for the segment before and the segment after the
// mark passing and add the results to our result.
List<ManeuverSpot> maneuverSpots = detectManeuvers(maneuverDetails.getTimePointBefore(),
markPassingTimePoint.minus(1));
maneuverSpots.addAll(detectManeuvers(markPassingTimePoint.plus(1), maneuverDetails.getTimePointAfter()));
maneuvers.addAll(getAllManeuversFromManeuverSpots(maneuverSpots));
} else {
// Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe.
// For the first tack/jibe combination (they must alternate because the course changes in the same direction
// and
// the wind is considered sufficiently stable to not allow for two successive tacks or two successive jibes)
// we create a PENALTY_CIRCLE maneuver and recurse for the time interval after the first penalty circle has
// completed.
if (numberOfTacks > 0 && numberOfJibes > 0 && markPassingTimePoint == null) {
TimePoint firstPenaltyCircleCompletedAt = getTimePointOfCompletionOfFirstPenaltyCircle(
maneuverMainCurveDetails.getTimePointBefore(), courseBeforeManeuver,
maneuverMainCurveDetails.getSpeedWithBearingSteps(), wind);
if (firstPenaltyCircleCompletedAt == null) {
// This should really not happen!
logger.warning(
"Maneuver detection has failed to process penalty circle maneuver correctly, because getTimePointOfCompletionOfFirstPenaltyCircle() returned null. Race-Id: "
+ trackedRace.getRace().getId() + ", Competitor: " + competitor.getName()
+ ", Time point before maneuver: " + maneuverDetails.getTimePointBefore());
// Use already detected maneuver details as fallback data to prevent Nullpointer
firstPenaltyCircleCompletedAt = maneuverDetails.getTimePointAfter();
if (numberOfTacks > 0 && numberOfJibes > 0) {
boolean performPenaltyCircleAnalysis = true;
if (markPassing != null) {
// In case of a mark passing we need to split the maneuver analysis into the phase before and after
// the mark passing to catch kiwi drops. First of all, this is important to identify the correct
// maneuver time point for
// each tack and jibe, second it is essential to call a penalty which is only the case if the tack and
// the jibe are on the same side of the mark passing; otherwise this may have been a
// kiwi drop.
// Therefore, we recursively detect the maneuvers for the segment before and the segment after the
// mark passing and add the results to our result.
List<ManeuverSpot> maneuverSpotsBeforeMarkPassing = detectManeuvers(
maneuverDetails.getTimePointBefore(), markPassing.getTimePoint().minus(1));
List<ManeuverSpot> maneuverSpotsAfterMarkPassing = detectManeuvers(markPassing.getTimePoint().plus(1),
maneuverDetails.getTimePointAfter());
// split the penalty circle maneuver only by mark passing time point if tacks or jibes are present on
// both legs
if (getNumberOfTacksAndJibesFromManeuverSpots(maneuverSpotsAfterMarkPassing) != 0
&& getNumberOfTacksAndJibesFromManeuverSpots(maneuverSpotsAfterMarkPassing) != 0) {
performPenaltyCircleAnalysis = false;
maneuverSpotsBeforeMarkPassing.addAll(maneuverSpotsAfterMarkPassing);
maneuvers.addAll(getAllManeuversFromManeuverSpots(maneuverSpotsBeforeMarkPassing));
}
}
if (performPenaltyCircleAnalysis) {
// Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe.
// For the first tack/jibe combination (they must alternate because the course changes in the same
// direction
// and
// the wind is considered sufficiently stable to not allow for two successive tacks or two successive
// jibes)
// we create a PENALTY_CIRCLE maneuver and recurse for the time interval after the first penalty circle
// has
// completed.
List<Maneuver> additionalManeuversAfterFirstPenaltyCircle = null;
if (numberOfTacks > 1 || numberOfJibes > 1) {
TimePoint firstPenaltyCircleCompletedAt = getTimePointOfCompletionOfFirstPenaltyCircle(
maneuverMainCurveDetails.getTimePointBefore(), courseBeforeManeuver,
maneuverMainCurveDetails.getSpeedWithBearingSteps(), wind);
final ManeuverCurveDetailsWithBearingSteps refinedPenaltyMainCurveDetails;
final ManeuverCurveDetails refinedPenaltyDetails;
if (firstPenaltyCircleCompletedAt == null) {
// This should really not happen!
logger.warning(
"Maneuver detection has failed to process penalty circle maneuver correctly, because getTimePointOfCompletionOfFirstPenaltyCircle() returned null. Race-Id: "
+ trackedRace.getRace().getId() + ", Competitor: " + competitor.getName()
+ ", Time point before maneuver: " + maneuverDetails.getTimePointBefore());
// Use already detected maneuver details as fallback data to prevent Nullpointer
} else {
refinedPenaltyMainCurveDetails = computeManeuverMainCurveDetails(
maneuverMainCurveDetails.getTimePointBefore(), firstPenaltyCircleCompletedAt,
maneuverDirection);
if (refinedPenaltyMainCurveDetails == 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: " + maneuverDetails.getTimePointBefore());
// Use already detected maneuver main curve as fallback data to prevent Nullpointer
} else {
refinedPenaltyDetails = computeManeuverDetails(refinedPenaltyMainCurveDetails,
maneuverDetails.getTimePointBefore(), firstPenaltyCircleCompletedAt);
// after we've "consumed" one tack and one jibe, recursively find more maneuvers if tacks
// and/or jibes
// remain
List<ManeuverSpot> maneuverSpots = detectManeuvers(firstPenaltyCircleCompletedAt,
maneuverDetails.getTimePointAfter());
additionalManeuversAfterFirstPenaltyCircle = getAllManeuversFromManeuverSpots(
maneuverSpots);
maneuverMainCurveDetails = refinedPenaltyMainCurveDetails;
maneuverDetails = refinedPenaltyDetails;
}
}
}
maneuverType = ManeuverType.PENALTY_CIRCLE;
ManeuverCurveDetailsWithBearingSteps refinedPenaltyMainCurveDetails = computeManeuverMainCurveDetails(
maneuverMainCurveDetails.getTimePointBefore(), firstPenaltyCircleCompletedAt,
maneuverDirection);
ManeuverCurveDetails refinedPenaltyDetails;
if (refinedPenaltyMainCurveDetails == 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: " + maneuverDetails.getTimePointBefore());
// Use already detected maneuver main curve as fallback data to prevent Nullpointer
refinedPenaltyMainCurveDetails = maneuverMainCurveDetails;
refinedPenaltyDetails = maneuverDetails;
firstPenaltyCircleCompletedAt = maneuverDetails.getTimePointAfter();
} else {
refinedPenaltyDetails = computeManeuverDetails(refinedPenaltyMainCurveDetails,
maneuverDetails.getTimePointBefore(), firstPenaltyCircleCompletedAt);
}
maneuverLoss = getManeuverLoss(maneuverDetails.getTimePointBefore(), maneuverDetails.getTimePoint(),
firstPenaltyCircleCompletedAt);
Position penaltyPosition = competitorTrack.getEstimatedPosition(refinedPenaltyDetails.getTimePoint(),
maneuverDetails.getTimePointAfter());
Position penaltyPosition = competitorTrack.getEstimatedPosition(maneuverDetails.getTimePoint(),
/* extrapolate */ false);
final Maneuver maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType,
tackAfterManeuver, penaltyPosition, maneuverLoss, refinedPenaltyDetails.getTimePoint(),
refinedPenaltyMainCurveDetails.extractCurveBoundariesOnly(),
refinedPenaltyDetails.extractCurveBoundariesOnly(),
refinedPenaltyMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond());
tackAfterManeuver, penaltyPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond(), markPassing);
maneuvers.add(maneuver);
// after we've "consumed" one tack and one jibe, recursively find more maneuvers if tacks and/or jibes
// remain
if (numberOfTacks > 1 || numberOfJibes > 1) {
List<ManeuverSpot> maneuverSpots = detectManeuvers(firstPenaltyCircleCompletedAt,
maneuverDetails.getTimePointAfter());
maneuvers.addAll(getAllManeuversFromManeuverSpots(maneuverSpots));
if (additionalManeuversAfterFirstPenaltyCircle != null) {
maneuvers.addAll(additionalManeuversAfterFirstPenaltyCircle);
}
} else {
final Maneuver maneuver;
if (numberOfTacks > 0 || numberOfJibes > 0) {
maneuverType = numberOfTacks > 0 ? ManeuverType.TACK : ManeuverType.JIBE;
maneuverLoss = getManeuverLoss(maneuverDetails.getTimePointBefore(), maneuverDetails.getTimePoint(),
maneuverDetails.getTimePointAfter());
maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond());
} else if (wind != null) {
// heading up or bearing away
Bearing windBearing = wind.getBearing();
Bearing toWindBeforeManeuver = windBearing
.getDifferenceTo(maneuverMainCurveDetails.getSpeedWithBearingBefore().getBearing());
Bearing toWindAfterManeuver = windBearing
.getDifferenceTo(maneuverMainCurveDetails.getSpeedWithBearingAfter().getBearing());
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math
.abs(toWindAfterManeuver.getDegrees()) ? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
// treat maneuver main curve details as main maneuver details, because the detected maneuver is
// either HEAD_UP or BEAR_AWAY
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond());
} else {
// no wind information; marking as UNKNOWN
maneuverType = ManeuverType.UNKNOWN;
maneuverLoss = getManeuverLoss(maneuverDetails.getTimePointBefore(), maneuverDetails.getTimePoint(),
maneuverDetails.getTimePointAfter());
maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond());
}
maneuvers.add(maneuver);
}
} else {
final Maneuver maneuver;
if (numberOfTacks > 0 || numberOfJibes > 0) {
maneuverType = numberOfTacks > 0 ? ManeuverType.TACK : ManeuverType.JIBE;
maneuverLoss = getManeuverLoss(maneuverDetails.getTimePointBefore(), maneuverDetails.getTimePoint(),
maneuverDetails.getTimePointAfter());
maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond(), markPassing);
} else if (wind != null) {
// heading up or bearing away
Bearing windBearing = wind.getBearing();
Bearing toWindBeforeManeuver = windBearing
.getDifferenceTo(maneuverMainCurveDetails.getSpeedWithBearingBefore().getBearing());
Bearing toWindAfterManeuver = windBearing
.getDifferenceTo(maneuverMainCurveDetails.getSpeedWithBearingAfter().getBearing());
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees())
? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
// treat maneuver main curve details as main maneuver details, because the detected maneuver is
// either HEAD_UP or BEAR_AWAY
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition,
maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond(), markPassing);
} else {
// no wind information; marking as UNKNOWN
maneuverType = ManeuverType.UNKNOWN;
maneuverLoss = getManeuverLoss(maneuverDetails.getTimePointBefore(), maneuverDetails.getTimePoint(),
maneuverDetails.getTimePointAfter());
maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(),
maneuverDetails.extractCurveBoundariesOnly(),
maneuverMainCurveDetails.getMaxAngularVelocityInDegreesPerSecond(), markPassing);
}
maneuvers.add(maneuver);
}
return new ManeuverSpot(new ArrayList<>(douglasPeuckerFixesGroup), maneuverDirection, maneuvers,
new WindMeasurement(maneuverDetails.getTimePoint(), maneuverPosition,
wind == null ? null : wind.getBearing()));
}
private int getNumberOfTacksAndJibesFromManeuverSpots(List<ManeuverSpot> maneuverSpots) {
int tackAndJibeCount = 0;
for (ManeuverSpot maneuverSpot : maneuverSpots) {
for (Maneuver maneuver : maneuverSpot.getManeuvers()) {
switch (maneuver.getType()) {
case JIBE:
case TACK:
++tackAndJibeCount;
break;
case PENALTY_CIRCLE:
tackAndJibeCount += 2;
break;
case BEAR_AWAY:
case HEAD_UP:
case UNKNOWN:
break;
}
}
}
return tackAndJibeCount;
}
/**
* Computes the maneuver loss as the distance projected onto the average course between entering and exiting the
* maneuver that the boat lost compared to not having maneuvered. With this distance measure, the competitors speed
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
@@ -107,7 +108,7 @@ public abstract class AbstractTrackedRegattaAndRaceObserver implements TrackedRe
RegattaListener.this.raceAdded(trackedRace);
}
};
trackedRegatta.addRaceListener(raceListener);
trackedRegatta.addRaceListener(raceListener, /* Not replicated */ Optional.empty());
}
public synchronized void raceRemoved(TrackedRace trackedRace) {
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
@@ -42,6 +43,7 @@ public interface Maneuver extends GPSFix {
*
* @return The type of maneuver
*/
@Dimension(messageKey = "ManeuverType", ordinal = 12)
ManeuverType getType();
/**
@@ -50,7 +52,7 @@ public interface Maneuver extends GPSFix {
*
* @return The new tack after the performed maneuver
*/
@Dimension(messageKey = "Tack", ordinal = 13)
@Dimension(messageKey = "Tack", ordinal = 14)
Tack getNewTack();
/**
@@ -141,4 +143,22 @@ public interface Maneuver extends GPSFix {
*/
Speed getLowestSpeed();
/**
* Gets the mark passing which is contained within maneuver. In case if no mark passing was passed, {@code null} is
* returned.
*/
MarkPassing getMarkPassing();
/**
* Determines whether the maneuver is mark passing maneuver.
*/
@Dimension(messageKey = "MarkPassing", ordinal = 13)
boolean isMarkPassing();
/**
* Gets the direction of the maneuver. It corresponds to the direction of mark passing side.
*/
@Dimension(messageKey = "ToSide", ordinal = 16)
NauticalSide getToSide();
}
@@ -1,10 +0,0 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
public interface MarkPassingManeuver extends Maneuver {
Waypoint getWaypointPassed();
NauticalSide getSide();
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.Optional;
import java.util.concurrent.Future;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
@@ -11,6 +12,7 @@ import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.impl.TrackedRaces;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.util.ThreadLocalTransporter;
/**
* Manages a set of {@link TrackedRace} objects that belong to the same {@link Regatta} (regatta, sailing regatta for a
@@ -65,7 +67,8 @@ public interface TrackedRegatta extends Serializable {
*/
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm, RaceLogResolver raceLogResolver);
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm, RaceLogResolver raceLogResolver,
Optional<ThreadLocalTransporter> beforeAndAfterNotificationHandler);
/**
* Obtains the tracked race for <code>race</code>. Blocks until the tracked race has been created
@@ -79,16 +82,16 @@ public interface TrackedRegatta extends Serializable {
*/
TrackedRace getExistingTrackedRace(RaceDefinition race);
void addTrackedRace(TrackedRace trackedRace);
void addTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> beforeAndAfterNotificationHandler);
void removeTrackedRace(TrackedRace trackedRace);
void removeTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> beforeAndAfterNotificationHandler);
/**
* Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and
* upon registration for each tracked race already known. Therefore, the listener
* won't miss any tracked race.
*/
void addRaceListener(RaceListener listener);
void addRaceListener(RaceListener listener, Optional<ThreadLocalTransporter> beforeAndAfterNotificationHandler);
/**
* Removes the given listener and returns a {@link Future} that will be completed
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.Optional;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -8,6 +10,7 @@ import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sse.util.ThreadLocalTransporter;
public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements DynamicTrackedRegatta {
private static final long serialVersionUID = -90155868534737120L;
@@ -35,9 +38,10 @@ public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements Dyn
@Override
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingCalculator, RaceLogResolver raceLogResolver) {
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingCalculator, RaceLogResolver raceLogResolver,
Optional<ThreadLocalTransporter> threadLocalTransporter) {
return (DynamicTrackedRace) super.createTrackedRace(raceDefinition, sidelines, windStore, delayToLiveInMillis,
millisecondsOverWhichToAverageWind,
millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate, useMarkPassingCalculator, raceLogResolver);
millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate, useMarkPassingCalculator, raceLogResolver, threadLocalTransporter);
}
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
@@ -9,6 +10,7 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.tracking.impl.AbstractGPSFixImpl;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.TimePoint;
/**
@@ -25,11 +27,12 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
private final double maxAngularVelocityInDegreesPerSecond;
private final ManeuverCurveBoundaries mainCurveBoundaries;
private final ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries;
private final MarkPassing markPassing;
public ManeuverImpl(ManeuverType type, Tack newTack, Position position, Distance maneuverLoss, TimePoint timePoint,
ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxAngularVelocityInDegreesPerSecond) {
double maxAngularVelocityInDegreesPerSecond, MarkPassing markPassing) {
this.type = type;
this.newTack = newTack;
this.position = position;
@@ -38,6 +41,7 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
this.mainCurveBoundaries = mainCurveBoundaries;
this.maneuverCurveWithStableSpeedAndCourseBoundaries = maneuverCurveWithStableSpeedAndCourseBoundaries;
this.maxAngularVelocityInDegreesPerSecond = maxAngularVelocityInDegreesPerSecond;
this.markPassing = markPassing;
}
@Override
@@ -100,7 +104,8 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
return super.toString() + " " + type + " on new tack " + newTack + " on position " + position
+ " at time point " + timePoint + ", " + getManeuverBoundaries() + ", max. angular velocity: "
+ maxAngularVelocityInDegreesPerSecond
+ (getManeuverLoss() == null ? "" : " Lost approximately " + getManeuverLoss());
+ (getManeuverLoss() == null ? "" : ", Lost approximately " + getManeuverLoss()) + ", Mark passing: "
+ markPassing;
}
@Override
@@ -108,4 +113,16 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
return maxAngularVelocityInDegreesPerSecond;
}
public MarkPassing getMarkPassing() {
return markPassing;
}
public boolean isMarkPassing() {
return markPassing != null;
}
public NauticalSide getToSide() {
return getMainCurveBoundaries().getDirectionChangeInDegrees() < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD;
}
}
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.TimePoint;
/**
@@ -21,9 +22,9 @@ public class ManeuverWithMainCurveBoundariesImpl extends ManeuverImpl {
public ManeuverWithMainCurveBoundariesImpl(ManeuverType type, Tack newTack, Position position,
Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxAngularVelocityInDegreesPerSecond) {
double maxAngularVelocityInDegreesPerSecond, MarkPassing markPassing) {
super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxAngularVelocityInDegreesPerSecond);
maneuverCurveWithStableSpeedAndCourseBoundaries, maxAngularVelocityInDegreesPerSecond, markPassing);
}
@Override
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.TimePoint;
/**
@@ -22,9 +23,9 @@ public class ManeuverWithStableSpeedAndCourseBoundariesImpl extends ManeuverImpl
public ManeuverWithStableSpeedAndCourseBoundariesImpl(ManeuverType type, Tack newTack, Position position,
Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxAngularVelocityInDegreesPerSecond) {
double maxAngularVelocityInDegreesPerSecond, MarkPassing markPassing) {
super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxAngularVelocityInDegreesPerSecond);
maneuverCurveWithStableSpeedAndCourseBoundaries, maxAngularVelocityInDegreesPerSecond, markPassing);
}
@Override
@@ -1,46 +0,0 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sse.common.TimePoint;
public class MarkPassingManeuverImpl extends ManeuverWithStableSpeedAndCourseBoundariesImpl
implements MarkPassingManeuver {
private static final long serialVersionUID = 8935348908557191614L;
private final Waypoint waypointPassed;
private final NauticalSide side;
public MarkPassingManeuverImpl(ManeuverType type, Tack newTack, Position position, Distance maneuverLoss,
TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxAngularVelocityInDegreesPerSecond, Waypoint waypointPassed, NauticalSide side) {
super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxAngularVelocityInDegreesPerSecond);
this.waypointPassed = waypointPassed;
this.side = side;
}
@Override
public Waypoint getWaypointPassed() {
return waypointPassed;
}
@Override
public NauticalSide getSide() {
return side;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(super.toString());
result.append(", passed waypoint " + getWaypointPassed() + " to " + getSide().name());
return result.toString();
}
}
@@ -549,7 +549,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
markPassingCalculator.stop();
}
}
});
}, /* Not relevant For replication */ Optional.empty());
} else {
markPassingCalculator = null;
}
@@ -9,6 +9,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@@ -34,6 +35,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.ThreadLocalTransporter;
public class TrackedRegattaImpl implements TrackedRegatta {
private static final long serialVersionUID = 6480508193567014285L;
@@ -128,7 +130,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
}
@Override
public void addTrackedRace(TrackedRace trackedRace) {
public void addTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
final TrackedRace oldTrackedRace;
lockTrackedRacesForWrite();
try {
@@ -136,39 +138,57 @@ public class TrackedRegattaImpl implements TrackedRegatta {
" with regatta hash code "+getRegatta().hashCode());
oldTrackedRace = trackedRaces.put(trackedRace.getRace(), trackedRace);
if (oldTrackedRace != trackedRace) {
notifyListenersAboutTrackedRaceAdded(trackedRace);
notifyListenersAboutTrackedRaceAdded(trackedRace, threadLocalTransporter);
}
} finally {
unlockTrackedRacesAfterWrite();
}
}
protected void notifyListenersAboutTrackedRaceAdded(TrackedRace trackedRace) {
enqueEvent(listener -> listener.raceAdded(trackedRace));
protected void notifyListenersAboutTrackedRaceAdded(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
enqueEvent(listener -> listener.raceAdded(trackedRace), threadLocalTransporter);
}
protected void enqueEvent(Consumer<RaceListener> fireEventCallback) {
/**
* Firing events is handled through {@link #eventQueue} to ensure that events are fired in order. This method
* enqueues an event while ensuring that the set of listeners is freezed to prevent events to be fired twice to a
* specific listener. Any Listener attached after will receive a consistent set of already added {@link TrackedRace
* TrackedRaces}. Firing events may not be done directly but only by using this method.
*/
protected void enqueEvent(Consumer<RaceListener> fireEventCallback, Optional<ThreadLocalTransporter> threadLocalTransporter) {
final Set<RaceListener> listenersToInform = new HashSet<>(raceListeners.keySet());
threadLocalTransporter.ifPresent(ThreadLocalTransporter::rememberThreadLocalStates);
eventQueue.addWork(() -> {
for (RaceListener listener : listenersToInform) {
fireEventCallback.accept(listener);
}
withBeforeAndAfterHandling(threadLocalTransporter, () -> {
for (RaceListener listener : listenersToInform) {
fireEventCallback.accept(listener);
}
});
});
}
private void withBeforeAndAfterHandling(Optional<ThreadLocalTransporter> threadLocalTransporter, Runnable action) {
threadLocalTransporter.ifPresent(ThreadLocalTransporter::pushThreadLocalStates);
try {
action.run();
} finally {
threadLocalTransporter.ifPresent(ThreadLocalTransporter::popThreadLocalStates);
}
}
@Override
public void removeTrackedRace(TrackedRace trackedRace) {
public void removeTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
lockTrackedRacesForWrite();
try {
trackedRaces.remove(trackedRace.getRace());
notifyListenersAboutTrackedRaceRemoved(trackedRace);
notifyListenersAboutTrackedRaceRemoved(trackedRace, threadLocalTransporter);
} finally {
unlockTrackedRacesAfterWrite();
}
}
protected void notifyListenersAboutTrackedRaceRemoved(TrackedRace trackedRace) {
enqueEvent(listener -> listener.raceRemoved(trackedRace));
protected void notifyListenersAboutTrackedRaceRemoved(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
enqueEvent(listener -> listener.raceRemoved(trackedRace), threadLocalTransporter);
}
@Override
@@ -201,7 +221,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
}
}
};
addRaceListener(listener);
addRaceListener(listener, Optional.empty());
try {
synchronized (mutex) {
result = getExistingTrackedRace(race);
@@ -232,16 +252,19 @@ public class TrackedRegattaImpl implements TrackedRegatta {
}
@Override
public void addRaceListener(RaceListener listener) {
public void addRaceListener(RaceListener listener, Optional<ThreadLocalTransporter> threadLocalTransporter) {
lockTrackedRacesForRead();
try {
raceListeners.put(listener, listener);
final List<TrackedRace> trackedRacesCopy = new ArrayList<>();
Util.addAll(getTrackedRaces(), trackedRacesCopy);
threadLocalTransporter.ifPresent(ThreadLocalTransporter::rememberThreadLocalStates);
eventQueue.addWork(() -> {
for (TrackedRace trackedRace : trackedRacesCopy) {
listener.raceAdded(trackedRace);
}
withBeforeAndAfterHandling(threadLocalTransporter, () -> {
for (TrackedRace trackedRace : trackedRacesCopy) {
listener.raceAdded(trackedRace);
}
});
});
} finally {
unlockTrackedRacesAfterRead();
@@ -284,7 +307,8 @@ public class TrackedRegattaImpl implements TrackedRegatta {
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines,
WindStore windStore, long delayToLiveInMillis,
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm, RaceLogResolver raceLogResolver) {
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm, RaceLogResolver raceLogResolver,
Optional<ThreadLocalTransporter> threadLocalTransporter) {
logger.log(Level.INFO, "Creating DynamicTrackedRaceImpl for RaceDefinition " + raceDefinition.getName());
DynamicTrackedRaceImpl result = new DynamicTrackedRaceImpl(this, raceDefinition, sidelines, windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind,
@@ -295,7 +319,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
if (raceDefinitionSetToUpdate != null) {
raceDefinitionSetToUpdate.addRaceDefinition(raceDefinition, result);
}
addTrackedRace(result);
addTrackedRace(result, threadLocalTransporter);
return result;
}
}
@@ -14,5 +14,6 @@ Require-Bundle: com.sap.sailing.domain.persistence,
com.sap.sse.mongodb,
org.mongodb.mongo-java-driver;bundle-version="2.13.0",
com.sap.sailing.expeditionconnector.common,
com.sap.sailing.server.gateway.serialization.shared.android
com.sap.sailing.server.gateway.serialization.shared.android,
com.sap.sse
Export-Package: com.sap.sailing.expeditionconnector.persistence
@@ -12,6 +12,7 @@ import org.osgi.framework.ServiceRegistration;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier;
import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier;
import com.sap.sse.MasterDataImportClassLoaderService;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.mongodb.MongoDBService;
@@ -37,6 +38,7 @@ public class Activator implements BundleActivator {
Activator.context = bundleContext;
registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new ExpeditionGpsDeviceIdentifierMongoHandler(), getDict(ExpeditionGpsDeviceIdentifier.TYPE)));
registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new ExpeditionSensorDeviceIdentifierMongoHandler(), getDict(ExpeditionSensorDeviceIdentifier.TYPE)));
registrations.add(context.registerService(MasterDataImportClassLoaderService.class, new MasterDataImportClassLoaderServiceImpl(), null));
for (CollectionNames name : CollectionNames.values()) {
MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name());
}
@@ -0,0 +1,12 @@
package com.sap.sailing.expeditionconnector.persistence.impl;
import com.sap.sse.MasterDataImportClassLoaderService;
public class MasterDataImportClassLoaderServiceImpl implements MasterDataImportClassLoaderService {
@Override
public ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
}
@@ -7,6 +7,7 @@ import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Future;
@@ -77,6 +78,7 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.util.ThreadLocalTransporter;
public class MockedTrackedRace implements DynamicTrackedRace {
private static final long serialVersionUID = 5827912985564121181L;
@@ -296,15 +298,15 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void addTrackedRace(TrackedRace trackedRace) {
public void addTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
public void removeTrackedRace(TrackedRace trackedRace) {
public void removeTrackedRace(TrackedRace trackedRace, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
public void addRaceListener(RaceListener listener) {
public void addRaceListener(RaceListener listener, Optional<ThreadLocalTransporter> threadLocalTransporter) {
}
@Override
@@ -326,7 +328,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate,
boolean useMarkPassingCalculator, RaceLogResolver raceLogResolver) {
boolean useMarkPassingCalculator, RaceLogResolver raceLogResolver, Optional<ThreadLocalTransporter> threadLocalTransporter) {
return null;
}
@@ -11,8 +11,6 @@ public class ManeuverTypeFormatter {
return stringMessages.headUp();
case JIBE:
return stringMessages.jibe();
case MARK_PASSING:
return stringMessages.markPassing();
case PENALTY_CIRCLE:
return stringMessages.penaltyCircle();
case TACK:
@@ -204,7 +204,6 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.LegIdentifier;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.NotFoundException;
@@ -339,7 +338,6 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.Track;
@@ -3425,14 +3423,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
List<ManeuverDTO> result = new ArrayList<ManeuverDTO>();
for (Maneuver maneuver : maneuvers) {
final ManeuverDTO maneuverDTO;
if (maneuver.getType() == ManeuverType.MARK_PASSING) {
if (maneuver.isMarkPassing()) {
maneuverDTO = new MarkpassingManeuverDTO(maneuver.getType(), maneuver.getNewTack(),
maneuver.getPosition(),
maneuver.getTimePoint().asDate(),
createSpeedWithBearingDTO(maneuver.getSpeedWithBearingBefore()),
createSpeedWithBearingDTO(maneuver.getSpeedWithBearingAfter()),
maneuver.getDirectionChangeInDegrees(), maneuver.getManeuverLoss()==null?null:maneuver.getManeuverLoss().getMeters(),
((MarkPassingManeuver) maneuver).getSide());
maneuver.getToSide());
} else {
maneuverDTO = new ManeuverDTO(maneuver.getType(), maneuver.getNewTack(),
maneuver.getPosition(),
@@ -9,6 +9,7 @@ import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@@ -104,7 +105,8 @@ public class TestStoringAndRetrievingWindTracksTest extends AbstractTracTracLive
@Override
public void addRaceDefinition(RaceDefinition race, DynamicTrackedRace trackedRace) {
}
}, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
}, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class),
Optional.empty());
WindSource windSource = new WindSourceImpl(WindSourceType.WEB);
Mongo myFirstMongo = newMongo();
DB firstDatabase = myFirstMongo.getDB(dbConfiguration.getDatabaseName());
@@ -17,6 +17,7 @@ public class ManeuverJsonSerializer implements JsonSerializer<Maneuver> {
public static final String POSITION_AND_TIME = "positionAndTime";
public static final String MAX_ANGULAR_VELOCITY_IN_DEGREES_PER_SECOND = "maxAngularVelocityInDegreesPerSecond";
public static final String LOWEST_SPEED_IN_KNOTS = "lowestSpeedInKnots";
public static final String MARK_PASSING = "markPassing";
private final GPSFixJsonSerializer gpsFixSerializer;
private final DistanceJsonSerializer distanceSerializer;
@@ -46,6 +47,7 @@ public class ManeuverJsonSerializer implements JsonSerializer<Maneuver> {
result.put(POSITION_AND_TIME, gpsFixSerializer.serialize(maneuver));
result.put(MAX_ANGULAR_VELOCITY_IN_DEGREES_PER_SECOND, maneuver.getMaxAngularVelocityInDegreesPerSecond());
result.put(LOWEST_SPEED_IN_KNOTS, maneuver.getLowestSpeed().getKnots());
result.put(MARK_PASSING, maneuver.isMarkPassing());
return result;
}
}
@@ -17,6 +17,7 @@ public class ManeuverWithEstimationDataJsonSerializer implements JsonSerializer<
public static final String NEW_TACK = "newTack";
public static final String POSITION_AND_TIME = "positionAndTime";
public static final String MAX_ANGULAR_VELOCITY_IN_DEGREES_PER_SECOND = "maxAngularVelocityInDegreesPerSecond";
public final static String MARK_PASSING = "markPassing";
public static final String MANEUVER_LOSS_IN_METERS = "maneuverLossInMeters";
public static final String MAIN_CURVE_BOUNDARIES = "mainCurveBoundaries";
public static final String MANEUVER_BOUNDARIES = "maneuverBoundaries";
@@ -51,6 +52,7 @@ public class ManeuverWithEstimationDataJsonSerializer implements JsonSerializer<
result.put(MANEUVER_TYPE, maneuver.getType() == null ? null : maneuver.getType().name());
result.put(NEW_TACK, maneuver.getNewTack() == null ? null : maneuver.getNewTack().name());
result.put(MAX_ANGULAR_VELOCITY_IN_DEGREES_PER_SECOND, maneuver.getMaxAngularVelocityInDegreesPerSecond());
result.put(MARK_PASSING, maneuver.isMarkPassing());
result.put(MANEUVER_LOSS_IN_METERS,
maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getMeters());
result.put(POSITION_AND_TIME, gpsFixSerializer.serialize(maneuver));
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@@ -87,7 +88,7 @@ public class StatisticsTest {
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(END_OF_TRACKING));
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(START_OF_RACE));
regatta.addTrackedRace(trackedRace);
regatta.addTrackedRace(trackedRace, Optional.empty());
}
private TrackedRaceStatisticsCacheImpl getStatisticsCacheWithRegattaAdded() throws Exception {
@@ -104,7 +105,7 @@ public class StatisticsTest {
public void raceAdded(TrackedRace trackedRace) {
}
};
regatta.addRaceListener(raceListener);
regatta.addRaceListener(raceListener, Optional.empty());
regatta.removeRaceListener(raceListener).get();
return trackedRaceStatisticsCache;
@@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -85,17 +86,20 @@ public class RaceTrackerStartStopTest {
trackedRegatta1.createTrackedRace(raceDef1, Collections.<Sideline> emptyList(),
/* windStore */ EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 0l,
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class),
Optional.empty());
regatta.addRace(raceDef2);
trackedRegatta1.createTrackedRace(raceDef2, Collections.<Sideline> emptyList(),
/* windStore */ EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 0l,
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class),
Optional.empty());
regatta.addRace(raceDef3);
trackedRegatta1.createTrackedRace(raceDef3, Collections.<Sideline> emptyList(),
/* windStore */ EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 0l,
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class),
Optional.empty());
Long trackerID1 = new Long(1);
Long trackerID2 = new Long(2);
Long trackerID3 = new Long(3);
@@ -10,6 +10,7 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Optional;
import java.util.logging.Logger;
import org.junit.After;
@@ -102,7 +103,7 @@ public class RaceTrackerTest {
@Override
public void raceRemoved(TrackedRace trackedRace) {
}
});
}, Optional.empty());
synchronized (trackedRaces) {
if (trackedRaces[0] == null) {
trackedRaces.wait();
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.Before;
@@ -101,7 +102,8 @@ public class RemoveLeaderboardTest {
trackedRace = trackedRegatta1.createTrackedRace(raceDef1, Collections.<Sideline> emptyList(),
/* windStore */ EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 0l,
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class),
Optional.empty());
}
@Test
@@ -35,7 +35,6 @@ import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMong
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.racelog.tracking.SensorFixStoreSupplier;
import com.sap.sailing.domain.tracking.TrackedRegattaListener;
import com.sap.sailing.server.MasterDataImportClassLoaderService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.RacingEventServiceMXBean;
import com.sap.sailing.server.impl.preferences.model.BoatClassNotificationPreferences;
@@ -43,6 +42,7 @@ import com.sap.sailing.server.impl.preferences.model.CompetitorNotificationPrefe
import com.sap.sailing.server.notification.impl.SailingNotificationServiceImpl;
import com.sap.sailing.server.statistics.TrackedRaceStatisticsCache;
import com.sap.sailing.server.statistics.TrackedRaceStatisticsCacheImpl;
import com.sap.sse.MasterDataImportClassLoaderService;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.common.Util;
import com.sap.sse.mail.MailService;
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -1753,13 +1754,15 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
RaceDefinition race = getRace(raceIdentifier);
return trackedRegatta.createTrackedRace(race, Collections.<Sideline> emptyList(), windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
/* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this);
/* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this,
Optional.of(this.getThreadLocalTransporterForCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster()));
}
private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(
DynamicTrackedRegatta trackedRegatta) {
if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) {
trackedRegatta.addRaceListener(new RaceAdditionListener());
trackedRegatta.addRaceListener(new RaceAdditionListener(),
Optional.of(this.getThreadLocalTransporterForCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster()));
}
}
@@ -1815,7 +1818,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
linkRaceToConfiguredLeaderboardColumns(trackedRace);
TrackedRaceReplicatorAndNotifier trackedRaceReplicator = new TrackedRaceReplicatorAndNotifier(trackedRace);
trackedRaceReplicators.put(trackedRace, trackedRaceReplicator);
trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, true);
trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, /* notifyAboutGPSFixesAlreadyLoaded */ true);
PolarFixCacheUpdater polarFixCacheUpdater = new PolarFixCacheUpdater(trackedRace);
polarFixCacheUpdaters.put(trackedRace, polarFixCacheUpdater);
@@ -2449,7 +2452,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
final int newSizeOfTrackedRaces;
oldSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
try {
trackedRegatta.removeTrackedRace(trackedRace);
trackedRegatta.removeTrackedRace(trackedRace, Optional.of(
getThreadLocalTransporterForCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster()));
newSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
isTrackedRacesBecameEmpty = (oldSizeOfTrackedRaces > 0 && newSizeOfTrackedRaces == 0);
} finally {
@@ -7,6 +7,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
@@ -477,7 +478,8 @@ public class ImportMasterDataOperation extends
trackedRegatta.unlockTrackedRacesAfterRead();
}
for (TrackedRace raceToRemove : toRemove) {
trackedRegatta.removeTrackedRace(raceToRemove);
trackedRegatta.removeTrackedRace(raceToRemove, Optional.of(toState
.getThreadLocalTransporterForCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster()));
RaceDefinition race = existingRegatta.getRaceByName(raceToRemove
.getRaceIdentifier().getRaceName());
if (race != null) {
@@ -6,6 +6,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -283,7 +284,7 @@ public class SimulationServiceImpl implements SimulationService {
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(regatta);
SimulationRaceListener raceListener = new SimulationRaceListener();
raceListeners.put(legIdentifier.getRegattaName(), raceListener);
trackedRegatta.addRaceListener(raceListener);
trackedRegatta.addRaceListener(raceListener, /* Not replicated */ Optional.empty());
}
if (!legListeners.containsKey(legIdentifier.getRaceIdentifier())) {
TrackedRace trackedRace = racingEventService.getTrackedRace(legIdentifier);
@@ -38,21 +38,8 @@
</li>
</ul>
<h2 class="articleSubheadline">February 2018</h2>
<ul class="bulletList">
<li>There is an integration with WindFinder (<a href="https://www.windfinder.com">https://www.windfinder.com</a>) now.
In the Event edit dialog there is a new tab "WindFinder" where so-called "spot collection ID" can be entered.
Such spot collection IDs are negotiated and manually selected by the WindFinder team upon request. Talk to
the SAP Sailing Analytics team or to WindFinder directly to obtain one for your area.<p>
When such spot collections are provided for an event, a "Weather Data" button will appear on the event
landing page. Races whose tracking is started with wind tracking included will obtain wind information
from the wind finder spots of the spot collections configured. When the wind arrows for those readings
appear on the map, the bubble displayed when clicking them also contains a link to the respective
WindFinder web page.<p>
The links to the WindFinder page are sensitive to the event time. If it's a few days before the event,
a forecast link will be used. During the event the actual report page is shown. Outside of these dates
a general weather statistics page is shown for the venue.
</li>
<li>Further improvements of maneuver detection algorithm were introduced. Mark Passing is no longer regarded as a separate maneuver type. Instead, it is regarded as a supplementary information which gets appended to a maneuver instance. Therefore, a new filtering dimension was added for maneuver data mining, which is boolean and is named as "Mark Passing".</li>
</ul>
<h2 class="articleSubheadline">January 2018</h2>
@@ -70,7 +70,7 @@ public class XMLExportTest extends OnlineTracTracBasedTest {
String resultData = leaderboardData.getResultXML();
assertNotNull(resultData);
int resultDataLength = resultData.length();
assertTrue("resultData length was " + resultDataLength + ", but expected to be > 261000", resultDataLength > 261000);
assertTrue("resultData length was " + resultDataLength + ", but expected to be > 254000", resultDataLength > 254000);
}
}
@@ -15,6 +15,7 @@ import com.sap.sse.operationaltransformation.Operation;
import com.sap.sse.operationaltransformation.OperationWithTransformationSupport;
import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
import com.sap.sse.util.ObjectInputStreamResolvingAgainstCache;
import com.sap.sse.util.ThreadLocalTransporter;
/**
* Represents a replicable part of an application. Such a replicable part is usually holder of application state and a
@@ -199,6 +200,29 @@ public interface Replicable<S, O extends OperationWithResult<S, ?>> extends Repl
*/
void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(boolean b);
default ThreadLocalTransporter getThreadLocalTransporterForCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() {
return new ThreadLocalTransporter() {
private boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster;
private boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMasterAtBeginningOfTask;
@Override
public void rememberThreadLocalStates() {
currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster();
}
@Override
public void pushThreadLocalStates() {
currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMasterAtBeginningOfTask = isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster();
setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster);
}
@Override
public void popThreadLocalStates() {
setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMasterAtBeginningOfTask);
}
};
}
/**
* If an operation equal to <code>operationWithResultWithIdWrapper</code> has previously been passed to a call to
* {@link #addOperationSentToMasterForReplication(OperationWithResultWithIdWrapper)}, the call returns <code>true</code>
@@ -1,4 +1,4 @@
package com.sap.sailing.server;
package com.sap.sse;
/**
* Bundles which com.sap.sailing.server has no dependency to can use this service to let the master data importer
@@ -13,7 +13,5 @@ package com.sap.sailing.server;
*
*/
public interface MasterDataImportClassLoaderService {
ClassLoader getClassLoader();
}
@@ -0,0 +1,31 @@
package com.sap.sse.util;
/**
* When a task is to be pushed out to another thread, {@link ThreadLocal} state may need
* to be preserved and re-established so that the task, when executed by another thread
* will see the {@link ThreadLocal}s' state just as it was when the task was created.
*
* @author Axel Uhl (d043530)
*
*/
public interface ThreadLocalTransporter {
/**
* Call this method when your task is created and to be submitted to an executor.
* This will store the state of all relevant {@link ThreadLocal}s in this object.
*/
void rememberThreadLocalStates();
/**
* Call this at the beginning of the task that is executed on another thread. This method
* will save the states of the relevant {@link ThreadLocal}s and will then establish their
* state as remembered earlier by the {@link #rememberThreadLocalStates()} method.
*/
void pushThreadLocalStates();
/**
* Call this in a {@code finally} block at the end of your task. This will restore the
* relevant {@link ThreadLocal} states as they were remembered when {@link #pushThreadLocalStates()}
* was invoked.
*/
void popThreadLocalStates();
}