diff --git a/doc/ADMIN b/doc/ADMIN index 2a022c71735..cd2664cc070 100755 --- a/doc/ADMIN +++ b/doc/ADMIN @@ -145,20 +145,18 @@ First, check out the branch you want to re-build: - ensure that there is an ssh tunnel to localhost that - - "mvn deploy" + - "mvn install" This will compile the sources, bundle them up in to OSGi bundle JAR files, run all tests which required online access to the TracTrac -servers, and, if successful, deploy the new JAR files to -maven-repositories/ where is one of dev, -test or prod. +servers, and, if successful, creates an eclipse product in the directory +/home/trac/git/java/com.sap.sailing.feature.p2build/bin/products/raceanalysis.product.id/linux/gtk/x86_64 +which contains all files required to install and run the build result. -Note that the running server(s) are not updated automatically by -deploying the new bundle versions to the maven-repositories -subdirectory. See next topic. +Note that the running server(s) are not updated automatically. See next topic. -Updating an OSGi Server Instance --------------------------------- +OSGi Server Instances +--------------------- We usually have four OSGi consoles running with a terminal window each, visible at the VNC server running on por 5902. The title bar @@ -170,11 +168,36 @@ At the "osgi>" prompt of the OSGi console you can type - "exit" -to terminate the particular Java server instance. To update the server -instance from its respective maven repository (located under -~/maven-repositories/...) you can issue +to terminate the particular Java server instance. +You can either install a complete new server instance or just update an existing one. +Install a new OSGi Server Instance +---------------------------------- + +To install a complete new server instance you can issue + + - "./install" + +This command will delete the old installation and copy all required files +from the build result to the right directories of the server instance. + +To start the server instance again, type + + - "./start" + +and the server should welcome you again with the "osgi>" prompt after +a successful start. + + +Update an existing OSGi Server Instance +----------------------------------------- + +To update the server you can issue + - "./update" + +This command will NOT delete the old installation but copy the updated plugins +from the build result to the right directories of the server instance. To start the server instance again, type diff --git a/doc/DEVELOPMENT b/doc/DEVELOPMENT index 8a06ce4c30b..aab1602948b 100644 --- a/doc/DEVELOPMENT +++ b/doc/DEVELOPMENT @@ -52,8 +52,8 @@ com.sap.sailing.feature -> contains the feature.xml for the race analysis projec com.sap.sailing.feature.runtime -> contains the feature.xml for additional runtime libraries which are not required for the source build (e.g. jetty.osgi bundle) com.sap.sailing.feature.p2build -> contains the product defintion (.product file) for the race analysis project (required for the product build) -Typical usecases for changing the target platform definition ------------------------------------------------------ +Typical usecases changing the target platform definition +-------------------------------------------------------- 1. Adding an external open source library to the target-platform @@ -70,4 +70,10 @@ g) Submit the changes to the git repository 3. Adding a p2 repository as new source of libraries +Typical usecases changing the features and product definition +------------------------------------------------------------- + +4. Adding a new bundle project + + diff --git a/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF index 0dcf7f0c7c7..183856a97e0 100755 --- a/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Require-Bundle: com.sap.sailing.declination, - com.sap.sailing.domain + com.sap.sailing.domain, + com.sap.sailing.domain.common Import-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.impl, com.sap.sailing.domain.common.quadtree, diff --git a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF index 0ed09efed42..c193bcee29f 100755 --- a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy -Require-Bundle: com.sap.sailing.domain +Require-Bundle: com.sap.sailing.domain, + com.sap.sailing.domain.common Export-Package: com.sap.sailing.declination, com.sap.sailing.declination.impl;x-friends:="com.sap.sailing.declination.test" Import-Package: com.sap.sailing.domain.common, diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java index 8fb75d80492..e37f18b310d 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java @@ -57,4 +57,6 @@ public abstract class AbstractBearing implements Bearing { public boolean equals(Object object) { return object != null && object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees(); } + + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/KilometersPerHourSpeedImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/KilometersPerHourSpeedImpl.java index 49622c2c357..61c09a0b004 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/KilometersPerHourSpeedImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/KilometersPerHourSpeedImpl.java @@ -5,11 +5,11 @@ import com.sap.sailing.domain.common.Speed; public class KilometersPerHourSpeedImpl extends AbstractSpeedImpl implements Speed { private final double speedInKilometersPerHour; - + public KilometersPerHourSpeedImpl(double speedInKilometersPerHour) { this.speedInKilometersPerHour = speedInKilometersPerHour; } - + @Override public double getKnots() { return getKilometersPerHour() * 1000. / Mile.METERS_PER_NAUTICAL_MILE; @@ -24,5 +24,4 @@ public class KilometersPerHourSpeedImpl extends AbstractSpeedImpl implements Spe public double getKilometersPerHour() { return speedInKilometersPerHour; } - } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java new file mode 100755 index 00000000000..a11df3753a5 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java @@ -0,0 +1,146 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import com.sap.sailing.domain.base.Bearing; +import com.sap.sailing.domain.base.impl.DegreeBearingImpl; +import com.sap.sailing.domain.base.impl.RadianBearingImpl; +import com.sap.sailing.domain.common.Util.Pair; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; +import com.sap.sailing.domain.confidence.ConfidenceBasedAveragerFactory; +import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.confidence.impl.ScalableDoubleWithConfidence; + +public class ConfidenceTest { + private static class ScalableBearing implements ScalableValue, Bearing> { + private final double sin; + private final double cos; + + public ScalableBearing(Bearing bearing) { + this.sin = Math.sin(bearing.getRadians()); + this.cos = Math.cos(bearing.getRadians()); + } + + private ScalableBearing(double sin, double cos) { + this.sin = sin; + this.cos = cos; + } + + @Override + public ScalableValue, Bearing> multiply(double factor) { + Pair pair = getValue(); + return new ScalableBearing(factor*pair.getA(), factor*pair.getB()); + } + + @Override + public ScalableValue, Bearing> add(ScalableValue, Bearing> t) { + Pair value = getValue(); + Pair tValue = t.getValue(); + return new ScalableBearing(value.getA()+tValue.getA(), value.getB()+tValue.getB()); + } + + @Override + public Bearing divide(double divisor, double confidence) { + double angle; + if (cos == 0) { + angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2; + } else { + angle = Math.atan2(sin, cos); + } + Bearing result = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); + return result; + } + + @Override + public Pair getValue() { + return new Pair(sin, cos); + } + } + + private static class ScalableBearingWithConfidence extends ScalableBearing implements HasConfidence, Bearing> { + private final double confidence; + + public ScalableBearingWithConfidence(Bearing bearing, double confidence) { + super(bearing); + this.confidence = confidence; + } + + @Override + public double getConfidence() { + return confidence; + } + + @Override + public ScalableValue, Bearing> getScalableValue() { + return this; + } + + } + + @Test + public void testAveragingWithEmptyListYieldsNull() { + ConfidenceBasedAverager averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + Set> emptySet = Collections.emptySet(); + Double average = averager.getAverage(emptySet); + assertNull(average); + } + + @Test + public void testAveragingWithNullArrayYieldsNull() { + ConfidenceBasedAverager averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + Double average = averager.getAverage(null); + assertNull(average); + } + + @Test + public void testAveragingWithTwoDoubles() { + ScalableDoubleWithConfidence d1 = new ScalableDoubleWithConfidence(1., 0.5); + ScalableDoubleWithConfidence d2 = new ScalableDoubleWithConfidence(2., 0.5); + ConfidenceBasedAverager averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + List list = Arrays.asList(d1, d2); + Double average = averager.getAverage(list); + assertEquals(1.5, average, 0.00000001); + } + + @Test + public void testAveragingWithThreeDoubles() { + ScalableDoubleWithConfidence d1 = new ScalableDoubleWithConfidence(1., 1.); + ScalableDoubleWithConfidence d2 = new ScalableDoubleWithConfidence(2., 1.); + ScalableDoubleWithConfidence d3 = new ScalableDoubleWithConfidence(3., 2.); + ConfidenceBasedAverager averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + List list = Arrays.asList(d1, d2, d3); + Double average = averager.getAverage(list); + assertEquals(2.25, average, 0.00000001); + } + + @Test + public void testAveragingWithTwoBearings() { + ScalableBearingWithConfidence d1 = new ScalableBearingWithConfidence(new DegreeBearingImpl(350.), 1.); + ScalableBearingWithConfidence d2 = new ScalableBearingWithConfidence(new DegreeBearingImpl(10.), 1.); + ConfidenceBasedAverager, Bearing> averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + List list = Arrays.asList(d1, d2); + Bearing average = averager.getAverage(list); + assertEquals(0, average.getDegrees(), 0.00000001); + } + + @Test + public void testAveragingWithThreeoBearings() { + ScalableBearingWithConfidence d1 = new ScalableBearingWithConfidence(new DegreeBearingImpl(350.), 1.); + ScalableBearingWithConfidence d2 = new ScalableBearingWithConfidence(new DegreeBearingImpl(10.), 1.); + ScalableBearingWithConfidence d3 = new ScalableBearingWithConfidence(new DegreeBearingImpl(20.), 2.); + ConfidenceBasedAverager, Bearing> averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + List list = Arrays.asList(d1, d2, d3); + Bearing average = averager.getAverage(list); + assertEquals(10, average.getDegrees(), 0.1); + } + +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java index 8a45029532e..1bed4c038ff 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java @@ -10,7 +10,9 @@ import java.io.IOException; import org.junit.Test; +import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.NoWindException; @@ -21,8 +23,92 @@ import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.BearingCluster; +import com.sap.sailing.domain.tracking.impl.BearingWithConfidenceCluster; public class WindEstimationOnStoredTracksTest extends StoredTrackBasedTestWithTrackedRace { + // ---------------- BearingWithConfidenceCluster ----------------- + + @Test + public void testEmptyBearingWithConfidenceClusterHasNullAverage() { + assertNull(new BearingWithConfidenceCluster().getAverage()); + } + + @Test + public void testBearingWithConfidenceClusterSplit() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + for (double bearingInDegrees : new double[] { 32.31650532600039, 16.99636033752683, 37.59302174779672, + 27.2860810183163, 319.47157698009613, 325.1617832132204, 31.678409742672212, 35.00547108150359, + 23.934778873669256, 29.76599976685808, 33.19487072661667, 19.0, 33.29318052266396, 32.7371445230587, + 38.26627143611533 }) { + cluster.add(new BearingWithConfidenceImpl(new DegreeBearingImpl(bearingInDegrees), /* confidence */ 0.9)); + } + BearingWithConfidenceCluster[] splitResult = cluster.splitInTwo(/* minimumDegreeDifferenceBetweenTacks */ 15); + assertEquals(2, splitResult.length); + assertNotNull(splitResult[0]); + assertNotNull(splitResult[1]); + assertFalse(splitResult[0].isEmpty()); + assertFalse(splitResult[1].isEmpty()); + assertEquals(322, splitResult[0].getAverage().getDegrees(), 1); + assertEquals(30, splitResult[1].getAverage().getDegrees(), 1); + } + + @Test + public void testBearingWithConfidenceClusterAverageAcrossZero() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + BearingWithConfidence b1 = new BearingWithConfidenceImpl(new DegreeBearingImpl(355), /* confidence */ 0.9); + BearingWithConfidence b2 = new BearingWithConfidenceImpl(new DegreeBearingImpl(5), /* confidence */ 0.9); + cluster.add(b1); + cluster.add(b2); + Bearing average = cluster.getAverage(); + assertEquals(0, average.getDegrees(), 0.00000001); + } + + @Test + public void testBearingWithConfidenceClusterAverageWithZeroSinus() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + BearingWithConfidence b1 = new BearingWithConfidenceImpl(new DegreeBearingImpl(0), /* confidence */ 0.9); + BearingWithConfidence b2 = new BearingWithConfidenceImpl(new DegreeBearingImpl(0), /* confidence */ 0.9); + cluster.add(b1); + cluster.add(b2); + Bearing average = cluster.getAverage(); + assertEquals(0, average.getDegrees(), 0.00000001); + } + + @Test + public void testBearingWithConfidenceClusterAverageWithZeroSinusAndNegativeCosinus() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + BearingWithConfidence b1 = new BearingWithConfidenceImpl(new DegreeBearingImpl(180), /* confidence */ 0.9); + BearingWithConfidence b2 = new BearingWithConfidenceImpl(new DegreeBearingImpl(180), /* confidence */ 0.9); + cluster.add(b1); + cluster.add(b2); + Bearing average = cluster.getAverage(); + assertEquals(180, average.getDegrees(), 0.00000001); + } + + @Test + public void testBearingWithConfidenceClusterAverageWithZeroCosinus() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + BearingWithConfidence b1 = new BearingWithConfidenceImpl(new DegreeBearingImpl(90), /* confidence */ 0.9); + BearingWithConfidence b2 = new BearingWithConfidenceImpl(new DegreeBearingImpl(90), /* confidence */ 0.9); + cluster.add(b1); + cluster.add(b2); + Bearing average = cluster.getAverage(); + assertEquals(90, average.getDegrees(), 0.00000001); + } + + @Test + public void testBearingWithConfidenceClusterAverageWithZeroCosinusAndNegativeSinus() { + BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(); + BearingWithConfidence b1 = new BearingWithConfidenceImpl(new DegreeBearingImpl(270), /* confidence */ 0.9); + BearingWithConfidence b2 = new BearingWithConfidenceImpl(new DegreeBearingImpl(270), /* confidence */ 0.9); + cluster.add(b1); + cluster.add(b2); + Bearing average = cluster.getAverage(); + assertEquals(270, average.getDegrees(), 0.00000001); + } + + // ---------------- Classical BearingCluster ----------------- + @Test public void testEmptyClusterHasNullAverage() { assertNull(new BearingCluster().getAverage()); diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index ca6f8472db7..eba2079beb6 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -8,6 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ClassPath: . Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.base.impl, + com.sap.sailing.domain.confidence;x-friends:="com.sap.sailing.domain.test", + com.sap.sailing.domain.confidence.impl;x-friends:="com.sap.sailing.domain.test", com.sap.sailing.domain.leaderboard, com.sap.sailing.domain.leaderboard.impl, com.sap.sailing.domain.tracking, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java new file mode 100755 index 00000000000..e82e9f40335 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.base; + +import com.sap.sailing.domain.common.Util.Pair; +import com.sap.sailing.domain.confidence.HasConfidence; + + +public interface BearingWithConfidence extends HasConfidence, BearingWithConfidence> { + Bearing getBearing(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/HasConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/HasConfidence.java deleted file mode 100755 index 60ac6a81771..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/HasConfidence.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.sap.sailing.domain.base; - -/** - * Some values, particularly those obtained from real-world measurements, are not always accurate. Some - * values are derived by interpolating or extrapolating data series obtained through measurement or - * even estimation. Some values are simply guessed by humans and entered into the system.

- * - * All those values have a certain level of confidence. In case multiple sources of information about the - * same entity or phenomenon are available, knowing the confidence of each value helps in weighing and - * averaging these values more properly than would be possible without a confidence value. - * - * @author Axel Uhl (d043530) - * - */ -public interface HasConfidence { - -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java new file mode 100755 index 00000000000..5c21bf58936 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.base; + +import com.sap.sailing.domain.common.Util.Triple; +import com.sap.sailing.domain.confidence.HasConfidence; + +public interface SpeedWithBearingWithConfidence extends + HasConfidence, SpeedWithBearingWithConfidence> { + SpeedWithBearing getSpeedWithBearing(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java new file mode 100755 index 00000000000..1712c5e96ea --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.base; + +import com.sap.sailing.domain.confidence.HasConfidence; + +public interface SpeedWithConfidence extends HasConfidence { + Speed getSpeed(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java new file mode 100755 index 00000000000..89eb328ffe4 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java @@ -0,0 +1,77 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.Bearing; +import com.sap.sailing.domain.base.BearingWithConfidence; +import com.sap.sailing.domain.common.Util.Pair; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class BearingWithConfidenceImpl implements BearingWithConfidence { + private final Bearing bearing; + + private final double confidence; + + public BearingWithConfidenceImpl(Bearing bearing, double confidence) { + this.bearing = bearing; + this.confidence = confidence; + } + + @Override + public Bearing getBearing() { + return bearing; + } + + @Override + public double getConfidence() { + return confidence; + } + + @Override + public ScalableValue, BearingWithConfidence> getScalableValue() { + return new ScalableBearing(getBearing()); + } + + private static class ScalableBearing implements ScalableValue, BearingWithConfidence> { + private final double sin; + private final double cos; + + public ScalableBearing(Bearing bearing) { + this.sin = Math.sin(bearing.getRadians()); + this.cos = Math.cos(bearing.getRadians()); + } + + private ScalableBearing(double sin, double cos) { + this.sin = sin; + this.cos = cos; + } + + @Override + public ScalableValue, BearingWithConfidence> multiply(double factor) { + Pair pair = getValue(); + return new ScalableBearing(factor*pair.getA(), factor*pair.getB()); + } + + @Override + public ScalableValue, BearingWithConfidence> add(ScalableValue, BearingWithConfidence> t) { + Pair value = getValue(); + Pair tValue = t.getValue(); + return new ScalableBearing(value.getA()+tValue.getA(), value.getB()+tValue.getB()); + } + + @Override + public BearingWithConfidence divide(double divisor, double confidence) { + double angle; + if (cos == 0) { + angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2; + } else { + angle = Math.atan2(sin, cos); + } + BearingWithConfidence result = new BearingWithConfidenceImpl(new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle), confidence); + return result; + } + + @Override + public Pair getValue() { + return new Pair(sin, cos); + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/HasConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/HasConfidenceImpl.java new file mode 100755 index 00000000000..be93f9f85a2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/HasConfidenceImpl.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.confidence.HasConfidence; + +public abstract class HasConfidenceImpl implements HasConfidence { + private final double confidence; + + public HasConfidenceImpl(double confidence) { + this.confidence = confidence; + } + + @Override + public double getConfidence() { + return confidence; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KnotSpeedWithBearingImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KnotSpeedWithBearingImpl.java index 3da48713256..568e9c8af85 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KnotSpeedWithBearingImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KnotSpeedWithBearingImpl.java @@ -9,7 +9,7 @@ public class KnotSpeedWithBearingImpl extends AbstractSpeedWithBearingImpl { super(bearing); this.speedInKnots = speedInKnots; } - + @Override public double getKnots() { return speedInKnots; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java new file mode 100755 index 00000000000..c3b5fc2c7b2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java @@ -0,0 +1,80 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.Bearing; +import com.sap.sailing.domain.base.Speed; +import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.common.Util.Triple; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class SpeedWithBearingWithConfidenceImpl extends + HasConfidenceImpl, SpeedWithBearingWithConfidence> implements + SpeedWithBearingWithConfidence { + private final SpeedWithBearing speedWithBearing; + + public SpeedWithBearingWithConfidenceImpl(SpeedWithBearing speedWithBearing, double confidence) { + super(confidence); + this.speedWithBearing = speedWithBearing; + } + + @Override + public SpeedWithBearing getSpeedWithBearing() { + return speedWithBearing; + } + + /** + * The scalable value used for averaging confidence-based objects of this type is a triple whose first component + * holds the speed with a confidence while the second and third element are the sine and cosine values of the bearing's + * angle. + */ + @Override + public ScalableValue, SpeedWithBearingWithConfidence> getScalableValue() { + return new ScalableSpeedWithBearing(new KnotSpeedImpl(getSpeedWithBearing().getKnots()), + Math.sin(getSpeedWithBearing().getBearing().getRadians()), Math.cos(getSpeedWithBearing().getBearing().getRadians())); + } + + private static class ScalableSpeedWithBearing implements ScalableValue, SpeedWithBearingWithConfidence> { + private final Speed speed; + private final double sin; + private final double cos; + + public ScalableSpeedWithBearing(Speed speed, double sin, double cos) { + this.speed = speed; + this.sin = sin; + this.cos = cos; + } + + @Override + public ScalableValue, SpeedWithBearingWithConfidence> multiply( + double factor) { + Speed newSpeed = new KnotSpeedImpl(factor*speed.getKnots()); + return new ScalableSpeedWithBearing(newSpeed, factor*sin, factor*cos); + } + + @Override + public ScalableValue, SpeedWithBearingWithConfidence> add( + ScalableValue, SpeedWithBearingWithConfidence> t) { + Speed newSpeed = new KnotSpeedImpl(speed.getKnots() + t.getValue().getA().getKnots()); + return new ScalableSpeedWithBearing(newSpeed, sin+t.getValue().getB(), cos+t.getValue().getC()); + } + + @Override + public SpeedWithBearingWithConfidence divide(double divisor, double confidence) { + Speed newSpeed = new KnotSpeedImpl(speed.getKnots() / divisor); + double angle; + if (cos == 0) { + angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2; + } else { + angle = Math.atan2(sin, cos); + } + Bearing bearing = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); + return new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl(newSpeed.getKnots(), bearing), confidence); + } + + @Override + public Triple getValue() { + return new Triple(speed, sin, cos); + } + + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java new file mode 100755 index 00000000000..8866576e8aa --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.confidence; + +/** + * A {@link ScalableValue} where the ValueType and the AveragesTo type are both the same because + * the value type is itself scalable, offering addition, multiplication and division. + * + * @author Axel Uhl (d043530) + * + */ +public interface AbstractScalarValue extends ScalableValue { +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java new file mode 100755 index 00000000000..0dd404e98da --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.confidence; + +public interface ConfidenceBasedAverager { + AveragesTo getAverage(Iterable> values); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAveragerFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAveragerFactory.java new file mode 100755 index 00000000000..f490d8f1c4b --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAveragerFactory.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.confidence; + +import com.sap.sailing.domain.confidence.impl.ConfidenceBasedAveragerFactoryImpl; + +public interface ConfidenceBasedAveragerFactory { + ConfidenceBasedAveragerFactory INSTANCE = new ConfidenceBasedAveragerFactoryImpl(); + + ConfidenceBasedAverager createAverager(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java new file mode 100755 index 00000000000..69293418007 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java @@ -0,0 +1,66 @@ +package com.sap.sailing.domain.confidence; + +import com.sap.sailing.domain.base.Distance; + +/** + * Some values, particularly those obtained from real-world measurements, are not always accurate. Some values are + * derived by interpolating or extrapolating data series obtained through measurement or even estimation. Some values + * are simply guessed by humans and entered into the system. + *

+ * + * All those values have a certain level of confidence. In case multiple sources of information about the same entity or + * phenomenon are available, knowing the confidence of each value helps in weighing and averaging these values more + * properly than would be possible without a confidence value. + *

+ * + * In simple cases, the type used to compute a weighed average over the things equipped with a confidence level is the + * same as the type of the things themselves. In particular, this is the case for scalar types such as {@link Double} + * and {@link Distance}. For non-scalar values, averaging may be non-trivial. For example, averaging a bearing cannot + * simply happen by computing the arithmetic average of the bearing's angles. Instead, an intermediate structure + * providing the sinus and cosinus values of the bearing's angle is used to compute the weighed average tangens. + *

+ * + * Generally, the relationship between the type implementing this interface, the ValueType and the + * AveragesTo type is this: an instance of the implementing type can transform itself into a + * {@link ScalableValue} which is then used for computing a weighed sum. The values' weight is their + * {@link #getConfidence() confidence}. The sum (which is still a {@link ScalableValue} because + * {@link ScalableValue#add(ScalableValue)} returns again a {@link ScalableValue}) is then + * {@link ScalableValue#divide(double, double) divided} by the sum of the confidences. This "division" is expected to + * produce an object of type AveragesTo. Usually, AveragesTo would be the same as + * the class implementing this interface. + * + * @author Axel Uhl (d043530) + * + * @param + * the type of the scalable value + * + */ +public interface HasConfidence { + /** + * A confidence is a number between 0.0 and 1.0 (inclusive) where 0.0 means that the value is randomly guessed while + * 1.0 means the value is authoritatively known for a fact. It represents the weight with which a value is to be + * considered by averaging, interpolation and extrapolation algorithms. + *

+ * + * An averaging algorithm for a sequence of n tuples (v1, c1), ..., (vn, cn) of a value + * vi with a confidence ci each can for example look like this: + * a := (c1*v1 + ... + cn*vn) / (c1 + ... + cn). For a single value with a confidence this trivially + * results in c1*v1 / (c1) which is equivalent to v1. As another example, consider two + * values with equal confidence 0.8. Then, a := (0.8*v1 + 0.8*vn) / (0.8 + 0.8) which + * resolves to 0.5*v1 + 0.5*v2 which is obviously the arithmetic mean of the two values. If one value + * has confidence 0.8 and the other 0.4, then + * a := (0.8*v1 + 0.4*vn) / (0.8 + 0.4) which resolves to 2/3*v1 + 1/3*v2 which is a + * weighed average. + *

+ * + * Note, that this doesn't exactly take facts for facts. In other words, if one value is provided with a confidence + * of 1.0, the average may still be influenced by other values. However, this cleanly resolves otherwise + * mutually contradictory "facts" such a (v1, 1.0), (v2, 1.0) with v1 != v2. It is + * considered bad practice to claim a fact as soon as it results from any kind of measurement or estimation. All + * measurement devices produce some statistical errors, no matter how small (cf. Heisenberg ;-) ). + */ + double getConfidence(); + + ScalableValue getScalableValue(); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java new file mode 100755 index 00000000000..436b86ed76f --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.confidence; + +public interface ScalableValue { + ScalableValue multiply(double factor); + + ScalableValue add(ScalableValue t); + + AveragesTo divide(double divisor, double confidence); + + ValueType getValue(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java new file mode 100755 index 00000000000..fd8bd9f89f6 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.confidence.impl; + +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; +import com.sap.sailing.domain.confidence.ConfidenceBasedAveragerFactory; + +public class ConfidenceBasedAveragerFactoryImpl implements ConfidenceBasedAveragerFactory { + @Override + public ConfidenceBasedAverager createAverager() { + return new ConfidenceBasedAveragerImpl(); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java new file mode 100755 index 00000000000..6c8120be2e4 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java @@ -0,0 +1,54 @@ +package com.sap.sailing.domain.confidence.impl; + +import java.util.Iterator; + +import com.sap.sailing.domain.common.Util; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; +import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.ScalableValue; + + +/** + * A set of values that {@link HasConfidence have a confidence attached} can be averaged using the confidence value to + * weigh values with greater confidence more than those with lesser confidence. It implements the following algorithm + * for a sequence of n tuples (v1, c1), ..., (vn, cn) of a value vi with a + * confidence ci each:

+ * + * a := (c1*v1 + ... + cn*vn) / (c1 + ... + cn). For a single value with a confidence this trivially + * results in c1*v1 / (c1) which is equivalent to v1. As another example, consider two values + * with equal confidence 0.8. Then, a := (0.8*v1 + 0.8*vn) / (0.8 + 0.8) which resolves to + * 0.5*v1 + 0.5*v2 which is obviously the arithmetic mean of the two values. If one value has confidence + * 0.8 and the other 0.4, then a := (0.8*v1 + 0.4*vn) / (0.8 + 0.4) which + * resolves to 2/3*v1 + 1/3*v2 which is a weighed average. + *

+ * + * Note, that this doesn't exactly take facts for facts. In other words, if one value is provided with a confidence of + * 1.0, the average may still be influenced by other values. However, this cleanly resolves otherwise + * mutually contradictory "facts" such a (v1, 1.0), (v2, 1.0) with v1 != v2. It is considered + * bad practice to claim a fact as soon as it results from any kind of measurement or estimation. All measurement + * devices produce some statistical errors, no matter how small (cf. Heisenberg ;-) ). + * + * @author Axel Uhl (d043530) + */ +public class ConfidenceBasedAveragerImpl implements ConfidenceBasedAverager { + @Override + public AveragesTo getAverage(Iterable> values) { + if (values == null || Util.isEmpty(values)) { + return null; + } else { + Iterator> iter = values.iterator(); + HasConfidence next = iter.next(); + ScalableValue numerator = next.getScalableValue().multiply(next.getConfidence()); + double confidenceSum = next.getConfidence(); + while (iter.hasNext()) { + next = iter.next(); + numerator = numerator.add(next.getScalableValue().multiply(next.getConfidence())); + confidenceSum += next.getConfidence(); + } + // TODO determine new confidence, e.g., based on the variance of estimates averaged + double newConfidence = confidenceSum / Util.size(values); + AveragesTo result = numerator.divide(confidenceSum, newConfidence); + return result; + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java new file mode 100755 index 00000000000..60c24dbed45 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.confidence.impl; + +import com.sap.sailing.domain.confidence.AbstractScalarValue; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class ScalableDouble implements AbstractScalarValue { + private final double value; + + public ScalableDouble(double value) { + this.value = value; + } + + @Override + public ScalableValue multiply(double factor) { + return new ScalableDouble(factor*getValue()); + } + + @Override + public ScalableDouble add(ScalableValue t) { + return new ScalableDouble(getValue()+t.getValue()); + } + + @Override + public Double divide(double divisor, double confidence) { + return getValue()/divisor; + } + + @Override + public Double getValue() { + return value; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDoubleWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDoubleWithConfidence.java new file mode 100755 index 00000000000..3f96f736833 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDoubleWithConfidence.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.confidence.impl; + +import com.sap.sailing.domain.confidence.HasConfidence; + +public class ScalableDoubleWithConfidence extends ScalableDouble implements HasConfidence { + private final double confidence; + + public ScalableDoubleWithConfidence(double d, double confidence) { + super(d); + this.confidence = confidence; + } + + @Override + public double getConfidence() { + return confidence; + } + + @Override + public ScalableDouble getScalableValue() { + return this; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingCluster.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingCluster.java index 23f6d4a03a9..fd2ca97ef98 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingCluster.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingCluster.java @@ -1,8 +1,5 @@ package com.sap.sailing.domain.tracking.impl; -import java.util.ArrayList; -import java.util.List; - import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.impl.RadianBearingImpl; import com.sap.sailing.domain.common.impl.Util.Pair; @@ -20,120 +17,23 @@ import com.sap.sailing.domain.common.impl.Util.Pair; * @author Axel Uhl (d043530) * */ -public class BearingCluster { - private final List bearings; - private double sumSin; - private double sumCos; - - public BearingCluster() { - bearings = new ArrayList(); - sumSin = 0.0; - sumCos = 0.0; - } - - /** - * Finds the two bearings in the cluster that are farthest apart (at least minimumDegreeDifferenceBetweenTacks). - * Then, the remaining bearings in this cluster are associated with the one of the two extreme bearings to which they are - * closer. The two resulting clusters are returned. - * - * @param minimumDegreeDifferenceBetweenTacks - * tells the minimum degree difference that must exist between the two extreme bearings before they are - * considered to represent boats on different tacks. If more than one bearing exists in this cluster - * but no two bearings are at least minimumDegreeDifferenceBetweenTacks degrees apart from - * each other, only fir first of the two clusters returned will contain bearings while the second one - * remains empty. - * @return two bearing clusters; both empty if this cluster is empty; only the second one empty if this cluster - * contains only one bearing. Otherwise, the two bearings farthest apart (greatest absolute - * {@link Bearing#getDifferenceTo(Bearing) difference}) are guaranteed not to be in different clusters, and - * all other bearings contained in this cluster will be contained in the cluster that contains the extreme - * bearing to which it's closer. - */ - public BearingCluster[] splitInTwo(double minimumDegreeDifferenceBetweenTacks) { - BearingCluster[] result = new BearingCluster[2]; - result[0] = new BearingCluster(); - result[1] = new BearingCluster(); - if (bearings.size() >= 2) { - Pair extremeBearings = getExtremeBearings(minimumDegreeDifferenceBetweenTacks); - if (extremeBearings != null) { - result[0].add(extremeBearings.getA()); - result[1].add(extremeBearings.getB()); - } - for (Bearing bearing : bearings) { - if (extremeBearings == null || (bearing != extremeBearings.getA() && bearing != extremeBearings.getB())) { - if (extremeBearings == null || result[0].getDifferenceFromAverage(bearing) <= result[1].getDifferenceFromAverage(bearing)) { - result[0].add(bearing); - } else { - result[1].add(bearing); - } - } - } - } else if (!bearings.isEmpty()) { - // add the only bearing to the first of the two resulting clusters - result[0].add(bearings.get(0)); - } - return result; - } - - private Pair getExtremeBearings(double minimumDegreeDifferenceBetweenTacks) { - assert bearings.size() >= 2; - double maxAbsDegDiff = minimumDegreeDifferenceBetweenTacks; - Pair result = null; - for (int i=0; i= maxAbsDegDiff) { - result = new Pair(bearings.get(i), bearings.get(j)); - maxAbsDegDiff = Math.abs(bearings.get(i).getDifferenceTo(bearings.get(j)).getDegrees()); - assert Math.abs(result.getA().getDegrees()-result.getB().getDegrees()) <= 180.; - } - } - } - return result; - } - - public boolean isEmpty() { - return bearings.isEmpty(); - } - - public int size() { - return bearings.size(); - } - - public void add(Bearing bearing) { - bearings.add(bearing); - sumSin += Math.sin(bearing.getRadians()); - sumCos += Math.cos(bearing.getRadians()); - } - - /** - * If the cluster contains no bearings, null is returned. Otherwise, the average angle is computed - * by adding up the sin and cos values of the individual bearings, then computing the atan2 of the ratio. - */ - public Bearing getAverage() { - Bearing result = null; - if (!bearings.isEmpty()) { - double angle; - if (sumCos == 0) { - angle = sumSin >= 0 ? Math.PI / 2 : -Math.PI / 2; - } else { - angle = Math.atan2(sumSin, sumCos); - } - result = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); - } - return result; - } - - /** - * Absolute difference to {@link #getAverage() this cluster's average bearing} in degrees. If there is no bearing stored in - * this cluster yet, 0.0 is returned. - * - * @return a value >=0.0 - */ - private double getDifferenceFromAverage(Bearing bearing) { - return bearings.size() == 0 ? 0.0 : Math.abs(getAverage().getDifferenceTo(bearing).getDegrees()); +public class BearingCluster extends GenericBearingCluster { + @Override + protected BearingCluster[] createBearingClusterArraySizeTwo() { + return new BearingCluster[2]; } @Override - public String toString() { - return bearings.toString(); + protected BearingCluster createEmptyCluster() { + return new BearingCluster(); + } + + public BearingCluster[] splitInTwo(double minimumDegreeDifferenceBetweenTacks) { + return (BearingCluster[]) super.splitInTwo(minimumDegreeDifferenceBetweenTacks); + } + + @Override + protected Bearing getBearing(Bearing b) { + return b; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java new file mode 100755 index 00000000000..41fa1df0851 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java @@ -0,0 +1,53 @@ +package com.sap.sailing.domain.tracking.impl; + +import com.sap.sailing.domain.base.Bearing; +import com.sap.sailing.domain.base.BearingWithConfidence; +import com.sap.sailing.domain.common.Util.Pair; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; +import com.sap.sailing.domain.confidence.ConfidenceBasedAveragerFactory; + +/** + * Contains a number of {@link Bearing} objects and maintains the average bearing. For a given {@link Bearing} it + * can determine the difference to this cluster's average bearing. It can also split the cluster into two, based + * on the two bearings farthest apart. The cluster can contain multiple occurrences of the same and also + * multiple occurrences of mutually equal {@link Bearing} objects which is one possible way of computing a + * weighted average.

+ * + * It is assumed that bearings added to this cluster are no further than 180 degrees apart. Violating this + * rule will lead to unpredictable results. + * + * @author Axel Uhl (d043530) + * + */ +public class BearingWithConfidenceCluster extends GenericBearingCluster { + /** + * If the cluster contains no bearings, null is returned. Otherwise, the average angle is computed + * by adding up the sin and cos values of the individual bearings, then computing the atan2 of the ratio. + */ + @Override + public Bearing getAverage() { + ConfidenceBasedAverager, BearingWithConfidence> averager = ConfidenceBasedAveragerFactory.INSTANCE.createAverager(); + BearingWithConfidence average = averager.getAverage(getBearings()); + return average == null ? null : average.getBearing(); + } + + @Override + protected BearingWithConfidenceCluster[] createBearingClusterArraySizeTwo() { + return new BearingWithConfidenceCluster[2]; + } + + @Override + protected BearingWithConfidenceCluster createEmptyCluster() { + return new BearingWithConfidenceCluster(); + } + + @Override + public BearingWithConfidenceCluster[] splitInTwo(double minimumDegreeDifferenceBetweenTacks) { + return (BearingWithConfidenceCluster[]) super.splitInTwo(minimumDegreeDifferenceBetweenTacks); + } + + @Override + protected Bearing getBearing(BearingWithConfidence b) { + return b.getBearing(); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GenericBearingCluster.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GenericBearingCluster.java new file mode 100755 index 00000000000..dd0610ecab8 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GenericBearingCluster.java @@ -0,0 +1,154 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sap.sailing.domain.base.Bearing; +import com.sap.sailing.domain.base.impl.RadianBearingImpl; +import com.sap.sailing.domain.common.Util.Pair; + +/** + * Contains a number of {@link Bearing} objects (or a subtype thereof) and maintains the average bearing. For a given + * {@link Bearing} it can determine the difference to this cluster's average bearing. It can also split the cluster into + * two, based on the two bearings farthest apart. The cluster can contain multiple occurrences of the same and also + * multiple occurrences of mutually equal {@link Bearing} objects which is one possible way of computing a weighted + * average. + *

+ * + * @author Axel Uhl (d043530) + * + * @param the class used for the bearing objects + */ +public abstract class GenericBearingCluster { + private final List bearings; + private double sumSin; + private double sumCos; + + public GenericBearingCluster() { + bearings = new ArrayList(); + sumSin = 0.0; + sumCos = 0.0; + } + + abstract protected Bearing getBearing(BearingType b); + + /** + * Finds the two bearings in the cluster that are farthest apart (at least minimumDegreeDifferenceBetweenTacks). + * Then, the remaining bearings in this cluster are associated with the one of the two extreme bearings to which they are + * closer. The two resulting clusters are returned. + * + * @param minimumDegreeDifferenceBetweenTacks + * tells the minimum degree difference that must exist between the two extreme bearings before they are + * considered to represent boats on different tacks. If more than one bearing exists in this cluster + * but no two bearings are at least minimumDegreeDifferenceBetweenTacks degrees apart from + * each other, only fir first of the two clusters returned will contain bearings while the second one + * remains empty. + * @return two bearing clusters; both empty if this cluster is empty; only the second one empty if this cluster + * contains only one bearing. Otherwise, the two bearings farthest apart (greatest absolute + * {@link Bearing#getDifferenceTo(Bearing) difference}) are guaranteed not to be in different clusters, and + * all other bearings contained in this cluster will be contained in the cluster that contains the extreme + * bearing to which it's closer. + */ + public GenericBearingCluster[] splitInTwo(double minimumDegreeDifferenceBetweenTacks) { + GenericBearingCluster[] result = createBearingClusterArraySizeTwo(); + result[0] = createEmptyCluster(); + result[1] = createEmptyCluster(); + if (bearings.size() >= 2) { + Pair extremeBearings = getExtremeBearings(minimumDegreeDifferenceBetweenTacks); + if (extremeBearings != null) { + result[0].add(extremeBearings.getA()); + result[1].add(extremeBearings.getB()); + } + for (BearingType bearing : bearings) { + if (extremeBearings == null || (bearing != extremeBearings.getA() && bearing != extremeBearings.getB())) { + if (extremeBearings == null + || result[0].getDifferenceFromAverage(getBearing(bearing)) <= result[1] + .getDifferenceFromAverage(getBearing(bearing))) { + result[0].add(bearing); + } else { + result[1].add(bearing); + } + } + } + } else if (!bearings.isEmpty()) { + // add the only bearing to the first of the two resulting clusters + result[0].add(bearings.get(0)); + } + return result; + } + + abstract protected GenericBearingCluster createEmptyCluster(); + + @SuppressWarnings("unchecked") + protected GenericBearingCluster[] createBearingClusterArraySizeTwo() { + return (GenericBearingCluster[]) new GenericBearingCluster[2]; + } + + private Pair getExtremeBearings(double minimumDegreeDifferenceBetweenTacks) { + assert bearings.size() >= 2; + double maxAbsDegDiff = minimumDegreeDifferenceBetweenTacks; + Pair result = null; + for (int i=0; i= maxAbsDegDiff) { + result = new Pair(bearings.get(i), bearings.get(j)); + maxAbsDegDiff = Math.abs(getBearing(bearings.get(i)).getDifferenceTo(getBearing(bearings.get(j))).getDegrees()); + assert Math.abs(getBearing(result.getA()).getDegrees()-getBearing(result.getB()).getDegrees()) <= 180.; + } + } + } + return result; + } + + public boolean isEmpty() { + return bearings.isEmpty(); + } + + public int size() { + return bearings.size(); + } + + public void add(BearingType bearing) { + bearings.add(bearing); + sumSin += Math.sin(getBearing(bearing).getRadians()); + sumCos += Math.cos(getBearing(bearing).getRadians()); + } + + /** + * If the cluster contains no bearings, null is returned. Otherwise, the average angle is computed + * by adding up the sin and cos values of the individual bearings, then computing the atan2 of the ratio. + */ + public Bearing getAverage() { + Bearing result = null; + if (!bearings.isEmpty()) { + double angle; + if (sumCos == 0) { + angle = sumSin >= 0 ? Math.PI / 2 : -Math.PI / 2; + } else { + angle = Math.atan2(sumSin, sumCos); + } + result = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); + } + return result; + } + + /** + * Absolute difference to {@link #getAverage() this cluster's average bearing} in degrees. If there is no bearing stored in + * this cluster yet, 0.0 is returned. + * + * @return a value >=0.0 + */ + private double getDifferenceFromAverage(Bearing bearing) { + return bearings.size() == 0 ? 0.0 : Math.abs(getAverage().getDifferenceTo(bearing).getDegrees()); + } + + protected Iterable getBearings() { + return Collections.unmodifiableCollection(bearings); + } + + @Override + public String toString() { + return bearings.toString(); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 819c79ffde7..84b982b21bf 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.concurrent.ConcurrentSkipListSet; +import java.util.logging.Level; import java.util.logging.Logger; import org.json.simple.parser.ParseException; @@ -889,8 +890,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { break; default: maneuverType = Type.UNKNOWN; - logger.fine("Unknown maneuver for " + competitor + " at " + maneuverTimePoint - + (legBeforeManeuver != null ? " on reaching leg " + legBeforeManeuver.getLeg() : " before start")); + if (logger.isLoggable(Level.FINE)) { + logger.fine("Unknown maneuver for " + + competitor + " at " + maneuverTimePoint + + (legBeforeManeuver != null ? " on reaching leg " + legBeforeManeuver.getLeg() + : " before start")); + } break; } } else { diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index cf1a87bb034..c7718ba7085 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -9,7 +9,7 @@ import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.ExpandableSortableColumn; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDAO; @@ -21,7 +21,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { //These objects should be created by calling GWT.create(Class c); private LeaderboardPanelMock leaderboardPanel; private MyTestSailingServiceAsync service; - private StringConstants sc; + private StringMessages sc; //Test data. private final String LEADERBOARD_NAME = "test"; @@ -43,7 +43,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { protected void gwtSetUp() throws Exception { super.gwtSetUp(); service = GWT.create(MyTestSailingService.class); - sc = GWT.create(StringConstants.class); + sc = GWT.create(StringMessages.class); } public void testColumnDeleting() { diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java index aaf4ba7b4ec..e5a68d13d72 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java @@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.test; import com.google.gwt.user.cellview.client.CellTable; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; @@ -13,7 +13,7 @@ public class LeaderboardPanelMock extends LeaderboardPanel { public LeaderboardPanelMock(SailingServiceAsync sailingService, String leaderboardName, ErrorReporter errorReporter, - StringConstants stringConstants) { + StringMessages stringConstants) { super(sailingService, leaderboardName, errorReporter, stringConstants); // TODO Auto-generated constructor stub } diff --git a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs index 7faf173b37f..3d1e6b08933 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,3 @@ -#Sat Oct 01 15:18:53 CEST 2011 +#Fri Jan 13 22:25:34 CET 2012 eclipse.preferences.version=1 -encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringConstants_de.properties=UTF-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java index 1751e44b9a3..10aa5514008 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java @@ -4,13 +4,13 @@ import com.sap.sailing.gwt.ui.client.AbstractEventPanel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; public abstract class AbstractEventManagementPanel extends AbstractEventPanel { protected final TrackedEventsComposite trackedEventsComposite; public AbstractEventManagementPanel(SailingServiceAsync sailingService, - EventRefresher eventRefresher, ErrorReporter errorReporter, StringConstants stringConstants) { + EventRefresher eventRefresher, ErrorReporter errorReporter, StringMessages stringConstants) { super(sailingService, eventRefresher, errorReporter, stringConstants); // TrackedEventsComposite should exist in every *ManagementPanel. trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsole.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsole.java index 83c0618948b..28411d9560a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsole.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsole.java @@ -14,6 +14,7 @@ import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.shared.EventDAO; +import com.sap.sailing.server.api.DefaultLeaderboardName; public class AdminConsole extends AbstractEntryPoint implements EventRefresher { private Set eventDisplayers; @@ -49,7 +50,8 @@ public class AdminConsole extends AbstractEntryPoint implements EventRefresher { eventDisplayers.add(raceMapPanel); raceMapPanel.setSize("90%", "90%"); tabPanel.add(raceMapPanel, stringConstants.map(), /* asHTML */ false); - LeaderboardPanel defaultLeaderboardPanel = new LeaderboardPanel(sailingService, stringConstants.defaultLeaderboard(), this, stringConstants); + LeaderboardPanel defaultLeaderboardPanel = new LeaderboardPanel(sailingService, + DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME, this, stringConstants); defaultLeaderboardPanel.setSize("90%", "90%"); tabPanel.add(defaultLeaderboardPanel, stringConstants.defaultLeaderboard(), /* asHTML */ false); LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, stringConstants); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorsDisplayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorsDisplayer.java new file mode 100644 index 00000000000..f0c44d99453 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorsDisplayer.java @@ -0,0 +1,9 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; + +public interface CompetitorsDisplayer { + void fillCompetitors(List competitors); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateSwissTimingRacePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateSwissTimingRacePanel.java index b2a88070068..9c273d19fdf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateSwissTimingRacePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateSwissTimingRacePanel.java @@ -24,7 +24,7 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; public class CreateSwissTimingRacePanel extends FormPanel { private TextBox txtbRaceName; @@ -40,7 +40,7 @@ public class CreateSwissTimingRacePanel extends FormPanel { private final SailingServiceAsync service; private ErrorReporter errorReporter; - public CreateSwissTimingRacePanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, final StringConstants stringConstants){ + public CreateSwissTimingRacePanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, final StringMessages stringConstants){ this.service = sailingService; this.errorReporter = errorReporter; competitors = new ArrayList(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageTransformer.java old mode 100755 new mode 100644 similarity index 50% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageTransformer.java index f8175b0c9a7..0a1df8a1b85 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageTransformer.java @@ -1,80 +1,96 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import com.google.gwt.canvas.client.Canvas; -import com.google.gwt.canvas.dom.client.Context2d; -import com.google.gwt.dom.client.ImageElement; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.maps.client.geom.Point; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.ui.Image; - -/** - * Loads an image from a URL, draws it to a {@link Canvas}, rotates it by a specified angle and allows clients - * to obtain a URL for the rotated image contents to be used in other contexts requiring an image URL. - * - * @author Axel Uhl (d043530) - * - */ -public class ImageRotator { - private final String unrotatedImageURL; - private final Canvas canvas; - private final int imageWidth; - private final int imageHeight; - private final int canvasRadius; - - private ImageElement imageElement; - - private final Context2d context; - - public ImageRotator(ImageResource unrotatedImage) { - this.unrotatedImageURL = unrotatedImage.getSafeUri().asString(); - canvas = Canvas.createIfSupported(); - imageWidth = unrotatedImage.getWidth(); - imageHeight = unrotatedImage.getHeight(); - canvasRadius = (int) Math.sqrt(imageWidth*imageWidth/4 + imageHeight*imageHeight/4); - canvas.setSize(""+2*canvasRadius+"px", ""+2*canvasRadius+"px"); - canvas.setCoordinateSpaceWidth(2*canvasRadius); - canvas.setCoordinateSpaceHeight(2*canvasRadius); - context = canvas.getContext2d(); - final Image image = new Image(unrotatedImageURL.toString()); - imageElement = (ImageElement) image.getElement().cast(); - if (imageElement == null) { - image.addLoadHandler(new LoadHandler() { - @Override - public void onLoad(LoadEvent event) { - imageElement = (ImageElement) image.getElement().cast(); - } - }); - } - } - - public Point getAnchor() { - return Point.newInstance(canvasRadius, canvasRadius); - } - - private String getUnrotatedImageURL() { - return unrotatedImageURL; - } - - /** - * If the platform supports the {@link Canvas} element, returns a view rotated by degrees degrees. Otherwise, - * the {@link #getUnrotatedImageURL() unrotated image's URL} is returned. - */ - public String getRotatedImageURL(double angleInDegrees) { - String result = getUnrotatedImageURL(); - if (canvas != null) { - if (imageElement != null) { - double angleInRadians = angleInDegrees/180.*Math.PI; - context.clearRect(0, 0, 2*canvasRadius, 2*canvasRadius); - context.save(); - context.translate(canvasRadius, canvasRadius); - context.rotate(angleInRadians); - context.drawImage(imageElement, -imageWidth/2, -imageHeight/2); - result = canvas.toDataUrl("image/png"); - context.restore(); - } - } - return result; - } -} +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.canvas.client.Canvas; +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.google.gwt.maps.client.geom.Point; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.ui.Image; + +/** + * Loads an image from a URL, draws it to a {@link Canvas}, rotates (specified angle) and scales it and allows clients + * to obtain a URL for the rotated image contents to be used in other contexts requiring an image URL. + * + * @author Axel Uhl (d043530) + * + */ +public class ImageTransformer { + private final String untransformedImageURL; + private final Canvas canvas; + private final int imageWidth; + private final int imageHeight; + private int canvasRadius; + private int canvasWidth; + private int canvasHeight; + + private double currentScale; + + private ImageElement imageElement; + + private Context2d context; + + public ImageTransformer(ImageResource untransformedImage) { + this.untransformedImageURL = untransformedImage.getSafeUri().asString(); + canvas = Canvas.createIfSupported(); + imageWidth = untransformedImage.getWidth(); + imageHeight = untransformedImage.getHeight(); + scale(1.0); + final Image image = new Image(untransformedImageURL.toString()); + imageElement = (ImageElement) image.getElement().cast(); + if (imageElement == null) { + image.addLoadHandler(new LoadHandler() { + @Override + public void onLoad(LoadEvent event) { + imageElement = (ImageElement) image.getElement().cast(); + } + }); + } + } + + private void scale(double scaleFactor) { + if (scaleFactor != currentScale) { + canvasWidth = (int) Math.round(imageWidth * scaleFactor); + canvasHeight = (int) Math.round(imageHeight * scaleFactor); + canvasRadius = (int) Math.sqrt(canvasWidth * canvasWidth / 4 + canvasHeight * canvasHeight / 4); + canvas.setSize("" + 2 * canvasRadius + "px", "" + 2 * canvasRadius + "px"); + canvas.setCoordinateSpaceWidth(2 * canvasRadius); + canvas.setCoordinateSpaceHeight(2 * canvasRadius); + context = canvas.getContext2d(); + currentScale = scaleFactor; + } + } + + public Point getAnchor(double scaleFactor) { + scale(scaleFactor); + return Point.newInstance(canvasRadius, canvasRadius); + } + + private String getUntransformedImageURL() { + return untransformedImageURL; + } + + /** + * If the platform supports the {@link Canvas} element, returns a view rotated by degrees degrees. Otherwise, + * the {@link #getUntransformedImageURL() untransformed image's URL} is returned. + */ + public String getTransformedImageURL(double angleInDegrees, double scaleFactor) { + String result = getUntransformedImageURL(); + if (canvas != null) { + if (imageElement != null) { + scale(scaleFactor); + double angleInRadians = angleInDegrees/180.*Math.PI; + context.clearRect(0, 0, 2*canvasRadius, 2*canvasRadius); + context.save(); + context.translate(canvasRadius, canvasRadius); + context.rotate(angleInRadians); + context.scale(scaleFactor, scaleFactor); + context.drawImage(imageElement, (-imageWidth/2), (-imageHeight/2)); + result = canvas.toDataUrl("image/png"); + context.restore(); + } + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index 0e3a7a7bece..c7e8c1fe9a6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -7,7 +7,7 @@ import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.text.shared.SafeHtmlRenderer; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; public class LeaderboardConfigImagesBarCell extends ImagesBarCell { @@ -16,12 +16,12 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { SafeHtml cell(String name, SafeStyles styles, String title, SafeHtml value); } - public LeaderboardConfigImagesBarCell(StringConstants stringConstants) { + public LeaderboardConfigImagesBarCell(StringMessages stringConstants) { super(); this.stringConstants = stringConstants; } - public LeaderboardConfigImagesBarCell(SafeHtmlRenderer renderer, StringConstants stringConstants) { + public LeaderboardConfigImagesBarCell(SafeHtmlRenderer renderer, StringMessages stringConstants) { super(); this.stringConstants = stringConstants; } @@ -36,7 +36,7 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { private static final SafeHtml ICON_EDIT_SCORES = makeImage(resources.scoresIcon()); - private StringConstants stringConstants; + private StringMessages stringConstants; @Override protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index 9aff2d50850..8e4a13f7145 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -47,7 +47,7 @@ import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; import com.sap.sailing.gwt.ui.shared.RaceDAO; @@ -60,7 +60,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer, private final TrackedEventsComposite trackedEventsComposite; - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final SailingServiceAsync sailingService; @@ -111,7 +111,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer, private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); public LeaderboardConfigPanel(SailingServiceAsync sailingService, AdminConsole adminConsole, - final ErrorReporter errorReporter, StringConstants theStringConstants) { + final ErrorReporter errorReporter, StringMessages theStringConstants) { this.stringConstants = theStringConstants; this.sailingService = sailingService; leaderboardList = new ListDataProvider(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardCreateDialog.java index c7af9c65bcb..8bddad47834 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardCreateDialog.java @@ -5,12 +5,12 @@ import java.util.Collection; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.LongBox; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; public class LeaderboardCreateDialog extends LeaderboardDialog{ - public LeaderboardCreateDialog(Collection existingLeaderboards, StringConstants stringConstants, + public LeaderboardCreateDialog(Collection existingLeaderboards, StringMessages stringConstants, ErrorReporter errorReporter, AsyncCallback callback) { super(new LeaderboardDAO(), stringConstants, errorReporter, new LeaderboardDialog.LeaderboardParameterValidator(stringConstants, existingLeaderboards), callback); @@ -18,7 +18,7 @@ public class LeaderboardCreateDialog extends LeaderboardDialog{ discardThresholdBoxes = new LongBox[MAX_NUMBER_OF_DISCARDED_RESULTS]; for (int i = 0; i < discardThresholdBoxes.length; i++) { - discardThresholdBoxes[i] = createIntegerBoxWithOptionalValue(null, 2); + discardThresholdBoxes[i] = createLongBoxWithOptionalValue(null, 2); discardThresholdBoxes[i].setVisibleLength(2); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardDialog.java index b612c7b2e4d..b0617024c24 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardDialog.java @@ -13,12 +13,12 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; public abstract class LeaderboardDialog extends DataEntryDialog { - protected final StringConstants stringConstants; + protected final StringMessages stringConstants; protected TextBox entryField; protected LeaderboardDAO leaderboard; @@ -26,10 +26,10 @@ public abstract class LeaderboardDialog extends DataEntryDialog protected static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4; protected static class LeaderboardParameterValidator implements Validator { - protected final StringConstants stringConstants; + protected final StringMessages stringConstants; protected final Collection existingLeaderboards; - public LeaderboardParameterValidator(StringConstants stringConstants, Collection existingLeaderboards){ + public LeaderboardParameterValidator(StringMessages stringConstants, Collection existingLeaderboards){ super(); this.stringConstants = stringConstants; this.existingLeaderboards = existingLeaderboards; @@ -69,7 +69,7 @@ public abstract class LeaderboardDialog extends DataEntryDialog } } - public LeaderboardDialog(LeaderboardDAO leaderboardDAO, StringConstants stringConstants, + public LeaderboardDialog(LeaderboardDAO leaderboardDAO, StringMessages stringConstants, ErrorReporter errorReporter, LeaderboardParameterValidator validator, AsyncCallback callback) { super(stringConstants.leaderboardName(), stringConstants.leaderboardName(), stringConstants.ok(), stringConstants.cancel(), validator, callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardEditDialog.java index c6e9f8a5351..f443d302930 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardEditDialog.java @@ -5,12 +5,12 @@ import java.util.Collection; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.LongBox; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; public class LeaderboardEditDialog extends LeaderboardDialog { - public LeaderboardEditDialog(Collection otherExistingLeaderboards, LeaderboardDAO leaderboard, StringConstants stringConstants, + public LeaderboardEditDialog(Collection otherExistingLeaderboards, LeaderboardDAO leaderboard, StringMessages stringConstants, ErrorReporter errorReporter, AsyncCallback callback) { super(leaderboard, stringConstants, errorReporter, new LeaderboardDialog.LeaderboardParameterValidator( stringConstants, otherExistingLeaderboards), callback); @@ -22,7 +22,7 @@ public class LeaderboardEditDialog extends LeaderboardDialog { if (i < leaderboard.discardThresholds.length) { discardThresholdBoxes[i] = createLongBox(leaderboard.discardThresholds[i], 2); } else { - discardThresholdBoxes[i] = createIntegerBoxWithOptionalValue(null, 2); + discardThresholdBoxes[i] = createLongBoxWithOptionalValue(null, 2); } discardThresholdBoxes[i].setVisibleLength(2); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java index d2d50bd74f7..8ef7ed015a5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java @@ -7,23 +7,23 @@ import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.text.shared.SafeHtmlRenderer; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { - private StringConstants stringConstants; + private StringMessages stringConstants; interface ImagesBarTemplates extends SafeHtmlTemplates { @SafeHtmlTemplates.Template("

{3}
") SafeHtml cell(String name, SafeStyles styles, String title, SafeHtml value); } - public LeaderboardRaceConfigImagesBarCell(StringConstants stringConstants) { + public LeaderboardRaceConfigImagesBarCell(StringMessages stringConstants) { super(); this.stringConstants = stringConstants; } - public LeaderboardRaceConfigImagesBarCell(SafeHtmlRenderer renderer, StringConstants stringConstants) { + public LeaderboardRaceConfigImagesBarCell(SafeHtmlRenderer renderer, StringMessages stringConstants) { super(); this.stringConstants = stringConstants; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/QuickRanksListBoxComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/QuickRanksListBoxComposite.java new file mode 100644 index 00000000000..f24b63ef624 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/QuickRanksListBoxComposite.java @@ -0,0 +1,140 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; +import com.sap.sailing.gwt.ui.shared.QuickRankDAO; + +public class QuickRanksListBoxComposite extends Composite implements CompetitorSelectionProvider { + private final Set competitorSelectionChangeListeners; + private final List quickRankList; + private final List competitorList; + private final ListBox quickRankListBox; + + public QuickRanksListBoxComposite(boolean hasMultiSelection) { + this.competitorSelectionChangeListeners = new HashSet(); + quickRankListBox = new ListBox(hasMultiSelection); + quickRankListBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + fireCompetitorSelectionChanged(Collections.singletonList(getSelectedCompetitor())); + } + }); + quickRankListBox.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + CompetitorDAO selectedCompetitor = getSelectedCompetitor(); + List selectedCompetitorsCollection = Collections.emptyList(); + if (selectedCompetitor != null) { + Collections.singletonList(selectedCompetitor); + } + fireCompetitorSelectionChanged(selectedCompetitorsCollection); + } + }); + quickRankList = new ArrayList(); + competitorList = new ArrayList(); + VerticalPanel vp = new VerticalPanel(); + vp.add(quickRankListBox); + + // All composites must call initWidget() in their constructors. + initWidget(vp); + } + + public void fillQuickRanks(List quickRanks, boolean isSorted) { + List oldSelection = getSelectedCompetitors(); + + quickRankList.clear(); + competitorList.clear(); + quickRankListBox.clear(); + + for (QuickRankDAO quickRank: quickRanks) { + quickRankList.add(quickRank); + competitorList.add(quickRank.competitor); + } + if(!isSorted) { + Collections.sort(quickRankList, new Comparator() { + @Override + public int compare(QuickRankDAO o1, QuickRankDAO o2) { + Integer rank1 = o1.rank; + Integer rank2 = o2.rank; + return rank1.compareTo(rank2); + } + + }); + } + int index = 0; + for (QuickRankDAO quickRank : quickRankList) { + quickRankListBox.addItem(toString(quickRank)); + if(oldSelection.contains(quickRank.competitor)) + quickRankListBox.setItemSelected(index, true); + index++; + } + fireCompetitorSelectionChanged(getSelectedCompetitors()); + } + + public List getCompetitors() { + return competitorList; + } + + private CompetitorDAO getSelectedCompetitor() { + int i = quickRankListBox.getSelectedIndex(); + CompetitorDAO result = null; + if (i >= 0) { + result = quickRankList.get(i).competitor; + } + return result; + } + + @Override + public List getSelectedCompetitors() { + int i=0; + List result = new ArrayList(); + for (QuickRankDAO quickRank: quickRankList) { + if (quickRankListBox.isItemSelected(i)) { + result.add(quickRank.competitor); + } + i++; + } + return result; + } + + @Override + public void addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) { + competitorSelectionChangeListeners.add(listener); + } + + @Override + public void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) { + competitorSelectionChangeListeners.remove(listener); + } + + private void fireCompetitorSelectionChanged(List selectedCompetitors) { + for (CompetitorSelectionChangeListener listener : competitorSelectionChangeListeners) { + listener.onCompetitorSelectionChange(selectedCompetitors); + } + } + + protected String toString(QuickRankDAO quickRank) { + return quickRank.rank + ". " + quickRank.competitor.name + " (" + + quickRank.competitor.threeLetterIocCountryCode + ") in leg #" + (quickRank.legNumber + 1); + } + + public ListBox getListBox() { + return quickRankListBox; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceInLeaderboardDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceInLeaderboardDialog.java index d5b2862c75b..37f2beef9b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceInLeaderboardDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceInLeaderboardDialog.java @@ -8,7 +8,7 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.DataEntryDialog; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RaceInLeaderboardDAO; public class RaceInLeaderboardDialog extends DataEntryDialog { @@ -20,10 +20,10 @@ public class RaceInLeaderboardDialog extends DataEntryDialog { - private StringConstants stringConstants; + private StringMessages stringConstants; private Collection existingRaces; - public RaceDialogValidator(StringConstants stringConstants, Collection existingRaces) { + public RaceDialogValidator(StringMessages stringConstants, Collection existingRaces) { this.stringConstants = stringConstants; this.existingRaces = existingRaces; } @@ -58,7 +58,7 @@ public class RaceInLeaderboardDialog extends DataEntryDialog existingRaces, - RaceInLeaderboardDAO raceInLeaderboard, StringConstants stringConstants, + RaceInLeaderboardDAO raceInLeaderboard, StringMessages stringConstants, AsyncCallback callback) { super(stringConstants.name(), stringConstants.name(), stringConstants.ok(), stringConstants.cancel(), new RaceDialogValidator(stringConstants, existingRaces), callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java new file mode 100644 index 00000000000..ffde8824428 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java @@ -0,0 +1,1008 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.maps.client.InfoWindowContent; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.Maps; +import com.google.gwt.maps.client.control.LargeMapControl3D; +import com.google.gwt.maps.client.control.MenuMapTypeControl; +import com.google.gwt.maps.client.control.ScaleControl; +import com.google.gwt.maps.client.event.MapDragEndHandler; +import com.google.gwt.maps.client.event.MapMouseMoveHandler; +import com.google.gwt.maps.client.event.MapZoomEndHandler; +import com.google.gwt.maps.client.event.MarkerClickHandler; +import com.google.gwt.maps.client.event.MarkerMouseOutHandler; +import com.google.gwt.maps.client.event.MarkerMouseOverHandler; +import com.google.gwt.maps.client.event.PolylineClickHandler; +import com.google.gwt.maps.client.event.PolylineMouseOutHandler; +import com.google.gwt.maps.client.event.PolylineMouseOverHandler; +import com.google.gwt.maps.client.geom.LatLng; +import com.google.gwt.maps.client.geom.LatLngBounds; +import com.google.gwt.maps.client.geom.Point; +import com.google.gwt.maps.client.overlay.Icon; +import com.google.gwt.maps.client.overlay.Marker; +import com.google.gwt.maps.client.overlay.MarkerOptions; +import com.google.gwt.maps.client.overlay.Polyline; +import com.google.gwt.maps.client.overlay.PolylineOptions; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Util.Triple; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.TimeListener; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; +import com.sap.sailing.gwt.ui.shared.EventDAO; +import com.sap.sailing.gwt.ui.shared.GPSFixDAO; +import com.sap.sailing.gwt.ui.shared.ManeuverDAO; +import com.sap.sailing.gwt.ui.shared.MarkDAO; +import com.sap.sailing.gwt.ui.shared.RaceDAO; +import com.sap.sailing.gwt.ui.shared.RegattaDAO; +import com.sap.sailing.server.api.EventNameAndRaceName; + +public class RaceMap implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener, CompetitorsDisplayer { + protected MapWidget map; + + private final SailingServiceAsync sailingService; + private final ErrorReporter errorReporter; + + /** + * Tails of competitors currently displayed as overlays on the map. + */ + private final Map tails; + + /** + * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown + * in {@link #tails} is . + */ + private final Map firstShownFix; + + /** + * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in + * {@link #tails} is . + */ + private final Map lastShownFix; + + /** + * Fixes of each competitors tail. If a list is contained for a competitor, the list contains a timely "contiguous" + * list of fixes for the competitor. This means the server has no more data for the time interval covered, unless + * the last fix was {@link GPSFixDAO#extrapolated obtained by extrapolation}. + */ + private final Map> fixes; + + /** + * Markers used as boat display on the map + */ + protected final Map boatMarkers; + + private final Map buoyMarkers; + + /** + * markers displayed in response to + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} + */ + protected Set douglasMarkers; + + /** + * markers displayed in response to + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} + */ + protected Set maneuverMarkers; + + protected Map> lastManeuverResult; + + protected Map> lastDouglasPeuckerResult; + + private LatLng lastMousePosition; + + private List allCompetitors; + + private Set selectedMapCompetitors; + + private List> selectedEventAndRace; + + /** + * If the user explicitly zoomed or panned the map, don't adjust zoom/pan unless a new race is selected + */ + private boolean mapZoomedOrPannedSinceLastRaceSelectionChange = false; + + /** + * Used to check if the first initial zoom to the buoy markers was already done. + */ + private boolean mapFirstZoomDone = false; + + // key for domain web4sap.com + private final String mapsAPIKey = "ABQIAAAAmvjPh3ZpHbnwuX3a66lDqRRLCigyC_gRDASMpyomD2do5awpNhRCyD_q-27hwxKe_T6ivSZ_0NgbUg"; + + private final Timer timer; + + /** + * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time + * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes, + * responses for requests send later may return before the responses to all earlier requests have been received and + * processed. This counter is used to number the requests. When processing of a response for a later request has + * already begun, responses to earlier requests will be ignored. + */ + private int boatPositionRequestIDCounter; + + /** + * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID + * begins, this attribute is set to the ID. A response won't be processed if a later response is already being + * processed. + */ + private int startedProcessingRequestID; + + private RaceMapResources imageResources; + + private RaceMapSettings settings; + + public RaceMap(SailingServiceAsync sailingService, ErrorReporter errorReporter, Timer timer) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.timer = timer; + + tails = new HashMap(); + firstShownFix = new HashMap(); + lastShownFix = new HashMap(); + buoyMarkers = new HashMap(); + boatMarkers = new HashMap(); + fixes = new HashMap>(); + + selectedMapCompetitors = new HashSet(); + allCompetitors = new ArrayList(); + + settings = new RaceMapSettings(); + } + + public double calculateDistance(double lat1, double lng1, double lat2, double lng2) { + // the calculation is based on the Haversine formula + double earthRadius = 6371; // in km + double dLat = Math.toRadians(lat2-lat1); + double dLng = Math.toRadians(lng2-lng1); + double a = Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLng/2) * Math.sin(dLng/2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + double dist = earthRadius * c; + + return dist; + } + + public void loadMapsAPI(final Grid grid, final int gridRow, final int gridColumn) { + Maps.loadMapsApi(mapsAPIKey, "2", false, new Runnable() { + public void run() { + map = new MapWidget(); + map.addControl(new LargeMapControl3D()); + map.addControl(new MenuMapTypeControl()); + map.addControl(new ScaleControl()); + // Add the map to the HTML host page + map.setSize("100%", "100%"); + map.setScrollWheelZoomEnabled(true); + map.setContinuousZoom(true); + + grid.setWidget(gridRow, gridColumn, map); + + imageResources = new RaceMapResources(map); + + map.addMapZoomEndHandler(new MapZoomEndHandler() { + @Override + public void onZoomEnd(MapZoomEndEvent event) { + mapZoomedOrPannedSinceLastRaceSelectionChange = true; + Set competitorDAOsOfUnusedMarkers = new HashSet(boatMarkers.keySet()); + for (CompetitorDAO competitorDAO : getCompetitorsToShow()) { + boolean usedExistingMarker = updateMarkerForCompetitor(competitorDAO); + if (usedExistingMarker) { + competitorDAOsOfUnusedMarkers.remove(competitorDAO); + } + } + for (CompetitorDAO unusedMarkerCompetitorDAO : competitorDAOsOfUnusedMarkers) { + map.removeOverlay(boatMarkers.remove(unusedMarkerCompetitorDAO)); + } + } + }); + map.addMapDragEndHandler(new MapDragEndHandler() { + @Override + public void onDragEnd(MapDragEndEvent event) { + mapZoomedOrPannedSinceLastRaceSelectionChange = true; + } + }); + map.addMapMouseMoveHandler(new MapMouseMoveHandler() { + @Override + public void onMouseMove(MapMouseMoveEvent event) { + lastMousePosition = event.getLatLng(); + } + }); + } + }); + } + + public void redraw() { + timeChanged(timer.getTime()); + } + + @Override + public void onRaceSelectionChange(List> selectedRaces) { + mapFirstZoomDone = false; + mapZoomedOrPannedSinceLastRaceSelectionChange = false; + + this.selectedEventAndRace = selectedRaces; + } + + @Override + public void onCompetitorSelectionChange(List newSelectedCompetitors) { + for (CompetitorDAO competitorDAO : newSelectedCompetitors) { + + if (!selectedMapCompetitors.contains(competitorDAO)) { + // "lowlight" currently selected competitor + Marker highlightedMarker = boatMarkers.get(competitorDAO); + if (highlightedMarker != null) { + Marker lowlightedMarker = createBoatMarker(competitorDAO, false); + map.removeOverlay(highlightedMarker); + map.addOverlay(lowlightedMarker); + boatMarkers.put(competitorDAO, lowlightedMarker); + selectedMapCompetitors.remove(competitorDAO); + } + } else { + Marker lowlightedMarker = boatMarkers.get(competitorDAO); + if (lowlightedMarker != null) { + Marker highlightedMarker = createBoatMarker(competitorDAO, true); + map.removeOverlay(lowlightedMarker); + map.addOverlay(highlightedMarker); + boatMarkers.put(competitorDAO, highlightedMarker); + } + // add the competitor even if not currently contained in map + selectedMapCompetitors.add(competitorDAO); + } + } + } + + @Override + public void timeChanged(final Date date) { + if (date != null) { + if (selectedEventAndRace != null && !selectedEventAndRace.isEmpty()) { + EventDAO event = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getA(); + RaceDAO race = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getC(); + if (event != null && race != null) { + final Triple, Map, Map> fromAndToAndOverlap = + computeFromAndTo(date, getCompetitorsToShow()); + final int requestID = boatPositionRequestIDCounter++; + sailingService.getBoatPositions(new EventNameAndRaceName(event.name, race.name), + fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), true, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining boat positions: " + caught.getMessage()); + } + + @Override + public void onSuccess(Map> result) { + // process response only if not received out of order + if (startedProcessingRequestID < requestID) { + startedProcessingRequestID = requestID; + Date from = new Date(date.getTime() - settings.getTailLengthInMilliSeconds()); + updateFixes(result, fromAndToAndOverlap.getC()); + showBoatsOnMap(from, date, getCompetitorsToShow()); + if (douglasMarkers != null) { + removeAllMarkDouglasPeuckerpoints(); + } + if (maneuverMarkers != null) { + removeAllManeuverMarkers(); + } + } + } + }); + sailingService.getMarkPositions(new EventNameAndRaceName(event.name, race.name), date, + new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to obtain mark positions: " + + caught.getMessage()); + } + + @Override + public void onSuccess(List result) { + showMarksOnMap(result); + } + }); + } + } + } + } + + public void fillCompetitors(List competitors) { + allCompetitors.clear(); + allCompetitors.addAll(competitors); + } + + /** + * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the + * from/to times for which to request GPS raceMapData.fixes from the server. No update is performed here to {@link #fixes}. The + * result guarantees that, when used in + * {@link SailingServiceAsync#getBoatPositions(String, String, Map, Map, boolean, AsyncCallback)}, for each + * competitor from {@link #competitorsToShow} there are all raceMapData.fixes known by the server for that competitor starting + * at upTo-{@link #tailLengthInMilliSeconds} and ending at upTo (exclusive). + * + * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() + * second} component contains the "to" times for the competitors whose trails / positions to show; the + * {@link Triple#getC() third} component tells whether the existing raceMapData.fixes can remain and be augmented by + * those requested or need to be replaced + */ + protected Triple, Map, Map> computeFromAndTo( + Date upTo, Collection competitorsToShow) { + Date tailstart = new Date(upTo.getTime() - settings.getTailLengthInMilliSeconds()); + Map from = new HashMap(); + Map to = new HashMap(); + Map overlapWithKnownFixes = new HashMap(); + + for (CompetitorDAO competitor : competitorsToShow) { + List fixesForCompetitor = fixes.get(competitor); + Date fromDate; + Date toDate; + Date timepointOfLastKnownFix = fixesForCompetitor == null ? null : getTimepointOfLastNonExtrapolated(fixesForCompetitor); + Date timepointOfFirstKnownFix = fixesForCompetitor == null ? null : getTimepointOfFirstNonExtrapolated(fixesForCompetitor); + boolean overlap = false; + if (fixesForCompetitor != null && timepointOfFirstKnownFix != null + && !tailstart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null + && !tailstart.after(timepointOfLastKnownFix)) { + // the beginning of what we need is contained in the interval we already have; skip what we already have + fromDate = timepointOfLastKnownFix; + overlap = true; + } else { + fromDate = tailstart; + } + if (fixesForCompetitor != null && timepointOfFirstKnownFix != null + && !upTo.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null + && !upTo.after(timepointOfLastKnownFix)) { + // the end of what we need is contained in the interval we already have; skip what we already have + toDate = timepointOfFirstKnownFix; + overlap = true; + } else { + toDate = upTo; + } + // only request something for the competitor if we're missing information at all + if (fromDate.before(toDate) || fromDate.equals(toDate)) { + from.put(competitor, fromDate); + to.put(competitor, toDate); + overlapWithKnownFixes.put(competitor, overlap); + } + } + return new Triple, Map, Map>(from, to, + overlapWithKnownFixes); + } + + + /** + * Adds the raceMapData.fixes received in result to {@link #fixes} and ensures they are still contiguous for each + * competitor. If overlapsWithKnownraceMapData.fixes indicates that the raceMapData.fixes received in result + * overlap with those already known, the raceMapData.fixes are merged into the list of already known raceMapData.fixes for the competitor. + * Otherwise, the raceMapData.fixes received in result replace those known so far for the respective competitor. + */ + protected void updateFixes(Map> result, + Map overlapsWithKnownFixes) { + for (Map.Entry> e : result.entrySet()) { + if (e.getValue() != null && !e.getValue().isEmpty()) { + List fixesForCompetitor = fixes.get(e.getKey()); + if (fixesForCompetitor == null) { + fixesForCompetitor = new ArrayList(); + fixes.put(e.getKey(), fixesForCompetitor); + } + if (!overlapsWithKnownFixes.get(e.getKey())) { + fixesForCompetitor.clear(); + // to re-establish the invariants for raceMapData.tails, raceMapData.firstShownFix and raceMapData.lastShownFix, we now need to remove + // all + // points from the competitor's polyline and clear the entries in raceMapData.firstShownFix and raceMapData.lastShownFix + if (map != null && tails.containsKey(e.getKey())) { + map.removeOverlay(tails.remove(e.getKey())); + } + firstShownFix.remove(e.getKey()); + lastShownFix.remove(e.getKey()); + fixesForCompetitor.addAll(e.getValue()); + } else { + mergeFixes(e.getKey(), e.getValue()); + } + } + } + } + + protected void showMarksOnMap(List result) { + if (map != null) { + Set toRemove = new HashSet(buoyMarkers.keySet()); + for (MarkDAO markDAO : result) { + Marker buoyMarker = buoyMarkers.get(markDAO); + if (buoyMarker == null) { + buoyMarker = createBuoyMarker(markDAO); + buoyMarkers.put(markDAO, buoyMarker); + map.addOverlay(buoyMarker); + } else { + buoyMarker.setLatLng(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg)); + toRemove.remove(markDAO); + } + } + for (MarkDAO toRemoveMarkDAO : toRemove) { + Marker marker = buoyMarkers.remove(toRemoveMarkDAO); + map.removeOverlay(marker); + } + zoomMapFirstTimeToMarks(buoyMarkers.keySet()); + } + } + +/** + * Zooms the map to the given marks if the map was not zoomed yet. If the map zoom to the marks was successful one + * time, the initial zoom to these marks can not be done again except the race selection is changed via {@link RaceMapPanel#onRaceSelectionChange(List). + * + * @param marksToZoomAt + * the marks to zoom at + */ + protected void zoomMapFirstTimeToMarks(Set marksToZoomAt) { + if (!mapZoomedOrPannedSinceLastRaceSelectionChange && !mapFirstZoomDone) { + LatLngBounds newBounds = null; + if (marksToZoomAt != null && !marksToZoomAt.isEmpty()) { + for (MarkDAO markDAO : marksToZoomAt) { + LatLng latLngZoomFirstTime = LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg); + LatLngBounds bounds = LatLngBounds.newInstance(latLngZoomFirstTime, latLngZoomFirstTime); + if (newBounds == null) { + newBounds = bounds; + } else { + newBounds.extend(bounds.getNorthEast()); + newBounds.extend(bounds.getSouthWest()); + } + } + + } + if (newBounds != null) { + map.setZoomLevel(map.getBoundsZoomLevel(newBounds)); + map.setCenter(newBounds.getCenter()); + map.checkResizeAndCenter(); + mapFirstZoomDone = true; + /* + * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that the map was just zoomed + * to the bounds of the buoys, it was not a zoom or pan triggered by the user. As a consequence the + * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. + */ + mapZoomedOrPannedSinceLastRaceSelectionChange = false; + } + } + } + + /** + * @param from + * time point for first fix to show in tails + * @param to + * time point for last fix to show in tails + */ + protected void showBoatsOnMap(Date from, Date to, Collection competitorsToShow) { + if (map != null) { + LatLngBounds newMapBounds = null; + Set competitorDAOsOfUnusedTails = new HashSet(tails.keySet()); + Set competitorDAOsOfUnusedMarkers = new HashSet(boatMarkers.keySet()); + + for (CompetitorDAO competitorDAO : competitorsToShow) { + if (fixes.containsKey(competitorDAO)) { + Polyline tail = tails.get(competitorDAO); + if (tail == null) { + tail = createTailAndUpdateIndices(competitorDAO, from, to); + map.addOverlay(tail); + } else { + updateTail(tail, competitorDAO, from, to); + competitorDAOsOfUnusedTails.remove(competitorDAO); + } + LatLngBounds bounds = tail.getBounds(); + if (newMapBounds == null) { + newMapBounds = bounds; + } else { + newMapBounds.extend(bounds.getNorthEast()); + newMapBounds.extend(bounds.getSouthWest()); + } + boolean usedExistingMarker = updateMarkerForCompetitor(competitorDAO); + if (usedExistingMarker) { + competitorDAOsOfUnusedMarkers.remove(competitorDAO); + } + } + } + if (!mapZoomedOrPannedSinceLastRaceSelectionChange && newMapBounds != null) { + map.setZoomLevel(map.getBoundsZoomLevel(newMapBounds)); + map.setCenter(newMapBounds.getCenter()); + mapFirstZoomDone = true; + } + for (CompetitorDAO unusedMarkerCompetitorDAO : competitorDAOsOfUnusedMarkers) { + map.removeOverlay(boatMarkers.remove(unusedMarkerCompetitorDAO)); + } + for (CompetitorDAO unusedTailCompetitorDAO : competitorDAOsOfUnusedTails) { + map.removeOverlay(tails.remove(unusedTailCompetitorDAO)); + } + } + } + + private boolean updateMarkerForCompetitor(CompetitorDAO competitorDAO) { + boolean usedExistingMarker = false; + if (lastShownFix.containsKey(competitorDAO) && lastShownFix.get(competitorDAO) != -1) { + GPSFixDAO lastPos = getBoatFix(competitorDAO); + Marker boatMarker = boatMarkers.get(competitorDAO); + if (boatMarker == null) { + boatMarker = createBoatMarker(competitorDAO, false); + map.addOverlay(boatMarker); + boatMarkers.put(competitorDAO, boatMarker); + } else { + usedExistingMarker = true; + // check if anchors match; re-use marker with setImage only if anchors match + ImageTransformer transformer = imageResources.getBoatImageTransformer(lastPos, + selectedMapCompetitors.contains(competitorDAO)); + Point newAnchor = transformer.getAnchor(imageResources.getRealBoatSizeScaleFactor()); + Point oldAnchor = boatMarker.getIcon().getIconAnchor(); + if (oldAnchor.getX() == newAnchor.getX() && oldAnchor.getY() == newAnchor.getY()) { + boatMarker.setLatLng(LatLng.newInstance(lastPos.position.latDeg, + lastPos.position.lngDeg)); + boatMarker.setImage(imageResources.getBoatImageURL(lastPos, + selectedMapCompetitors.contains(competitorDAO))); + } else { + // anchors don't match; replace marker + map.removeOverlay(boatMarker); + boatMarker = createBoatMarker(competitorDAO, + selectedMapCompetitors.contains(competitorDAO)); + map.addOverlay(boatMarker); + boatMarkers.put(competitorDAO, boatMarker); + } + } + } + return usedExistingMarker; + } + + protected Marker createBuoyMarker(final MarkDAO markDAO) { + MarkerOptions options = MarkerOptions.newInstance(); + if (imageResources.buoyIcon != null) { + options.setIcon(imageResources.buoyIcon); + } + options.setTitle(markDAO.name); + final Marker buoyMarker = new Marker(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg), + options); + buoyMarker.addMarkerClickHandler(new MarkerClickHandler() { + @Override + public void onClick(MarkerClickEvent event) { + LatLng latlng = buoyMarker.getLatLng(); + showMarkInfoWindow(markDAO, latlng); + } + }); + return buoyMarker; + } + + protected Marker createBoatMarker(final CompetitorDAO competitorDAO, boolean highlighted) { + GPSFixDAO boatFix = getBoatFix(competitorDAO); + double latDeg = boatFix.position.latDeg; + double lngDeg = boatFix.position.lngDeg; + MarkerOptions options = MarkerOptions.newInstance(); + Icon icon = imageResources.getBoatImageIcon(boatFix, highlighted); + options.setIcon(icon); + options.setTitle(competitorDAO.name); + final Marker boatMarker = new Marker(LatLng.newInstance(latDeg, lngDeg), options); + boatMarker.addMarkerClickHandler(new MarkerClickHandler() { + @Override + public void onClick(MarkerClickEvent event) { + LatLng latlng = boatMarker.getLatLng(); + showCompetitorInfoWindow(competitorDAO, latlng); + } + }); + boatMarker.addMarkerMouseOverHandler(new MarkerMouseOverHandler() { + @Override + public void onMouseOver(MarkerMouseOverEvent event) { + map.setTitle(competitorDAO.name); + // setSelectedInMap(competitorDAO, true); + // quickRanksBox.setItemSelected(quickRanksList.indexOf(competitorDAO), true); + } + }); + boatMarker.addMarkerMouseOutHandler(new MarkerMouseOutHandler() { + @Override + public void onMouseOut(MarkerMouseOutEvent event) { + map.setTitle(""); + } + }); + return boatMarker; + } + + private void showMarkInfoWindow(MarkDAO markDAO, LatLng latlng) { + map.getInfoWindow().open(latlng, new InfoWindowContent(getInfoWindowContent(markDAO))); + } + + private void showCompetitorInfoWindow(final CompetitorDAO competitorDAO, LatLng where) { + GPSFixDAO latestFixForCompetitor = getBoatFix(competitorDAO); + map.getInfoWindow().open(where, + new InfoWindowContent(getInfoWindowContent(competitorDAO, latestFixForCompetitor))); + } + + private Widget getInfoWindowContent(MarkDAO markDAO) { + VerticalPanel result = new VerticalPanel(); + result.add(new Label("Mark " + markDAO.name)); + result.add(new Label("" + markDAO.position)); + return result; + } + + private Widget getInfoWindowContent(CompetitorDAO competitorDAO, GPSFixDAO lastFix) { + final VerticalPanel result = new VerticalPanel(); + result.add(new Label("Competitor " + competitorDAO.name)); + result.add(new Label("" + lastFix.position)); + result.add(new Label(lastFix.speedWithBearing.speedInKnots + "kts " + lastFix.speedWithBearing.bearingInDegrees + + "deg")); + result.add(new Label("Tack: " + lastFix.tack.name())); + if (!selectedEventAndRace.isEmpty()) { + EventDAO event = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getA(); + RaceDAO race = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getC(); + if (event != null && race != null) { + Map from = new HashMap(); + from.put(competitorDAO, fixes.get(competitorDAO).get(firstShownFix.get(competitorDAO)).timepoint); + Map to = new HashMap(); + to.put(competitorDAO, getBoatFix(competitorDAO).timepoint); + /* currently not showing Douglas-Peucker points; TODO use checkboxes to select what to show (Bug #6) */ + sailingService.getDouglasPoints(new EventNameAndRaceName(event.name, race.name), from, to, 3, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage()); + } + + @Override + public void onSuccess(Map> result) { + lastDouglasPeuckerResult = result; + if (douglasMarkers != null) { + removeAllMarkDouglasPeuckerpoints(); + } + if (!timer.isPlaying()) { + if (settings.isShowDouglasPeuckerPoints()) { + showMarkDouglasPeuckerPoints(result); + } + } + } + }); + sailingService.getManeuvers(new EventNameAndRaceName(event.name, race.name), from, to, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage()); + } + + @Override + public void onSuccess(Map> result) { + lastManeuverResult = result; + if (maneuverMarkers != null) { + removeAllManeuverMarkers(); + } + if (!timer.isPlaying()) { + showManeuvers(result); + } + } + }); + + } + } + return result; + } + + private Collection getCompetitorsToShow() { + if (settings.isShowOnlySelectedCompetitors()) { + return selectedMapCompetitors; + } else { + return allCompetitors; + } + } + + private String getColorString(CompetitorDAO competitorDAO) { + // TODO green no more than 70, red no less than 120 + return "#" + (Integer.toHexString(competitorDAO.hashCode()) + "000000").substring(0, 4).toUpperCase() + "00"; + } + + /** + * Creates a polyline for the competitor represented by competitorDAO, taking the fixes from + * {@link #fixes fixes.get(competitorDAO)} and using the fixes starting at time point from (inclusive) + * up to the last fix with time point before to. The polyline is returned. Updates are applied to + * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. + */ + protected Polyline createTailAndUpdateIndices(final CompetitorDAO competitorDAO, Date from, Date to) { + List points = new ArrayList(); + List fixesForCompetitor = fixes.get(competitorDAO); + int indexOfFirst = -1; + int indexOfLast = -1; + int i = 0; + for (Iterator fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) { + GPSFixDAO fix = fixIter.next(); + if (!fix.timepoint.before(to)) { + indexOfLast = i; + } else { + LatLng point = null; + if (indexOfFirst == -1) { + if (!fix.timepoint.before(from)) { + indexOfFirst = i; + point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + } + } else { + point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + } + if (point != null) { + points.add(point); + } + } + i++; + } + if (indexOfLast == -1) { + indexOfLast = i - 1; + } + if (indexOfFirst != -1 && indexOfLast != -1) { + firstShownFix.put(competitorDAO, indexOfFirst); + lastShownFix.put(competitorDAO, indexOfLast); + } + PolylineOptions options = PolylineOptions.newInstance( + /* clickable */true, /* geodesic */true); + Polyline result = new Polyline(points.toArray(new LatLng[0]), getColorString(competitorDAO), /* width */ 1, + /* opacity */0.5, options); + result.addPolylineClickHandler(new PolylineClickHandler() { + @Override + public void onClick(PolylineClickEvent event) { + showCompetitorInfoWindow(competitorDAO, lastMousePosition); + } + }); + result.addPolylineMouseOverHandler(new PolylineMouseOverHandler() { + @Override + public void onMouseOver(PolylineMouseOverEvent event) { + map.setTitle(competitorDAO.name); + } + }); + result.addPolylineMouseOutHandler(new PolylineMouseOutHandler() { + @Override + public void onMouseOut(PolylineMouseOutEvent event) { + map.setTitle(""); + } + }); + tails.put(competitorDAO, result); + return result; + } + + protected void removeAllMarkDouglasPeuckerpoints() { + if (douglasMarkers != null) { + for (Marker marker : douglasMarkers) { + map.removeOverlay(marker); + } + } + douglasMarkers = null; + } + + protected void removeAllManeuverMarkers() { + if (maneuverMarkers != null) { + for (Marker marker : maneuverMarkers) { + map.removeOverlay(marker); + } + maneuverMarkers = null; + } + } + + protected void showMarkDouglasPeuckerPoints(Map> gpsFixPointMapForCompetitors) { + douglasMarkers = new HashSet(); + if (map != null && gpsFixPointMapForCompetitors != null) { + Set keySet = gpsFixPointMapForCompetitors.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) { + CompetitorDAO competitorDAO = iter.next(); + List gpsFix = gpsFixPointMapForCompetitors.get(competitorDAO); + for (GPSFixDAO fix : gpsFix) { + LatLng latLng = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + MarkerOptions options = MarkerOptions.newInstance(); + options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString()); + Marker marker = new Marker(latLng, options); + douglasMarkers.add(marker); + map.addOverlay(marker); + } + } + } + } + + protected void showManeuvers(Map> maneuvers) { + maneuverMarkers = new HashSet(); + if (map != null && maneuvers != null) { + Set keySet = maneuvers.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) { + CompetitorDAO competitorDAO = iter.next(); + List maneuversForCompetitor = maneuvers.get(competitorDAO); + for (ManeuverDAO maneuver : maneuversForCompetitor) { + boolean showThisManeuver = true; + LatLng latLng = LatLng.newInstance(maneuver.position.latDeg, maneuver.position.lngDeg); + MarkerOptions options = MarkerOptions.newInstance(); + options.setTitle("" + maneuver.timepoint + ": " + maneuver.type + " " + + maneuver.directionChangeInDegrees + "deg from " + maneuver.speedWithBearingBefore + + " to " + maneuver.speedWithBearingAfter); + if (maneuver.type.equals("TACK") && settings.isShowManeuverTack()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.tackToPortIcon); + } else { + options.setIcon(imageResources.tackToStarboardIcon); + } + } else if (maneuver.type.equals("JIBE") && settings.isShowManeuverJibe()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.jibeToPortIcon); + } else { + options.setIcon(imageResources.jibeToStarboardIcon); + } + } else if (maneuver.type.equals("HEAD_UP") && settings.isShowManeuverHeadUp()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.headUpOnPortIcon); + } else { + options.setIcon(imageResources.headUpOnStarboardIcon); + } + } else if (maneuver.type.equals("BEAR_AWAY") && settings.isShowManeuverBearAway()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.bearAwayOnPortIcon); + } else { + options.setIcon(imageResources.bearAwayOnStarboardIcon); + } + } else if (maneuver.type.equals("PENALTY_CIRCLE") && settings.isShowManeuverPenaltyCircle()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.penaltyCircleToPortIcon); + } else { + options.setIcon(imageResources.penaltyCircleToStarboardIcon); + } + } else if (maneuver.type.equals("MARK_PASSING") && settings.isShowManeuverMarkPassing()) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(imageResources.markPassingToPortIcon); + } else { + options.setIcon(imageResources.markPassingToStarboardIcon); + } + } else { + if (maneuver.type.equals("UNKNOWN") && settings.isShowManeuverOther()) { + options.setIcon(imageResources.unknownManeuverIcon); + } else { + showThisManeuver = false; + } + } + if (showThisManeuver) { + Marker marker = new Marker(latLng, options); + maneuverMarkers.add(marker); + map.addOverlay(marker); + } + } + } + } + } + + protected Date getTimepointOfFirstNonExtrapolated(List fixesForCompetitor) { + for (GPSFixDAO fix : fixesForCompetitor) { + if (!fix.extrapolated) { + return fix.timepoint; + } + } + return null; + } + + protected Date getTimepointOfLastNonExtrapolated(List fixesForCompetitor) { + if (!fixesForCompetitor.isEmpty()) { + for (ListIterator fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter + .hasPrevious();) { + GPSFixDAO fix = fixIter.previous(); + if (!fix.extrapolated) { + return fix.timepoint; + } + } + } + return null; + } + + /** + * While updating the {@link #fixes} for competitorDAO, the invariants for {@link #tails} and + * {@link #firstShownFix} and {@link #lastShownFix} are maintained: each time a fix is inserted, the + * {@link #firstShownFix}/{@link #lastShownFix} records for competitorDAO are incremented if they are + * greater or equal to the insertion index and we have a tail in {@link #tails} for competitorDAO. + * Additionally, if the fix is in between the fixes shown in the competitor's tail, the tail is adjusted by + * inserting the corresponding fix. + */ + protected void mergeFixes(CompetitorDAO competitorDAO, List mergeThis) { + List intoThis = fixes.get(competitorDAO); + int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); + int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); + Polyline tail = tails.get(competitorDAO); + int intoThisIndex = 0; + for (GPSFixDAO mergeThisFix : mergeThis) { + while (intoThisIndex < intoThis.size() + && intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) { + intoThisIndex++; + } + if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) { + // exactly same time point; replace with fix from mergeThis + intoThis.set(intoThisIndex, mergeThisFix); + } else { + intoThis.add(intoThisIndex, mergeThisFix); + if (indexOfFirstShownFix >= intoThisIndex) { + indexOfFirstShownFix++; + } + if (indexOfLastShownFix >= intoThisIndex) { + indexOfLastShownFix++; + } + if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { + tail.insertVertex(intoThisIndex - indexOfFirstShownFix, + LatLng.newInstance(mergeThisFix.position.latDeg, mergeThisFix.position.lngDeg)); + } + } + intoThisIndex++; + } + } + + protected GPSFixDAO getBoatFix(CompetitorDAO competitorDAO) { + return fixes.get(competitorDAO).get(lastShownFix.get(competitorDAO)); + } + + /** + * If the tail starts before from, removes leading vertices from tail that are before + * from. This is determined by using the {@link #firstShownFix} index which tells us where in + * {@link #fixes} we find the sequence of fixes currently represented in the tail. + *

+ * + * If the tail starts after from, vertices for those {@link #fixes} for competitorDAO at + * or after time point from and before the time point of the first fix displayed so far in the tail and + * before to are prepended to the tail. + *

+ * + * Now to the end of the tail: if the existing tail's end exceeds to, the vertices in excess are + * removed (aided by {@link #lastShownFix}). Otherwise, for the competitor's fixes starting at the tail's end up to + * to are appended to the tail. + *

+ * + * When this method returns, {@link #firstShownFix} and {@link #lastShownFix} have been updated accordingly. + */ + protected void updateTail(Polyline tail, CompetitorDAO competitorDAO, Date from, Date to) { + List fixesForCompetitor = fixes.get(competitorDAO); + int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); + while (indexOfFirstShownFix != -1 && tail.getVertexCount() > 0 + && fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) { + tail.deleteVertex(0); + indexOfFirstShownFix++; + } + // now the polyline contains no more vertices representing fixes before "from"; + // go back in time starting at indexOfFirstShownFix while the fixes are still at or after "from" + // and insert corresponding vertices into the polyline + while (indexOfFirstShownFix > 0 && !fixesForCompetitor.get(indexOfFirstShownFix - 1).timepoint.before(from)) { + indexOfFirstShownFix--; + GPSFixDAO fix = fixesForCompetitor.get(indexOfFirstShownFix); + tail.insertVertex(0, LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); + } + // now adjust the polylines tail: remove excess vertices that are after "to" + int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); + while (indexOfLastShownFix != -1 && tail.getVertexCount() > 0 + && fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) { + tail.deleteVertex(tail.getVertexCount() - 1); + indexOfLastShownFix--; + } + // now the polyline contains no more vertices representing fixes after "to"; + // go forward in time starting at indexOfLastShownFix while the fixes are still at or before "to" + // and insert corresponding vertices into the polyline + while (indexOfLastShownFix < fixesForCompetitor.size() - 1 + && !fixesForCompetitor.get(indexOfLastShownFix + 1).timepoint.after(to)) { + indexOfLastShownFix++; + GPSFixDAO fix = fixesForCompetitor.get(indexOfLastShownFix); + tail.insertVertex(tail.getVertexCount(), LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); + } + firstShownFix.put(competitorDAO, indexOfFirstShownFix); + lastShownFix.put(competitorDAO, indexOfLastShownFix); + } + + public Set getSelectedMapCompetitors() { + return selectedMapCompetitors; + } + + public RaceMapSettings getSettings() { + return settings; + } + + public void setSettings(RaceMapSettings settings) { + this.settings = settings; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java index c2fe09a15b4..9bb360336eb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java @@ -1,46 +1,13 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; -import java.util.Set; -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.maps.client.InfoWindowContent; -import com.google.gwt.maps.client.MapWidget; -import com.google.gwt.maps.client.Maps; -import com.google.gwt.maps.client.control.LargeMapControl3D; -import com.google.gwt.maps.client.control.MenuMapTypeControl; -import com.google.gwt.maps.client.control.ScaleControl; -import com.google.gwt.maps.client.event.MapDragEndHandler; -import com.google.gwt.maps.client.event.MapMouseMoveHandler; -import com.google.gwt.maps.client.event.MapZoomEndHandler; -import com.google.gwt.maps.client.event.MarkerClickHandler; -import com.google.gwt.maps.client.event.MarkerMouseOutHandler; -import com.google.gwt.maps.client.event.MarkerMouseOverHandler; -import com.google.gwt.maps.client.event.PolylineClickHandler; -import com.google.gwt.maps.client.event.PolylineMouseOutHandler; -import com.google.gwt.maps.client.event.PolylineMouseOverHandler; import com.google.gwt.maps.client.geom.LatLng; -import com.google.gwt.maps.client.geom.LatLngBounds; -import com.google.gwt.maps.client.geom.Point; -import com.google.gwt.maps.client.overlay.Icon; -import com.google.gwt.maps.client.overlay.Marker; -import com.google.gwt.maps.client.overlay.MarkerOptions; -import com.google.gwt.maps.client.overlay.Polyline; -import com.google.gwt.maps.client.overlay.PolylineOptions; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FormPanel; @@ -48,13 +15,10 @@ import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.IntegerBox; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ProvidesResize; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -62,15 +26,13 @@ import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.TimeListener; import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.leaderboard.SmallWindHistoryPanel; import com.sap.sailing.gwt.ui.shared.CompetitorDAO; import com.sap.sailing.gwt.ui.shared.EventDAO; -import com.sap.sailing.gwt.ui.shared.GPSFixDAO; import com.sap.sailing.gwt.ui.shared.ManeuverDAO; -import com.sap.sailing.gwt.ui.shared.MarkDAO; import com.sap.sailing.gwt.ui.shared.PositionDAO; import com.sap.sailing.gwt.ui.shared.QuickRankDAO; import com.sap.sailing.gwt.ui.shared.RaceDAO; @@ -82,167 +44,23 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final Grid grid; - private MapWidget map; - private final RacesListBoxPanel newRaceListBox; - private final ListBox quickRanksBox; - private final List quickRanksList; + private final RacesListBoxPanel raceListBox; private final TimePanel timePanel; - /** - * Two sails on downwind leg, wind from port (sails on starboard); no highlighting - */ - private ImageRotator boatIconDownwindPortRotator; - - /** - * Two sails on downwind leg, wind from port (sails on starboard); with highlighting - */ - private ImageRotator boatIconHighlightedDownwindPortRotator; - - /** - * Two sails on downwind leg, wind from starboard (sails on port); no highlighting - */ - private ImageRotator boatIconDownwindStarboardRotator; - - /** - * Two sails on downwind leg, wind from starboard (sails on port); with highlighting - */ - private ImageRotator boatIconHighlightedDownwindStarboardRotator; - - /** - * One sail, wind from port (sails on starboard); no highlighting - */ - private ImageRotator boatIconPortRotator; - - /** - * One sail, wind from port (sails on starboard); with highlighting - */ - private ImageRotator boatIconHighlightedPortRotator; - - /** - * One sail, wind from starboard (sails on port); no highlighting - */ - private ImageRotator boatIconStarboardRotator; - - /** - * One sail, wind from starboard (sails on port); with highlighting - */ - private ImageRotator boatIconHighlightedStarboardRotator; - - private Icon buoyIcon; - private Icon tackToStarboardIcon; - private Icon tackToPortIcon; - private Icon jibeToStarboardIcon; - private Icon jibeToPortIcon; - private Icon markPassingToStarboardIcon; - private Icon markPassingToPortIcon; - private Icon headUpOnStarboardIcon; - private Icon headUpOnPortIcon; - private Icon bearAwayOnStarboardIcon; - private Icon bearAwayOnPortIcon; - private Icon unknownManeuverIcon; - private Icon penaltyCircleToStarboardIcon; - private Icon penaltyCircleToPortIcon; - private LatLng lastMousePosition; - private final Set competitorsSelectedInMap; private final Timer timer; private List> checkboxAndType; private CheckBox checkBoxDouglasPeuckerPoints; - - private long TAILLENGTHINMILLISECONDS = 30000l; - - /** - * If the user explicitly zoomed or panned the map, don't adjust zoom/pan unless a new race is selected - */ - private boolean mapZoomedOrPannedSinceLastRaceSelectionChange = false; - - /** - * Used to check if the first initial zoom to the buoy markers was already done. - */ - private boolean mapFirstZoomDone = false; - - /** - * Tails of competitors currently displayed as overlays on the map. - */ - private final Map tails; - - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown - * in {@link #tails} is . - */ - private final Map firstShownFix; - - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in - * {@link #tails} is . - */ - private final Map lastShownFix; - - /** - * Fixes of each competitors tail. If a list is contained for a competitor, the list contains a timely "contiguous" - * list of fixes for the competitor. This means the server has no more data for the time interval covered, unless - * the last fix was {@link GPSFixDAO#extrapolated obtained by extrapolation}. - */ - private final Map> fixes; - - /** - * Markers used as boat display on the map - */ - private final Map boatMarkers; - - private final Map buoyMarkers; - - /** - * markers displayed in response to - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} - */ - private Set douglasMarkers; - - /** - * markers displayed in response to - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} - */ - private Set maneuverMarkers; - - // key for domain web4sap.com - private final String mapsAPIKey = "ABQIAAAAmvjPh3ZpHbnwuX3a66lDqRRLCigyC_gRDASMpyomD2do5awpNhRCyD_q-27hwxKe_T6ivSZ_0NgbUg"; - - private final CheckBox showOnlySelected; - + private final CheckBox showOnlySelectedCompetitors; private final IntegerBox tailLengthBox; - protected Map> lastManeuverResult; - - protected Map> lastDouglasPeuckerResult; - - /** - * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time - * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes, - * responses for requests send later may return before the responses to all earlier requests have been received and - * processed. This counter is used to number the requests. When processing of a response for a later request has - * already begun, responses to earlier requests will be ignored. - */ - private int boatPositionRequestIDCounter; - - /** - * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID - * begins, this attribute is set to the ID. A response won't be processed if a later response is already being - * processed. - */ - private int startedProcessingRequestID; - - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - + private final RaceMap raceMap; + private final QuickRanksListBoxComposite quickRanksListBox; + public RaceMapPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, - final EventRefresher eventRefresher, StringConstants stringConstants) { + final EventRefresher eventRefresher, StringMessages stringConstants) { this.sailingService = sailingService; this.errorReporter = errorReporter; this.timer = new Timer(/* delayBetweenAutoAdvancesInMilliseconds */500); - competitorsSelectedInMap = new HashSet(); - tails = new HashMap(); - firstShownFix = new HashMap(); - lastShownFix = new HashMap(); - buoyMarkers = new HashMap(); - boatMarkers = new HashMap(); checkboxAndType = new ArrayList>(); final VerticalPanel verticalCheckBoxPanel = new VerticalPanel(); verticalCheckBoxPanel.add(new Label(stringConstants.maneuverTypes())); @@ -261,95 +79,95 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe checkBoxMarkPassing.setValue(true); checkboxAndType.add(new Pair(checkBoxMarkPassing, "MARK_PASSING")); checkboxAndType.add(new Pair(new CheckBox(stringConstants.otherManeuver()), "OTHER")); + for (Pair pair : checkboxAndType) { pair.getA().addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - if (!timer.isPlaying() && lastManeuverResult != null) { - removeAllManeuverMarkers(); - showManeuvers(lastManeuverResult); + if (!timer.isPlaying() && raceMap.lastManeuverResult != null) { + raceMap.removeAllManeuverMarkers(); + showManeuvers(raceMap.lastManeuverResult); } } }); verticalCheckBoxPanel.add(pair.getA()); } + checkBoxDouglasPeuckerPoints = new CheckBox(stringConstants.douglasPeuckerPoints()); checkBoxDouglasPeuckerPoints.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - if (!timer.isPlaying() && lastDouglasPeuckerResult != null && event.getValue()) { - removeAllMarkDouglasPeuckerpoints(); - showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult); + if (!timer.isPlaying() && raceMap.lastDouglasPeuckerResult != null && event.getValue()) { + raceMap.getSettings().setShowDouglasPeuckerPoints(true); + raceMap.removeAllMarkDouglasPeuckerpoints(); + raceMap.showMarkDouglasPeuckerPoints(raceMap.lastDouglasPeuckerResult); } else if (!event.getValue()) { - removeAllMarkDouglasPeuckerpoints(); + raceMap.getSettings().setShowDouglasPeuckerPoints(false); + raceMap.removeAllMarkDouglasPeuckerpoints(); } } }); verticalCheckBoxPanel.add(checkBoxDouglasPeuckerPoints); - fixes = new HashMap>(); this.grid = new Grid(3, 2); setWidget(grid); grid.setSize("100%", "100%"); grid.getColumnFormatter().setWidth(0, "20%"); grid.getColumnFormatter().setWidth(1, "80%"); grid.getCellFormatter().setHeight(2, 1, "100%"); - loadMapsAPI(); - newRaceListBox = new RacesListBoxPanel(eventRefresher, stringConstants); - newRaceListBox.addRaceSelectionChangeListener(this); - grid.setWidget(0, 0, newRaceListBox); + + raceMap = new RaceMap(sailingService, errorReporter, timer); + raceMap.loadMapsAPI(grid, 2, 1); + + setMapDisplayOptions(); + + raceListBox = new RacesListBoxPanel(eventRefresher, stringConstants); + raceListBox.addRaceSelectionChangeListener(this); + grid.setWidget(0, 0, raceListBox); PositionDAO pos = new PositionDAO(); - if (!boatMarkers.isEmpty()) { - LatLng latLng = boatMarkers.values().iterator().next().getLatLng(); + if (!raceMap.boatMarkers.isEmpty()) { + LatLng latLng = raceMap.boatMarkers.values().iterator().next().getLatLng(); pos.latDeg = latLng.getLatitude(); pos.lngDeg = latLng.getLongitude(); } SmallWindHistoryPanel windHistory = new SmallWindHistoryPanel(sailingService, pos, /* number of wind displays */5, /* time interval between displays in milliseconds */5000, stringConstants, errorReporter); - newRaceListBox.addRaceSelectionChangeListener(windHistory); + raceListBox.addRaceSelectionChangeListener(windHistory); grid.setWidget(1, 0, windHistory); HorizontalPanel horizontalRanksVerticalAndCheckboxesManeuversPanel = new HorizontalPanel(); horizontalRanksVerticalAndCheckboxesManeuversPanel.setSpacing(15); - VerticalPanel ranksAndCheckboxAndTailLength = new VerticalPanel(); - HorizontalPanel labelAndTailLengthBox = new HorizontalPanel(); - labelAndTailLengthBox.add(new Label(stringConstants.tailLength())); + VerticalPanel ranksAndCheckboxAndTailLengthPanel = new VerticalPanel(); + HorizontalPanel labelAndTailLengthBoxPanel = new HorizontalPanel(); + labelAndTailLengthBoxPanel.add(new Label(stringConstants.tailLength())); tailLengthBox = new IntegerBox(); - tailLengthBox.setValue((int) (TAILLENGTHINMILLISECONDS / 1000)); + tailLengthBox.setValue((int) (raceMap.getSettings().getTailLengthInMilliSeconds() / 1000)); tailLengthBox.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - TAILLENGTHINMILLISECONDS = 1000l * event.getValue(); - refreshMapContents(); + raceMap.getSettings().setTailLengthInMilliSeconds(1000l * event.getValue()); + raceMap.redraw(); } }); - labelAndTailLengthBox.add(tailLengthBox); - ranksAndCheckboxAndTailLength.add(labelAndTailLengthBox); - showOnlySelected = new CheckBox(stringConstants.showOnlySelected()); - showOnlySelected.addValueChangeHandler(new ValueChangeHandler() { + labelAndTailLengthBoxPanel.add(tailLengthBox); + ranksAndCheckboxAndTailLengthPanel.add(labelAndTailLengthBoxPanel); + showOnlySelectedCompetitors = new CheckBox(stringConstants.showOnlySelected()); + showOnlySelectedCompetitors.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - refreshMapContents(); + raceMap.getSettings().setShowOnlySelectedCompetitors(event.getValue()); + raceMap.redraw(); } }); - ranksAndCheckboxAndTailLength.add(showOnlySelected); - quickRanksList = new ArrayList(); - quickRanksBox = new ListBox(/* isMultipleSelect */true); - quickRanksBox.setVisibleItemCount(20); - quickRanksBox.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - updateBoatSelection(); - } - }); - quickRanksBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - updateBoatSelection(); - } - }); - ranksAndCheckboxAndTailLength.add(quickRanksBox); - horizontalRanksVerticalAndCheckboxesManeuversPanel.add(ranksAndCheckboxAndTailLength); + ranksAndCheckboxAndTailLengthPanel.add(showOnlySelectedCompetitors); + + quickRanksListBox = new QuickRanksListBoxComposite(true); + quickRanksListBox.addCompetitorSelectionChangeListener(raceMap); + quickRanksListBox.getListBox().setVisibleItemCount(20); + + ranksAndCheckboxAndTailLengthPanel.add(quickRanksListBox); + + horizontalRanksVerticalAndCheckboxesManeuversPanel.add(ranksAndCheckboxAndTailLengthPanel); VerticalPanel verticalPanelRadioAndCheckboxes = new VerticalPanel(); verticalPanelRadioAndCheckboxes.add(verticalCheckBoxPanel); @@ -357,6 +175,7 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe grid.setWidget(2, 0, horizontalRanksVerticalAndCheckboxesManeuversPanel); timePanel = new TimePanel(stringConstants, timer); + timer.addTimeListener(raceMap); timer.addTimeListener(this); timer.addTimeListener(windHistory); grid.setWidget(1, 1, timePanel); @@ -371,134 +190,14 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe return false; } - private void updateBoatSelection() { - for (int i = 0; i < quickRanksBox.getItemCount(); i++) { - setSelectedInMap(quickRanksList.get(i), quickRanksBox.isItemSelected(i)); - } - if (showOnlySelected.getValue()) { - refreshMapContents(); - } - } - - private void setSelectedInMap(CompetitorDAO competitorDAO, boolean itemSelected) { - if (!itemSelected && competitorsSelectedInMap.contains(competitorDAO)) { - // "lowlight" currently selected competitor - Marker highlightedMarker = boatMarkers.get(competitorDAO); - if (highlightedMarker != null) { - Marker lowlightedMarker = createBoatMarker(competitorDAO, false); - map.removeOverlay(highlightedMarker); - map.addOverlay(lowlightedMarker); - boatMarkers.put(competitorDAO, lowlightedMarker); - competitorsSelectedInMap.remove(competitorDAO); - } - } else if (itemSelected && !competitorsSelectedInMap.contains(competitorDAO)) { - Marker lowlightedMarker = boatMarkers.get(competitorDAO); - if (lowlightedMarker != null) { - Marker highlightedMarker = createBoatMarker(competitorDAO, true); - map.removeOverlay(lowlightedMarker); - map.addOverlay(highlightedMarker); - boatMarkers.put(competitorDAO, highlightedMarker); - int selectionIndex = quickRanksList.indexOf(competitorDAO); - quickRanksBox.setItemSelected(selectionIndex, true); - } - // add the competitor even if not currently contained in map - competitorsSelectedInMap.add(competitorDAO); - } - } - - private void loadMapsAPI() { - Maps.loadMapsApi(mapsAPIKey, "2", false, new Runnable() { - public void run() { - map = new MapWidget(); - map.addControl(new LargeMapControl3D()); - map.addControl(new MenuMapTypeControl()); - map.addControl(new ScaleControl()); - // Add the map to the HTML host page - grid.setWidget(2, 1, map); - map.setSize("100%", "100%"); - map.setScrollWheelZoomEnabled(true); - map.setContinuousZoom(true); - map.addMapZoomEndHandler(new MapZoomEndHandler() { - @Override - public void onZoomEnd(MapZoomEndEvent event) { - mapZoomedOrPannedSinceLastRaceSelectionChange = true; - } - }); - map.addMapDragEndHandler(new MapDragEndHandler() { - @Override - public void onDragEnd(MapDragEndEvent event) { - mapZoomedOrPannedSinceLastRaceSelectionChange = true; - } - }); - map.addMapMouseMoveHandler(new MapMouseMoveHandler() { - @Override - public void onMouseMove(MapMouseMoveEvent event) { - lastMousePosition = event.getLatLng(); - } - }); - boatIconDownwindPortRotator = new ImageRotator(resources.lowlightedBoatIconDW_Port()); - boatIconHighlightedDownwindPortRotator = new ImageRotator(resources.highlightedBoatIconDW_Port()); - boatIconDownwindStarboardRotator = new ImageRotator(resources.lowlightedBoatIconDW_Starboard()); - boatIconHighlightedDownwindStarboardRotator = new ImageRotator(resources - .highlightedBoatIconDW_Starboard()); - boatIconPortRotator = new ImageRotator(resources.lowlightedBoatIcon_Port()); - boatIconHighlightedPortRotator = new ImageRotator(resources.highlightedBoatIcon_Port()); - boatIconStarboardRotator = new ImageRotator(resources.lowlightedBoatIcon_Starboard()); - boatIconHighlightedStarboardRotator = new ImageRotator(resources.highlightedBoatIcon_Starboard()); - buoyIcon = Icon.newInstance(resources.buoyIcon().getSafeUri().asString()); - buoyIcon.setIconAnchor(Point.newInstance(4, 4)); - tackToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|00FF00|000000"); - tackToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - tackToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|FF0000|000000"); - tackToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - jibeToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|00FF00|000000"); - jibeToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - jibeToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|FF0000|000000"); - jibeToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - headUpOnStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|00FF00|000000"); - headUpOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - headUpOnPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|FF0000|000000"); - headUpOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); - bearAwayOnStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|00FF00|000000"); - bearAwayOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - bearAwayOnPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|FF0000|000000"); - bearAwayOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); - markPassingToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|00FF00|000000"); - markPassingToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - markPassingToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|FF0000|000000"); - markPassingToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - unknownManeuverIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000"); - unknownManeuverIcon.setIconAnchor(Point.newInstance(10, 33)); - penaltyCircleToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|00FF00|000000"); - penaltyCircleToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - penaltyCircleToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|FF0000|000000"); - penaltyCircleToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - } - }); - } - @Override public void fillEvents(List result) { - newRaceListBox.fillEvents(result); + raceListBox.fillEvents(result); } @Override public void onRaceSelectionChange(List> selectedRaces) { - mapFirstZoomDone = false; - mapZoomedOrPannedSinceLastRaceSelectionChange = false; + if (!selectedRaces.isEmpty() && selectedRaces.get(selectedRaces.size() - 1) != null) { RaceDAO raceDAO = selectedRaces.get(selectedRaces.size() - 1).getC(); if (raceDAO.startOfRace != null) { @@ -507,12 +206,8 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } updateSlider(raceDAO); } - // force display of currently selected race - refreshMapContents(); - } - - private void refreshMapContents() { - timeChanged(timer.getTime()); + + raceMap.onRaceSelectionChange(selectedRaces); } private void updateSlider(RaceDAO selectedRace) { @@ -527,51 +222,11 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe @Override public void timeChanged(final Date date) { if (date != null) { - List> selection = newRaceListBox.getSelectedEventAndRace(); + List> selection = raceListBox.getSelectedEventAndRace(); if (!selection.isEmpty()) { EventDAO event = selection.get(selection.size() - 1).getA(); RaceDAO race = selection.get(selection.size() - 1).getC(); if (event != null && race != null) { - final Triple, Map, Map> fromAndToAndOverlap = computeFromAndTo(date); - final int requestID = boatPositionRequestIDCounter++; - sailingService.getBoatPositions(new EventNameAndRaceName(event.name, race.name), - fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), true, - new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining boat positions: " + caught.getMessage()); - } - - @Override - public void onSuccess(Map> result) { - // process response only if not received out of order - if (startedProcessingRequestID < requestID) { - startedProcessingRequestID = requestID; - Date from = new Date(date.getTime() - TAILLENGTHINMILLISECONDS); - updateFixes(result, fromAndToAndOverlap.getC()); - showBoatsOnMap(from, date); - if (douglasMarkers != null) { - removeAllMarkDouglasPeuckerpoints(); - } - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - } - } - }); - sailingService.getMarkPositions(new EventNameAndRaceName(event.name, race.name), date, - new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to obtain mark positions: " - + caught.getMessage()); - } - - @Override - public void onSuccess(List result) { - showMarksOnMap(result); - } - }); sailingService.getQuickRanks(new EventNameAndRaceName(event.name, race.name), date, new AsyncCallback>() { @Override @@ -589,616 +244,9 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } } - /** - * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the - * from/to times for which to request GPS fixes from the server. No update is performed here to {@link #fixes}. The - * result guarantees that, when used in - * {@link SailingServiceAsync#getBoatPositions(String, String, Map, Map, boolean, AsyncCallback)}, for each - * competitor from {@link #competitorsToShow} there are all fixes known by the server for that competitor starting - * at upTo-{@link #TAILLENGTHINMILLISECONDS} and ending at upTo (exclusive). - * - * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() - * second} component contains the "to" times for the competitors whose trails / positions to show; the - * {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by - * those requested or need to be replaced - */ - private Triple, Map, Map> computeFromAndTo( - Date upTo) { - Date tailStart = new Date(upTo.getTime() - TAILLENGTHINMILLISECONDS); - Map from = new HashMap(); - Map to = new HashMap(); - Map overlapWithKnownFixes = new HashMap(); - for (CompetitorDAO competitor : getCompetitorsToShow()) { - List fixesForCompetitor = fixes.get(competitor); - Date fromDate; - Date toDate; - Date timepointOfLastKnownFix = fixesForCompetitor == null ? null - : getTimepointOfLastNonExtrapolated(fixesForCompetitor); - Date timepointOfFirstKnownFix = fixesForCompetitor == null ? null - : getTimepointOfFirstNonExtrapolated(fixesForCompetitor); - boolean overlap = false; - if (fixesForCompetitor != null && timepointOfFirstKnownFix != null - && !tailStart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null - && !tailStart.after(timepointOfLastKnownFix)) { - // the beginning of what we need is contained in the interval we already have; skip what we already have - fromDate = timepointOfLastKnownFix; - overlap = true; - } else { - fromDate = tailStart; - } - if (fixesForCompetitor != null && timepointOfFirstKnownFix != null - && !upTo.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null - && !upTo.after(timepointOfLastKnownFix)) { - // the end of what we need is contained in the interval we already have; skip what we already have - toDate = timepointOfFirstKnownFix; - overlap = true; - } else { - toDate = upTo; - } - // only request something for the competitor if we're missing information at all - if (fromDate.before(toDate) || fromDate.equals(toDate)) { - from.put(competitor, fromDate); - to.put(competitor, toDate); - overlapWithKnownFixes.put(competitor, overlap); - } - } - return new Triple, Map, Map>(from, to, - overlapWithKnownFixes); - } - - private Date getTimepointOfFirstNonExtrapolated(List fixesForCompetitor) { - for (GPSFixDAO fix : fixesForCompetitor) { - if (!fix.extrapolated) { - return fix.timepoint; - } - } - return null; - } - - private Date getTimepointOfLastNonExtrapolated(List fixesForCompetitor) { - if (!fixesForCompetitor.isEmpty()) { - for (ListIterator fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter - .hasPrevious();) { - GPSFixDAO fix = fixIter.previous(); - if (!fix.extrapolated) { - return fix.timepoint; - } - } - } - return null; - } - - /** - * Adds the fixes received in result to {@link #fixes} and ensures they are still contiguous for each - * competitor. If overlapsWithKnownFixes indicates that the fixes received in result - * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. - * Otherwise, the fixes received in result replace those known so far for the respective competitor. - */ - private void updateFixes(Map> result, - Map overlapsWithKnownFixes) { - for (Map.Entry> e : result.entrySet()) { - if (e.getValue() != null && !e.getValue().isEmpty()) { - List fixesForCompetitor = fixes.get(e.getKey()); - if (fixesForCompetitor == null) { - fixesForCompetitor = new ArrayList(); - fixes.put(e.getKey(), fixesForCompetitor); - } - if (!overlapsWithKnownFixes.get(e.getKey())) { - fixesForCompetitor.clear(); - // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove - // all - // points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - if (map != null && tails.containsKey(e.getKey())) { - map.removeOverlay(tails.remove(e.getKey())); - } - firstShownFix.remove(e.getKey()); - lastShownFix.remove(e.getKey()); - fixesForCompetitor.addAll(e.getValue()); - } else { - mergeFixes(e.getKey(), e.getValue()); - } - } - } - } - - /** - * While updating the {@link #fixes} for competitorDAO, the invariants for {@link #tails} and - * {@link #firstShownFix} and {@link #lastShownFix} are maintained: each time a fix is inserted, the - * {@link #firstShownFix}/{@link #lastShownFix} records for competitorDAO are incremented if they are - * greater or equal to the insertion index and we have a tail in {@link #tails} for competitorDAO. - * Additionally, if the fix is in between the fixes shown in the competitor's tail, the tail is adjusted by - * inserting the corresponding fix. - */ - private void mergeFixes(CompetitorDAO competitorDAO, List mergeThis) { - List intoThis = fixes.get(competitorDAO); - int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); - int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); - Polyline tail = tails.get(competitorDAO); - int intoThisIndex = 0; - for (GPSFixDAO mergeThisFix : mergeThis) { - while (intoThisIndex < intoThis.size() - && intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) { - intoThisIndex++; - } - if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) { - // exactly same time point; replace with fix from mergeThis - intoThis.set(intoThisIndex, mergeThisFix); - } else { - intoThis.add(intoThisIndex, mergeThisFix); - if (indexOfFirstShownFix >= intoThisIndex) { - indexOfFirstShownFix++; - } - if (indexOfLastShownFix >= intoThisIndex) { - indexOfLastShownFix++; - } - if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { - tail.insertVertex(intoThisIndex - indexOfFirstShownFix, - LatLng.newInstance(mergeThisFix.position.latDeg, mergeThisFix.position.lngDeg)); - } - } - intoThisIndex++; - } - } - private void showQuickRanks(List result) { - quickRanksBox.clear(); - quickRanksList.clear(); - int i = 0; - for (QuickRankDAO quickRank : result) { - quickRanksList.add(quickRank.competitor); - quickRanksBox.addItem("" + quickRank.rank + ". " + quickRank.competitor.name + " (" - + quickRank.competitor.threeLetterIocCountryCode + ") in leg #" + (quickRank.legNumber + 1)); - // maintain previous selection, based on competitorsSelectedInMap - if (competitorsSelectedInMap.contains(quickRank.competitor)) { - quickRanksBox.setItemSelected(i, /* selected */true); - } - i++; - } - } - - private void showMarksOnMap(List result) { - if (map != null) { - Set toRemove = new HashSet(buoyMarkers.keySet()); - for (MarkDAO markDAO : result) { - Marker buoyMarker = buoyMarkers.get(markDAO); - if (buoyMarker == null) { - buoyMarker = createBuoyMarker(markDAO); - buoyMarkers.put(markDAO, buoyMarker); - map.addOverlay(buoyMarker); - } else { - buoyMarker.setLatLng(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg)); - toRemove.remove(markDAO); - } - } - for (MarkDAO toRemoveMarkDAO : toRemove) { - Marker marker = buoyMarkers.remove(toRemoveMarkDAO); - map.removeOverlay(marker); - } - zoomMapFirstTimeToMarks(buoyMarkers.keySet()); - } - } - -/** - * Zooms the map to the given marks if the map was not zoomed yet. If the map zoom to the marks was successful one - * time, the initial zoom to these marks can not be done again except the race selection is changed via {@link RaceMapPanel#onRaceSelectionChange(List). - * - * @param marksToZoomAt - * the marks to zoom at - */ - private void zoomMapFirstTimeToMarks(Set marksToZoomAt) { - if (!mapZoomedOrPannedSinceLastRaceSelectionChange && !mapFirstZoomDone) { - LatLngBounds newBounds = null; - if (marksToZoomAt != null && !marksToZoomAt.isEmpty()) { - for (MarkDAO markDAO : marksToZoomAt) { - LatLng latLngZoomFirstTime = LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg); - LatLngBounds bounds = LatLngBounds.newInstance(latLngZoomFirstTime, latLngZoomFirstTime); - if (newBounds == null) { - newBounds = bounds; - } else { - newBounds.extend(bounds.getNorthEast()); - newBounds.extend(bounds.getSouthWest()); - } - } - - } - if (newBounds != null) { - map.setZoomLevel(map.getBoundsZoomLevel(newBounds)); - map.setCenter(newBounds.getCenter()); - mapFirstZoomDone = true; - /* - * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that the map was just zoomed - * to the bounds of the buoys, it was not a zoom or pan triggered by the user. As a consequence the - * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. - */ - mapZoomedOrPannedSinceLastRaceSelectionChange = false; - } - } - } - - /** - * @param from - * time point for first fix to show in tails - * @param to - * time point for last fix to show in tails - */ - private void showBoatsOnMap(Date from, Date to) { - if (map != null) { - LatLngBounds newMapBounds = null; - Set competitorDAOsOfUnusedTails = new HashSet(tails.keySet()); - Set competitorDAOsOfUnusedMarkers = new HashSet(boatMarkers.keySet()); - Collection competitorsToShow = getCompetitorsToShow(); - for (CompetitorDAO competitorDAO : competitorsToShow) { - if (fixes.containsKey(competitorDAO)) { - Polyline tail = tails.get(competitorDAO); - if (tail == null) { - tail = createTailAndUpdateIndices(competitorDAO, from, to); - map.addOverlay(tail); - } else { - updateTail(tail, competitorDAO, from, to); - competitorDAOsOfUnusedTails.remove(competitorDAO); - } - LatLngBounds bounds = tail.getBounds(); - if (newMapBounds == null) { - newMapBounds = bounds; - } else { - newMapBounds.extend(bounds.getNorthEast()); - newMapBounds.extend(bounds.getSouthWest()); - } - if (lastShownFix.containsKey(competitorDAO) && lastShownFix.get(competitorDAO) != -1) { - GPSFixDAO lastPos = getBoatFix(competitorDAO); - Marker boatMarker = boatMarkers.get(competitorDAO); - if (boatMarker == null) { - boatMarker = createBoatMarker(competitorDAO, false); - map.addOverlay(boatMarker); - boatMarkers.put(competitorDAO, boatMarker); - } else { - competitorDAOsOfUnusedMarkers.remove(competitorDAO); - // check if anchors match; re-use marker with setImage only if anchors match - Point newAnchor = getBoatImageRotator(lastPos, - competitorsSelectedInMap.contains(competitorDAO)).getAnchor(); - Point oldAnchor = boatMarker.getIcon().getIconAnchor(); - if (oldAnchor.getX() == newAnchor.getX() && oldAnchor.getY() == newAnchor.getY()) { - boatMarker.setLatLng(LatLng.newInstance(lastPos.position.latDeg, - lastPos.position.lngDeg)); - boatMarker.setImage(getBoatImageURL(lastPos, - competitorsSelectedInMap.contains(competitorDAO))); - } else { - // anchors don't match; replace marker - map.removeOverlay(boatMarker); - boatMarker = createBoatMarker(competitorDAO, - competitorsSelectedInMap.contains(competitorDAO)); - map.addOverlay(boatMarker); - boatMarkers.put(competitorDAO, boatMarker); - } - } - } - } - } - if (!mapZoomedOrPannedSinceLastRaceSelectionChange && newMapBounds != null) { - map.setZoomLevel(map.getBoundsZoomLevel(newMapBounds)); - map.setCenter(newMapBounds.getCenter()); - mapFirstZoomDone = true; - } - for (CompetitorDAO unusedMarkerCompetitorDAO : competitorDAOsOfUnusedMarkers) { - map.removeOverlay(boatMarkers.remove(unusedMarkerCompetitorDAO)); - } - for (CompetitorDAO unusedTailCompetitorDAO : competitorDAOsOfUnusedTails) { - map.removeOverlay(tails.remove(unusedTailCompetitorDAO)); - } - } - } - - private String getBoatImageURL(GPSFixDAO boatFix, boolean highlighted) { - return getBoatImageURL(getBoatImageRotator(boatFix, highlighted), boatFix); - } - - private String getBoatImageURL(ImageRotator boatImageRotator, GPSFixDAO boatFix) { - return boatImageRotator.getRotatedImageURL(boatFix.speedWithBearing.bearingInDegrees); - } - - private Icon getBoatImageIcon(GPSFixDAO boatFix, boolean highlighted) { - ImageRotator boatImageRotator = getBoatImageRotator(boatFix, highlighted); - Icon icon = Icon.newInstance(getBoatImageURL(boatImageRotator, boatFix)); - icon.setIconAnchor(boatImageRotator.getAnchor()); - return icon; - } - - private ImageRotator getBoatImageRotator(GPSFixDAO boatFix, boolean highlighted) { - if (boatFix.tack == Tack.PORT) { - if (LegType.DOWNWIND == boatFix.legType) { - if (highlighted) { - return boatIconHighlightedDownwindStarboardRotator; - } else { - return boatIconDownwindStarboardRotator; - } - } else { - if (highlighted) { - return boatIconHighlightedStarboardRotator; - } else { - return boatIconStarboardRotator; - } - } - } else { - if (LegType.DOWNWIND == boatFix.legType) { - if (highlighted) { - return boatIconHighlightedDownwindPortRotator; - } else { - return boatIconDownwindPortRotator; - } - } else { - if (highlighted) { - return boatIconHighlightedPortRotator; - } else { - return boatIconPortRotator; - } - } - } - } - - private GPSFixDAO getBoatFix(CompetitorDAO competitorDAO) { - return fixes.get(competitorDAO).get(lastShownFix.get(competitorDAO)); - } - - private Collection getCompetitorsToShow() { - if (showOnlySelected.getValue()) { - return competitorsSelectedInMap; - } else { - // here the quickrankslist is emtpy, because of that te map is not zoomed correctly - return quickRanksList; - } - } - - /** - * If the tail starts before from, removes leading vertices from tail that are before - * from. This is determined by using the {@link #firstShownFix} index which tells us where in - * {@link #fixes} we find the sequence of fixes currently represented in the tail. - *

- * - * If the tail starts after from, vertices for those {@link #fixes} for competitorDAO at - * or after time point from and before the time point of the first fix displayed so far in the tail and - * before to are prepended to the tail. - *

- * - * Now to the end of the tail: if the existing tail's end exceeds to, the vertices in excess are - * removed (aided by {@link #lastShownFix}). Otherwise, for the competitor's fixes starting at the tail's end up to - * to are appended to the tail. - *

- * - * When this method returns, {@link #firstShownFix} and {@link #lastShownFix} have been updated accordingly. - */ - private void updateTail(Polyline tail, CompetitorDAO competitorDAO, Date from, Date to) { - List fixesForCompetitor = fixes.get(competitorDAO); - int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); - while (indexOfFirstShownFix != -1 && tail.getVertexCount() > 0 - && fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) { - tail.deleteVertex(0); - indexOfFirstShownFix++; - } - // now the polyline contains no more vertices representing fixes before "from"; - // go back in time starting at indexOfFirstShownFix while the fixes are still at or after "from" - // and insert corresponding vertices into the polyline - while (indexOfFirstShownFix > 0 && !fixesForCompetitor.get(indexOfFirstShownFix - 1).timepoint.before(from)) { - indexOfFirstShownFix--; - GPSFixDAO fix = fixesForCompetitor.get(indexOfFirstShownFix); - tail.insertVertex(0, LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); - } - // now adjust the polylines tail: remove excess vertices that are after "to" - int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); - while (indexOfLastShownFix != -1 && tail.getVertexCount() > 0 - && fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) { - tail.deleteVertex(tail.getVertexCount() - 1); - indexOfLastShownFix--; - } - // now the polyline contains no more vertices representing fixes after "to"; - // go forward in time starting at indexOfLastShownFix while the fixes are still at or before "to" - // and insert corresponding vertices into the polyline - while (indexOfLastShownFix < fixesForCompetitor.size() - 1 - && !fixesForCompetitor.get(indexOfLastShownFix + 1).timepoint.after(to)) { - indexOfLastShownFix++; - GPSFixDAO fix = fixesForCompetitor.get(indexOfLastShownFix); - tail.insertVertex(tail.getVertexCount(), LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); - } - firstShownFix.put(competitorDAO, indexOfFirstShownFix); - lastShownFix.put(competitorDAO, indexOfLastShownFix); - } - - private Marker createBuoyMarker(final MarkDAO markDAO) { - MarkerOptions options = MarkerOptions.newInstance(); - if (buoyIcon != null) { - options.setIcon(buoyIcon); - } - options.setTitle(markDAO.name); - final Marker buoyMarker = new Marker(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg), - options); - buoyMarker.addMarkerClickHandler(new MarkerClickHandler() { - @Override - public void onClick(MarkerClickEvent event) { - LatLng latlng = buoyMarker.getLatLng(); - showMarkInfoWindow(markDAO, latlng); - } - }); - return buoyMarker; - } - - private Marker createBoatMarker(final CompetitorDAO competitorDAO, boolean highlighted) { - GPSFixDAO boatFix = getBoatFix(competitorDAO); - double latDeg = boatFix.position.latDeg; - double lngDeg = boatFix.position.lngDeg; - MarkerOptions options = MarkerOptions.newInstance(); - Icon icon = getBoatImageIcon(boatFix, highlighted); - options.setIcon(icon); - options.setTitle(competitorDAO.name); - final Marker boatMarker = new Marker(LatLng.newInstance(latDeg, lngDeg), options); - boatMarker.addMarkerClickHandler(new MarkerClickHandler() { - @Override - public void onClick(MarkerClickEvent event) { - LatLng latlng = boatMarker.getLatLng(); - showCompetitorInfoWindow(competitorDAO, latlng); - } - }); - boatMarker.addMarkerMouseOverHandler(new MarkerMouseOverHandler() { - @Override - public void onMouseOver(MarkerMouseOverEvent event) { - map.setTitle(competitorDAO.name); - // setSelectedInMap(competitorDAO, true); - // quickRanksBox.setItemSelected(quickRanksList.indexOf(competitorDAO), true); - } - }); - boatMarker.addMarkerMouseOutHandler(new MarkerMouseOutHandler() { - @Override - public void onMouseOut(MarkerMouseOutEvent event) { - map.setTitle(""); - } - }); - return boatMarker; - } - - private void showMarkInfoWindow(MarkDAO markDAO, LatLng latlng) { - map.getInfoWindow().open(latlng, new InfoWindowContent(getInfoWindowContent(markDAO))); - } - - private void showCompetitorInfoWindow(final CompetitorDAO competitorDAO, LatLng where) { - GPSFixDAO latestFixForCompetitor = getBoatFix(competitorDAO); - map.getInfoWindow().open(where, - new InfoWindowContent(getInfoWindowContent(competitorDAO, latestFixForCompetitor))); - } - - private Widget getInfoWindowContent(MarkDAO markDAO) { - VerticalPanel result = new VerticalPanel(); - result.add(new Label("Mark " + markDAO.name)); - result.add(new Label("" + markDAO.position)); - return result; - } - - private Widget getInfoWindowContent(CompetitorDAO competitorDAO, GPSFixDAO lastFix) { - final VerticalPanel result = new VerticalPanel(); - result.add(new Label("Competitor " + competitorDAO.name)); - result.add(new Label("" + lastFix.position)); - result.add(new Label(lastFix.speedWithBearing.speedInKnots + "kts " + lastFix.speedWithBearing.bearingInDegrees - + "deg")); - result.add(new Label("Tack: " + lastFix.tack.name())); - List> selection = newRaceListBox.getSelectedEventAndRace(); - if (!selection.isEmpty()) { - EventDAO event = selection.get(selection.size() - 1).getA(); - RaceDAO race = selection.get(selection.size() - 1).getC(); - if (event != null && race != null) { - Map from = new HashMap(); - from.put(competitorDAO, fixes.get(competitorDAO).get(firstShownFix.get(competitorDAO)).timepoint); - Map to = new HashMap(); - to.put(competitorDAO, getBoatFix(competitorDAO).timepoint); - /* currently not showing Douglas-Peucker points; TODO use checkboxes to select what to show (Bug #6) */ - sailingService.getDouglasPoints(new EventNameAndRaceName(event.name, race.name), from, to, 3, - new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage()); - } - - @Override - public void onSuccess(Map> result) { - RaceMapPanel.this.lastDouglasPeuckerResult = result; - if (douglasMarkers != null) { - removeAllMarkDouglasPeuckerpoints(); - } - if (!timer.isPlaying()) { - if (checkBoxDouglasPeuckerPoints.getValue()) { - showMarkDouglasPeuckerPoints(result); - } - } - } - }); - sailingService.getManeuvers(new EventNameAndRaceName(event.name, race.name), from, to, - new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage()); - } - - @Override - public void onSuccess(Map> result) { - RaceMapPanel.this.lastManeuverResult = result; - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - if (!timer.isPlaying()) { - showManeuvers(result); - } - } - }); - - } - } - return result; - } - - private String getColorString(CompetitorDAO competitorDAO) { - // TODO green no more than 70, red no less than 120 - return "#" + (Integer.toHexString(competitorDAO.hashCode()) + "000000").substring(0, 4).toUpperCase() + "00"; - } - - /** - * Creates a polyline for the competitor represented by competitorDAO, taking the fixes from - * {@link #fixes fixes.get(competitorDAO)} and using the fixes starting at time point from (inclusive) - * up to the last fix with time point before to. The polyline is returned. Updates are applied to - * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. - */ - private Polyline createTailAndUpdateIndices(final CompetitorDAO competitorDAO, Date from, Date to) { - List points = new ArrayList(); - List fixesForCompetitor = fixes.get(competitorDAO); - int indexOfFirst = -1; - int indexOfLast = -1; - int i = 0; - for (Iterator fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) { - GPSFixDAO fix = fixIter.next(); - if (!fix.timepoint.before(to)) { - indexOfLast = i; - } else { - LatLng point = null; - if (indexOfFirst == -1) { - if (!fix.timepoint.before(from)) { - indexOfFirst = i; - point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - } - } else { - point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - } - if (point != null) { - points.add(point); - } - } - i++; - } - if (indexOfLast == -1) { - indexOfLast = i - 1; - } - if (indexOfFirst != -1 && indexOfLast != -1) { - firstShownFix.put(competitorDAO, indexOfFirst); - lastShownFix.put(competitorDAO, indexOfLast); - } - PolylineOptions options = PolylineOptions.newInstance( - /* clickable */true, /* geodesic */true); - Polyline result = new Polyline(points.toArray(new LatLng[0]), getColorString(competitorDAO), /* width */3, - /* opacity */0.5, options); - result.addPolylineClickHandler(new PolylineClickHandler() { - @Override - public void onClick(PolylineClickEvent event) { - showCompetitorInfoWindow(competitorDAO, lastMousePosition); - } - }); - result.addPolylineMouseOverHandler(new PolylineMouseOverHandler() { - @Override - public void onMouseOver(PolylineMouseOverEvent event) { - map.setTitle(competitorDAO.name); - } - }); - result.addPolylineMouseOutHandler(new PolylineMouseOutHandler() { - @Override - public void onMouseOut(PolylineMouseOutEvent event) { - map.setTitle(""); - } - }); - tails.put(competitorDAO, result); - return result; + quickRanksListBox.fillQuickRanks(result, true); + raceMap.fillCompetitors(quickRanksListBox.getCompetitors()); } /* @@ -1214,114 +262,27 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe ((RequiresResize) child).onResize(); } // and ensure the map (indirect child) is also informed about resize - if (this.map != null) { - this.map.onResize(); + if (raceMap.map != null) { + raceMap.map.onResize(); } } - private void removeAllMarkDouglasPeuckerpoints() { - if (douglasMarkers != null) { - for (Marker marker : douglasMarkers) { - map.removeOverlay(marker); - } - } - douglasMarkers = null; + private void setMapDisplayOptions() + { + RaceMapSettings settings = raceMap.getSettings(); + settings.setShowManeuverTack(getCheckboxValueManeuver("TACK")); + settings.setShowManeuverJibe(getCheckboxValueManeuver("JIBE")); + settings.setShowManeuverHeadUp(getCheckboxValueManeuver("HEAD_UP")); + settings.setShowManeuverBearAway(getCheckboxValueManeuver("BEAR_AWAY")); + settings.setShowManeuverPenaltyCircle(getCheckboxValueManeuver("PENALTY_CIRCLE")); + settings.setShowManeuverMarkPassing(getCheckboxValueManeuver("MARK_PASSING")); + settings.setShowManeuverOther(getCheckboxValueManeuver("OTHER")); + settings.setShowDouglasPeuckerPoints(checkBoxDouglasPeuckerPoints.getValue()); } - - private void removeAllManeuverMarkers() { - if (maneuverMarkers != null) { - for (Marker marker : maneuverMarkers) { - map.removeOverlay(marker); - } - maneuverMarkers = null; - } - } - - private void showMarkDouglasPeuckerPoints(Map> gpsFixPointMapForCompetitors) { - douglasMarkers = new HashSet(); - if (map != null && gpsFixPointMapForCompetitors != null) { - Set keySet = gpsFixPointMapForCompetitors.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) { - CompetitorDAO competitorDAO = iter.next(); - List gpsFix = gpsFixPointMapForCompetitors.get(competitorDAO); - for (GPSFixDAO fix : gpsFix) { - LatLng latLng = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - MarkerOptions options = MarkerOptions.newInstance(); - options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString()); - Marker marker = new Marker(latLng, options); - douglasMarkers.add(marker); - map.addOverlay(marker); - } - } - } - } - + private void showManeuvers(Map> maneuvers) { - maneuverMarkers = new HashSet(); - if (map != null && maneuvers != null) { - Set keySet = maneuvers.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) { - CompetitorDAO competitorDAO = iter.next(); - List maneuversForCompetitor = maneuvers.get(competitorDAO); - for (ManeuverDAO maneuver : maneuversForCompetitor) { - boolean showThisManeuver = true; - LatLng latLng = LatLng.newInstance(maneuver.position.latDeg, maneuver.position.lngDeg); - MarkerOptions options = MarkerOptions.newInstance(); - options.setTitle("" + maneuver.timepoint + ": " + maneuver.type + " " - + maneuver.directionChangeInDegrees + "deg from " + maneuver.speedWithBearingBefore - + " to " + maneuver.speedWithBearingAfter); - if (maneuver.type.equals("TACK") && getCheckboxValueManeuver("TACK")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(tackToPortIcon); - } else { - options.setIcon(tackToStarboardIcon); - } - } else if (maneuver.type.equals("JIBE") && getCheckboxValueManeuver("JIBE")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(jibeToPortIcon); - } else { - options.setIcon(jibeToStarboardIcon); - } - } else if (maneuver.type.equals("HEAD_UP") && getCheckboxValueManeuver("HEAD_UP")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(headUpOnPortIcon); - } else { - options.setIcon(headUpOnStarboardIcon); - } - } else if (maneuver.type.equals("BEAR_AWAY") && getCheckboxValueManeuver("BEAR_AWAY")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(bearAwayOnPortIcon); - } else { - options.setIcon(bearAwayOnStarboardIcon); - } - } else if (maneuver.type.equals("PENALTY_CIRCLE") && getCheckboxValueManeuver("PENALTY_CIRCLE")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(penaltyCircleToPortIcon); - } else { - options.setIcon(penaltyCircleToStarboardIcon); - } - } else if (maneuver.type.equals("MARK_PASSING") && getCheckboxValueManeuver("MARK_PASSING")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(markPassingToPortIcon); - } else { - options.setIcon(markPassingToStarboardIcon); - } - } else { - if (maneuver.type.equals("UNKNOWN") && getCheckboxValueManeuver("OTHER")) { - options.setIcon(unknownManeuverIcon); - } else { - showThisManeuver = false; - } - } - if (showThisManeuver) { - Marker marker = new Marker(latLng, options); - maneuverMarkers.add(marker); - map.addOverlay(marker); - } - } - } - } + setMapDisplayOptions(); + + raceMap.showManeuvers(maneuvers); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapResources.java new file mode 100644 index 00000000000..e24f3b2a67a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapResources.java @@ -0,0 +1,200 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.geom.LatLng; +import com.google.gwt.maps.client.geom.LatLngBounds; +import com.google.gwt.maps.client.geom.Point; +import com.google.gwt.maps.client.overlay.Icon; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.gwt.ui.shared.GPSFixDAO; + +public class RaceMapResources { + + private final MapWidget map; + + /** + * Two sails on downwind leg, wind from port (sails on starboard); no highlighting + */ + protected ImageTransformer boatIconDownwindPortRotator; + + /** + * Two sails on downwind leg, wind from port (sails on starboard); with highlighting + */ + protected ImageTransformer boatIconHighlightedDownwindPortRotator; + + /** + * Two sails on downwind leg, wind from starboard (sails on port); no highlighting + */ + protected ImageTransformer boatIconDownwindStarboardRotator; + + /** + * Two sails on downwind leg, wind from starboard (sails on port); with highlighting + */ + protected ImageTransformer boatIconHighlightedDownwindStarboardRotator; + + /** + * One sail, wind from port (sails on starboard); no highlighting + */ + protected ImageTransformer boatIconPortRotator; + + /** + * One sail, wind from port (sails on starboard); with highlighting + */ + protected ImageTransformer boatIconHighlightedPortRotator; + + /** + * One sail, wind from starboard (sails on port); no highlighting + */ + protected ImageTransformer boatIconStarboardRotator; + + /** + * One sail, wind from starboard (sails on port); with highlighting + */ + protected ImageTransformer boatIconHighlightedStarboardRotator; + + protected Icon buoyIcon; + protected Icon tackToStarboardIcon; + protected Icon tackToPortIcon; + protected Icon jibeToStarboardIcon; + protected Icon jibeToPortIcon; + protected Icon markPassingToStarboardIcon; + protected Icon markPassingToPortIcon; + protected Icon headUpOnStarboardIcon; + protected Icon headUpOnPortIcon; + protected Icon bearAwayOnStarboardIcon; + protected Icon bearAwayOnPortIcon; + protected Icon unknownManeuverIcon; + protected Icon penaltyCircleToStarboardIcon; + protected Icon penaltyCircleToPortIcon; + + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + public RaceMapResources(final MapWidget map) + { + this.map = map; + + boatIconDownwindPortRotator = new ImageTransformer(resources.lowlightedBoatIconDW_Port()); + boatIconHighlightedDownwindPortRotator = new ImageTransformer(resources.highlightedBoatIconDW_Port()); + boatIconDownwindStarboardRotator = new ImageTransformer(resources.lowlightedBoatIconDW_Starboard()); + boatIconHighlightedDownwindStarboardRotator = new ImageTransformer(resources + .highlightedBoatIconDW_Starboard()); + boatIconPortRotator = new ImageTransformer(resources.lowlightedBoatIcon_Port()); + boatIconHighlightedPortRotator = new ImageTransformer(resources.highlightedBoatIcon_Port()); + boatIconStarboardRotator = new ImageTransformer(resources.lowlightedBoatIcon_Starboard()); + boatIconHighlightedStarboardRotator = new ImageTransformer(resources.highlightedBoatIcon_Starboard()); + buoyIcon = Icon.newInstance(resources.buoyIcon().getSafeUri().asString()); + buoyIcon.setIconAnchor(Point.newInstance(4, 4)); + tackToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|00FF00|000000"); + tackToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + tackToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|FF0000|000000"); + tackToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + jibeToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|00FF00|000000"); + jibeToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + jibeToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|FF0000|000000"); + jibeToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + headUpOnStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|00FF00|000000"); + headUpOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + headUpOnPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|FF0000|000000"); + headUpOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); + bearAwayOnStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|00FF00|000000"); + bearAwayOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + bearAwayOnPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|FF0000|000000"); + bearAwayOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); + markPassingToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|00FF00|000000"); + markPassingToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + markPassingToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|FF0000|000000"); + markPassingToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + unknownManeuverIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000"); + unknownManeuverIcon.setIconAnchor(Point.newInstance(10, 33)); + penaltyCircleToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|00FF00|000000"); + penaltyCircleToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + penaltyCircleToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|FF0000|000000"); + penaltyCircleToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + } + + public ImageTransformer getBoatImageTransformer(GPSFixDAO boatFix, boolean highlighted) { + if (boatFix.tack == Tack.PORT) { + if (LegType.DOWNWIND == boatFix.legType) { + if (highlighted) { + return boatIconHighlightedDownwindStarboardRotator; + } else { + return boatIconDownwindStarboardRotator; + } + } else { + if (highlighted) { + return boatIconHighlightedStarboardRotator; + } else { + return boatIconStarboardRotator; + } + } + } else { + if (LegType.DOWNWIND == boatFix.legType) { + if (highlighted) { + return boatIconHighlightedDownwindPortRotator; + } else { + return boatIconDownwindPortRotator; + } + } else { + if (highlighted) { + return boatIconHighlightedPortRotator; + } else { + return boatIconPortRotator; + } + } + } + } + + public String getBoatImageURL(GPSFixDAO boatFix, boolean highlighted) { + return getBoatImageURL(getBoatImageTransformer(boatFix, highlighted), boatFix); + } + + public String getBoatImageURL(ImageTransformer boatImageTransformer, GPSFixDAO boatFix) { + double realBoatSizeScaleFactor = getRealBoatSizeScaleFactor(); + return boatImageTransformer.getTransformedImageURL(boatFix.speedWithBearing.bearingInDegrees, realBoatSizeScaleFactor); + } + + public double getRealBoatSizeScaleFactor() { + // the possible zoom level range is 0 to 21 (zoom level 0 would show the whole world) + int zoomLevel = map.getZoomLevel(); + double minScaleFactor = 0.33; + double realBoatSizeScaleFactor = minScaleFactor; + double boatLengthInMeter = 5.0; + if(zoomLevel > 5) { + int boatSizeXInPixel = 41; + LatLngBounds bounds = map.getBounds(); + LatLng upperRight = bounds.getNorthEast(); + LatLng bottomLeft = bounds.getSouthWest(); + LatLng upperLeft = LatLng.newInstance(upperRight.getLatitude(), bottomLeft.getLongitude()); + double distXInMeters = upperLeft.distanceFrom(upperRight); + int widthInPixel = map.getSize().getWidth(); + double realBoatSizeInPixel = (widthInPixel * boatLengthInMeter) / distXInMeters; + realBoatSizeScaleFactor = realBoatSizeInPixel / (double) boatSizeXInPixel; + if(realBoatSizeScaleFactor < minScaleFactor) { + realBoatSizeScaleFactor = minScaleFactor; + } + } + return realBoatSizeScaleFactor; + } + + public Icon getBoatImageIcon(GPSFixDAO boatFix, boolean highlighted) { + ImageTransformer boatImageTransformer = getBoatImageTransformer(boatFix, highlighted); + Icon icon = Icon.newInstance(getBoatImageURL(boatImageTransformer, boatFix)); + icon.setIconAnchor(boatImageTransformer.getAnchor(getRealBoatSizeScaleFactor())); + return icon; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapSettings.java new file mode 100644 index 00000000000..a3c55fa1c32 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapSettings.java @@ -0,0 +1,105 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +public class RaceMapSettings { + + private boolean showDouglasPeuckerPoints = false; + + private boolean showManeuverHeadUp = false; + + private boolean showManeuverBearAway = false; + + private boolean showManeuverTack = false; + + private boolean showManeuverJibe = false; + + private boolean showManeuverPenaltyCircle = false; + + private boolean showManeuverMarkPassing = false; + + private boolean showManeuverOther = false; + + private boolean showOnlySelectedCompetitors = false; + + private long tailLengthInMilliSeconds = 30000l; + + public long getTailLengthInMilliSeconds() { + return tailLengthInMilliSeconds; + } + + public void setTailLengthInMilliSeconds(long tailLengthInMilliSeconds) { + this.tailLengthInMilliSeconds = tailLengthInMilliSeconds; + } + + public boolean isShowDouglasPeuckerPoints() { + return showDouglasPeuckerPoints; + } + + public void setShowDouglasPeuckerPoints(boolean showDouglasPeuckerPoints) { + this.showDouglasPeuckerPoints = showDouglasPeuckerPoints; + } + + public boolean isShowManeuverHeadUp() { + return showManeuverHeadUp; + } + + public void setShowManeuverHeadUp(boolean showManeuverHeadUp) { + this.showManeuverHeadUp = showManeuverHeadUp; + } + + public boolean isShowManeuverBearAway() { + return showManeuverBearAway; + } + + public void setShowManeuverBearAway(boolean showManeuverBearAway) { + this.showManeuverBearAway = showManeuverBearAway; + } + + public boolean isShowManeuverTack() { + return showManeuverTack; + } + + public void setShowManeuverTack(boolean showManeuverTack) { + this.showManeuverTack = showManeuverTack; + } + + public boolean isShowManeuverJibe() { + return showManeuverJibe; + } + + public void setShowManeuverJibe(boolean showManeuverJibe) { + this.showManeuverJibe = showManeuverJibe; + } + + public boolean isShowManeuverPenaltyCircle() { + return showManeuverPenaltyCircle; + } + + public void setShowManeuverPenaltyCircle(boolean showManeuverPenaltyCircle) { + this.showManeuverPenaltyCircle = showManeuverPenaltyCircle; + } + + public boolean isShowManeuverMarkPassing() { + return showManeuverMarkPassing; + } + + public void setShowManeuverMarkPassing(boolean showManeuverMarkPassing) { + this.showManeuverMarkPassing = showManeuverMarkPassing; + } + + public boolean isShowManeuverOther() { + return showManeuverOther; + } + + public void setShowManeuverOther(boolean showManeuverOther) { + this.showManeuverOther = showManeuverOther; + } + + public boolean isShowOnlySelectedCompetitors() { + return showOnlySelectedCompetitors; + } + + public void setShowOnlySelectedCompetitors(boolean showOnlySelectedCompetitors) { + this.showOnlySelectedCompetitors = showOnlySelectedCompetitors; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java index 73579aa8f45..11a2764ac67 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java @@ -21,7 +21,7 @@ import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.RaceDAO; import com.sap.sailing.gwt.ui.shared.RegattaDAO; @@ -31,9 +31,9 @@ public class RacesListBoxPanel extends FormPanel implements RaceSelectionProvide private final List> raceList; private final ListBox raceListBox; private final EventRefresher eventRefresher; - private final StringConstants stringConstants; + private final StringMessages stringConstants; - public RacesListBoxPanel(EventRefresher eventRefresher, StringConstants stringConstants) { + public RacesListBoxPanel(EventRefresher eventRefresher, StringMessages stringConstants) { this.eventRefresher = eventRefresher; this.stringConstants = stringConstants; this.raceSelectionChangeListeners = new HashSet(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 8fa0c874d6f..812dd1fa9c8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -37,7 +37,7 @@ import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDAO; import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDAO; @@ -67,7 +67,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane private final List availableSwissTimingRaces = new ArrayList(); public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, - EventRefresher eventRefresher, StringConstants stringConstants) { + EventRefresher eventRefresher, StringMessages stringConstants) { super(sailingService, eventRefresher, errorReporter, stringConstants); this.errorReporter = errorReporter; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimePanel.java index 26b15f81def..f8043bf2caf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimePanel.java @@ -15,7 +15,7 @@ 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.gwt.ui.client.PlayStateListener; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.TimeListener; import com.sap.sailing.gwt.ui.client.Timer; import com.sap.ui.commons.client.SliderWidget; @@ -31,7 +31,7 @@ public class TimePanel extends FormPanel implements TimeListener, PlayStateListe private boolean delayBoxHasFocus; private final Label timeLabel; - public TimePanel(StringConstants stringConstants, Timer timer) { + public TimePanel(StringMessages stringConstants, Timer timer) { this.timer = timer; timer.addTimeListener(this); timer.addPlayStateListener(this); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 5b8685e21a5..dd96ee0c9a6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -38,7 +38,7 @@ import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDAO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDAO; @@ -74,7 +74,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { private final List availableTracTracRaces; public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, - EventRefresher eventRefresher, StringConstants stringConstants) { + EventRefresher eventRefresher, StringMessages stringConstants) { super(sailingService, eventRefresher, errorReporter, stringConstants); this.errorReporter = errorReporter; availableTracTracRaces = new ArrayList(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedEventsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedEventsComposite.java index e84a35052e1..b883dbfc221 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedEventsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedEventsComposite.java @@ -40,7 +40,7 @@ import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.RaceDAO; import com.sap.sailing.gwt.ui.shared.RegattaDAO; @@ -88,7 +88,7 @@ public class TrackedEventsComposite extends FormPanel implements EventDisplayer, private Triple lastSelectedTriple; public TrackedEventsComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, - final EventRefresher eventRefresher, StringConstants stringConstants, boolean hasMultiSelection) { + final EventRefresher eventRefresher, StringMessages stringConstants, boolean hasMultiSelection) { if (eventRefresher == null) { throw new IllegalArgumentException("eventRefresher must not be null"); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index 8294b4409f7..b8f06704a6b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -61,7 +61,7 @@ import com.sap.sailing.gwt.ui.client.EventDisplayer; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.RaceDAO; import com.sap.sailing.gwt.ui.shared.RegattaDAO; @@ -74,7 +74,7 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower, private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final Grid grid; - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final WindSettingPanel windSettingPanel; private ColumnSortList columnSortList; private final IdentityColumn removeColumn; @@ -93,7 +93,7 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower, private final Map stripChartSeries; public WindPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, - EventRefresher eventRefresher, StringConstants stringConstants) { + EventRefresher eventRefresher, StringMessages stringConstants) { this.sailingService = sailingService; this.errorReporter = errorReporter; this.stringConstants = stringConstants; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java index 80d88ba65a7..58f1cae2d84 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java @@ -22,7 +22,7 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter { private DialogBox errorDialogBox; private HTML serverResponseLabel; private Button dialogCloseButton; - protected StringConstants stringConstants; + protected StringMessages stringConstants; /** * Create a remote service proxy to talk to the server-side sailing service. @@ -38,7 +38,7 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter { @Override public void onModuleLoad() { - stringConstants = GWT.create(StringConstants.class); + stringConstants = GWT.create(StringMessages.class); errorDialogBox = createErrorDialog(); ServiceDefTarget serviceDef = (ServiceDefTarget) sailingService; String moduleBaseURL = GWT.getModuleBaseURL(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java new file mode 100644 index 00000000000..488a92e5f94 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.List; + +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; + + +/** + * Allows UI components to observe a competitor selector, such as a drop-down box showing a list of competitors + */ +public interface CompetitorSelectionChangeListener { + + /** + * The first element is the first one selected + * @param selectedCompetitors a non-null list which is empty if nothing is selected + */ + void onCompetitorSelectionChange(List selectedCompetitors); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java new file mode 100644 index 00000000000..c8d1d264125 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java @@ -0,0 +1,17 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.List; + +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; + +public interface CompetitorSelectionProvider { + + /** + * @return a non-null list which may be empty + */ + List getSelectedCompetitors(); + + void addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener); + + void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java index 2f7b17ec318..f977f1e7c09 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java @@ -20,6 +20,8 @@ import com.google.gwt.user.client.ui.Widget; /** * An abstract data entry dialog class, capturing data of type T, with generic OK/Cancel buttons, title and * message. Subclasses may override the {@link #show()} method to set the focus on their favorable initial entry field. + * Subclasses can specify a widget to show in the dialog to capture properties specific to the result type T by + * overriding the {@link #getAdditionalWidget()} method. * * @author Axel Uhl (d043530) */ @@ -38,6 +40,15 @@ public abstract class DataEntryDialog { String getErrorMessage(T valueToValidate); } + /** + * @param validator + * an optional validator; if null, no validation of data entered is performed; otherwise, + * data validation is triggered upon any noticeable change in any of the elements constructed by + * {@link #createCheckbox(String)}, {@link #createTextBox(String)}, etc. + * @param callback + * will be called when the dialog if {@link AsyncCallback#onFailure(Throwable) cancelled} or + * {@link AsyncCallback#onSuccess(Object) confirmed} + */ public DataEntryDialog(String title, String message, String okButtonName, String cancelButtonName, Validator validator, final AsyncCallback callback) { dateEntryDialog = new DialogBox(); @@ -102,7 +113,7 @@ public abstract class DataEntryDialog { * * @param initialValue initial value to show in text box; null is permissible */ - protected TextBox createTextBox(String initialValue) { + public TextBox createTextBox(String initialValue) { TextBox textBox = new TextBox(); textBox.setText(initialValue == null ? "" : initialValue); AbstractEntryPoint.addFocusUponKeyUpToggler(textBox); @@ -123,7 +134,7 @@ public abstract class DataEntryDialog { * * @param initialValue initial value to show in text box; null is permissible */ - protected LongBox createLongBox(long initialValue, int visibleLength) { + public LongBox createLongBox(long initialValue, int visibleLength) { LongBox longBox = new LongBox(); longBox.setVisibleLength(visibleLength); longBox.setValue(initialValue); @@ -139,7 +150,7 @@ public abstract class DataEntryDialog { return longBox; } - protected LongBox createIntegerBoxWithOptionalValue(Long initialValue, int visibleLength) { + public LongBox createLongBoxWithOptionalValue(Long initialValue, int visibleLength) { LongBox longBox = new LongBox(); longBox.setVisibleLength(visibleLength); longBox.setValue(initialValue); @@ -155,7 +166,7 @@ public abstract class DataEntryDialog { return longBox; } - protected CheckBox createCheckbox(String checkboxLabel) { + public CheckBox createCheckbox(String checkboxLabel) { CheckBox result = new CheckBox(checkboxLabel); result.addValueChangeHandler(new ValueChangeHandler() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java index b1386ea54e2..f400a77521c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java @@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.client; import com.sap.sailing.server.api.DetailType; public class DetailTypeFormatter { - public static String format(DetailType detailType, StringConstants stringConstants) { + public static String format(DetailType detailType, StringMessages stringConstants) { switch (detailType) { case DISTANCE_TRAVELED: return stringConstants.distanceInMeters(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LogoAndTitlePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LogoAndTitlePanel.java index dd4a9d3aec1..3037daa3691 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LogoAndTitlePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LogoAndTitlePanel.java @@ -7,7 +7,7 @@ import com.google.gwt.user.client.ui.Label; public class LogoAndTitlePanel extends FlowPanel { - public LogoAndTitlePanel(StringConstants stringConstants) { + public LogoAndTitlePanel(StringMessages stringConstants) { Anchor sapLogo = new Anchor(new SafeHtmlBuilder().appendHtmlConstant( "").toSafeHtml()); sapLogo.setHref("http://www.sap.com"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java similarity index 94% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 601f4e14a78..aea66cbf57f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1,8 +1,8 @@ package com.sap.sailing.gwt.ui.client; -import com.google.gwt.i18n.client.Constants; +import com.google.gwt.i18n.client.Messages; -public interface StringConstants extends Constants { +public interface StringMessages extends Messages { String helloWorld(); String kielWeel2011(); String stgAccount(); @@ -72,7 +72,6 @@ public interface StringConstants extends Constants { String leaderboard(); String leaderboards(); String leaderboardSettings(); - String selectLegDetails(); String settings(); String selectAtLeastOneLegDetail(); String currentSpeedOverGroundInKnots(); @@ -195,6 +194,7 @@ public interface StringConstants extends Constants { String noSelection(); String raceIsKnownToStartUpwind(); String events(); + String settingsForComponent(String localizedComponentName); String noEventsFound(); String noEventSelected(); String noLeaderboardsFound(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties similarity index 96% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.properties rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index aad2fb26dce..5fe5468f54c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -67,7 +67,6 @@ sapSailingAnalytics=Sailing Analytics leaderboard=LEADERBOARD leaderboards=Leaderboards leaderboardSettings=Leaderboard Settings -selectLegDetails=Select Timing and Leg Details settings=Settings (e.g., leg detail selection) selectAtLeastOneLegDetail=Select at least one leg detail currentSpeedOverGroundInKnots=SOG @@ -101,7 +100,7 @@ connectAndReadRaces=Connect and read races ports=Ports liveData=Live data storedData=Stored data -uris=URI's +uris=URIs jsonUrl=JSON URL livePort=Port Live Data storedPort=Port Stored Data @@ -176,7 +175,7 @@ trackSettings=Track settings actionEdit=Edit actionEditScores=Edit scores actionOpenBrowser=Open in Browser -actionRemove=Remove leaderboard +actionRemove=Remove leaderboard actionRaceEdit=Edit or rename race actionRaceUnlink=Unlink race actionRaceRemove=Remove race @@ -184,12 +183,13 @@ filterLeaderboardsByName=Filter leaderboards by name windwardDistanceToOverallLeader=Windward distance to overall leader actions=Actions islinked=Is linked -yes=Yes -no=No +yes=Yes +no=No hoverOverAPoint=Hover over a point for details noSelection=No selection raceIsKnownToStartUpwind=Race starts with upwind leg events=Events +settingsForComponent=Settings for {0} noEventsFound=No events were found noEventSelected=No event selected noLeaderboardsFound=No leaderboards were found diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties similarity index 95% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants_de.properties rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index a0db009f393..7581e4f3227 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringConstants_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -67,7 +67,6 @@ sapSailingAnalytics=Sailing Analytics leaderboard=ERGEBNISSE leaderboards=Ergebnislisten leaderboardSettings=Einstellungen Ergebnisliste -selectLegDetails=Detaileinstellungen settings=Einstellungen (z.B. Abschnittdetails auswählen) selectAtLeastOneLegDetail=Mindestens ein Detail auswählen currentSpeedOverGroundInKnots=FüG @@ -102,7 +101,7 @@ trackSettings=Track settings ports=Ports liveData=Live data storedData=Stored data -uris=URI's +uris=URIs jsonUrl=JSON URL livePort=Port Live Data storedPort=Port Stored Data @@ -139,7 +138,7 @@ tacks=Wenden jibes=Halsen penaltyCircles=Strafkringel medalRaceIsNull=Medialienwert nicht erlaubt -maneuverTypes=Man�ver +maneuverTypes=Manöver raceNameEmpty=Der Name des Rennens ist leer configuration=Konfiguration chooseChart=Diagramm wählen @@ -180,17 +179,18 @@ actionOpenBrowser=Im Browser öffnen actionRemove=Leaderboard entfernen actionRaceEdit=Rennen ändern oder umbenennen actionRaceUnlink=Rennverknüpfung entfernen -actionRaceRemove=Rennen entfernen +actionRaceRemove=Rennen entfernen filterLeaderboardsByName=Leaderboards nach Name filtern windwardDistanceToOverallLeader=Windward distance to overall leader actions=Aktionen islinked=Verknüpft yes=Ja -no=Nein +no=Nein hoverOverAPoint=Für Details Maus über dem Graph positionieren noSelection=Keine Selektion raceIsKnownToStartUpwind=Rennen beginnt mit Kreuz events=Rennen +settingsForComponent=Einstellungen für {0} noEventsFound=Es wurden keine Rennen gefunden noEventSelected=Kein Rennen ausgewählt noLeaderboardsFound=Es wurden keine Leaderboards gefunden diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ChartsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ChartsPanel.java index c830ce599df..f284488aeda 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ChartsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ChartsPanel.java @@ -60,7 +60,7 @@ import com.sap.sailing.gwt.ui.client.ColorMap; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CompetitorDAO; import com.sap.sailing.gwt.ui.shared.CompetitorInRaceDAO; import com.sap.sailing.gwt.ui.shared.CompetitorsAndTimePointsDAO; @@ -92,7 +92,7 @@ public class ChartsPanel extends FormPanel { private final ColorMap colorMap; private int selectedRace = 0; private int stepsToLoad = 100; - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final Set competitorVisible; private VerticalPanel selectCompetitors; private PlotWithOverview plot; @@ -108,7 +108,7 @@ public class ChartsPanel extends FormPanel { private AbsolutePanel loadingPanel; public ChartsPanel(SailingServiceAsync sailingService, final List competitors, - RaceIdentifier[] races, StringConstants stringConstants, int chartWidth, int chartHeight, ErrorReporter errorReporter) { + RaceIdentifier[] races, StringMessages stringConstants, int chartWidth, int chartHeight, ErrorReporter errorReporter) { width = chartWidth; height = chartHeight; this.errorReporter = errorReporter; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompareCompetitorsChartDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompareCompetitorsChartDialog.java index 1b0b68d9236..673a9b36425 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompareCompetitorsChartDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompareCompetitorsChartDialog.java @@ -16,7 +16,7 @@ import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.ChartsPanel.DataLoadedEvent; import com.sap.sailing.gwt.ui.leaderboard.ChartsPanel.DataLoadedHandler; import com.sap.sailing.gwt.ui.shared.CompetitorDAO; @@ -25,7 +25,7 @@ import com.sap.sailing.server.api.RaceIdentifier; public class CompareCompetitorsChartDialog extends DialogBox { private Anchor closeAnchor; - public CompareCompetitorsChartDialog(SailingServiceAsync sailingService, List competitors, RaceIdentifier[] races, StringConstants stringConstants, ErrorReporter errorReporter){ + public CompareCompetitorsChartDialog(SailingServiceAsync sailingService, List competitors, RaceIdentifier[] races, StringMessages stringConstants, ErrorReporter errorReporter){ super(false); final ChartsPanel ccp = new ChartsPanel(sailingService, competitors, races, stringConstants, (int) (Window.getClientWidth()-350), (int) (Window.getClientHeight()-170),errorReporter); ccp.addDataLoadedHandler(new DataLoadedHandler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java index 71794b69d7f..88761fd9951 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java @@ -12,7 +12,7 @@ import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Header; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDAO; import com.sap.sailing.server.api.DetailType; @@ -45,7 +45,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn cell, - StringConstants stringConstants, String detailHeaderStyle, String detailColumnStyle, + StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle, List detailSelection) { super(cell); this.enableExpansion = enableExpansion; @@ -59,7 +59,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn> getDetailColumnMap( - LeaderboardPanel leaderboardPanel, StringConstants stringConstants, String detailHeaderStyle, + LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { return Collections.emptyMap(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 4fcece85135..fb7b2e9476e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -36,11 +36,10 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.gwt.ui.client.Collator; -import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.PlayStateListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.TimeListener; import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField; @@ -50,6 +49,9 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDAO; import com.sap.sailing.gwt.ui.shared.LegEntryDAO; import com.sap.sailing.gwt.ui.shared.RaceInLeaderboardDAO; +import com.sap.sailing.gwt.ui.shared.components.Component; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; import com.sap.sailing.server.api.DetailType; import com.sap.sailing.server.api.LeaderboardNameAndRaceColumnName; import com.sap.sailing.server.api.RaceIdentifier; @@ -60,7 +62,7 @@ import com.sap.sailing.server.api.RaceIdentifier; * @author Axel Uhl (D043530) * */ -public class LeaderboardPanel extends FormPanel implements TimeListener, PlayStateListener { +public class LeaderboardPanel extends FormPanel implements TimeListener, PlayStateListener, Component { private static final int RANK_COLUMN_INDEX = 0; private static final int SAIL_ID_COLUMN_INDEX = 1; @@ -78,7 +80,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta private final ErrorReporter errorReporter; - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final CellTable leaderboardTable; @@ -139,69 +141,49 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta */ private final Anchor playPause; private class SettingsClickHandler implements ClickHandler { - private final StringConstants stringConstants; + private final StringMessages stringConstants; - private SettingsClickHandler(StringConstants stringConstants) { + private SettingsClickHandler(StringMessages stringConstants) { this.stringConstants = stringConstants; } @Override public void onClick(ClickEvent event) { - new LeaderboardSettingsPanel(Collections.unmodifiableList(selectedManeuverDetails), Collections.unmodifiableList(selectedLegDetails), - Collections.unmodifiableList(selectedRaceDetails), /* All races to select */ - leaderboard.getRaceColumnNameList(), selectedRaceColumns, timer.getDelayBetweenAutoAdvancesInMilliseconds(), - stringConstants.leaderboardSettings(), stringConstants.selectLegDetails(), stringConstants.ok(), - stringConstants.cancel(), new Validator() { - @Override - public String getErrorMessage(LeaderboardSettingsPanel.Result valueToValidate) { - if (valueToValidate.getLegDetailsToShow().isEmpty()) { - return stringConstants.selectAtLeastOneLegDetail(); - } else if (valueToValidate.getDelayBetweenAutoAdvancesInMilliseconds() < 1000) { - return stringConstants.chooseUpdateIntervalOfAtLeastOneSecond(); - } else { - return null; - } - } - }, new AsyncCallback() { - @Override - public void onSuccess(LeaderboardSettingsPanel.Result result) { - List> columnsToExpandAgain = new ArrayList>(); - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column c = getLeaderboardTable().getColumn(i); - if (c instanceof ExpandableSortableColumn) { - ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; - if (expandableSortableColumn.isExpanded()) { - // now toggle expansion back and forth, - // enforcing a re-build of the visible - // child columns - expandableSortableColumn.toggleExpansion(); - columnsToExpandAgain.add(expandableSortableColumn); - } - } - } - selectedManeuverDetails.clear(); - selectedManeuverDetails.addAll(result.getManeuverDetailsToShow()); - selectedLegDetails.clear(); - selectedLegDetails.addAll(result.getLegDetailsToShow()); - selectedRaceDetails.clear(); - selectedRaceDetails.addAll(result.getRaceDetailsToShow()); - selectedRaceColumns.clear(); - selectedRaceColumns.addAll(result.getRaceColumnsToShow()); - // update leaderboard after settings panel column selection change - updateLeaderboard(leaderboard); - - timer.setDelayBetweenAutoAdvancesInMilliseconds(result - .getDelayBetweenAutoAdvancesInMilliseconds()); - setDelayInMilliseconds(result.getDelayInMilliseconds()); - for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { - expandableSortableColumn.toggleExpansion(); - } - } + new SettingsDialog(LeaderboardPanel.this, stringConstants).show(); + } + } + + @Override + public void updateSettings(LeaderboardSettings result) { + List> columnsToExpandAgain = new ArrayList>(); + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column c = getLeaderboardTable().getColumn(i); + if (c instanceof ExpandableSortableColumn) { + ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; + if (expandableSortableColumn.isExpanded()) { + // now toggle expansion back and forth, + // enforcing a re-build of the visible + // child columns + expandableSortableColumn.toggleExpansion(); + columnsToExpandAgain.add(expandableSortableColumn); + } + } + } + selectedManeuverDetails.clear(); + selectedManeuverDetails.addAll(result.getManeuverDetailsToShow()); + selectedLegDetails.clear(); + selectedLegDetails.addAll(result.getLegDetailsToShow()); + selectedRaceDetails.clear(); + selectedRaceDetails.addAll(result.getRaceDetailsToShow()); + selectedRaceColumns.clear(); + selectedRaceColumns.addAll(result.getRaceColumnsToShow()); + // update leaderboard after settings panel column selection change + updateLeaderboard(leaderboard); - @Override - public void onFailure(Throwable caught) { - } - }, stringConstants, getDelayInMilliseconds()).show(); + timer.setDelayBetweenAutoAdvancesInMilliseconds(result.getDelayBetweenAutoAdvancesInMilliseconds()); + setDelayInMilliseconds(result.getDelayInMilliseconds()); + for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { + expandableSortableColumn.toggleExpansion(); } } @@ -446,7 +428,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta @Override protected Map> getDetailColumnMap( - LeaderboardPanel leaderboardPanel, StringConstants stringConstants, String detailHeaderStyle, + LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); result.put(DetailType.RACE_DISTANCE_TRAVELED, @@ -696,7 +678,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta } public LeaderboardPanel(SailingServiceAsync sailingService, String leaderboardName, ErrorReporter errorReporter, - final StringConstants stringConstants) { + final StringMessages stringConstants) { this.sailingService = sailingService; this.setLeaderboardName(leaderboardName); this.errorReporter = errorReporter; @@ -1314,4 +1296,23 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta CompareCompetitorsChartDialog chartDialog = new CompareCompetitorsChartDialog(sailingService, competitors, races.toArray(new LeaderboardNameAndRaceColumnName[0]), stringConstants, errorReporter); chartDialog.show(); } + + @Override + public boolean hasSettings() { + return true; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent() { + return new LeaderboardSettingsDialogComponent(Collections.unmodifiableList(selectedManeuverDetails), + Collections.unmodifiableList(selectedLegDetails), + Collections.unmodifiableList(selectedRaceDetails), /* All races to select */ + leaderboard.getRaceColumnNameList(), selectedRaceColumns, timer.getDelayBetweenAutoAdvancesInMilliseconds(), + delayInMilliseconds, stringConstants); + } + + @Override + public String getLocalizedShortName() { + return stringConstants.leaderboard(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java new file mode 100755 index 00000000000..d459e58f06c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java @@ -0,0 +1,49 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +import java.util.List; + +import com.sap.sailing.server.api.DetailType; + +public class LeaderboardSettings { + private final List raceColumnsToShow; + private final List maneuverDetailsToShow; + private final List legDetailsToShow; + private final List raceDetailsToShow; + private final long delayBetweenAutoAdvancesInMilliseconds; + private final long delayInMilliseconds; + + public LeaderboardSettings(List meneuverDetailsToShow, List legDetailsToShow, + List raceDetailsToShow, List raceColumnsToShow, + long delayBetweenAutoAdvancesInMilliseconds, long delayInMilliseconds) { + this.legDetailsToShow = legDetailsToShow; + this.raceDetailsToShow = raceDetailsToShow; + this.raceColumnsToShow = raceColumnsToShow; + this.delayBetweenAutoAdvancesInMilliseconds = delayBetweenAutoAdvancesInMilliseconds; + this.delayInMilliseconds = delayInMilliseconds; + this.maneuverDetailsToShow = meneuverDetailsToShow; + } + + public List getManeuverDetailsToShow() { + return maneuverDetailsToShow; + } + + public List getLegDetailsToShow() { + return legDetailsToShow; + } + + public List getRaceDetailsToShow() { + return raceDetailsToShow; + } + + public List getRaceColumnsToShow(){ + return raceColumnsToShow; + } + + public long getDelayBetweenAutoAdvancesInMilliseconds() { + return delayBetweenAutoAdvancesInMilliseconds; + } + + public long getDelayInMilliseconds() { + return delayInMilliseconds; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java similarity index 58% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsPanel.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index f55cc78b690..1218efe4d97 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -5,20 +5,21 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; -import com.sap.sailing.gwt.ui.client.StringConstants; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsPanel.Result; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; import com.sap.sailing.server.api.DetailType; -public class LeaderboardSettingsPanel extends DataEntryDialog { +public class LeaderboardSettingsDialogComponent implements SettingsDialogComponent { private final List raceColumnSelection; private final List raceAllRaceColumns; private final List maneuverDetailSelection; @@ -28,57 +29,16 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { private final Map maneuverDetailCheckboxes; private final Map legDetailCheckboxes; private final Map raceDetailCheckboxes; - private final StringConstants stringConstants; - private final LongBox delayBetweenAutoAdvancesInSecondsBox; - private final LongBox delayInSecondsBox; - - public static class Result { - private final List raceColumnsToShow; - private final List maneuverDetailsToShow; - private final List legDetailsToShow; - private final List raceDetailsToShow; - private final long delayBetweenAutoAdvancesInMilliseconds; - private final long delayInMilliseconds; - - public Result(List meneuverDetailsToShow, List legDetailsToShow, List raceDetailsToShow, List raceColumnsToShow, long delayBetweenAutoAdvancesInMilliseconds, long delayInMilliseconds) { - this.legDetailsToShow = legDetailsToShow; - this.raceDetailsToShow = raceDetailsToShow; - this.raceColumnsToShow = raceColumnsToShow; - this.delayBetweenAutoAdvancesInMilliseconds = delayBetweenAutoAdvancesInMilliseconds; - this.delayInMilliseconds = delayInMilliseconds; - this.maneuverDetailsToShow = meneuverDetailsToShow; - } - - public List getManeuverDetailsToShow() { - return maneuverDetailsToShow; - } + private final StringMessages stringConstants; + private LongBox delayBetweenAutoAdvancesInSecondsBox; + private LongBox delayInSecondsBox; + private final long delayBetweenAutoAdvancesInMilliseconds; + private final long delayInMilliseconds; - public List getLegDetailsToShow() { - return legDetailsToShow; - } - - public List getRaceDetailsToShow() { - return raceDetailsToShow; - } - - public List getRaceColumnsToShow(){ - return raceColumnsToShow; - } - - public long getDelayBetweenAutoAdvancesInMilliseconds() { - return delayBetweenAutoAdvancesInMilliseconds; - } - - public long getDelayInMilliseconds() { - return delayInMilliseconds; - } - } - - public LeaderboardSettingsPanel(List maneuverDetailSelection, List legDetailSelection, List raceDetailSelection, List raceAllRaceColumns, List raceColumnSelection, - long delayBetweenAutoAdvancesInMilliseconds, String title, String message, - String okButtonName, - String cancelButtonName, com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator validator, AsyncCallback callback, StringConstants stringConstants, long delayInMilliseconds) { - super(title, message, okButtonName, cancelButtonName, validator, callback); + public LeaderboardSettingsDialogComponent(List maneuverDetailSelection, + List legDetailSelection, List raceDetailSelection, List raceAllRaceColumns, + List raceColumnSelection, long delayBetweenAutoAdvancesInMilliseconds, long delayInMilliseconds, + StringMessages stringConstants) { this.maneuverDetailSelection = maneuverDetailSelection; this.raceColumnSelection = raceColumnSelection; this.raceAllRaceColumns = raceAllRaceColumns; @@ -89,19 +49,21 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { maneuverDetailCheckboxes = new LinkedHashMap(); legDetailCheckboxes = new LinkedHashMap(); raceDetailCheckboxes = new LinkedHashMap(); - delayBetweenAutoAdvancesInSecondsBox = createLongBox(delayBetweenAutoAdvancesInMilliseconds/1000l, 4); - delayInSecondsBox = createLongBox(delayInMilliseconds/1000l, 4); + this.delayBetweenAutoAdvancesInMilliseconds = delayBetweenAutoAdvancesInMilliseconds; + this.delayInMilliseconds = delayInMilliseconds; } - + @Override - protected Widget getAdditionalWidget() { + public Widget getAdditionalWidget(DataEntryDialog dialog) { + delayBetweenAutoAdvancesInSecondsBox = dialog.createLongBox(delayBetweenAutoAdvancesInMilliseconds/1000l, 4); + delayInSecondsBox = dialog.createLongBox(delayInMilliseconds/1000l, 4); HorizontalPanel hp = new HorizontalPanel(); VerticalPanel vpMeneuvers = new VerticalPanel(); vpMeneuvers.setSpacing(5); vpMeneuvers.add(new Label(stringConstants.maneuverTypes())); List currentMeneuverDetailSelection = maneuverDetailSelection; for (DetailType detailType : ManeuverCountRaceColumn.getAvailableManeuverDetailColumnTypes()) { - CheckBox checkbox = createCheckbox(DetailTypeFormatter.format(detailType, stringConstants)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(detailType, stringConstants)); checkbox.setValue(currentMeneuverDetailSelection.contains(detailType)); maneuverDetailCheckboxes.put(detailType, checkbox); vpMeneuvers.add(checkbox); @@ -121,7 +83,7 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { vpLeft.add(new Label(stringConstants.raceDetailsToShow())); List currentRaceDetailSelection = raceDetailSelection; for (DetailType type : LeaderboardPanel.getAvailableRaceDetailColumnTypes()) { - CheckBox checkbox = createCheckbox(DetailTypeFormatter.format(type, stringConstants)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type, stringConstants)); checkbox.setValue(currentRaceDetailSelection.contains(type)); raceDetailCheckboxes.put(type, checkbox); vpLeft.add(checkbox); @@ -129,7 +91,7 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { vpLeft.add(new Label(stringConstants.legDetailsToShow())); List currentLegDetailSelection = legDetailSelection; for (DetailType type : LegColumn.getAvailableLegDetailColumnTypes()) { - CheckBox checkbox = createCheckbox(DetailTypeFormatter.format(type, stringConstants)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type, stringConstants)); checkbox.setValue(currentLegDetailSelection.contains(type)); legDetailCheckboxes.put(type, checkbox); vpLeft.add(checkbox); @@ -139,7 +101,7 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { vpRight.add(new Label(stringConstants.selectedRaces())); List allColumns = raceAllRaceColumns; for (String expandableSortableColumn : allColumns) { - CheckBox checkbox = createCheckbox(expandableSortableColumn); + CheckBox checkbox = dialog.createCheckbox(expandableSortableColumn); checkbox.setValue(raceColumnSelection.contains(expandableSortableColumn)); raceColumnCheckboxes.put(expandableSortableColumn, checkbox); vpRight.add(checkbox); @@ -149,7 +111,7 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { } @Override - protected Result getResult() { + public LeaderboardSettings getResult() { List maneuverDetailsToShow = new ArrayList(); for (Map.Entry entry : maneuverDetailCheckboxes.entrySet()) { if (entry.getValue().getValue()) { @@ -176,14 +138,30 @@ public class LeaderboardSettingsPanel extends DataEntryDialog { } Long delayBetweenAutoAdvancesValue = delayBetweenAutoAdvancesInSecondsBox.getValue(); Long delayInSecondsValue = delayInSecondsBox.getValue(); - return new Result(maneuverDetailsToShow, legDetailsToShow, raceDetailsToShow, raceColumnsToShow, 1000l*(delayBetweenAutoAdvancesValue==null?0l:delayBetweenAutoAdvancesValue.longValue()), - 1000*(delayInSecondsValue==null?0:delayInSecondsValue.longValue())); + return new LeaderboardSettings(maneuverDetailsToShow, legDetailsToShow, raceDetailsToShow, raceColumnsToShow, + 1000l * (delayBetweenAutoAdvancesValue == null ? 0l : delayBetweenAutoAdvancesValue.longValue()), + 1000 * (delayInSecondsValue==null?0:delayInSecondsValue.longValue())); } @Override - public void show() { - super.show(); - delayInSecondsBox.setFocus(true); + public Validator getValidator() { + return new Validator() { + @Override + public String getErrorMessage(LeaderboardSettings valueToValidate) { + if (valueToValidate.getLegDetailsToShow().isEmpty()) { + return stringConstants.selectAtLeastOneLegDetail(); + } else if (valueToValidate.getDelayBetweenAutoAdvancesInMilliseconds() < 1000) { + return stringConstants.chooseUpdateIntervalOfAtLeastOneSecond(); + } else { + return null; + } + } + }; } - + + @Override + public FocusWidget getFocusWidget() { + return delayInSecondsBox; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java index e4672bc0571..f3a592ba68b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java @@ -9,7 +9,7 @@ import com.google.gwt.cell.client.TextCell; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Header; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField; import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDAO; @@ -26,7 +26,7 @@ import com.sap.sailing.server.api.DetailType; public class LegColumn extends ExpandableSortableColumn { private final String raceName; private final int legIndex; - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final String headerStyle; private final String columnStyle; @@ -188,7 +188,7 @@ public class LegColumn extends ExpandableSortableColumn { } } - public LegColumn(LeaderboardPanel leaderboardPanel, String raceName, int legIndex, StringConstants stringConstants, + public LegColumn(LeaderboardPanel leaderboardPanel, String raceName, int legIndex, StringMessages stringConstants, List legDetailSelection, String headerStyle, String columnStyle, String detailHeaderStyle, String detailColumnStyle) { super(leaderboardPanel, /* expandable */true /* all legs have details */, new TextCell(), stringConstants, @@ -213,7 +213,7 @@ public class LegColumn extends ExpandableSortableColumn { @Override protected Map> getDetailColumnMap( - LeaderboardPanel leaderboardPanel, StringConstants stringConstants, String detailHeaderStyle, + LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); result.put(DetailType.DISTANCE_TRAVELED, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java index 263f8a81bf8..046a314b8de 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java @@ -10,7 +10,7 @@ import com.google.gwt.cell.client.TextCell; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Header; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDAO; @@ -21,7 +21,7 @@ import com.sap.sailing.domain.common.impl.Util.Triple; public class ManeuverCountRaceColumn extends ExpandableSortableColumn implements HasStringAndDoubleValue { - private final StringConstants stringConstants; + private final StringMessages stringConstants; private final RaceNameProvider raceNameProvider; private final String headerStyle; @@ -68,7 +68,7 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im } public ManeuverCountRaceColumn(LeaderboardPanel leaderboardPanel, RaceNameProvider raceNameProvider, - StringConstants stringConstants, List maneuverDetailSelection, String headerStyle, + StringMessages stringConstants, List maneuverDetailSelection, String headerStyle, String columnStylee, String detailHeaderStyle, String detailColumnStyle) { super(leaderboardPanel, /* expandable */true /* all legs have details */, new TextCell(), stringConstants, detailHeaderStyle, detailColumnStyle, maneuverDetailSelection); @@ -254,7 +254,7 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im @Override protected Map> getDetailColumnMap( - LeaderboardPanel leaderboardPanel, StringConstants stringConstants, String detailHeaderStyle, + LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); result.put(DetailType.TACK, new FormattedDoubleLegDetailColumn(stringConstants.tack(), "", new NumberOfTacks(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SmallWindHistoryPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SmallWindHistoryPanel.java index 7e45b0cc994..e5daac7f375 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SmallWindHistoryPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SmallWindHistoryPanel.java @@ -14,7 +14,7 @@ import com.sap.sailing.gwt.ui.adminconsole.WindIndicator; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.TimeListener; import com.sap.sailing.gwt.ui.shared.EventDAO; import com.sap.sailing.gwt.ui.shared.PositionDAO; @@ -40,7 +40,7 @@ public class SmallWindHistoryPanel extends FormPanel implements TimeListener, Ra private final SailingServiceAsync sailingService; private final long millisecondStepsPerLabel; private final ErrorReporter errorReporter; - private final StringConstants stringConstants; + private final StringMessages stringConstants; /** * @param position @@ -52,7 +52,7 @@ public class SmallWindHistoryPanel extends FormPanel implements TimeListener, Ra * the wind track to visualize using this panel */ public SmallWindHistoryPanel(SailingServiceAsync sailingService, PositionDAO position, - int numberOfTimepoints, long millisecondStepsPerLabel, StringConstants stringConstants, ErrorReporter errorReporter) { + int numberOfTimepoints, long millisecondStepsPerLabel, StringMessages stringConstants, ErrorReporter errorReporter) { this.sailingService = sailingService; this.position = position; this.stringConstants = stringConstants; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableExpandableColumnHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableExpandableColumnHeader.java index be09ac8633a..24e1fee6f3b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableExpandableColumnHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableExpandableColumnHeader.java @@ -22,7 +22,7 @@ import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Header; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; /** * A {@link CellTable} {@link Header} implementation that uses a {@link CompositeCell} containing a @@ -51,12 +51,12 @@ public class SortableExpandableColumnHeader extends Header { private static RaceCellTemplates template = GWT.create(RaceCellTemplates.class); public SortableExpandableColumnHeader(String title, String iconURL, LeaderboardPanel leaderboardPanel, - ExpandableSortableColumn column, StringConstants stringConstants) { + ExpandableSortableColumn column, StringMessages stringConstants) { super(constructCell(title, iconURL, column.isExpansionEnabled(), leaderboardPanel, column, stringConstants)); } private static Cell constructCell(final String title, final String iconURL, boolean isExpansionEnabled, - final LeaderboardPanel leaderboardPanel, final ExpandableSortableColumn column, final StringConstants stringConstants) { + final LeaderboardPanel leaderboardPanel, final ExpandableSortableColumn column, final StringMessages stringConstants) { final List> cells = new ArrayList>(3); // if it's a medal race, add the cell rendering the medal image // add the cell rendering the expand/collapse button: diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java index b23c9e7196e..f27870b20f4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java @@ -16,7 +16,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringConstants; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.shared.CompetitorDAO; import com.sap.sailing.gwt.ui.shared.LeaderboardDAO; @@ -247,7 +247,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } public EditableLeaderboardPanel(SailingServiceAsync sailingService, String leaderboardName, - ErrorReporter errorReporter, StringConstants stringConstants) { + ErrorReporter errorReporter, StringMessages stringConstants) { super(sailingService, leaderboardName, errorReporter, stringConstants); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/Component.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/Component.java new file mode 100755 index 00000000000..b72b8eaff09 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/Component.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.shared.components; + + +/** + * + * @author Axel Uhl (d043530) + * + * @param + * the type that describes this component's settings (if any). Use {@link Object} in case your components + * doesn't support any settings. + */ +public interface Component extends ComponentForestEntry { + /** + * @return whether this component has settings that a user may change; if so, + */ + boolean hasSettings(); + + /** + * If this component {@link #hasSettings has settings}, this method may return a component for the settings dialog. + * It will be used to obtain a widget shown in the settings dialog, a validator for the component-specific settings, + * as well as to produce a result from the widget's state when the settings dialog wants to validate or return the + * settings. + */ + SettingsDialogComponent getSettingsDialogComponent(); + + /** + * Updates the settings of this component. Expected to be called when a settings dialog using this component's + * {@link #getSettingsDialogComponent()} has been confirmed. + */ + void updateSettings(SettingsType newSettings); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForest.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForest.java new file mode 100755 index 00000000000..965d94bffdd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForest.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.shared.components; + +/** + * {@link Component}s are arranged in an "ordered forest" structure (multi-rooted tree with the roots ordered in a sequence). + * Each hierarchy level can optionally provide a localized name. Each hierarchy level consists of one or more + * + * @author Axel Uhl (d043530) + * + */ +public interface ComponentForest extends ComponentForestEntry { + Iterable getEntries(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForestEntry.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForestEntry.java new file mode 100755 index 00000000000..50cec5ce4ae --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ComponentForestEntry.java @@ -0,0 +1,8 @@ +package com.sap.sailing.gwt.ui.shared.components; + +public interface ComponentForestEntry { + /** + * @return the name to display to a user for quick navigation to this component + */ + String getLocalizedShortName(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialog.java new file mode 100755 index 00000000000..41b22435ad2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialog.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.ui.shared.components; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class SettingsDialog extends DataEntryDialog { + private final SettingsDialogComponent settingsDialogComponent; + + public SettingsDialog(final Component component, StringMessages stringConstants) { + super(stringConstants.settingsForComponent(component.getLocalizedShortName()), + stringConstants.settingsForComponent(component.getLocalizedShortName()), + stringConstants.ok(), stringConstants.cancel(), component.getSettingsDialogComponent().getValidator(), + new AsyncCallback() { + @Override + public void onFailure(Throwable t) {} + @Override + public void onSuccess(SettingsType newSettings) { + component.updateSettings(newSettings); + } + }); + this.settingsDialogComponent = component.getSettingsDialogComponent(); + } + + @Override + protected Widget getAdditionalWidget() { + return settingsDialogComponent.getAdditionalWidget(this); + } + + @Override + protected SettingsType getResult() { + return settingsDialogComponent.getResult(); + } + + @Override + public void show() { + super.show(); + FocusWidget focusWidget = settingsDialogComponent.getFocusWidget(); + if (focusWidget != null) { + focusWidget.setFocus(true); + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialogComponent.java new file mode 100755 index 00000000000..9add54e3ed1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SettingsDialogComponent.java @@ -0,0 +1,37 @@ +package com.sap.sailing.gwt.ui.shared.components; + +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator; + +public interface SettingsDialogComponent { + /** + * @param dialog + * can be used to {@link DataEntryDialog#createCheckbox(String) produce a checkbox}, a + * {@link DataEntryDialog#createTextBox(String)} or other data entry elements that participate in key + * handling for validation triggering and for canceling and confirming the dialog + * @return null in case this component doesn't make any special contribution to the settings dialog, or + * a valid widget that will be displayed in the settings dialog + */ + Widget getAdditionalWidget(DataEntryDialog dialog); + + /** + * Obtains the result from this settings dialog component; usually, the result object is contructed by reading data + * from the elements of the widget constructed by {@link #getAdditionalWidget(DataEntryDialog)}. + */ + SettingsType getResult(); + + /** + * Obtains an optional validator that is used to validate the {@link #getAdditionalWidget(DataEntryDialog)} contents upon + * changes. If null is returned, no validation will be performed. + */ + Validator getValidator(); + + /** + * @return null if no initial focus on any widget is desired, or a widget from the + * {@link #getAdditionalWidget(DataEntryDialog)} result that will then receive the focus when the settings + * dialog is shown + */ + FocusWidget getFocusWidget(); +} diff --git a/java/com.sap.sailing.server.api/src/com/sap/sailing/server/api/DefaultLeaderboardName.java b/java/com.sap.sailing.server.api/src/com/sap/sailing/server/api/DefaultLeaderboardName.java new file mode 100755 index 00000000000..b97ca914a58 --- /dev/null +++ b/java/com.sap.sailing.server.api/src/com/sap/sailing/server/api/DefaultLeaderboardName.java @@ -0,0 +1,5 @@ +package com.sap.sailing.server.api; + +public interface DefaultLeaderboardName { + static final String DEFAULT_LEADERBOARD_NAME = "Default Leaderboard"; +} diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index d12d0325179..c1ed3d7bb70 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -17,7 +17,7 @@ - + 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 726514e2209..e0407cdccfa 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 @@ -59,6 +59,7 @@ import com.sap.sailing.domain.tractracadapter.RaceRecord; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.api.DefaultLeaderboardName; import com.sap.sailing.server.api.EventFetcher; import com.sap.sailing.server.api.EventIdentifier; import com.sap.sailing.server.api.EventName; @@ -102,8 +103,6 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher, */ private final Map leaderboardsByName; - private static final String DEFAULT_LEADERBOARD_NAME = "Default Leaderboard"; - private Set eventsObservedForDefaultLeaderboard = new HashSet(); private final MongoObjectFactory mongoObjectFactory; @@ -132,7 +131,7 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher, leaderboardsByName = new HashMap(); // Add one default leaderboard that aggregates all races currently tracked by this service. // This is more for debugging purposes than for anything else. - addLeaderboard(DEFAULT_LEADERBOARD_NAME, new int[] { 5, 8 }); + addLeaderboard(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME, new int[] { 5, 8 }); loadStoredLeaderboards(); } @@ -354,7 +353,7 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher, @Override public void raceAdded(TrackedRace trackedRace) { - leaderboardsByName.get(DEFAULT_LEADERBOARD_NAME).addRace(trackedRace, + leaderboardsByName.get(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME).addRace(trackedRace, trackedRace.getRace().getName(), /* medalRace */ false); } }); @@ -384,7 +383,7 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher, for (RaceDefinition race : event.getAllRaces()) { stopTrackingWind(event, race); // remove from default leaderboard - Leaderboard defaultLeaderboard = getLeaderboardByName(DEFAULT_LEADERBOARD_NAME); + Leaderboard defaultLeaderboard = getLeaderboardByName(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME); defaultLeaderboard.removeRaceColumn(race.getName()); } } diff --git a/settings.xml b/settings.xml index 7a871fa4fde..0da14730939 100644 --- a/settings.xml +++ b/settings.xml @@ -15,46 +15,6 @@ localhost|*.sap.corp - - - - snapshot.build - - - build.snapshots - http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots/ - - - - - central - http://repo1.maven.org/maven2 - - - p2 - eclipse - http://download.eclipse.org/releases/indigo/ - - - build.snapshots - http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots/ - - - - - - - - - - leandi.nexus.mirror - http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots/ - *,!build.snapshots,!build.milestones,!sonar - - - - -