diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index c524eef8479..2dfa8c3313f 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -90,6 +90,10 @@ MAVEN_SETTINGS=$ACDIR/maven-settings.xml if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then # yield build so that we get updated product + + # preserve old build log + cp $ACDIR/build.log $ACDIR/build.log.old + cd $PROJECT_HOME/java if [ $gwtcompile -eq 1 ]; then echo "INFO: Compiling GWT (rm -rf com.sap.sailing.gwt.ui/com.sap.sailing.*)" @@ -116,7 +120,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then extra="$extra -P no-debug.without-proxy" fi - echo "Using following command: mvn $extra -P no-debug.without-proxy -fae -s $MAVEN_SETTINGS $clean install" + echo "Using following command: mvn $extra -fae -s $MAVEN_SETTINGS $clean install" mvn $extra -fae -s $MAVEN_SETTINGS $clean install 2>&1 | tee $ACDIR/build.log echo "Build complete. Do not forget to install product..." diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationTriggerResponse.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationTriggerResponse.java new file mode 100644 index 00000000000..78615496226 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationTriggerResponse.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface PolarSheetGenerationTriggerResponse extends Serializable{ + + public String getId(); + + public String getBoatClassName(); + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsData.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsData.java new file mode 100644 index 00000000000..d435b59acfb --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsData.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface PolarSheetsData extends Serializable { + + Number[][] getAveragedPolarDataByWindSpeed(); + + int getDataCount(); + + boolean isComplete(); + + Integer[] getDataCountPerAngleForWindspeed(int beaufort); + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsHistogramData.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsHistogramData.java new file mode 100644 index 00000000000..712962b82f9 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsHistogramData.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface PolarSheetsHistogramData extends Serializable{ + + public int getAngle(); + + public int getDataCount(); + + public Number[] getyValues(); + + public Number[] getxValues(); + + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationTriggerResponseImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationTriggerResponseImpl.java new file mode 100644 index 00000000000..ecd5033359e --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationTriggerResponseImpl.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; + +public class PolarSheetGenerationTriggerResponseImpl implements PolarSheetGenerationTriggerResponse { + + private static final long serialVersionUID = -2160795576114448218L; + private String id; + private String boatClassName; + + // For GWT serialization + PolarSheetGenerationTriggerResponseImpl() { + }; + + public PolarSheetGenerationTriggerResponseImpl(String id, String boatClassName) { + this.id = id; + this.boatClassName = boatClassName; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getBoatClassName() { + return boatClassName; + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsDataImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsDataImpl.java new file mode 100644 index 00000000000..0d38c4920fd --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsDataImpl.java @@ -0,0 +1,50 @@ +package com.sap.sailing.domain.common.impl; + +import java.util.Map; + +import com.sap.sailing.domain.common.PolarSheetsData; + +public class PolarSheetsDataImpl implements PolarSheetsData { + + private static final long serialVersionUID = -4649254807341866894L; + + private Number[][] averagedPolarDataByWindSpeed; + + private boolean complete; + + private int dataCount; + + private Map dataCountPerAngleForWindspeed; + + //For GWT Serialization + PolarSheetsDataImpl() {}; + + public PolarSheetsDataImpl(Number[][] averagedPolarDataByWindSpeed, boolean complete, int dataCount, Map dataCountPerAngleForWindspeed) { + this.averagedPolarDataByWindSpeed = averagedPolarDataByWindSpeed; + this.complete = complete; + this.dataCount = dataCount; + this.dataCountPerAngleForWindspeed = dataCountPerAngleForWindspeed; + } + + @Override + public Number[][] getAveragedPolarDataByWindSpeed() { + return averagedPolarDataByWindSpeed; + } + + @Override + public boolean isComplete() { + return complete; + } + + @Override + public int getDataCount() { + return dataCount; + } + + + @Override + public Integer[] getDataCountPerAngleForWindspeed(int beaufort) { + return dataCountPerAngleForWindspeed.get(beaufort); + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsHistogramDataImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsHistogramDataImpl.java new file mode 100644 index 00000000000..c5c321882c0 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsHistogramDataImpl.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.PolarSheetsHistogramData; + +public class PolarSheetsHistogramDataImpl implements PolarSheetsHistogramData { + + private static final long serialVersionUID = 7492650285773447757L; + + private Number[] yValues; + + private Number[] xValues; + + private int angle; + + private int dataCount; + + //For GWT serialization + PolarSheetsHistogramDataImpl() {} + + public PolarSheetsHistogramDataImpl(int angle, Number[] xValues, Number[] yValues, int dataCount) { + super(); + this.angle = angle; + this.yValues = yValues; + this.xValues = xValues; + this.dataCount = dataCount; + } + + @Override + public Number[] getyValues() { + return yValues; + } + + @Override + public Number[] getxValues() { + return xValues; + } + + @Override + public int getAngle() { + return angle; + } + + @Override + public int getDataCount() { + return dataCount; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java index fdfc9f0bb77..bf55a8d43ce 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java @@ -84,4 +84,6 @@ public interface MongoObjectFactory { void storeRegattaForRaceID(String id, Regatta regatta); + void removeRegattaForRaceID(String raceIDAsString, Regatta regatta); + } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 8467eac8c96..c18ca5fda3d 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -467,4 +467,11 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName()); regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false); } + + @Override + public void removeRegattaForRaceID(String raceIDAsString, Regatta regatta) { + DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name()); + DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString); + regattaForRaceIDCollection.remove(query); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java new file mode 100644 index 00000000000..0ec3cb3e87d --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java @@ -0,0 +1,137 @@ +package com.sap.sailing.domain.test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.NavigableSet; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.polarsheets.PerRaceAndCompetitorPolarSheetGenerationWorker; +import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker; +import com.sap.sailing.domain.test.mock.MockedTrackedRace; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; + +public class PolarSheetGenerationTest { + + @Test + public void testPolarSheetRawDataGeneration() throws InterruptedException { + Executor executor = new ThreadPoolExecutor(/* corePoolSize */ 0, + /* maximumPoolSize */ Runtime.getRuntime().availableProcessors(), + /* keepAliveTime */ 60, TimeUnit.SECONDS, + /* workQueue */ new LinkedBlockingQueue()); + + MockTrackedRaceForPolarSheetGeneration race = new MockTrackedRaceForPolarSheetGeneration(); + + TimePoint startTime = new MillisecondsTimePoint(1); + TimePoint endTime = new MillisecondsTimePoint(4); + //Only used for storing and exporting results in this test case: + PolarSheetGenerationWorker resultContainer = new PolarSheetGenerationWorker(new HashSet(), executor); + BoatClass forelle = new BoatClassImpl("Forelle", true); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); + PerRaceAndCompetitorPolarSheetGenerationWorker task = new PerRaceAndCompetitorPolarSheetGenerationWorker(race, resultContainer, startTime, endTime, competitor); + + executor.execute(task); + + double timeUntilTimeout = 1000; + while (!task.isDone() && timeUntilTimeout > 0) { + Thread.sleep(100); + timeUntilTimeout = timeUntilTimeout - 0.1; + } + + Assert.assertTrue(task.isDone()); + + PolarSheetsData data = resultContainer.getPolarData(); + Assert.assertEquals(4, data.getDataCount()); + Assert.assertEquals(4.0, data.getAveragedPolarDataByWindSpeed()[1][45]); + Assert.assertEquals(2.0, data.getAveragedPolarDataByWindSpeed()[1][55]); + Assert.assertEquals(6.0, data.getAveragedPolarDataByWindSpeed()[1][30]); + + } + + + @SuppressWarnings("serial") + private class MockTrackedRaceForPolarSheetGeneration extends MockedTrackedRace { + @Override + public DynamicGPSFixTrack getTrack(Competitor competitor) { + DynamicGPSFixTrack track = new MockDynamicGPSFixMovinTrackForPolarSheetGeneration(competitor, 0); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.0, 1.0), new MillisecondsTimePoint(1), new KnotSpeedWithBearingImpl(3.0, new DegreeBearingImpl(-45.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.001, 1.001), new MillisecondsTimePoint(2), new KnotSpeedWithBearingImpl(5.0, new DegreeBearingImpl(-45.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.002, 1.002), new MillisecondsTimePoint(3), new KnotSpeedWithBearingImpl(2.0, new DegreeBearingImpl(-55.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.003, 1.003), new MillisecondsTimePoint(4), new KnotSpeedWithBearingImpl(6.0, new DegreeBearingImpl(-30.0)))); + return track; + } + + @Override + public Wind getWind(Position p, TimePoint at) { + Wind wind = new WindImpl(p, at, new KnotSpeedWithBearingImpl(2.0, new DegreeBearingImpl(180.0))); + return wind; + } + + @Override + public RaceDefinition getRace() { + BoatClass forelle = new BoatClassImpl("Forelle", true); + RaceDefinition race = new RaceDefinitionImpl("Forelle1", new CourseImpl("ForelleCourse", new ArrayList()), forelle, new ArrayList()); + return race; + } + } + + @SuppressWarnings("serial") + private class MockGPSFixMovingForPolarSheetGeneration extends GPSFixMovingImpl { + + public MockGPSFixMovingForPolarSheetGeneration(Position position, TimePoint timePoint, SpeedWithBearing speed) { + super(position, timePoint, speed); + } + + @Override + public boolean isValid() { + return true; + } + + } + + @SuppressWarnings("serial") + private class MockDynamicGPSFixMovinTrackForPolarSheetGeneration extends DynamicGPSFixMovingTrackImpl { + + public MockDynamicGPSFixMovinTrackForPolarSheetGeneration(ItemType trackedItem, + long millisecondsOverWhichToAverage) { + super(trackedItem, millisecondsOverWhichToAverage); + } + + @Override + protected boolean isValid(NavigableSet rawFixes, GPSFixMoving e) { + return true; + } + + } + +} diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index bc76fb9fd21..0231cb99102 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -13,6 +13,7 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.leaderboard, com.sap.sailing.domain.leaderboard.impl, com.sap.sailing.domain.leaderboard.meta, + com.sap.sailing.domain.polarsheets, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, com.sap.sailing.util, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index e167ac671d8..e167982678b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -29,12 +29,6 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R private static final long serialVersionUID = 2370461218294770084L; private final Regatta regatta; - /** - * If this member is null, {@link #getName()} will use the regatta name as the default name for this - * leaderboard. Otherwise, the {@link #displayName} is used. - */ - private String displayName; - public RegattaLeaderboardImpl(Regatta regatta, SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) { super(scoreCorrection, resultDiscardingRule); @@ -47,7 +41,7 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R */ @Override public void setName(String newName) { - displayName = newName; + setDisplayName(newName); } @Override @@ -58,8 +52,8 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R @Override public String getName() { String result; - if (displayName != null) { - result = displayName; + if (getDisplayName() != null) { + result = getDisplayName(); } else { result = getRegatta().getName(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeed.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeed.java new file mode 100644 index 00000000000..b28ce358c86 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeed.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.polarsheets; + +import com.sap.sailing.domain.common.Speed; + +public interface BoatAndWindSpeed { + + public Speed getBoatSpeed(); + + public Speed getWindSpeed(); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeedImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeedImpl.java new file mode 100644 index 00000000000..0fc2ccc1eff --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/BoatAndWindSpeedImpl.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.polarsheets; + +import com.sap.sailing.domain.common.Speed; + +public class BoatAndWindSpeedImpl implements BoatAndWindSpeed{ + + private Speed boatSpeed; + private Speed windSpeed; + + + + public BoatAndWindSpeedImpl(Speed boatSpeed, Speed windSpeed) { + this.boatSpeed = boatSpeed; + this.windSpeed = windSpeed; + } + + @Override + public Speed getBoatSpeed() { + return boatSpeed; + } + + @Override + public Speed getWindSpeed() { + return windSpeed; + } + + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PerRaceAndCompetitorPolarSheetGenerationWorker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PerRaceAndCompetitorPolarSheetGenerationWorker.java new file mode 100644 index 00000000000..f44c8b176d9 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PerRaceAndCompetitorPolarSheetGenerationWorker.java @@ -0,0 +1,109 @@ +package com.sap.sailing.domain.polarsheets; + +import java.util.Iterator; +import java.util.NavigableSet; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; + +/** + * Iterates through the fixes of one competitor in one tracked race and fills the {@link PolarSheetGenerationWorker} + * with datapoints that are found for the speed of the boat and its angle to the wind + * + * @author D054528 Frederik Petersen + * + */ +public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable{ + + private final TrackedRace race; + + private final PolarSheetGenerationWorker polarSheetGenerationWorker; + + private TimePoint startTime; + + private TimePoint endTime; + + private final Competitor competitor; + + private boolean done = false; + + public PerRaceAndCompetitorPolarSheetGenerationWorker(TrackedRace race, + PolarSheetGenerationWorker polarSheetGenerationWorker, TimePoint startTime, TimePoint endTime, + Competitor competitor) { + super(); + this.race = race; + this.polarSheetGenerationWorker = polarSheetGenerationWorker; + this.startTime = startTime; + this.endTime = endTime; + this.competitor = competitor; + optimizeStartTime(); + optimizeEndTime(); + } + + private void optimizeEndTime() { + NavigableSet markPassings = race.getMarkPassings(competitor); + if (markPassings == null || markPassings.size() < 1) { + return; + } + MarkPassing passedFinish = markPassings.last(); + TimePoint passedFinishTimePoint = passedFinish.getTimePoint(); + if (passedFinishTimePoint.before(endTime)) { + endTime = passedFinishTimePoint; + } + } + + private void optimizeStartTime() { + NavigableSet markPassings = race.getMarkPassings(competitor); + if (markPassings == null || markPassings.size() < 1) { + return; + } + MarkPassing passedStart = markPassings.first(); + TimePoint passedStartTimePoint = passedStart.getTimePoint(); + if (passedStartTimePoint.after(startTime)) { + startTime = passedStartTimePoint; + } + } + + @Override + public void run() { + GPSFixTrack track = race.getTrack(competitor); + track.lockForRead(); + Iterator fixesIterator = track.getFixesIterator(startTime, true); + + while (fixesIterator.hasNext()) { + GPSFixMoving fix = fixesIterator.next(); + if (fix.getTimePoint().after(endTime)) { + break; + } + + if (track.hasDirectionChange(fix.getTimePoint(), race.getRace().getBoatClass() + .getManeuverDegreeAngleThreshold())) { + continue; + } + + SpeedWithBearing speedWithBearing = fix.getSpeed(); + Bearing bearing = speedWithBearing.getBearing(); + Position position = fix.getPosition(); + Wind wind = race.getWind(position, fix.getTimePoint()); + Bearing windBearing = wind.getFrom(); + double angleToWind = bearing.getDifferenceTo(windBearing).getDegrees(); + + polarSheetGenerationWorker.addPolarData(Math.round(angleToWind), speedWithBearing, wind); + } + + track.unlockAfterRead(); + done = true; + } + + public boolean isDone() { + return done; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PolarSheetGenerationWorker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PolarSheetGenerationWorker.java new file mode 100644 index 00000000000..276590f0fde --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polarsheets/PolarSheetGenerationWorker.java @@ -0,0 +1,174 @@ +package com.sap.sailing.domain.polarsheets; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl; +import com.sap.sailing.domain.tracking.TrackedRace; + +/** + * Allows extracting data about average speed for each angle creating the foundation of polar-sheet generation. Tasks + * are assigned to the executor per race & per competitor and the results are filled on the go to allow progress + * indication in the polar-sheet itself. + * + * @author D054528 Frederik Petersen + * + */ +public class PolarSheetGenerationWorker { + + private final Set workers; + + private final List> polarData; + + private final Executor executor; + + /** + * Will prepare the {@link PerRaceAndCompetitorPolarSheetGenerationWorker}s per race & per competitor. This includes + * determining start and end time. + * + * @param trackedRaces + * from which the data is to be collected + * @param executor + * executes the tasks upon {@link #startPolarSheetGeneration()} + */ + public PolarSheetGenerationWorker(Set trackedRaces, Executor executor) { + polarData = initializePolarDataContainer(); + this.executor = executor; + workers = new HashSet(); + for (TrackedRace race : trackedRaces) { + TimePoint startTime = race.getStartOfRace(); + TimePoint endTime = race.getEndOfRace(); + if (endTime == null) { + // TODO Figure out if there is an alternative: + endTime = race.getTimePointOfNewestEvent(); + } + RaceDefinition raceDefinition = race.getRace(); + Iterable competitors = raceDefinition.getCompetitors(); + + for (Competitor competitor : competitors) { + PerRaceAndCompetitorPolarSheetGenerationWorker task = new PerRaceAndCompetitorPolarSheetGenerationWorker(race, this, startTime, endTime, competitor); + workers.add(task); + } + + } + } + + private List> initializePolarDataContainer() { + List> container = new ArrayList>(); + for (int i = 0; i < 360; i++) { + container.add(new ArrayList()); + } + return container; + } + + /** + * Starts the {@link PerRaceAndCompetitorPolarSheetGenerationWorker}s + */ + public void startPolarSheetGeneration() { + for (Runnable task : workers) { + executor.execute(task); + } + } + + /** + * To be called from {@link PerRaceAndCompetitorPolarSheetGenerationWorker} for adding a datapoint to the list of + * results + * + * @param roundedAngle + * boat's angle to the wind + * @param boatSpeed + * boat's speed in knots + * @param windSpeed + * wind's speed in knots + */ + protected void addPolarData(long roundedAngle, Speed boatSpeed, Speed windSpeed) { + int angle = (int) roundedAngle; + if (angle < 0) { + angle = (360 + angle); + } + BoatAndWindSpeed speeds = new BoatAndWindSpeedImpl(boatSpeed, windSpeed); + polarData.get(angle).add(speeds); + } + + /** + * + * @return results already averaged per angle. Also datacount overall and per angle. And a completion flag to show + * if the complete polar sheet has been generated or if the current results are only an intermediate + * product. + */ + public PolarSheetsData getPolarData() { + Number[][] averagedPolarDataByWindSpeed = new Number[13][360]; + Integer[] dataCountPerAngle = new Integer[360]; + int dataCount = 0; + Map dataCountPerAngleForWindspeed = new HashMap(); + for (int i = 0; i < 13; i++) { + dataCountPerAngleForWindspeed.put(i, new Integer[360]); + } + for (int i = 0; i < 360; i++) { + // Avoid Concurrent modification, lock would slow things down + BoatAndWindSpeed[] values = polarData.get(i).toArray(new BoatAndWindSpeed[polarData.get(i).size()]); + dataCount = dataCount + values.length; + dataCountPerAngle[i] = values.length; + double[] sumsPerWindSpeed = new double[13]; + int[] dataCountPerWindSpeed = new int[13]; + for (BoatAndWindSpeed singleDataPoint : values) { + if (singleDataPoint != null) { + int windSpeed = (int) singleDataPoint.getWindSpeed().getBeaufort(); + //Somehow beaufort sometimes gets bigger than twelve. TODO: Investigation + if (windSpeed > 12) { + windSpeed = 12; + } + //TODO enable different kinds of metrics for boats speed + sumsPerWindSpeed[windSpeed] = sumsPerWindSpeed[windSpeed] + singleDataPoint.getBoatSpeed().getKnots(); + dataCountPerWindSpeed[windSpeed]++; + } + } + + for (int j = 0; j < 13; j++) { + Double average = sumsPerWindSpeed[j] / dataCountPerWindSpeed[j]; + if (average.isNaN()) { + average = new Double(0); + } + averagedPolarDataByWindSpeed[j][i] = average; + dataCountPerAngleForWindspeed.get(j)[i] = dataCountPerWindSpeed[j]; + } + + + + + } + boolean complete = true; + for (PerRaceAndCompetitorPolarSheetGenerationWorker task : workers) { + if (!task.isDone()) { + complete = false; + break; + } + } + + PolarSheetsData data = new PolarSheetsDataImpl(averagedPolarDataByWindSpeed, complete, dataCount, + dataCountPerAngleForWindspeed); + + return data; + } + + /** + * Can be used to present more detailed data. + * + * @return The complete set of datapoints that have been added. Can be quite big, depending on the amount of races + * and competitors. + */ + public List> getCompleteData() { + return polarData; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index e135b2a8cfe..03e0137ab39 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -5,10 +5,10 @@ import java.util.List; import java.util.NavigableSet; import java.util.SortedSet; -import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.DouglasPeucker; @@ -41,41 +41,42 @@ import com.sap.sailing.domain.common.impl.Util.Pair; */ public interface TrackedRace extends Serializable { final long MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS = 30000; - + final long DEFAULT_LIVE_DELAY_IN_MILLISECONDS = 5000; RaceDefinition getRace(); - + RegattaAndRaceIdentifier getRaceIdentifier(); - + /** - * Computes the estimated start time for this race (not to be confused with the {@link #getStartOfTracking()} time point - * which is expected to be before the race start time). When there are no {@link MarkPassing}s for the first mark, null - * is returned. If there are mark passings for the first mark and the start time is less than - * {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} before the first mark passing for the + * Computes the estimated start time for this race (not to be confused with the {@link #getStartOfTracking()} time + * point which is expected to be before the race start time). When there are no {@link MarkPassing}s for the first + * mark, null is returned. If there are mark passings for the first mark and the start time is less + * than {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} before the first mark passing for the * first mark. Otherwise, the first mark passing for the first mark minus - * {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} is returned as the race start time.

+ * {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} is returned as the race start time. + *

* * If no start time can be determined this way, null is returned. */ TimePoint getStartOfRace(); - + /** - * Determine the race end time is tricky. Boats may sink, stop, not finish, although they started the race. We therefore - * cannot wait for all boats to reach the finish line. - * The following rules are used to calculate the endOfRace: + * Determine the race end time is tricky. Boats may sink, stop, not finish, although they started the race. We + * therefore cannot wait for all boats to reach the finish line. The following rules are used to calculate the + * endOfRace: *

    - *
  1. Returns null if no boat passed the finish line
  2. - *
  3. Returns time of the last mark passing recorded for the finish line
  4. - *
  5. TODO: Returns the time of the first passing of the finish line + the target window (defined in the competition rules) - * if a target window has been defined for the race
  6. - *
+ *
  • Returns null if no boat passed the finish line
  • + *
  • Returns time of the last mark passing recorded for the finish line
  • + *
  • TODO: Returns the time of the first passing of the finish line + the target window (defined in the + * competition rules) if a target window has been defined for the race
  • + * */ TimePoint getEndOfRace(); /** - * Returns a list of the first and last mark passing times of all course waypoints. Callers wanting to iterate over the - * result must synchronize on the result. + * Returns a list of the first and last mark passing times of all course waypoints. Callers wanting to iterate over + * the result must synchronize on the result. */ Iterable>> getMarkPassingsTimes(); @@ -83,89 +84,84 @@ public interface TrackedRace extends Serializable { * Shorthand for {@link #getStart()}.{@link TimePoint#compareTo(TimePoint) compareTo(at)} <= 0 */ boolean hasStarted(TimePoint at); - + /** * Clients can safely iterate over the iterable returned because it's a non-live copy of the tracked legs of this - * tracked race. This implies that should an update to the underlying list of waypoints in this race's {@link Course} - * take place after this method has returned, then this won't be reflected in the result returned. Callers should - * obtain the {@link Course#lockForRead() course's read lock} while using the result of this call if they want to - * ensure that no course update is applied concurrently. + * tracked race. This implies that should an update to the underlying list of waypoints in this race's + * {@link Course} take place after this method has returned, then this won't be reflected in the result returned. + * Callers should obtain the {@link Course#lockForRead() course's read lock} while using the result of this call if + * they want to ensure that no course update is applied concurrently. */ Iterable getTrackedLegs(); - + TrackedLeg getTrackedLeg(Leg leg); - + /** - * Tracking information about the leg competitor is on at timePoint, or - * null if the competitor hasn't started any leg yet at timePoint or has - * already finished the race. + * Tracking information about the leg competitor is on at timePoint, or null + * if the competitor hasn't started any leg yet at timePoint or has already finished the race. */ TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint); - + /** * Tells which leg the leader at timePoint is on */ TrackedLeg getCurrentLeg(TimePoint timePoint); - + /** * Precondition: waypoint must still be part of {@link #getRace()}.{@link RaceDefinition#getCourse() getCourse()}. */ TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg); - + /** * Precondition: waypoint must still be part of {@link #getRace()}.{@link RaceDefinition#getCourse() getCourse()}. */ TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg); - + /** * The raw, updating feed of a single competitor participating in this race */ GPSFixTrack getTrack(Competitor competitor); - + /** - * Tells the leg on which the competitor was at time at. - * If the competitor hasn't passed the start waypoint yet, null is - * returned because the competitor was not yet on any leg at that point in time. If - * the time point happens to be after the last fix received from that competitor, - * the last known leg for that competitor is returned. If the time point is after the - * competitor's mark passing for the finish line, null is returned. - * For all legs except the last, if the time point equals a mark passing time point - * of the leg's starting waypoint, that leg is returned. For the time point of - * the mark passing for the finish line, the last leg is returned. + * Tells the leg on which the competitor was at time at. If the competitor hasn't passed + * the start waypoint yet, null is returned because the competitor was not yet on any leg at that point + * in time. If the time point happens to be after the last fix received from that competitor, the last known leg for + * that competitor is returned. If the time point is after the competitor's mark passing for the finish line, + * null is returned. For all legs except the last, if the time point equals a mark passing time point + * of the leg's starting waypoint, that leg is returned. For the time point of the mark passing for the finish line, + * the last leg is returned. */ TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at); - + TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, Leg leg); - + /** * @return a sequential number counting the updates that occurred to this tracked race. Callers may use this to ask * for updates newer than such a sequence number. */ long getUpdateCount(); - + int getRankDifference(Competitor competitor, Leg leg, TimePoint timePoint); - + /** - * Computes the rank of the competitor in this race for the current time. + * Computes the rank of the competitor in this race for the current time. */ int getRank(Competitor competitor) throws NoWindException; - + /** - * Computes the rank of competitor in this race. A competitor is ahead of all - * competitors that are one or more legs behind. Within the same leg, the rank is determined - * by the windward distance to go and therefore depends on the assumptions of the wind direction - * for the given timePoint. If the race hasn't {@link #hasStarted(TimePoint) started} - * yet, the result is undefined. + * Computes the rank of competitor in this race. A competitor is ahead of all competitors that are one + * or more legs behind. Within the same leg, the rank is determined by the windward distance to go and therefore + * depends on the assumptions of the wind direction for the given timePoint. If the race hasn't + * {@link #hasStarted(TimePoint) started} yet, the result is undefined. * - * @return 0 in case the competitor hasn't participated in the race; a rank starting - * with 1 where rank 1 identifies the leader otherwise + * @return 0 in case the competitor hasn't participated in the race; a rank starting with + * 1 where rank 1 identifies the leader otherwise */ int getRank(Competitor competitor, TimePoint timePoint) throws NoWindException; - + /** - * For a competitor, computes the distance (TODO not yet clear whether over ground or - * projected onto wind direction) into the race secondsIntoTheRace after - * the race {@link TrackedRace#getStart() started}. + * For a competitor, computes the distance (TODO not yet clear whether over ground or projected onto wind direction) + * into the race secondsIntoTheRace after the race {@link TrackedRace#getStart() started}. */ Distance getStartAdvantage(Competitor competitor, double secondsIntoTheRace); @@ -183,14 +179,14 @@ public interface TrackedRace extends Serializable { Iterable getMarkPassingsInOrder(Waypoint waypoint); /** - * Obtains the {@link MarkPassing} for competitor passing waypoint. If no such - * mark passing has been reported (yet), null is returned. + * Obtains the {@link MarkPassing} for competitor passing waypoint. If no such mark + * passing has been reported (yet), null is returned. */ MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint); /** - * Yields the track describing mark's movement over time; never null because a - * new track will be created in case no track was present for mark so far. + * Yields the track describing mark's movement over time; never null because a new track + * will be created in case no track was present for mark so far. */ GPSFixTrack getOrCreateTrack(Mark mark); @@ -204,7 +200,7 @@ public interface TrackedRace extends Serializable { * is returned. Otherwise, the center of gravity between the mark positions is computed and returned. */ Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint); - + /** * Same as {@link #getWind(Position, TimePoint, Iterable) getWind(p, at, Collections.emptyList())} */ @@ -212,8 +208,8 @@ public interface TrackedRace extends Serializable { /** * Obtains estimated interpolated wind information for a given position and time point. The information is taken - * from all wind sources available except for those listed in windSourcesToExclude, using the confidences - * of the wind values provided by the various sources during averaging. + * from all wind sources available except for those listed in windSourcesToExclude, using the + * confidences of the wind values provided by the various sources during averaging. */ Wind getWind(Position p, TimePoint at, Iterable windSourcesToExclude); @@ -231,10 +227,11 @@ public interface TrackedRace extends Serializable { Iterable getWindSources(); /** - * Same as {@link #getOrCreateWindTrack(WindSource, long) getOrCreateWindTrack(windSource, getMillisecondsOverWhichToAverageWind())}. + * Same as {@link #getOrCreateWindTrack(WindSource, long) getOrCreateWindTrack(windSource, + * getMillisecondsOverWhichToAverageWind())}. */ WindTrack getOrCreateWindTrack(WindSource windSource); - + WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation); /** @@ -243,26 +240,26 @@ public interface TrackedRace extends Serializable { void waitForNextUpdate(int sinceUpdate) throws InterruptedException; /** - * Time stamp of the start of the actual tracking. - * The value can be null (e.g. if we have not received any signal from the tracking infrastructure) + * Time stamp of the start of the actual tracking. The value can be null (e.g. if we have not received any signal + * from the tracking infrastructure) */ TimePoint getStartOfTracking(); /** - * Time stamp of the end of the actual tracking. - * The value can be null (e.g. if we have not received any signal from the tracking infrastructure) + * Time stamp of the end of the actual tracking. The value can be null (e.g. if we have not received any signal from + * the tracking infrastructure) */ TimePoint getEndOfTracking(); /** - * Regardless of the order in which events were received, this method returns the latest time point contained by any of - * the events received and processed. + * Regardless of the order in which events were received, this method returns the latest time point contained by any + * of the events received and processed. */ TimePoint getTimePointOfNewestEvent(); /** - * Regardless of the order in which events were received, this method returns the oldest time point contained by any of - * the events received and processed. + * Regardless of the order in which events were received, this method returns the oldest time point contained by any + * of the events received and processed. */ TimePoint getTimePointOfOldestEvent(); @@ -274,17 +271,18 @@ public interface TrackedRace extends Serializable { * some tracking providers such as TracTrac. If the caller wants to iterate on the resulting collection or * construct a {@link SortedSet#headSet(Object)} or {@link SortedSet#tailSet(Object)} and then iterate over * that, the caller needs to invoke {@link #lockForRead(Iterable)} with the collection returned as parameter - * because insertions into the competitor's mark passing collection will obtain the corresponding write lock. + * because insertions into the competitor's mark passing collection will obtain the corresponding write + * lock. */ NavigableSet getMarkPassings(Competitor competitor); - + void lockForRead(Iterable markPassings); - + void unlockAfterRead(Iterable markPassings); /** - * Time stamp that the event received last from the underlying push service carried on it. - * Note that these times may not increase monotonically. + * Time stamp that the event received last from the underlying push service carried on it. Note that these times may + * not increase monotonically. */ TimePoint getTimePointOfLastEvent(); @@ -296,7 +294,7 @@ public interface TrackedRace extends Serializable { * Gets the current delay of incoming events to the real time of the events in milliseconds */ long getDelayToLiveInMillis(); - + /** * Estimates the wind direction based on the observed boat courses at the time given for the position provided. The * estimate is based on the assumption that the boats which are on an upwind or a downwind leg sail with very @@ -321,13 +319,12 @@ public interface TrackedRace extends Serializable { * we may be able to also infer the wind speed from the boat tracks. */ Wind getEstimatedWindDirection(Position position, TimePoint timePoint); - + /** - * Determines whether the competitor is sailing on port or starboard tack at the - * timePoint requested. Note that this will have to retrieve information about the wind. - * This, in turn, can lead to the current thread obtaining the monitor of the various wind tracks, - * and, if the {@link WindSource#TRACK_BASED_ESTIMATION} source is used, also the monitors of the - * competitors' GPS tracks. + * Determines whether the competitor is sailing on port or starboard tack at the timePoint + * requested. Note that this will have to retrieve information about the wind. This, in turn, can lead to the + * current thread obtaining the monitor of the various wind tracks, and, if the + * {@link WindSource#TRACK_BASED_ESTIMATION} source is used, also the monitors of the competitors' GPS tracks. */ Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException; @@ -345,30 +342,34 @@ public interface TrackedRace extends Serializable { * mark positions are not known (yet) */ Wind getDirectionFromStartToNextMark(TimePoint at); - + /** - * Uses a {@link DouglasPeucker Douglas-Peucker} algorithm to approximate this track's fixes starting at - * time from until time point to such that the maximum distance between the - * track's fixes and the approximation is at most maxDistance. + * Uses a {@link DouglasPeucker Douglas-Peucker} algorithm to approximate this track's fixes starting at time + * from until time point to such that the maximum distance between the track's fixes and + * the approximation is at most maxDistance. */ List approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to); /** * @return a non-null but perhaps empty list of the maneuvers that competitor performed in - * this race between from and to. + * this race between from and to. Depending on waitForLatest the + * result is taken from the cache straight away (waitForLatest==false) or, if a re-calculation + * for the key is still ongoing, the result of that ongoing re-calculation is returned. */ - List getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) throws NoWindException; + List getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) + throws NoWindException; /** - * @return true if this race is known to start with an {@link LegType#UPWIND upwind} leg. - * If this is the case, the wind estimation may default to using the first leg's direction at race start - * time as the direction the wind comes from. + * @return true if this race is known to start with an {@link LegType#UPWIND upwind} leg. If this is + * the case, the wind estimation may default to using the first leg's direction at race start time as the + * direction the wind comes from. */ boolean raceIsKnownToStartUpwind(); /** - * Many calculations require valid wind data. In order to prevent NoWindException's to be handled by those calculation - * this method can be used to check whether the tracked race has sufficient wind information available. + * Many calculations require valid wind data. In order to prevent NoWindException's to be handled by those + * calculation this method can be used to check whether the tracked race has sufficient wind information available. + * * @return true if {@link #getWind(Position, TimePoint)} delivers a (not null) wind fix. */ boolean hasWindData(); @@ -380,23 +381,23 @@ public interface TrackedRace extends Serializable { boolean hasGPSData(); /** - * Adds a race change listener to the set of listeners that will be notified about changes to this race. - * The listener won't be serialized together with this object. + * Adds a race change listener to the set of listeners that will be notified about changes to this race. The + * listener won't be serialized together with this object. */ void addListener(RaceChangeListener listener); - + void removeListener(RaceChangeListener listener); Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint); Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) throws NoWindException; - + /** * Calls {@link #getWindWithConfidence(Position, TimePoint, Iterable)} and excludes those wind sources listed in * {@link #getWindSourcesToExclude}. */ WindWithConfidence> getWindWithConfidence(Position p, TimePoint at); - + /** * Lists those wind sources which by default are not considered in {@link #getWind(Position, TimePoint)} and * {@link #getWindWithConfidence(Position, TimePoint)}. @@ -406,8 +407,8 @@ public interface TrackedRace extends Serializable { /** * Loops over this tracked race's wind sources and from each asks its averaged wind for the position p * and time point at, using the particular wind source's averaging interval. The confidences delivered - * by each wind source are used during computing the averaged result across the wind sources. The result has the averaged - * confidence attached. + * by each wind source are used during computing the averaged result across the wind sources. The result has the + * averaged confidence attached. */ WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude); @@ -419,7 +420,8 @@ public interface TrackedRace extends Serializable { WindWithConfidence getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint); /** - * After the call returns, {@link #getWindSourcesToExclude()} returns an iterable that equals windSourcesToExclude + * After the call returns, {@link #getWindSourcesToExclude()} returns an iterable that equals + * windSourcesToExclude */ void setWindSourcesToExclude(Iterable windSourcesToExclude); @@ -430,25 +432,27 @@ public interface TrackedRace extends Serializable { * if true and any cache update is currently going on, wait for the update to complete and * then fetch the updated value; otherwise, serve this requests from whatever is currently in the cache */ - Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; + Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) + throws NoWindException; WindStore getWindStore(); Competitor getOverallLeader(TimePoint timePoint) throws NoWindException; /** - * Returns the competitors of this tracked race, according to their ranking. Competitors whose {@link #getRank(Competitor)} is 0 will - * be sorted "worst". + * Returns the competitors of this tracked race, according to their ranking. Competitors whose + * {@link #getRank(Competitor)} is 0 will be sorted "worst". */ List getCompetitorsFromBestToWorst(TimePoint timePoint) throws NoWindException; - Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException; + Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatestAnalyses) throws NoWindException; /** * When provided with a {@link WindStore} during construction, the tracked race will asynchronously load the wind - * data for this tracked race from the wind store in a background thread and update this tracked race with the results. - * Clients that want to wait for the wind loading process to complete can do so by calling this method which will block - * until the wind loading has completed. + * data for this tracked race from the wind store in a background thread and update this tracked race with the + * results. Clients that want to wait for the wind loading process to complete can do so by calling this method + * which will block until the wind loading has completed. */ void waitUntilWindLoadingComplete() throws InterruptedException; diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index b2f8e43b4b5..09a8f55521a 100755 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -7,7 +7,8 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Fragment-Host: com.sap.sailing.gwt.ui Require-Bundle: com.sap.sailing.server, - org.hamcrest.core;bundle-version="1.1.0" + org.hamcrest.core;bundle-version="1.1.0", + com.sap.sailing.expeditionconnector Bundle-ClassPath: . Import-Package: javax.jms;version="1.1.0", javax.servlet;version="2.5.0", diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java new file mode 100644 index 00000000000..d18a11b9bc6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -0,0 +1,727 @@ +package com.sap.sailing.gwt.ui.test; + +import java.io.Serializable; +import java.util.List; +import java.util.NavigableSet; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RegattaListener; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.RaceListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.WindTrackImpl; + +public class MockedTrackedRace implements DynamicTrackedRace { + private static final long serialVersionUID = 5827912985564121181L; + private final WindTrack windTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true, "TestWindTrack"); + + public WindTrack getWindTrack() { + return windTrack; + } + + @Override + public RaceDefinition getRace() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TimePoint getStartOfRace() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getTrackedLegs() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLeg getTrackedLeg(Leg leg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLeg getCurrentLeg(TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, Leg leg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getUpdateCount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getRankDifference(Competitor competitor, Leg leg, TimePoint timePoint) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getRank(Competitor competitor) throws NoWindException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getRank(Competitor competitor, TimePoint timePoint) throws NoWindException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Distance getStartAdvantage(Competitor competitor, double secondsIntoTheRace) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getMarkPassingsInOrder(Waypoint waypoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public DynamicGPSFixTrack getOrCreateTrack(Mark mark) { + // TODO Auto-generated method stub + return null; + } + + @Override + public WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitForNextUpdate(int sinceUpdate) throws InterruptedException { + // TODO Auto-generated method stub + + } + + @Override + public TimePoint getStartOfTracking() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TimePoint getTimePointOfNewestEvent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public NavigableSet getMarkPassings(Competitor competitor) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void recordFix(Competitor competitor, GPSFixMoving fix) { + // TODO Auto-generated method stub + + } + + @Override + public void recordWind(Wind wind, WindSource windSource) { + if (windSource.getType() == WindSourceType.EXPEDITION) { + windTrack.add(wind); + } + } + + @Override + public void addListener(RaceChangeListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public void updateMarkPassings(Competitor competitor, Iterable markPassings) { + // TODO Auto-generated method stub + + } + + @Override + public void setStartTimeReceived(TimePoint start) { + // TODO Auto-generated method stub + + } + + @Override + public DynamicGPSFixTrack getTrack(Competitor competitor) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void removeWind(Wind wind, WindSource windSource) { + // TODO Auto-generated method stub + + } + + @Override + public TimePoint getTimePointOfLastEvent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) { + // TODO Auto-generated method stub + + } + + @Override + public void setMillisecondsOverWhichToAverageWind(long millisecondsOverWhichToAverageWind) { + // TODO Auto-generated method stub + + } + + @Override + public long getMillisecondsOverWhichToAverageSpeed() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getMillisecondsOverWhichToAverageWind() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Wind getEstimatedWindDirection(Position position, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean hasStarted(TimePoint at) { + // TODO Auto-generated method stub + return false; + } + + @Override + public DynamicTrackedRegatta getTrackedRegatta() { + return new DynamicTrackedRegatta() { + private static final long serialVersionUID = 2651590861333064588L; + + @Override + public Regatta getRegatta() { + return new Regatta() { + private static final long serialVersionUID = -4908774269425170811L; + + @Override + public String getName() { + return "A Mocked Test Regatta"; + } + + @Override + public Serializable getId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getAllRaces() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BoatClass getBoatClass() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getCompetitors() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addRace(RaceDefinition race) { + // TODO Auto-generated method stub + } + + @Override + public void removeRace(RaceDefinition raceDefinition) { + // TODO Auto-generated method stub + } + + @Override + public RaceDefinition getRaceByName(String raceName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addRegattaListener(RegattaListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public void removeRegattaListener(RegattaListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public RegattaIdentifier getRegattaIdentifier() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getBaseName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getSeries() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Series getSeriesByName(String seriesName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isPersistent() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void addRaceColumnListener(RaceColumnListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public void removeRaceColumnListener(RaceColumnListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public ScoringScheme getScoringScheme() { + // TODO Auto-generated method stub + return null; + } + }; + } + + @Override + public Iterable getTrackedRaces() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getTrackedRaces(BoatClass boatClass) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addTrackedRace(TrackedRace trackedRace) { + // TODO Auto-generated method stub + + } + + @Override + public void removeTrackedRace(TrackedRace trackedRace) { + // TODO Auto-generated method stub + + } + + @Override + public void addRaceListener(RaceListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public DynamicTrackedRace getTrackedRace(RaceDefinition race) { + // TODO Auto-generated method stub + return null; + } + + @Override + public DynamicTrackedRace getExistingTrackedRace(RaceDefinition race) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void removeTrackedRace(RaceDefinition raceDefinition) { + // TODO Auto-generated method stub + + } + + @Override + public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, WindStore windStore, + long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + // TODO Auto-generated method stub + return null; + } + }; + } + + @Override + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Tack getTack(Competitor competitor, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Wind getDirectionFromStartToNextMark(TimePoint at) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean raceIsKnownToStartUpwind() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind) { + // TODO Auto-generated method stub + } + + @Override + public RegattaAndRaceIdentifier getRaceIdentifier() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TimePoint getEndOfRace() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) + throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Wind getWind(Position p, TimePoint at) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Wind getWind(Position p, TimePoint at, Iterable windSourcesToExclude) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getWindSources(WindSourceType type) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getWindSources() { + // TODO Auto-generated method stub + return null; + } + + @Override + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + Iterable windSourcesToExclude) { + // TODO Auto-generated method stub + return null; + } + + @Override + public WindWithConfidence getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getWindSourcesToExclude() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TimePoint getEndOfTracking() { + // TODO Auto-generated method stub + return null; + } + + @Override + public TimePoint getTimePointOfOldestEvent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) { + // TODO Auto-generated method stub + + } + + @Override + public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { + // TODO Auto-generated method stub + + } + + @Override + public Iterable>> getMarkPassingsTimes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public WindTrack getOrCreateWindTrack(WindSource windSource) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void recordFix(Mark mark, GPSFix fix) { + // TODO Auto-generated method stub + + } + + @Override + public void removeListener(RaceChangeListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public WindStore getWindStore() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setWindSourcesToExclude(Iterable windSourcesToExclude) { + // TODO Auto-generated method stub + } + + @Override + public Competitor getOverallLeader(TimePoint timePoint) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getDelayToLiveInMillis() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setDelayToLiveInMillis(long delayToLiveInMillis) { + // TODO Auto-generated method stub + + } + + @Override + public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { + // TODO Auto-generated method stub + + } + + @Override + public List getCompetitorsFromBestToWorst(TimePoint timePoint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) + throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitUntilWindLoadingComplete() throws InterruptedException { + // TODO Auto-generated method stub + } + + @Override + public Iterable getMarks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean hasWindData() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean hasGPSData() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void lockForRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } + + @Override + public TrackedRaceStatus getStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setStatus(TrackedRaceStatus newStatus) { + // TODO Auto-generated method stub + } + + @Override + public void waitUntilNotLoading() { + // TODO Auto-generated method stub + } +} diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java new file mode 100644 index 00000000000..80535f1975f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -0,0 +1,203 @@ +package com.sap.sailing.gwt.ui.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.ScoreCorrectionProvider; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sailing.gwt.ui.client.SailingService; +import com.sap.sailing.gwt.ui.server.SailingServiceImpl; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.replication.ReplicationService; + +public class PolarSheetGenerationServiceTest { + + private static final String BOAT_CLASS = "Forelle"; + + @Test + public void testPolarSheetGenerationService() throws InterruptedException { + + SailingService service = new MockSailingServiceForPolarSheetGeneration(); + + List idList = new ArrayList(); + idList.add(new RegattaNameAndRaceName("IrgendeineRegatta", "IrgendeinRennen")); + + PolarSheetGenerationTriggerResponse triggerData = service.generatePolarSheetForRaces(idList); + Assert.assertNotNull(triggerData); + Assert.assertEquals(BOAT_CLASS, triggerData.getBoatClassName()); + Assert.assertNotNull(triggerData.getId()); + + boolean complete = false; + PolarSheetsData results = null; + double timeOut = 10; + while (!complete && timeOut > 0) { + Thread.sleep(200); + timeOut = timeOut - 0.2; + results = service.getPolarSheetsGenerationResults(triggerData.getId()); + complete = results.isComplete(); + } + + Assert.assertTrue(complete); + Assert.assertNotNull(results); + + Assert.assertEquals(4, results.getDataCount()); + Assert.assertEquals(4.0, results.getAveragedPolarDataByWindSpeed()[1][45]); + Assert.assertEquals(2.0, results.getAveragedPolarDataByWindSpeed()[1][55]); + Assert.assertEquals(6.0, results.getAveragedPolarDataByWindSpeed()[1][30]); + + + + + } + + + @SuppressWarnings("serial") + private class MockSailingServiceForPolarSheetGeneration extends SailingServiceImpl { + + @Override + protected RacingEventService getService() { + RacingEventService service = new MockRacingEventServiceForPolarSheetGeneration(); + return service; + } + + @Override + protected ServiceTracker createAndOpenRacingEventServiceTracker( + BundleContext context) { + return null; + } + + @Override + protected ServiceTracker createAndOpenReplicationServiceTracker( + BundleContext context) { + return null; + } + + @Override + protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( + BundleContext bundleContext) {; + return null; + } + + } + + private class MockRacingEventServiceForPolarSheetGeneration extends RacingEventServiceImpl { + + @Override + public TrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) { + MockTrackedRaceForPolarSheetGeneration trackedRace = new MockTrackedRaceForPolarSheetGeneration(); + return trackedRace; + } + + } + + @SuppressWarnings("serial") + private class MockTrackedRaceForPolarSheetGeneration extends MockedTrackedRace { + + + @Override + public DynamicGPSFixTrack getTrack(Competitor competitor) { + DynamicGPSFixTrack track = new MockDynamicGPSFixMovinTrackForPolarSheetGeneration(competitor, 0); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.0, 1.0), new MillisecondsTimePoint(1), new KnotSpeedWithBearingImpl(3.0, new DegreeBearingImpl(-45.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.001, 1.001), new MillisecondsTimePoint(2), new KnotSpeedWithBearingImpl(5.0, new DegreeBearingImpl(-45.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.002, 1.002), new MillisecondsTimePoint(3), new KnotSpeedWithBearingImpl(2.0, new DegreeBearingImpl(-55.0)))); + track.addGPSFix(new MockGPSFixMovingForPolarSheetGeneration(new DegreePosition(1.003, 1.003), new MillisecondsTimePoint(4), new KnotSpeedWithBearingImpl(6.0, new DegreeBearingImpl(-30.0)))); + + //Should not be taken into consideration because it happens after the race has ended + track.addGPSFix(new GPSFixMovingImpl(new DegreePosition(2.0, 3.0), new MillisecondsTimePoint(7), new KnotSpeedWithBearingImpl(5.0, new DegreeBearingImpl(-45.0)))); + return track; + } + + @Override + public Wind getWind(Position p, TimePoint at) { + Wind wind = new WindImpl(p, at, new KnotSpeedWithBearingImpl(2.0, new DegreeBearingImpl(180.0))); + return wind; + } + + @Override + public RaceDefinition getRace() { + BoatClass forelle = new BoatClassImpl(BOAT_CLASS, true); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); + ArrayList competitors = new ArrayList(); + competitors.add(competitor); + RaceDefinition race = new RaceDefinitionImpl("Forelle1", new CourseImpl("ForelleCourse", new ArrayList()), forelle, competitors); + return race; + } + + @Override + public TimePoint getStartOfRace() { + return new MillisecondsTimePoint(1); + } + + @Override + public TimePoint getEndOfRace() { + return new MillisecondsTimePoint(5); + } + } + + @SuppressWarnings("serial") + private class MockGPSFixMovingForPolarSheetGeneration extends GPSFixMovingImpl { + + public MockGPSFixMovingForPolarSheetGeneration(Position position, TimePoint timePoint, SpeedWithBearing speed) { + super(position, timePoint, speed); + } + + @Override + public boolean isValid() { + return true; + } + + } + + @SuppressWarnings("serial") + private class MockDynamicGPSFixMovinTrackForPolarSheetGeneration extends DynamicGPSFixMovingTrackImpl { + + public MockDynamicGPSFixMovinTrackForPolarSheetGeneration(ItemType trackedItem, + long millisecondsOverWhichToAverage) { + super(trackedItem, millisecondsOverWhichToAverage); + } + + @Override + protected boolean isValid(NavigableSet rawFixes, GPSFixMoving e) { + return true; + } + + } + + +} diff --git a/java/com.sap.sailing.gwt.ui/.gitignore b/java/com.sap.sailing.gwt.ui/.gitignore index 609a3512f53..e93253c29d3 100755 --- a/java/com.sap.sailing.gwt.ui/.gitignore +++ b/java/com.sap.sailing.gwt.ui/.gitignore @@ -2,4 +2,5 @@ /.gwt /.generated /gwt-unitCache +/extras /com.sap.sailing.gwt.ui.* diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index e520b60654c..7c0fb0f2a1a 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,4 @@ -eclipse.preferences.version=1 -entryPointModules= -filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDEwMjRtXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TcGVjdGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVXNlck1hbmFnZW1lbnQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVHZWaWV3PC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlZpZGVvUG9wdXA8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuWW91dHViZVBvcHVwPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +eclipse.preferences.version=1 +entryPointModules= +filesCopiedToWebInfLib=gwt-servlet.jar +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDEwMjRtXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TcGVjdGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVXNlck1hbmFnZW1lbnQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVHZWaWV3PC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlZpZGVvUG9wdXA8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuWW91dHViZVBvcHVwPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlBvbGFyU2hlZXRzPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= diff --git a/java/com.sap.sailing.gwt.ui/PolarSheets.css b/java/com.sap.sailing.gwt.ui/PolarSheets.css new file mode 100644 index 00000000000..d423ec58e1d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/PolarSheets.css @@ -0,0 +1,133 @@ +@import url(fontface.css); +@import url(CommonControls.css); + +/** Add css rules here for your application. */ +/* + body #calendar:after + content: " "; + display: block; + height: 0; + clear: both; + visibility: hidden; +*/ + +.clear { + clear: both; + font-size: 1px; + height: 0px; + line-height: 0px; + margin: -1px 0 0; + width: 100%; } + +.cover { + left: -1000px; + overflow: hidden; + position: absolute; + top: -1000px; } + +body { + background: #fff; + font-family: 'UbuntuRegular', Arial, Verdana, sans-serif; + line-height: 1; + font-size: 14px; + font-weight: 400; + padding: 85px 0 120px 0; + margin: 0; + } + +/** Example rules used by the template application (remove for your app) */ +h1 { + font-size: 2em; + font-weight: bold; + color: #777777; + margin: 40px 0px 70px; + text-align: center; + } + +table { + border-collapse: separate; } + +.leaderboardMargin { + margin: 5px; +} + +.leaderboardContent { +} + +.leaderboardContent td, .leaderboardContent th { + vertical-align: bottom; + line-height: 1.3; + padding-top: 2px; +} + +.leaderboardContent-toolbar { + +} + +.leaderboardLabel { + border-bottom: 2px solid #007dc0; + font-weight: bold; + margin: 5px; + } + +.refreshAndSettings { + background: #f2f2f2; + border-bottom: 2px solid #fff; + border-top: 2px solid #fff; + height: 30px; } + +.refreshAndSettings table td { + padding: 0 10px 0 0;} + +.refreshAndSettings table table td{ + padding: 0 0 0 0;} + +input.magnifier{ + display: block; + margin: 3px auto 0 auto !important; +} + +body, table td, select, button { + font-family: 'UbuntuLight', Arial, Verdana, sans-serif; + font-size: 14px; +} + +.busyIndicator-simpleBusyIndicator { + margin-top: 7px; +} + +.errorLabel { + color: #FF0000; +} + +.abstractChartPanel-importantMessageOfChart { + font-family: 'UbuntuBold',Arial,Verdana,sans-serif; + font-size: 20px; + height: 70px; + margin: 0 auto; + padding: 49px 0 0 80px; + width: 300px; + color: #7c7c7c; + background: url(images/important-message.png) left center no-repeat; +} + +.LeaderboardHeader { + position: relative; + margin: 0 auto; + width: 400px; + text-align: center; + font-family: 'UbuntuRegular', Arial, Verdana, sans-serif; + font-size: 18px; + color: #fff; + margin-top: 22px; +} + +.leaderboardHeading { + font-size: 16px; + font-family: 'UbuntuRegular', Arial, Verdana, sans-serif; + font-weight: bold; +} + +.gwt-DialogBox { + z-index: 20; +} diff --git a/java/com.sap.sailing.gwt.ui/PolarSheets.html b/java/com.sap.sailing.gwt.ui/PolarSheets.html new file mode 100644 index 00000000000..ee8905e056c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/PolarSheets.html @@ -0,0 +1,34 @@ + + + + + + SAP Sailing Analytics Polar Sheets + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch index 194bef6417f..3c38c4ca347 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch @@ -4,7 +4,7 @@ - + @@ -48,7 +48,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index b2618376ad5..e32f7e6c78d 100644 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -4,7 +4,7 @@ - + @@ -47,7 +47,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/lib/Highcharts-2.3.5.zip b/java/com.sap.sailing.gwt.ui/WEB-INF/lib/Highcharts-2.3.5.zip new file mode 100644 index 00000000000..49d2320068b Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/WEB-INF/lib/Highcharts-2.3.5.zip differ diff --git a/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png b/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png index 9ad08955f9f..a7efa78aedc 100644 Binary files a/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png and b/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetPolarSheetDataByAngleAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetPolarSheetDataByAngleAction.java new file mode 100644 index 00000000000..6d4ecaab442 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetPolarSheetDataByAngleAction.java @@ -0,0 +1,32 @@ +package com.sap.sailing.gwt.ui.actions; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; + +public class GetPolarSheetDataByAngleAction extends DefaultAsyncAction { + + private final SailingServiceAsync sailingService; + private final String polarSheetId; + private final int angle; + private final int windSpeed; + + + + public GetPolarSheetDataByAngleAction(SailingServiceAsync sailingService, + String polarSheetId, int angle, int windSpeed, AsyncCallback callback) { + super(callback); + this.sailingService = sailingService; + this.polarSheetId = polarSheetId; + this.angle = angle; + this.windSpeed = windSpeed; + } + + + + @Override + public void execute(AsyncActionsExecutor asyncActionsExecutor) { + sailingService.getPolarSheetData(polarSheetId, angle, windSpeed, (AsyncCallback) getWrapperCallback(asyncActionsExecutor)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index d5e5e451ff8..ccb3d6a7d75 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -14,6 +14,9 @@ import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.ScoringSchemeType; @@ -283,4 +286,10 @@ public interface SailingService extends RemoteService { List getPreviousSwissTimingArchiveConfigurations(); void storeSwissTimingArchiveConfiguration(String swissTimingUrl); + + PolarSheetGenerationTriggerResponse generatePolarSheetForRaces(List selectedRaces); + + PolarSheetsData getPolarSheetsGenerationResults(String id); + + PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle, int windSpeed); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java old mode 100644 new mode 100755 index 5db3ff2da99..d504de78e45 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -1,438 +1,447 @@ -package com.sap.sailing.gwt.ui.client; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.domain.common.Color; -import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaIdentifier; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; -import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; -import com.sap.sailing.gwt.ui.shared.CompetitorDTO; -import com.sap.sailing.gwt.ui.shared.ControlPointDTO; -import com.sap.sailing.gwt.ui.shared.CourseDTO; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO; -import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.GPSFixDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sailing.gwt.ui.shared.ManeuverDTO; -import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; -import com.sap.sailing.gwt.ui.shared.QuickRankDTO; -import com.sap.sailing.gwt.ui.shared.RaceDTO; -import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; -import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; -import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; -import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; -import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; -import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; -import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; -import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; -import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; -import com.sap.sailing.gwt.ui.shared.VenueDTO; -import com.sap.sailing.gwt.ui.shared.WindDTO; -import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; - -/** - * The async counterpart of {@link SailingService} - */ -public interface SailingServiceAsync { - - void getRegattas(AsyncCallback> callback); - - /** - * The string returned in the callback's pair is the common event name - */ - void listTracTracRacesInEvent(String eventJsonURL, AsyncCallback>> callback); - - /** - * @param regattaToAddTo - * if null, an existing regatta by the name of the TracTrac event with the boat class name - * appended in parentheses will be looked up; if not found, a default regatta with that name will be - * created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier} - * is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used - * to add the races to. Otherwise, a default regatta as described above will be created and used. - * @param liveURI - * may be null or the empty string in which case the server will use the - * {@link TracTracRaceRecordDTO#liveURI} from the rr race record. - * @param simulateWithStartTimeNow - * if true, the connector will adjust the time stamps of all events received such that the - * first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all - * events received such that they seem to be sent in "real-time." So, more or less the time points attached - * to the events sent to the receivers will again approximate the wall time. - * @param storedURImay - * be null or the empty string in which case the server will use the - * {@link TracTracRaceRecordDTO#storedURI} from the rr race record. - */ - void trackWithTracTrac(RegattaIdentifier regattaToAddTo, - Iterable rrs, String liveURI, String storedURI, boolean trackWind, boolean correctWindByDeclination, - boolean simulateWithStartTimeNow, AsyncCallback callback); - - void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, - String hostname, int port, boolean canSendRequests, boolean trackWind, boolean correctWindByDeclination, - AsyncCallback asyncCallback); - - void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, - boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow, - AsyncCallback asyncCallback); - - void getPreviousTracTracConfigurations(AsyncCallback> callback); - - void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, - AsyncCallback callback); - - void stopTrackingEvent(RegattaIdentifier eventIdentifier, AsyncCallback callback); - - void stopTrackingRaces(Iterable racesToStopTracking, AsyncCallback asyncCallback); - - /** - * Untracks the race and removes it from the regatta. It will also be removed in all leaderboards - * @param regattaNamesAndRaceNames The identifier for the regatta name, and the race name to remove - */ - void removeAndUntrackRaces(Iterable regattaNamesAndRaceNames, AsyncCallback callback); - - void getRawWindFixes(RegattaAndRaceIdentifier raceIdentifier, Collection windSources, AsyncCallback callback); - - /** - * @param from if null, the tracked race's start of tracking is used - * @param to if null, the tracked race's time point of newest event is used - */ - void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds, - Collection windSourceTypeNames, AsyncCallback callback); - - /** - * @param windSourceTypeNames - * if null, data from all available wind sources will be returned, otherwise only from those - * whose {@link WindSource} name is contained in the windSources collection. - */ - void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, - double latDeg, double lngDeg, Collection windSourceTypeNames, - AsyncCallback callback); - - /** - * Same as {@link #getWindInfo(RegattaAndRaceIdentifier, Date, long, int, double, double, Collection, AsyncCallback)}, only - * that the wind is not requested for a specific position, but instead the wind sources associated with the tracked - * race identified by raceIdentifier are requested to deliver their original position. This will in - * particular preserve the positions of actual measurements and will deliver the averaged positions for averaged / - * combined wind read-outs. - * - * @param from - * must not be null - * @param numberOfFixes - * no matter how great this value is chosen, never returns data beyond the newest event recorded in the - * race - */ - void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, - Collection windSourceTypeNames, AsyncCallback callback); - - void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback callback); - - void removeWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO windDTO, AsyncCallback callback); - - /** - * @param from - * for the list of competitors provided as keys of this map, requests the GPS fixes starting with the - * date provided as value - * @param to - * for the list of competitors provided as keys (expected to be equal to the set of competitors used as - * keys in the from parameter, requests the GPS fixes up to but excluding the date provided - * as value - * @param extrapolate - * if true and no position is known for date, the last entry returned in the - * list of GPS fixes will be obtained by extrapolating from the competitors last known position before - * date and the estimated speed. - * @return a map where for each competitor participating in the race the list of GPS fixes in increasing - * chronological order is provided. The last one is the last position at or before date. - */ - void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, - Map from, Map to, - boolean extrapolate, AsyncCallback>> callback); - - void getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); - - void getRaceTimesInfos(Collection raceIdentifiers, AsyncCallback> callback); - - void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback asyncCallback); - - void getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback); - - /** - * Returns a {@link LeaderboardDTO} will information about all races, their points and competitor display names - * filled in. The column details are filled for the races whose named are provided in - * namesOfRacesForWhichToLoadLegDetails. - * - * @param date - * the time point for the leaderboard data to retrieve, or null for "live mode" which means - * that the server will produce whatever it has ready for the currently active live delay set on the - * server; in live mode, data served may be taken from caches that lag up to a few seconds. - * @param namesOfRaceColumnsForWhichToLoadLegDetails - * if null, no {@link LeaderboardEntryDTO#legDetails leg details} will be present in the - * result ({@link LeaderboardEntryDTO#legDetails} will be null for all - * {@link LeaderboardEntryDTO} objects contained). Otherwise, the {@link LeaderboardEntryDTO#legDetails} - * list will contain one entry per leg of the race {@link com.sap.sailing.domain.base.Course} for those race columns whose - * {@link com.sap.sailing.domain.base.RaceColumn#getType() name} is contained in namesOfRacesForWhichToLoadLegDetails. - * For all other columns, {@link LeaderboardEntryDTO#legDetails} is null. - */ - void getLeaderboardByName(String leaderboardName, Date date, - Collection namesOfRaceColumnsForWhichToLoadLegDetails, - AsyncCallback callback); - - void getLeaderboardNames(AsyncCallback> callback); - - void getLeaderboards(AsyncCallback> callback); - - void getLeaderboardsByEvent(RegattaDTO regatta, AsyncCallback> callback); - - void getLeaderboardsByRace(RaceDTO race, AsyncCallback> callback); - - void updateLeaderboard(String leaderboardName, String newLeaderboardName, String newLeaderboardDisplayName, - int[] newDiscardingThreasholds, AsyncCallback callback); - - void createFlexibleLeaderboard(String leaderboardName, int[] discardThresholds, ScoringSchemeType scoringSchemeType, - AsyncCallback asyncCallback); - - void createRegattaLeaderboard(RegattaIdentifier regattaIdentifier, int[] discardThresholds, - AsyncCallback asyncCallback); - - void removeLeaderboard(String leaderboardName, AsyncCallback asyncCallback); - - void renameLeaderboard(String leaderboardName, String newLeaderboardName, AsyncCallback asyncCallback); - - void addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace, - AsyncCallback callback); - - void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName, AsyncCallback callback); - - void removeLeaderboardColumn(String leaderboardName, String columnName, AsyncCallback callback); - - void connectTrackedRaceToLeaderboardColumn(String leaderboardName, String raceColumnName, String fleetName, - RegattaAndRaceIdentifier raceIdentifier, AsyncCallback asyncCallback); - - /** - * The key set of the map returned contains all fleets of the race column identified by the combination of - * leaderboardName and raceColumnName. If a value is null, there is no - * tracked race currently linked to the fleet in the race column; otherwise, the value is the {@link RaceIdentifier} - * of the tracked race currently connected for the fleet whose name is the key. The map returned is never null. - */ - void getRegattaAndRaceNameOfTrackedRaceConnectedToLeaderboardColumn(String leaderboardName, String raceColumnName, - AsyncCallback> callback); - - void disconnectLeaderboardColumnFromTrackedRace(String leaderboardName, String raceColumnName, String fleetName, - AsyncCallback callback); - - void updateLeaderboardCarryValue(String leaderboardName, String competitorIdAsString, Double carriedPoints, AsyncCallback callback); - - void updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, - MaxPointsReason maxPointsReason, Date date, AsyncCallback> asyncCallback); - - void updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String columnName, - Double correctedScore, Date date, AsyncCallback> asyncCallback); - - void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, - String comment, AsyncCallback callback); - - void updateLeaderboardScoreCorrectionsAndMaxPointsReasons(BulkScoreCorrectionDTO updates, - AsyncCallback callback); - - void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorID, String displayName, - AsyncCallback callback); - - void moveLeaderboardColumnUp(String leaderboardName, String columnName, AsyncCallback callback); - - void moveLeaderboardColumnDown(String leaderboardName, String columnName, AsyncCallback callback); - - void updateIsMedalRace(String leaderboardName, String columnName, boolean isMedalRace, AsyncCallback callback); - - void updateRaceDelayToLive(RegattaAndRaceIdentifier regattaAndRaceIdentifier, long delayToLiveInMs, AsyncCallback callback); - - void updateRacesDelayToLive(List regattaAndRaceIdentifiers, long delayToLiveInMs, AsyncCallback callback); - - void getPreviousSwissTimingConfigurations(AsyncCallback> asyncCallback); - - void listSwissTimingRaces(String hostname, int port, boolean canSendRequests, - AsyncCallback> asyncCallback); - - void storeSwissTimingConfiguration(String configName, String hostname, int port, boolean canSendRequests, AsyncCallback asyncCallback); - - void sendSwissTimingDummyRace(String racMessage, String stlMesssage, String ccgMessage, AsyncCallback callback); - - void getCountryCodes(AsyncCallback callback); - - void getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier, Map from, Map to, - double meters, AsyncCallback>> callback); - - void getManeuvers(RegattaAndRaceIdentifier raceIdentifier, Map from, Map to, - AsyncCallback>> callback); - - void getLeaderboardGroups(boolean withGeoLocationData, AsyncCallback> callback); - - void getLeaderboardGroupByName(String groupName, boolean withGeoLocationData, - AsyncCallback callback); - - /** - * Renames the group with the name oldName to the newName.
    - * If there's no group with the name oldName or there's already a group with the name - * newName a {@link IllegalArgumentException} is thrown. - */ - void renameLeaderboardGroup(String oldName, String newName, AsyncCallback callback); - - /** - * Removes the leaderboard group with the name groupName from the service and the persistant store. - */ - void removeLeaderboardGroup(String groupName, AsyncCallback callback); - - /** - * Creates a new group with the name groupname, the description description and an empty list of leaderboards.
    - * @param displayGroupsInReverseOrder TODO - */ - void createLeaderboardGroup(String groupName, String description, - boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds, - ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); - - /** - * Updates the data of the group with the name oldName. - * - * @param oldName The old name of the group - * @param newName The new name of the group - * @param description The new description of the group - * @param leaderboardNames The list of names of the new leaderboards of the group - */ - void updateLeaderboardGroup(String oldName, String newName, String description, - List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); - - - void setRaceIsKnownToStartUpwind(RegattaAndRaceIdentifier raceIdentifier, boolean raceIsKnownToStartUpwind, - AsyncCallback callback); - - void setWindSourcesToExclude(RegattaAndRaceIdentifier raceIdentifier, Iterable windSourcesToExclude, - AsyncCallback callback); - - void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map from, - Map to, boolean extrapolate, AsyncCallback callback); - - void getReplicaInfo(AsyncCallback callback); - - void startReplicatingFromMaster(String masterName, String exchangeName, int servletPort, int messagingPort, - AsyncCallback callback); - - void getEvents(AsyncCallback> callback); - - /** - * Creates a {@link EventDTO} for the {@link com.sap.sailing.domain.base.Event} with the name eventName, which contains the - * name, the description and a list with {@link RegattaDTO RegattaDTOs} contained in the event.
    - * If no event with the name eventName is known, an {@link IllegalArgumentException} is thrown. - */ - void getEventByName(String eventName, AsyncCallback callback); - - /** - * Renames the event with the name oldName to the newName.
    - * If there's no event with the name oldName or there's already a event with the name - * newName a {@link IllegalArgumentException} is thrown. - */ - void renameEvent(String oldName, String newName, AsyncCallback callback); - - /** - * Removes the event with the name eventName from the service and the persistence store. - */ - void removeEvent(String eventName, AsyncCallback callback); - - void createEvent(String eventName, String description, String publicationUrl, boolean isPublic, AsyncCallback callback); - - void updateEvent(String eventName, Serializable id, VenueDTO venue, String publicationUrl, boolean isPublic, - List regattaNames, AsyncCallback callback); - - void removeRegatta(RegattaIdentifier regattaIdentifier, AsyncCallback callback); - - void addRaceColumnToSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, - AsyncCallback callback); - - void removeRaceColumnFromSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, - AsyncCallback callback); - - void moveRaceColumnInSeriesUp(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, - AsyncCallback callback); - - void moveRaceColumnInSeriesDown(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, - AsyncCallback callback); - - void createRegatta(String regattaName, String boatClassName, - LinkedHashMap>, Boolean>> seriesNamesWithFleetNamesAndFleetOrderingAndMedal, - boolean persistent, ScoringSchemeType scoringSchemeType, AsyncCallback callback); - - void addRaceColumnsToSeries(RegattaIdentifier regattaIdentifier, String seriesName, List columnNames, - AsyncCallback> callback); - - void removeRaceColumnsFromSeries(RegattaIdentifier regattaIdentifier, String seriesName, List columnNames, - AsyncCallback callback); - - void getScoreCorrectionProviderDTOs(AsyncCallback> callback); - - void getScoreCorrections(String scoreCorrectionProviderName, String eventName, String boatClassName, - Date timePointWhenResultPublished, AsyncCallback asyncCallback); - - void getWindSourcesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); - - void getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback); - - void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List controlPoints, AsyncCallback callback); - - void getFregResultUrls(AsyncCallback> asyncCallback); - - void removeFregURLs(Set toRemove, AsyncCallback asyncCallback); - - void addFragUrl(String result, AsyncCallback asyncCallback); - - void getRaceCourseMarks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback callback); - - void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd, - AsyncCallback callback); - - void removeLeaderboardColumns(String leaderboardName, List columnsToRemove, AsyncCallback callback); - - void getLeaderboard(String leaderboardName, AsyncCallback callback); - - void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed, AsyncCallback asyncCallback); - - void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double newFactor, - AsyncCallback callback); - - void listSwissTiminigReplayRaces(String swissTimingUrl, AsyncCallback> asyncCallback); - - void getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(String leaderboardName, Date date, - AsyncCallback>>> callback); - - void getCompetitorsRaceData(RegattaAndRaceIdentifier race, List competitors, Date from, Date to, - long stepSize, DetailType detailType, String leaderboarGroupName, String leaderboardName, AsyncCallback callback); - - /** - * Finds out the names of all {@link com.sap.sailing.domain.leaderboard.MetaLeaderboard}s managed by this server that - * {@link com.sap.sailing.domain.leaderboard.MetaLeaderboard#getLeaderboards() contain} the leaderboard identified by leaderboardName. The - * names of those meta-leaderboards are returned. The list returned is never null but may be empty if no such - * leaderboard is found. - */ - void getOverallLeaderboardNamesContaining(String leaderboardName, AsyncCallback> asyncCallback); - - void getPreviousSwissTimingArchiveConfigurations( - AsyncCallback> asyncCallback); - - void storeSwissTimingArchiveConfiguration(String swissTimingUrl, AsyncCallback asyncCallback); -} +package com.sap.sailing.gwt.ui.client; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompetitorDTO; +import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; +import com.sap.sailing.gwt.ui.shared.ControlPointDTO; +import com.sap.sailing.gwt.ui.shared.CourseDTO; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.GPSFixDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sailing.gwt.ui.shared.QuickRankDTO; +import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO; +import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO; +import com.sap.sailing.gwt.ui.shared.RaceDTO; +import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; +import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; +import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; +import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sailing.gwt.ui.shared.VenueDTO; +import com.sap.sailing.gwt.ui.shared.WindDTO; +import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; + +/** + * The async counterpart of {@link SailingService} + */ +public interface SailingServiceAsync { + + void getRegattas(AsyncCallback> callback); + + /** + * The string returned in the callback's pair is the common event name + */ + void listTracTracRacesInEvent(String eventJsonURL, AsyncCallback>> callback); + + /** + * @param regattaToAddTo + * if null, an existing regatta by the name of the TracTrac event with the boat class name + * appended in parentheses will be looked up; if not found, a default regatta with that name will be + * created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier} + * is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used + * to add the races to. Otherwise, a default regatta as described above will be created and used. + * @param liveURI + * may be null or the empty string in which case the server will use the + * {@link TracTracRaceRecordDTO#liveURI} from the rr race record. + * @param simulateWithStartTimeNow + * if true, the connector will adjust the time stamps of all events received such that the + * first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all + * events received such that they seem to be sent in "real-time." So, more or less the time points attached + * to the events sent to the receivers will again approximate the wall time. + * @param storedURImay + * be null or the empty string in which case the server will use the + * {@link TracTracRaceRecordDTO#storedURI} from the rr race record. + */ + void trackWithTracTrac(RegattaIdentifier regattaToAddTo, + Iterable rrs, String liveURI, String storedURI, boolean trackWind, boolean correctWindByDeclination, + boolean simulateWithStartTimeNow, AsyncCallback callback); + + void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, + String hostname, int port, boolean canSendRequests, boolean trackWind, boolean correctWindByDeclination, + AsyncCallback asyncCallback); + + void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, + boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow, + AsyncCallback asyncCallback); + + void getPreviousTracTracConfigurations(AsyncCallback> callback); + + void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, + AsyncCallback callback); + + void stopTrackingEvent(RegattaIdentifier eventIdentifier, AsyncCallback callback); + + void stopTrackingRaces(Iterable racesToStopTracking, AsyncCallback asyncCallback); + + /** + * Untracks the race and removes it from the regatta. It will also be removed in all leaderboards + * @param regattaNamesAndRaceNames The identifier for the regatta name, and the race name to remove + */ + void removeAndUntrackRaces(Iterable regattaNamesAndRaceNames, AsyncCallback callback); + + void getRawWindFixes(RegattaAndRaceIdentifier raceIdentifier, Collection windSources, AsyncCallback callback); + + /** + * @param from if null, the tracked race's start of tracking is used + * @param to if null, the tracked race's time point of newest event is used + */ + void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds, + Collection windSourceTypeNames, AsyncCallback callback); + + /** + * @param windSourceTypeNames + * if null, data from all available wind sources will be returned, otherwise only from those + * whose {@link WindSource} name is contained in the windSources collection. + */ + void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, + double latDeg, double lngDeg, Collection windSourceTypeNames, + AsyncCallback callback); + + /** + * Same as {@link #getWindInfo(RegattaAndRaceIdentifier, Date, long, int, double, double, Collection, AsyncCallback)}, only + * that the wind is not requested for a specific position, but instead the wind sources associated with the tracked + * race identified by raceIdentifier are requested to deliver their original position. This will in + * particular preserve the positions of actual measurements and will deliver the averaged positions for averaged / + * combined wind read-outs. + * + * @param from + * must not be null + * @param numberOfFixes + * no matter how great this value is chosen, never returns data beyond the newest event recorded in the + * race + */ + void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, + Collection windSourceTypeNames, AsyncCallback callback); + + void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback callback); + + void removeWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO windDTO, AsyncCallback callback); + + /** + * @param from + * for the list of competitors provided as keys of this map, requests the GPS fixes starting with the + * date provided as value + * @param to + * for the list of competitors provided as keys (expected to be equal to the set of competitors used as + * keys in the from parameter, requests the GPS fixes up to but excluding the date provided + * as value + * @param extrapolate + * if true and no position is known for date, the last entry returned in the + * list of GPS fixes will be obtained by extrapolating from the competitors last known position before + * date and the estimated speed. + * @return a map where for each competitor participating in the race the list of GPS fixes in increasing + * chronological order is provided. The last one is the last position at or before date. + */ + void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + Map from, Map to, + boolean extrapolate, AsyncCallback>> callback); + + void getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); + + void getRaceTimesInfos(Collection raceIdentifiers, AsyncCallback> callback); + + void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback asyncCallback); + + void getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback); + + /** + * Returns a {@link LeaderboardDTO} will information about all races, their points and competitor display names + * filled in. The column details are filled for the races whose named are provided in + * namesOfRacesForWhichToLoadLegDetails. + * + * @param date + * the time point for the leaderboard data to retrieve, or null for "live mode" which means + * that the server will produce whatever it has ready for the currently active live delay set on the + * server; in live mode, data served may be taken from caches that lag up to a few seconds. + * @param namesOfRaceColumnsForWhichToLoadLegDetails + * if null, no {@link LeaderboardEntryDTO#legDetails leg details} will be present in the + * result ({@link LeaderboardEntryDTO#legDetails} will be null for all + * {@link LeaderboardEntryDTO} objects contained). Otherwise, the {@link LeaderboardEntryDTO#legDetails} + * list will contain one entry per leg of the race {@link com.sap.sailing.domain.base.Course} for those race columns whose + * {@link com.sap.sailing.domain.base.RaceColumn#getType() name} is contained in namesOfRacesForWhichToLoadLegDetails. + * For all other columns, {@link LeaderboardEntryDTO#legDetails} is null. + */ + void getLeaderboardByName(String leaderboardName, Date date, + Collection namesOfRaceColumnsForWhichToLoadLegDetails, + AsyncCallback callback); + + void getLeaderboardNames(AsyncCallback> callback); + + void getLeaderboards(AsyncCallback> callback); + + void getLeaderboardsByEvent(RegattaDTO regatta, AsyncCallback> callback); + + void getLeaderboardsByRace(RaceDTO race, AsyncCallback> callback); + + void updateLeaderboard(String leaderboardName, String newLeaderboardName, String newLeaderboardDisplayName, + int[] newDiscardingThreasholds, AsyncCallback callback); + + void createFlexibleLeaderboard(String leaderboardName, int[] discardThresholds, ScoringSchemeType scoringSchemeType, + AsyncCallback asyncCallback); + + void createRegattaLeaderboard(RegattaIdentifier regattaIdentifier, int[] discardThresholds, + AsyncCallback asyncCallback); + + void removeLeaderboard(String leaderboardName, AsyncCallback asyncCallback); + + void renameLeaderboard(String leaderboardName, String newLeaderboardName, AsyncCallback asyncCallback); + + void addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace, + AsyncCallback callback); + + void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName, AsyncCallback callback); + + void removeLeaderboardColumn(String leaderboardName, String columnName, AsyncCallback callback); + + void connectTrackedRaceToLeaderboardColumn(String leaderboardName, String raceColumnName, String fleetName, + RegattaAndRaceIdentifier raceIdentifier, AsyncCallback asyncCallback); + + /** + * The key set of the map returned contains all fleets of the race column identified by the combination of + * leaderboardName and raceColumnName. If a value is null, there is no + * tracked race currently linked to the fleet in the race column; otherwise, the value is the {@link RaceIdentifier} + * of the tracked race currently connected for the fleet whose name is the key. The map returned is never null. + */ + void getRegattaAndRaceNameOfTrackedRaceConnectedToLeaderboardColumn(String leaderboardName, String raceColumnName, + AsyncCallback> callback); + + void disconnectLeaderboardColumnFromTrackedRace(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback callback); + + void updateLeaderboardCarryValue(String leaderboardName, String competitorIdAsString, Double carriedPoints, AsyncCallback callback); + + void updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, + MaxPointsReason maxPointsReason, Date date, AsyncCallback> asyncCallback); + + void updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String columnName, + Double correctedScore, Date date, AsyncCallback> asyncCallback); + + void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, + String comment, AsyncCallback callback); + + void updateLeaderboardScoreCorrectionsAndMaxPointsReasons(BulkScoreCorrectionDTO updates, + AsyncCallback callback); + + void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorID, String displayName, + AsyncCallback callback); + + void moveLeaderboardColumnUp(String leaderboardName, String columnName, AsyncCallback callback); + + void moveLeaderboardColumnDown(String leaderboardName, String columnName, AsyncCallback callback); + + void updateIsMedalRace(String leaderboardName, String columnName, boolean isMedalRace, AsyncCallback callback); + + void updateRaceDelayToLive(RegattaAndRaceIdentifier regattaAndRaceIdentifier, long delayToLiveInMs, AsyncCallback callback); + + void updateRacesDelayToLive(List regattaAndRaceIdentifiers, long delayToLiveInMs, AsyncCallback callback); + + void getPreviousSwissTimingConfigurations(AsyncCallback> asyncCallback); + + void listSwissTimingRaces(String hostname, int port, boolean canSendRequests, + AsyncCallback> asyncCallback); + + void storeSwissTimingConfiguration(String configName, String hostname, int port, boolean canSendRequests, AsyncCallback asyncCallback); + + void sendSwissTimingDummyRace(String racMessage, String stlMesssage, String ccgMessage, AsyncCallback callback); + + void getCountryCodes(AsyncCallback callback); + + void getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier, Map from, Map to, + double meters, AsyncCallback>> callback); + + void getManeuvers(RegattaAndRaceIdentifier raceIdentifier, Map from, Map to, + AsyncCallback>> callback); + + void getLeaderboardGroups(boolean withGeoLocationData, AsyncCallback> callback); + + void getLeaderboardGroupByName(String groupName, boolean withGeoLocationData, + AsyncCallback callback); + + /** + * Renames the group with the name oldName to the newName.
    + * If there's no group with the name oldName or there's already a group with the name + * newName a {@link IllegalArgumentException} is thrown. + */ + void renameLeaderboardGroup(String oldName, String newName, AsyncCallback callback); + + /** + * Removes the leaderboard group with the name groupName from the service and the persistant store. + */ + void removeLeaderboardGroup(String groupName, AsyncCallback callback); + + /** + * Creates a new group with the name groupname, the description description and an empty list of leaderboards.
    + * @param displayGroupsInReverseOrder TODO + */ + void createLeaderboardGroup(String groupName, String description, + boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds, + ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); + + /** + * Updates the data of the group with the name oldName. + * + * @param oldName The old name of the group + * @param newName The new name of the group + * @param description The new description of the group + * @param leaderboardNames The list of names of the new leaderboards of the group + */ + void updateLeaderboardGroup(String oldName, String newName, String description, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); + + + void setRaceIsKnownToStartUpwind(RegattaAndRaceIdentifier raceIdentifier, boolean raceIsKnownToStartUpwind, + AsyncCallback callback); + + void setWindSourcesToExclude(RegattaAndRaceIdentifier raceIdentifier, Iterable windSourcesToExclude, + AsyncCallback callback); + + void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map from, + Map to, boolean extrapolate, AsyncCallback callback); + + void getReplicaInfo(AsyncCallback callback); + + void startReplicatingFromMaster(String masterName, String exchangeName, int servletPort, int messagingPort, + AsyncCallback callback); + + void getEvents(AsyncCallback> callback); + + /** + * Creates a {@link EventDTO} for the {@link com.sap.sailing.domain.base.Event} with the name eventName, which contains the + * name, the description and a list with {@link RegattaDTO RegattaDTOs} contained in the event.
    + * If no event with the name eventName is known, an {@link IllegalArgumentException} is thrown. + */ + void getEventByName(String eventName, AsyncCallback callback); + + /** + * Renames the event with the name oldName to the newName.
    + * If there's no event with the name oldName or there's already a event with the name + * newName a {@link IllegalArgumentException} is thrown. + */ + void renameEvent(String oldName, String newName, AsyncCallback callback); + + /** + * Removes the event with the name eventName from the service and the persistence store. + */ + void removeEvent(String eventName, AsyncCallback callback); + + void createEvent(String eventName, String description, String publicationUrl, boolean isPublic, AsyncCallback callback); + + void updateEvent(String eventName, Serializable id, VenueDTO venue, String publicationUrl, boolean isPublic, + List regattaNames, AsyncCallback callback); + + void removeRegatta(RegattaIdentifier regattaIdentifier, AsyncCallback callback); + + void addRaceColumnToSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, + AsyncCallback callback); + + void removeRaceColumnFromSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, + AsyncCallback callback); + + void moveRaceColumnInSeriesUp(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, + AsyncCallback callback); + + void moveRaceColumnInSeriesDown(RegattaIdentifier regattaIdentifier, String seriesName, String columnName, + AsyncCallback callback); + + void createRegatta(String regattaName, String boatClassName, + LinkedHashMap>, Boolean>> seriesNamesWithFleetNamesAndFleetOrderingAndMedal, + boolean persistent, ScoringSchemeType scoringSchemeType, AsyncCallback callback); + + void addRaceColumnsToSeries(RegattaIdentifier regattaIdentifier, String seriesName, List columnNames, + AsyncCallback> callback); + + void removeRaceColumnsFromSeries(RegattaIdentifier regattaIdentifier, String seriesName, List columnNames, + AsyncCallback callback); + + void getScoreCorrectionProviderDTOs(AsyncCallback> callback); + + void getScoreCorrections(String scoreCorrectionProviderName, String eventName, String boatClassName, + Date timePointWhenResultPublished, AsyncCallback asyncCallback); + + void getWindSourcesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); + + void getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback); + + void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List controlPoints, AsyncCallback callback); + + void getFregResultUrls(AsyncCallback> asyncCallback); + + void removeFregURLs(Set toRemove, AsyncCallback asyncCallback); + + void addFragUrl(String result, AsyncCallback asyncCallback); + + void getRaceCourseMarks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback callback); + + void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd, + AsyncCallback callback); + + void removeLeaderboardColumns(String leaderboardName, List columnsToRemove, AsyncCallback callback); + + void getLeaderboard(String leaderboardName, AsyncCallback callback); + + void suppressCompetitorInLeaderboard(String leaderboardName, String competitorIdAsString, boolean suppressed, AsyncCallback asyncCallback); + + void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double newFactor, + AsyncCallback callback); + + void listSwissTiminigReplayRaces(String swissTimingUrl, AsyncCallback> asyncCallback); + + void getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(String leaderboardName, Date date, + AsyncCallback>>> callback); + + void getCompetitorsRaceData(RegattaAndRaceIdentifier race, List competitors, Date from, Date to, + long stepSize, DetailType detailType, String leaderboarGroupName, String leaderboardName, AsyncCallback callback); + + /** + * Finds out the names of all {@link com.sap.sailing.domain.leaderboard.MetaLeaderboard}s managed by this server that + * {@link com.sap.sailing.domain.leaderboard.MetaLeaderboard#getLeaderboards() contain} the leaderboard identified by leaderboardName. The + * names of those meta-leaderboards are returned. The list returned is never null but may be empty if no such + * leaderboard is found. + */ + void getOverallLeaderboardNamesContaining(String leaderboardName, AsyncCallback> asyncCallback); + + void getPreviousSwissTimingArchiveConfigurations( + AsyncCallback> asyncCallback); + + void storeSwissTimingArchiveConfiguration(String swissTimingUrl, AsyncCallback asyncCallback); + + void generatePolarSheetForRaces(List selectedRaces, AsyncCallback asyncCallback); + + void getPolarSheetsGenerationResults(String id, AsyncCallback asyncCallback); + + void getPolarSheetData(String polarSheetId, int angle, int windSpeed, AsyncCallback wrapperCallback); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index bd6ecf6243a..03124eeb4a3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -482,6 +482,8 @@ public interface StringMessages extends Messages { String gapChangeSinceLegStartInSecondsTooltip(); String velocityMadeGoodInKnotsTooltip(); String sideToWhichMarkAtLegStartWasRoundedTooltip(); + String generatePolarSheet(); + String polarSheetChart(); String numberOfManeuversInRaceTooltip(); String competitorColumnTooltip(); String sailIdColumnTooltip(); @@ -497,6 +499,7 @@ public interface StringMessages extends Messages { String overallRankTooltip(); String noDataFound(); String displayName(); - String buoyZone(); - String radiusInMeters(); + String histogram(); + String numberOfDataPoints(); + String angleAndTotalNumberOfDataPoints(int angle, int numberOfDataPoints); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 8f892aed35d..eb19eeed1c7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -486,8 +486,11 @@ sideToWhichMarkAtLegStartWasRounded=Mark rounded to sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded. raceIsLive=Race {0} is live racesAreLive=Races {0} are live +knotsUnit=kts scoringSchemeHighPointFirstGetsTen=High Point, winner gets 10 points knotsUnit=kts +generatePolarSheet=Generate Polar Sheet +polarSheetChart=Polar Sheet competitorColumnTooltip=Names of the members of this competitor. sailIdColumnTooltip=Shows the flag of the country and the sail number of the competitor. rankColumnTooltip=The overall rank of this competitor in the regatta. @@ -498,5 +501,6 @@ gpsData=GPS data status=Status noDataFound=No data found displayName=Display name -buoyZone=Buoy zone -radiusInMeters=Radius (m) +histogram=Histogram +numberOfDataPoints=Number of data points +angleAndTotalNumberOfDataPoints=Angle: {0}; Total number of data-points: {1} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 67dd8fe1b9b..f30715c8eb2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -489,6 +489,8 @@ raceIsLive=Rennen {0} ist live racesAreLive=Rennen {0} sind live scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte knotsUnit=kn +generatePolarSheet=Polardiagramm erstellen +polarSheetChart=Polardiagramm competitorColumnTooltip=Namen der Segler dieses Teilnehmers sailIdColumnTooltip=Zeigt die Länderflagge und die Segelnummer des Teilnehmers rankColumnTooltip=Der Gesamtplatz des Teilnehmers in der Regatta @@ -499,5 +501,6 @@ gpsData=GPS-Daten status=Status noDataFound=Keine Daten gefunden displayName=Anzeigename -buoyZone=Bojenzone -radiusInMeters=Radius (m) +histogram=Histogramm +numberOfDataPoints=Anzahl Datenpunkte +angleAndTotalNumberOfDataPoints=Winkel: {0}; Anzahl Datenpunkte: {1} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/AbstractFilteredTrackedRacesList.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/AbstractFilteredTrackedRacesList.java new file mode 100644 index 00000000000..2c1d3bcd6f6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/AbstractFilteredTrackedRacesList.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import com.sap.sailing.gwt.ui.adminconsole.AbstractTrackedRacesListComposite; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; +import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.RaceDTO; + +public abstract class AbstractFilteredTrackedRacesList extends AbstractTrackedRacesListComposite { + + private RaceFilter filter; + + public AbstractFilteredTrackedRacesList(SailingServiceAsync sailingService, ErrorReporter errorReporter, + RegattaRefresher regattaRefresher, RaceSelectionProvider raceSelectionProvider, + StringMessages stringMessages, boolean hasMultiSelection, RaceFilter filter) { + super(sailingService, errorReporter, regattaRefresher, raceSelectionProvider, stringMessages, hasMultiSelection); + this.filter = filter; + } + + @Override + protected boolean raceIsToBeAddedToList(RaceDTO race) { + if (filter!=null) { + return filter.compliesToFilter(race); + } + return true; + } + + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java new file mode 100644 index 00000000000..a782c9d1eaf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java @@ -0,0 +1,171 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.moxieapps.gwt.highcharts.client.Chart; +import org.moxieapps.gwt.highcharts.client.Color; +import org.moxieapps.gwt.highcharts.client.Point; +import org.moxieapps.gwt.highcharts.client.Series; +import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEventHandler; +import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions; +import org.moxieapps.gwt.highcharts.client.plotOptions.Marker; +import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions; + +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.SimplePanel; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class PolarSheetsChartPanel extends SimplePanel implements RequiresResize { + + private StringMessages stringMessages; + private Chart chart; + private Map seriesMap; + + public PolarSheetsChartPanel(StringMessages stringMessages) { + this.stringMessages = stringMessages; + setPixelSize(800, 800); + chart = createPolarSheetChart(); + seriesMap = new HashMap(); + setWidget(chart); + } + + private Chart createPolarSheetChart() { + Chart polarSheetChart = new Chart().setType(Series.Type.LINE) + .setLinePlotOptions(new LinePlotOptions().setLineWidth(1)).setZoomType(Chart.ZoomType.X_AND_Y) + .setPolar(true).setSize(800, 800); + polarSheetChart.setChartTitleText(stringMessages.polarSheetChart()); + polarSheetChart.getYAxis().setMin(0); + return polarSheetChart; + } + + private void newSeriesArray(String name) { + if (!seriesMap.containsKey(name)) { + Series[] seriesPerWindSpeed = new Series[13]; + seriesMap.put(name, seriesPerWindSpeed); + } else { + // TODO exception handling + } + } + + private void createSeriesForWindspeed(String name, int windSpeed) { + Series[] seriesPerWindSpeed = seriesMap.get(name); + Number[] forEachDeg = initializeDataForNewSeries(); + seriesPerWindSpeed[windSpeed] = chart.createSeries().setPoints(forEachDeg); + seriesPerWindSpeed[windSpeed].setName(name + "-" + (windSpeed)); + chart.addSeries(seriesPerWindSpeed[windSpeed]); + } + + private Number[] initializeDataForNewSeries() { + Number[] forEachDeg = new Number[360]; + return forEachDeg; + } + + private void addValuesToSeries(String seriesId, PolarSheetsData result) { + if (seriesMap.containsKey(seriesId)) { + for (int i = 0; i < 13; i++) { + if (hasSufficientDataForWindspeed(result.getDataCountPerAngleForWindspeed(i), result.getDataCount())) { + if (seriesMap.get(seriesId)[i] == null) { + createSeriesForWindspeed(seriesId, i); + } + Series series = seriesMap.get(seriesId)[i]; + series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false); + Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result, i); + if (points != null) { + series.setPoints(points); + } + } + } + } + } + + private boolean hasSufficientDataForWindspeed(Integer[] dataCountPerAngleForWindspeed, int countOverall) { + int sum = 0; + for (int count : dataCountPerAngleForWindspeed) { + sum = sum + count; + } + //TODO make configurable + if (sum >= (0.05 * countOverall)) { + return true; + } + return false; + } + + private Point[] createPointsWithMarkerAlphaAccordingToDataCount(PolarSheetsData result, int beaufort) { + Point[] points = new Point[360]; + List dataCountList = Arrays.asList(result.getDataCountPerAngleForWindspeed(beaufort)); + Integer max = Collections.max(dataCountList); + if (max <= 0) { + return null; + } + for (int i = 0; i < 360; i++) { + points[i] = new Point(result.getAveragedPolarDataByWindSpeed()[beaufort][i]); + if (points[i] == null) { + points[i] = new Point(0); + } + double alpha = (double) dataCountList.get(i) / (double) max; + int blue; + int red; + int radius; + if (alpha > 0.2) { + red = (int) (alpha * 255); + blue = 0; + radius = 4; + } else { + blue = (int) ((1 - alpha) * 255); + red = 0; + radius = 2; + } + + // Don't let the markers be invisible + alpha = 0.5 + 0.5 * alpha; + // TODO maybe set to series color. Not sure if this (highcharts-generated color) can be queried before + // rendering + points[i].setMarker(new Marker().setFillColor(new Color(red, 0, blue, alpha)).setRadius(radius)); + } + + return points; + } + + public void removeSeries(String seriesId) { + if (seriesMap.containsKey(seriesId)) { + Series[] seriesPerWindSpeed = seriesMap.get(seriesId); + for (Series series : seriesPerWindSpeed) { + chart.removeSeries(series); + } + seriesMap.remove(seriesId); + } + } + + public void removeAllSeries() { + chart.removeAllSeries(); + seriesMap.clear(); + } + + public void setData(String id, PolarSheetsData result) { + if (id == null) { + // TODO Exception handling + return; + } + if (!seriesMap.containsKey(id)) { + newSeriesArray(id); + } + addValuesToSeries(id, result); + chart.redraw(); + } + + public void setSeriesPointMouseOverHandler(PointMouseOverEventHandler pointMouseOverHandler) { + chart.setSeriesPlotOptions(new SeriesPlotOptions().setPointMouseOverEventHandler(pointMouseOverHandler)); + } + + @Override + public void onResize() { + chart.setSizeToMatchContainer(); + chart.redraw(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java new file mode 100644 index 00000000000..f1d0511829e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java @@ -0,0 +1,63 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.RegattaDisplayer; +import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; + +public class PolarSheetsEntryPoint extends AbstractEntryPoint implements RegattaRefresher { + + // private static final Logger logger = + // Logger.getLogger(PolarSheetsEntryPoint.class.getName()); + + private Set regattaDisplayers; + + @Override + protected void doOnModuleLoad() { + super.doOnModuleLoad(); + regattaDisplayers = new HashSet(); + createUI(); + fillRegattas(); + } + + private void createUI() { + RootPanel rootPanel = RootPanel.get(); + DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); + RootLayoutPanel.get().add(mainPanel); + ScrollPanel contentScrollPanel = new ScrollPanel(); + PolarSheetsPanel polarSheetsPanel = new PolarSheetsPanel(sailingService, this, stringMessages, this); + polarSheetsPanel.addStyleName(PolarSheetsPanel.POLARSHEETS_STYLE); + regattaDisplayers.add(polarSheetsPanel); + contentScrollPanel.setWidget(polarSheetsPanel); + mainPanel.add(contentScrollPanel); + rootPanel.add(mainPanel); + } + + @Override + public void fillRegattas() { + sailingService.getRegattas(new AsyncCallback>() { + @Override + public void onSuccess(List result) { + for (RegattaDisplayer regattaDisplayer : regattaDisplayers) { + regattaDisplayer.fillRegattas(result); + } + } + + @Override + public void onFailure(Throwable caught) { + reportError("Remote Procedure Call getRegattas() - Failure"); + } + }); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsHistogramPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsHistogramPanel.java new file mode 100644 index 00000000000..251bc69423c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsHistogramPanel.java @@ -0,0 +1,67 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import org.moxieapps.gwt.highcharts.client.AxisTitle; +import org.moxieapps.gwt.highcharts.client.Chart; +import org.moxieapps.gwt.highcharts.client.ChartSubtitle; +import org.moxieapps.gwt.highcharts.client.ChartTitle; +import org.moxieapps.gwt.highcharts.client.Legend; +import org.moxieapps.gwt.highcharts.client.Point; +import org.moxieapps.gwt.highcharts.client.Series; +import org.moxieapps.gwt.highcharts.client.Series.Type; +import org.moxieapps.gwt.highcharts.client.labels.XAxisLabels; + +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.SimplePanel; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class PolarSheetsHistogramPanel extends SimplePanel implements RequiresResize{ + + private final Chart chart; + private final StringMessages stringMessages; + + public PolarSheetsHistogramPanel(StringMessages stringMessages) { + this.stringMessages = stringMessages; + setSize("100%", "100%"); + chart = createHistogramChart(); + chart.getElement().setAttribute("align", "top"); + setWidget(chart); + } + + private Chart createHistogramChart() { + Chart histogramChart = new Chart().setType(Type.COLUMN).setZoomType(Chart.ZoomType.X).setWidth(800); + histogramChart.setChartTitleText(stringMessages.histogram()); + histogramChart.getYAxis().setMin(0).setAxisTitle(new AxisTitle().setText(stringMessages.numberOfDataPoints())); + histogramChart.getXAxis().setLabels(new XAxisLabels().setRotation(-90f).setY(10)).setAxisTitle(new AxisTitle().setText( + stringMessages.speedInKnots())); + histogramChart.setLegend(new Legend().setEnabled(false)); + return histogramChart; + } + + public void setData(PolarSheetsHistogramData data) { + chart.removeAllSeries(); + chart.setTitle(new ChartTitle().setText(stringMessages.histogram()), + new ChartSubtitle().setText(stringMessages.angleAndTotalNumberOfDataPoints(data.getAngle(), data.getDataCount()))); + Point[] points = toPoints(data); + Series series = chart.createSeries(); + series.setPoints(points); + chart.addSeries(series); + } + + private Point[] toPoints(PolarSheetsHistogramData data) { + Number[] xValues = data.getxValues(); + Number[] yValues = data.getyValues(); + Point[] points = new Point[xValues.length]; + for (int i = 0; i < xValues.length; i++) { + points[i] = new Point(xValues[i], yValues[i]); + } + return points; + } + + @Override + public void onResize() { + chart.setSizeToMatchContainer(); + chart.redraw(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java new file mode 100644 index 00000000000..9c3180feb06 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java @@ -0,0 +1,259 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEvent; +import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEventHandler; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.actions.GetPolarSheetDataByAngleAction; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.RaceSelectionModel; +import com.sap.sailing.gwt.ui.client.RegattaDisplayer; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; + +public class PolarSheetsPanel extends FormPanel implements RaceSelectionChangeListener, RegattaDisplayer { + + // TODO UI stuff + public static final String POLARSHEETS_STYLE = "polarSheets"; + + private FlowPanel mainPanel; + private SailingServiceAsync sailingService; + private PolarSheetsTrackedRacesList polarSheetsTrackedRacesList; + private ErrorReporter errorReporter; + private StringMessages stringMessages; + private RaceSelectionModel raceSelectionProvider; + private PolarSheetsEntryPoint polarSheetsEntryPoint; + private List selectedRaces; + private PolarSheetsChartPanel chartPanel; + private PolarSheetsHistogramPanel histogramPanel; + private AsyncActionsExecutor asyncActionsExecutor; + + private Label polarSheetsGenerationLabel; + private Label dataCountLabel; + + //Dual mapping. Another solution would be using commons BidiMap. + //But this would require to attach source code for gwt + private Map idNameMapping; + private Map nameIdMapping; + + public PolarSheetsPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, + StringMessages stringMessages, PolarSheetsEntryPoint polarSheetsEntryPoint) { + this.polarSheetsEntryPoint = polarSheetsEntryPoint; + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + + idNameMapping = new HashMap(); + nameIdMapping = new HashMap(); + + this.mainPanel = new FlowPanel(); + setSize("100%", "100%"); + + mainPanel.setSize("100%", "100%"); + setWidget(mainPanel); + HorizontalPanel splitPanel = createSplitPanel(); + VerticalPanel leftPanel = addFilteredTrackedRacesList(splitPanel); + polarSheetsGenerationLabel = createPolarSheetGenerationStatusLabel(); + leftPanel.add(polarSheetsGenerationLabel); + dataCountLabel = new Label(); + leftPanel.add(dataCountLabel); + VerticalPanel rightPanel = addPolarSheetsChartPanel(splitPanel); + histogramPanel = new PolarSheetsHistogramPanel(stringMessages); + histogramPanel.getElement().setAttribute("align", "top"); + rightPanel.add(histogramPanel); + mainPanel.add(splitPanel); + + asyncActionsExecutor = new AsyncActionsExecutor(); + setEventListenersForPolarSheetChart(); + } + + private void setEventListenersForPolarSheetChart() { + PointMouseOverEventHandler pointMouseOverHandler = new PointMouseOverEventHandler() { + + @Override + public boolean onMouseOver(PointMouseOverEvent pointMouseOverEvent) { + int angle = (int) pointMouseOverEvent.getXAsLong(); + String polarSheetNameWithWind = pointMouseOverEvent.getSeriesName(); + String[] split = polarSheetNameWithWind.split("-"); + String polarSheetName = split[0] + "-" + split[1]; + String polarSheetId = nameIdMapping.get(polarSheetName); + int windSpeed = Integer.parseInt(split[2]); + + GetPolarSheetDataByAngleAction action = new GetPolarSheetDataByAngleAction(sailingService, + polarSheetId, angle, windSpeed, new AsyncCallback() { + + @Override + public void onSuccess(PolarSheetsHistogramData result) { + if (result != null) { + histogramPanel.setData(result); + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getLocalizedMessage()); + } + + }); + asyncActionsExecutor.execute(action); + return true; + } + }; + chartPanel.setSeriesPointMouseOverHandler(pointMouseOverHandler); + } + + private Label createPolarSheetGenerationStatusLabel() { + Label polarSheetsGenerationStatusLabel = new Label(); + return polarSheetsGenerationStatusLabel; + } + + private VerticalPanel addPolarSheetsChartPanel(HorizontalPanel splitPanel) { + VerticalPanel verticalPanel = new VerticalPanel(); + verticalPanel.setSize("100%", "100%"); + chartPanel = new PolarSheetsChartPanel(stringMessages); + verticalPanel.add(chartPanel); + verticalPanel.setCellHeight(chartPanel, "800px"); + splitPanel.add(verticalPanel); + splitPanel.setCellWidth(verticalPanel, "50%"); + return verticalPanel; + } + + private HorizontalPanel createSplitPanel() { + HorizontalPanel splitPanel = new HorizontalPanel(); + splitPanel.setSize("100%", "100%"); + return splitPanel; + } + + private VerticalPanel addFilteredTrackedRacesList(HorizontalPanel splitPanel) { + VerticalPanel trackedRacesPanel = new VerticalPanel(); + trackedRacesPanel.setWidth("100%"); + + createPolarSheetsTrackedRacesList(); + trackedRacesPanel.add(polarSheetsTrackedRacesList); + + splitPanel.add(trackedRacesPanel); + splitPanel.setCellWidth(trackedRacesPanel, "50%"); + return trackedRacesPanel; + } + + private void createPolarSheetsTrackedRacesList() { + raceSelectionProvider = new RaceSelectionModel(); + ClickHandler polarSheetsGenerationButtonClickHandler = new ClickHandler() { + + @Override + public void onClick(ClickEvent arg0) { + startPolarSheetGeneration(); + } + }; + polarSheetsTrackedRacesList = new PolarSheetsTrackedRacesList(sailingService, errorReporter, + polarSheetsEntryPoint, raceSelectionProvider, stringMessages, true, new RaceFilter(true, true), + polarSheetsGenerationButtonClickHandler); + raceSelectionProvider.addRaceSelectionChangeListener(this); + } + + protected void startPolarSheetGeneration() { + // List conversion, to make List serializable + final List selectedRacesInArrayList = new ArrayList(); + selectedRacesInArrayList.addAll(selectedRaces); + polarSheetsTrackedRacesList.changeGenerationButtonState(false); + sailingService.generatePolarSheetForRaces(selectedRacesInArrayList, new AsyncCallback() { + + @Override + public void onSuccess(PolarSheetGenerationTriggerResponse result) { + // TODO string messages + setCompletionLabel("Generating..."); + startPullingResults(result.getId()); + addNameForPolarSheet(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getLocalizedMessage()); + } + }); + } + + protected void addNameForPolarSheet(PolarSheetGenerationTriggerResponse result) { + String boatClassName = result.getBoatClassName(); + int index = 0; + String name = ""; + do { + index++; + name = boatClassName + "-" + index; + } while (nameIdMapping.containsKey(name)); + idNameMapping.put(result.getId(), name); + nameIdMapping.put(name, result.getId()); + } + + protected void startPullingResults(final String id) { + + sailingService.getPolarSheetsGenerationResults(id, new AsyncCallback() { + + @Override + public void onSuccess(PolarSheetsData result) { + // TODO string messages + setCompletionLabel("Generating..."); + dataCountLabel.setText("DataCount: " + result.getDataCount()); + chartPanel.setData(idNameMapping.get(id), result); + if (!result.isComplete()) { + Timer timer = new Timer() { + + @Override + public void run() { + startPullingResults(id); + } + }; + + timer.schedule(1500); + + } else { + // TODO string messages + setCompletionLabel("Generation finished!"); + polarSheetsTrackedRacesList.changeGenerationButtonState(true); + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getLocalizedMessage()); + } + + }); + + } + + protected void setCompletionLabel(String string) { + polarSheetsGenerationLabel.setText(string); + } + + @Override + public void onRaceSelectionChange(List selectedRaces) { + this.selectedRaces = selectedRaces; + } + + @Override + public void fillRegattas(List regattas) { + polarSheetsTrackedRacesList.fillRegattas(regattas); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java new file mode 100644 index 00000000000..6bec44e3948 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java @@ -0,0 +1,63 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import java.util.List; + +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; +import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.RaceDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; + +public class PolarSheetsTrackedRacesList extends AbstractFilteredTrackedRacesList { + + private Button btnPolarSheetGeneration; + + public PolarSheetsTrackedRacesList(SailingServiceAsync sailingService, ErrorReporter errorReporter, + RegattaRefresher regattaRefresher, RaceSelectionProvider raceSelectionProvider, + StringMessages stringMessages, boolean hasMultiSelection, RaceFilter filter, + ClickHandler polarSheetsGenerationButtonClickedHandler) { + super(sailingService, errorReporter, regattaRefresher, raceSelectionProvider, stringMessages, + hasMultiSelection, filter); + btnPolarSheetGeneration.addClickHandler(polarSheetsGenerationButtonClickedHandler); + } + + @Override + protected void addControlButtons(HorizontalPanel trackedRacesButtonPanel) { + btnPolarSheetGeneration = new Button(stringMessages.generatePolarSheet()); + btnPolarSheetGeneration.ensureDebugId("PolarSheetGeneration"); + trackedRacesButtonPanel.add(btnPolarSheetGeneration); + } + + @Override + protected void makeControlsReactToSelectionChange(List selectedRaces) { + if (selectedRaces.isEmpty()) { + btnPolarSheetGeneration.setEnabled(false); + } else { + btnPolarSheetGeneration.setEnabled(true); + } + } + + @Override + protected void makeControlsReactToFillRegattas(List regattas) { + if (regattas.isEmpty()) { + btnPolarSheetGeneration.setVisible(false); + } else { + btnPolarSheetGeneration.setVisible(true); + btnPolarSheetGeneration.setEnabled(false); + } + } + + /** + * Changes the state of the generation-start button + * @param enable + */ + public void changeGenerationButtonState(boolean enable) { + btnPolarSheetGeneration.setEnabled(enable); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/RaceFilter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/RaceFilter.java new file mode 100644 index 00000000000..9da90b8c2ac --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/RaceFilter.java @@ -0,0 +1,47 @@ +package com.sap.sailing.gwt.ui.polarsheets; + +import com.sap.sailing.gwt.ui.shared.RaceDTO; + +public class RaceFilter { + + private Boolean withGpsFixes; + private Boolean withWindData; + + + + + public RaceFilter(Boolean withGpsFixes, Boolean withWindData) { + this.withGpsFixes = withGpsFixes; + this.withWindData = withWindData; + } + + + + + public boolean compliesToFilter(RaceDTO race) { + if (!compliesToGpsFixFilter(race) || !compliesToWindDataFilter(race)) { + return false; + } + return true; + } + + + private boolean compliesToGpsFixFilter(RaceDTO race) { + if (withGpsFixes!=null) { + if (race.trackedRace.hasGPSData != withGpsFixes) { + return false; + } + } + return true; + } + + private boolean compliesToWindDataFilter(RaceDTO race) { + if (withGpsFixes!=null) { + if (race.trackedRace.hasWindData != withWindData) { + return false; + } + } + return true; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 6bd2560bc4d..0871b24a34a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -38,6 +38,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; @@ -75,6 +77,9 @@ import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindError; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Placemark; +import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse; +import com.sap.sailing.domain.common.PolarSheetsData; +import com.sap.sailing.domain.common.PolarSheetsHistogramData; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RaceFetcher; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -96,6 +101,8 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.impl.PolarSheetGenerationTriggerResponseImpl; +import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl; import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Triple; @@ -109,6 +116,8 @@ import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.MongoWindStoreFactory; +import com.sap.sailing.domain.polarsheets.BoatAndWindSpeed; +import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker; import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration; import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; @@ -304,6 +313,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final DomainFactory tractracDomainFactory; private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory; + + private final Map polarSheetGenerationWorkers = new HashMap(); public SailingServiceImpl() { BundleContext context = Activator.getDefault(); @@ -353,7 +364,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /** * Asks the OSGi system for registered score correction provider services */ - private ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( + protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( BundleContext bundleContext) { ServiceTracker tracker = new ServiceTracker(bundleContext, ScoreCorrectionProvider.class.getName(), @@ -3095,4 +3106,113 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S swissTimingAdapterPersistence.storeSwissTimingArchiveConfiguration(swissTimingFactory.createSwissTimingArchiveConfiguration( swissTimingJsonUrl)); } + + @Override + public PolarSheetGenerationTriggerResponse generatePolarSheetForRaces(List selectedRaces) { + String id = UUID.randomUUID().toString(); + RacingEventService service = getService(); + Set trackedRaces = new HashSet(); + for (RegattaAndRaceIdentifier race : selectedRaces) { + trackedRaces.add(service.getTrackedRace(race)); + } + PolarSheetGenerationWorker genWorker = new PolarSheetGenerationWorker(trackedRaces, executor); + polarSheetGenerationWorkers.put(id, genWorker); + genWorker.startPolarSheetGeneration(); + String name = getCommonBoatClass(trackedRaces); + return new PolarSheetGenerationTriggerResponseImpl(id, name); + } + + private String getCommonBoatClass(Set trackedRaces) { + BoatClass boatClass = null; + for (TrackedRace race : trackedRaces) { + if (boatClass == null) { + boatClass = race.getRace().getBoatClass(); + } + if (!boatClass.getName().matches(race.getRace().getBoatClass().getName())) { + return "Mixed"; + } + } + + return boatClass.getName(); + } + + @Override + public PolarSheetsData getPolarSheetsGenerationResults(String id) { + PolarSheetsData data = null; + if (polarSheetGenerationWorkers.containsKey(id)) { + PolarSheetGenerationWorker worker = polarSheetGenerationWorkers.get(id); + data = worker.getPolarData(); + if (data.isComplete()) { + polarSheetGenerationWorkers.remove(id); + HttpServletRequest httpServletRequest = this.getThreadLocalRequest(); + if (httpServletRequest != null) { + HttpSession session = httpServletRequest.getSession(); + session.setAttribute(id, worker.getCompleteData()); + } + } + } else { + //TODO Exception handling + } + + return data; + } + + @Override + public PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle, int windSpeed) { + HttpServletRequest httpServletRequest = this.getThreadLocalRequest(); + HttpSession session = httpServletRequest.getSession(); + @SuppressWarnings("unchecked") + List> data = (List>) session.getAttribute(polarSheetId); + if (data == null) { + //TODO exception handling + return null; + } + List dataForAngle = data.get(angle); + if (dataForAngle.size() < 1) { + //TODO exception handling + return null; + } + + List dataForAngleAndWindSpeed = new ArrayList(); + + for (BoatAndWindSpeed dataPoint: dataForAngle) { + if (((int) dataPoint.getWindSpeed().getBeaufort()) == windSpeed) { + dataForAngleAndWindSpeed.add(dataPoint.getBoatSpeed().getKnots()); + } + } + + if (dataForAngleAndWindSpeed.size() < 1) { + //TODO exception handling + return null; + } + + Double min = Collections.min(dataForAngleAndWindSpeed); + Double max = Collections.max(dataForAngleAndWindSpeed); + //TODO make number of columns dynamic to chart size + int numberOfColumns = 20; + double range = (max - min) / numberOfColumns; + Double[] xValues = new Double[numberOfColumns]; + for (int i = 0; i < numberOfColumns; i++) { + xValues[i] = min + i * range + ( 0.5 * range); + } + + Integer[] yValues = new Integer[numberOfColumns]; + for (Double dataPoint : dataForAngleAndWindSpeed) { + int i = (int) (((dataPoint - min) / range)); + if (i == numberOfColumns) { + //For max value + i = 19; + } + if (yValues[i] == null) { + yValues[i] = 0; + } + yValues[i]++; + } + + PolarSheetsHistogramData histogramData = new PolarSheetsHistogramDataImpl(angle, xValues, yValues, dataForAngleAndWindSpeed.size()); + + + return histogramData; + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml new file mode 100644 index 00000000000..ae2af017d01 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 930eac1d317..cc94325b1ee 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -21,20 +21,26 @@ import org.junit.Test; import com.mongodb.MongoException; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CompetitorImpl; import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.EventImpl; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.SeriesImpl; import com.sap.sailing.domain.base.impl.VenueImpl; @@ -386,4 +392,25 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest } } + @Test + public void testRegattaRaceAssociationStore() throws Exception { + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("112er", /* typicallyStartsUpwind */ true); + Regatta regatta = createRegatta("Cologne Masters", boatClass, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + + List competitors = new ArrayList(); + competitors.add(new CompetitorImpl("Axel", "Axel Uhl", null, null)); + Iterable waypoints = Collections.emptyList(); + Course course = new CourseImpl("Course", waypoints); + + RaceDefinition racedef = new RaceDefinitionImpl("M1", course, boatClass, competitors); + regatta.addRace(racedef); + + RacingEventServiceImpl evs = new RacingEventServiceImpl(getMongoService()); + assertNull(evs.getRememberedRegattaForRace(racedef.getId())); + evs.raceAdded(regatta, racedef); + assertNotNull(evs.getRememberedRegattaForRace(racedef.getId())); + evs.removeRegatta(regatta); + assertNull(evs.getRememberedRegattaForRace(racedef.getId())); + } + } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 94d389b231d..bd27e8cabf5 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1080,6 +1080,8 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen public void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException { for (RaceDefinition race : regatta.getAllRaces()) { removeRace(regatta, race); + mongoObjectFactory.removeRegattaForRaceID(race.getName(), regatta); + persistentRegattasForRaceIDs.remove(race.getId().toString()); } if (regatta.isPersistent()) { mongoObjectFactory.removeRegatta(regatta); diff --git a/wiki/test-simon-2.md b/wiki/test-simon-2.md deleted file mode 100644 index 793aa682b06..00000000000 --- a/wiki/test-simon-2.md +++ /dev/null @@ -1 +0,0 @@ -This is a test \ No newline at end of file