Merge branch 'master' into service-monitoring

This commit is contained in:
Axel Uhl
2012-11-29 13:05:47 +01:00
55 changed files with 1291 additions and 655 deletions
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding//src/com/sap/sailing/domain/common/AbstractBearing.java=UTF-8
@@ -47,7 +47,7 @@ public abstract class AbstractBearing implements Bearing {
@Override
public String toString() {
return ""+getDegrees()+"°";
return ""+getDegrees()+"°";
}
@Override
@@ -0,0 +1,3 @@
package com.sap.sailing.domain.common;
public enum MarkType { BUOY, CAMERABOAT, STARTBOAT, UMPIREBOAT, LANDMARK }
@@ -42,6 +42,10 @@ public interface Speed extends Comparable<Speed>, Serializable {
return Distance.NULL;
}
@Override
public String toString() {
return "0kn";
}
};
double getKnots();
@@ -10,6 +10,7 @@ import java.util.List;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.Maneuver;
public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBasedTest {
@@ -39,7 +40,7 @@ public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBa
* The tolerance of time, the maneuver should have happened in milliseconds.
*/
protected void assertManeuver(List<Maneuver> maneuverList, ManeuverType maneuverType,
MillisecondsTimePoint maneuverTimePoint, int tolerance) {
TimePoint maneuverTimePoint, int tolerance) {
for (Maneuver maneuver : maneuverList) {
assertNotNull(maneuver.getTimePoint());
if (maneuver.getType() == maneuverType
@@ -7,6 +7,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -64,10 +65,10 @@ public class ManeuverAnalysisIDMChampionsFinalTest extends AbstractManeuverDetec
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> markPositions = new HashMap<String, Position>();
markPositions.put("G2 Start-Finish (left)", new DegreePosition(53.96003300000019, 10.878697000000084));
markPositions.put("G2 Start-Finish (right)", new DegreePosition(53.9674420000693, 10.894410000058738));
markPositions.put("G2 Mark4 (right)", new DegreePosition(53.96002200000019, 10.878875000000063));
markPositions.put("G2 Mark4 (left)", new DegreePosition(53.9599880000002, 10.878665000000069));
markPositions.put("G2 Start-Finish (1)", new DegreePosition(53.96003300000019, 10.878697000000084));
markPositions.put("G2 Start-Finish (2)", new DegreePosition(53.9674420000693, 10.894410000058738));
markPositions.put("G2 Mark4 (2)", new DegreePosition(53.96002200000019, 10.878875000000063));
markPositions.put("G2 Mark4 (1)", new DegreePosition(53.9599880000002, 10.878665000000069));
markPositions.put("G2 Mark1", new DegreePosition(53.96355800000006, 10.885751999999806));
for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
for (Mark mark : w.getMarks()) {
@@ -91,12 +92,12 @@ public class ManeuverAnalysisIDMChampionsFinalTest extends AbstractManeuverDetec
assertNotNull(competitor);
Date toDate = new Date(1317650038784l); // that's shortly after their penalty circle
Date fromDate = new Date(toDate.getTime()-450000l);
Date maneuverTime = new Date(1317649967712l);
final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
TimePoint maneuverTime = new MillisecondsTimePoint(dateFormatter.parse("2011-10-03T15:52:30.000+0200"));
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
assertManeuver(maneuvers, ManeuverType.PENALTY_CIRCLE,
new MillisecondsTimePoint(maneuverTime), PENALTYCIRCLE_TOLERANCE);
assertManeuver(maneuvers, ManeuverType.PENALTY_CIRCLE, maneuverTime, PENALTYCIRCLE_TOLERANCE);
List<ManeuverType> maneuverTypesFound = new ArrayList<ManeuverType>();
maneuverTypesFound.add(ManeuverType.PENALTY_CIRCLE);
assertAllManeuversOfTypesDetected(maneuverTypesFound, maneuversInvalid);
@@ -83,9 +83,9 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends OnlineTracTr
hassosMarkPassings.last().getTimePoint(), /* waitForLatest */ true);
Calendar c = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
c.clear();
c.set(2011, 6-1, 23, 16, 5, 49);
c.set(2011, 6-1, 23, 16, 5, 47);
assertManeuver(maneuvers, ManeuverType.TACK, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
c.set(2011, 6-1, 23, 16, 8, 37);
c.set(2011, 6-1, 23, 16, 8, 31);
assertManeuver(maneuvers, ManeuverType.TACK, Tack.PORT, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
c.set(2011, 6-1, 23, 16, 11, 03);
assertManeuver(maneuvers, ManeuverType.TACK, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
@@ -54,8 +54,8 @@ public class ManeuverDetectionOnMdM2011SemifinalTest extends OnlineTracTracBased
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> markPositions = new HashMap<String, Position>();
markPositions.put("CR Start (left)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("CR Start (right)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("CR Start (1)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("CR Start (2)", new DegreePosition(53.562944999999985, 10.010104000000046));
markPositions.put("Leeward mark", new DegreePosition(53.562145000000015, 10.009252));
markPositions.put("Luvtonne", new DegreePosition(53.560581899999995, 10.005657));
for (Waypoint w : getTrackedRace().getRace().getCourse().getWaypoints()) {
@@ -157,13 +157,13 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> markPositions = new HashMap<String, Position>();
markPositions.put("K Start (left)", new DegreePosition(54.497439439999994, 10.205943000000001));
markPositions.put("K Start (right)", new DegreePosition(54.500209999999996, 10.20206472));
markPositions.put("K Mark4 (right)", new DegreePosition(54.499422999999986, 10.200381692));
markPositions.put("K Mark4 (left)", new DegreePosition(54.498954999999995, 10.200982));
markPositions.put("K Start (1)", new DegreePosition(54.497439439999994, 10.205943000000001));
markPositions.put("K Start (2)", new DegreePosition(54.500209999999996, 10.20206472));
markPositions.put("K Mark4 (2)", new DegreePosition(54.499422999999986, 10.200381692));
markPositions.put("K Mark4 (1)", new DegreePosition(54.498954999999995, 10.200982));
markPositions.put("K Mark1", new DegreePosition(54.489738990000006, 10.17079423000015));
markPositions.put("K Finish (left)", new DegreePosition(54.48918199999999, 10.17003714));
markPositions.put("K Finish (right)", new DegreePosition(54.48891756, 10.170632146666675));
markPositions.put("K Finish (1)", new DegreePosition(54.48918199999999, 10.17003714));
markPositions.put("K Finish (2)", new DegreePosition(54.48891756, 10.170632146666675));
for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
for (Mark mark : w.getMarks()) {
race.getOrCreateTrack(mark).addGPSFix(new GPSFixImpl(markPositions.get(mark.getName()), epoch));
@@ -8,6 +8,8 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
@@ -750,4 +752,78 @@ public class TrackTest {
assertFalse(newPositionAtEndOfTime.equals(positionAtEndOfTime));
assertTrue(!intervalAffected.getB().before(endOfTime));
}
@Test
public void testSpeedEstimationForCourseChangeAtFix() throws ParseException {
/*
* See bug 1065, first test data set: [2011-06-23T16:08:27.000+0200: (54.49155,10.184947) with
* 5.399568034557236kn to 291.0�, 2011-06-23T16:08:31.000+0200: (54.491597,10.184808) with 5.939524838012959kn
* to 296.0�, 2011-06-23T16:08:37.000+0200: (54.491572999999995,10.184669999999999) with 3.7796976241900646kn
* to 210.0�, 2011-06-23T16:08:39.000+0200: (54.491523,10.184602) with 5.939524838012959kn to 219.0�,
* 2011-06-23T16:08:43.000+0200: (54.491457999999994,10.18451) with 4.859611231101512kn to 225.0�,
* 2011-06-23T16:08:45.000+0200: (54.491386999999996,10.184455) with 5.939524838012959kn to 196.0�]
*
* let the track estimate the speed at 16:08:37 at
*
* 5.469864974239993kn to 215.50791079454874�
*/
DynamicGPSFixMovingTrackImpl<Object> myTrack = new DynamicGPSFixMovingTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */5000);
GPSFixMoving fix1 = createFix("2011-06-23T16:08:27.000+0200", 54.49155, 10.184947, 5.399568034557236, 291.0);
myTrack.addGPSFix(fix1);
GPSFixMoving fix2 = createFix("2011-06-23T16:08:31.000+0200", 54.491597,10.184808, 5.939524838012959, 296.0);
myTrack.addGPSFix(fix2);
GPSFixMoving fix3 = createFix("2011-06-23T16:08:37.000+0200", 54.491572999999995,10.184669999999999, 3.7796976241900646, 210.0);
myTrack.addGPSFix(fix3);
GPSFixMoving fix4 = createFix("2011-06-23T16:08:39.000+0200", 54.491523, 10.184602, 5.939524838012959, 219.0);
myTrack.addGPSFix(fix4);
GPSFixMoving fix5 = createFix("2011-06-23T16:08:43.000+0200", 54.491457999999994, 10.18451, 4.859611231101512, 225.0);
myTrack.addGPSFix(fix5);
GPSFixMoving fix6 = createFix("2011-06-23T16:08:45.000+0200", 54.491386999999996, 10.184455, 5.939524838012959, 196.0);
myTrack.addGPSFix(fix6);
final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
SpeedWithBearing estimatedSpeedWithBearing = myTrack.getEstimatedSpeed(new MillisecondsTimePoint(dateFormatter.parse("2011-06-23T16:08:37.000+0200")));
assertEquals(230., estimatedSpeedWithBearing.getBearing().getDegrees(), 5);
assertEquals(5.0, estimatedSpeedWithBearing.getKnots(), 0.2);
}
@Test
public void testSpeedEstimationForCourseChangeBetweenFixes() throws ParseException {
/*
* See bug 1065, first test data set: [2012-10-21T15:00:32.000+0200: (43.692862999999996,7.267875) with
* 16.73866090712743kn to 294.0°, 2012-10-21T15:00:34.000+0200: (43.692937,7.267683) with 16.73866090712743kn
* to 299.0°, 2012-10-21T15:00:36.000+0200: (43.693017,7.267487999999999) with 16.73866090712743kn to 300.0°,
* 2012-10-21T15:00:38.000+0200: (43.693103,7.267297999999999) with 16.73866090712743kn to 300.0°,
* 2012-10-21T15:00:40.000+0200: (43.693191999999996,7.267112) with 16.73866090712743kn to 303.0°,
* 2012-10-21T15:00:43.000+0200: (43.693283,7.2669179999999995) with 18.898488120950326kn to 302.0°]
* 2012-10-21T15:00:51.000+0200: (43.693245,7.266183) with 11.339092872570195kn to 237.0°
*/
DynamicGPSFixMovingTrackImpl<Object> myTrack = new DynamicGPSFixMovingTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */5000);
GPSFixMoving fix1 = createFix("2012-10-21T15:00:32.000+0200", 43.692862999999996,7.267875, 16.73866090712743, 294.0);
myTrack.addGPSFix(fix1);
GPSFixMoving fix2 = createFix("2012-10-21T15:00:34.000+0200", 43.692937, 7.267683, 16.73866090712743, 299.0);
myTrack.addGPSFix(fix2);
GPSFixMoving fix3 = createFix("2012-10-21T15:00:36.000+0200", 43.693017, 7.267487999999999, 16.73866090712743, 300.0);
myTrack.addGPSFix(fix3);
GPSFixMoving fix4 = createFix("2012-10-21T15:00:38.000+0200", 43.693103, 7.267297999999999, 16.73866090712743, 300.0);
myTrack.addGPSFix(fix4);
GPSFixMoving fix5 = createFix("2012-10-21T15:00:40.000+0200", 43.693191999999996, 7.267112, 16.73866090712743, 303.0);
myTrack.addGPSFix(fix5);
GPSFixMoving fix6 = createFix("2012-10-21T15:00:43.000+0200", 43.693283, 7.2669179999999995, 18.898488120950326, 302.0);
myTrack.addGPSFix(fix6);
GPSFixMoving fix7 = createFix("2012-10-21T15:00:51.000+0200", 43.693245, 7.266183, 11.339092872570195, 237.0);
myTrack.addGPSFix(fix7);
final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
SpeedWithBearing estimatedSpeedWithBearing = myTrack.getEstimatedSpeed(new MillisecondsTimePoint(dateFormatter.parse("2012-10-21T15:00:45.000+0200")));
assertEquals(275., estimatedSpeedWithBearing.getBearing().getDegrees(), 5);
assertEquals(15.5, estimatedSpeedWithBearing.getKnots(), 0.2);
}
private GPSFixMoving createFix(String isoDateTime, double lat, double lng, double knotSpeed, double bearingDeg) throws ParseException {
final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
TimePoint timePoint = new MillisecondsTimePoint(dateFormatter.parse(isoDateTime));
Position position = new DegreePosition(lat, lng);
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(knotSpeed, new DegreeBearingImpl(bearingDeg));
GPSFixMoving fix = new GPSFixMovingImpl(position, timePoint, speed);
return fix;
}
}
@@ -5,17 +5,28 @@ import static org.junit.Assert.assertNotNull;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
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.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
public class WindEstimationOnStoredTracksTest extends StoredTrackBasedTestWithTrackedRace {
// ---------------- BearingWithConfidenceCluster -----------------
@@ -28,12 +39,29 @@ public class WindEstimationOnStoredTracksTest extends StoredTrackBasedTestWithTr
@Test
public void testSimpleWindEstimation() throws NoWindException, FileNotFoundException, IOException {
Map<String, Position> markPositions = new HashMap<>();
markPositions.put("Left lee gate buoy", new DegreePosition(54.4680424, 10.234451));
markPositions.put("Right lee gate buoy", new DegreePosition(54.4681424, 10.234451));
markPositions.put("Windward mark", new DegreePosition(54.4680424, 10.034451));
Competitor hasso = getCompetitorByName("Dr.Plattner");
GPSFixTrack<Competitor, GPSFixMoving> hassosTrack = getTrackedRace().getTrack(hasso);
TimePoint start = hassosTrack.getFirstRawFix().getTimePoint();
TimePoint stop = hassosTrack.getLastRawFix().getTimePoint();
TimePoint middle = new MillisecondsTimePoint(start.asMillis() + (stop.asMillis()-start.asMillis())*3/4);
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(/* position */ null, middle);
TimePoint somewhenOnAnUpwind = new MillisecondsTimePoint(start.asMillis() + (stop.asMillis()-start.asMillis())*3/4);
for (Mark mark : getTrackedRace().getMarks()) {
getTrackedRace().getOrCreateTrack(mark).addGPSFix(
new GPSFixImpl(markPositions.get(mark.getName()), new MillisecondsTimePoint(0)));
getTrackedRace().getOrCreateTrack(mark).addGPSFix(
new GPSFixImpl(markPositions.get(mark.getName()), somewhenOnAnUpwind));
}
getTrackedRace().recordWind(new WindImpl(markPositions.get("Windward mark"), new MillisecondsTimePoint(0),
new KnotSpeedWithBearingImpl(
/* speedInKnots */14.7, new DegreeBearingImpl(90))), new WindSourceImpl(WindSourceType.WEB));
getTrackedRace().recordWind(new WindImpl(markPositions.get("Windward mark"), somewhenOnAnUpwind,
new KnotSpeedWithBearingImpl(
/* speedInKnots */14.7, new DegreeBearingImpl(90))), new WindSourceImpl(WindSourceType.WEB));
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(/* position */ null, somewhenOnAnUpwind);
assertNotNull(estimatedWindDirection);
}
@@ -37,6 +37,7 @@ import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
@@ -144,16 +145,27 @@ public class DomainFactoryImpl implements DomainFactory {
if (domainControlPoint == null) {
String controlPointName = controlPoint.getName();
Map<String, String> controlPointMetadata = parseControlPointMetadata(controlPoint);
String markColor = controlPointMetadata.get("Color");
String markShape = controlPointMetadata.get("Shape");
String markPattern = controlPointMetadata.get("Pattern");
if (controlPoint.getHasTwoPoints()) {
// it's a gate
Mark leftMark = baseDomainFactory.getOrCreateMark(controlPointName + " (left)", markColor, markShape, markPattern);
Mark rightMark = baseDomainFactory.getOrCreateMark(controlPointName + " (right)", markColor, markShape, markPattern);
domainControlPoint = baseDomainFactory.createGate(leftMark, rightMark, controlPointName);
MarkType type1 = resolveMarkTypeFromMetadata(controlPointMetadata, "P1.Type");
MarkType type2 = resolveMarkTypeFromMetadata(controlPointMetadata, "P2.Type");
String color1 = controlPointMetadata.get("P1.Color");
String color2 = controlPointMetadata.get("P2.Color");
String shape1 = controlPointMetadata.get("P1.Shape");
String shape2 = controlPointMetadata.get("P2.Shape");
String pattern1 = controlPointMetadata.get("P1.Pattern");
String pattern2 = controlPointMetadata.get("P2.Pattern");
Mark mark1 = baseDomainFactory.getOrCreateMark(controlPointName + " (1)", type1, color1, shape1, pattern1);
Mark mark2 = baseDomainFactory.getOrCreateMark(controlPointName + " (2)", type2, color2, shape2, pattern2);
domainControlPoint = baseDomainFactory.createGate(mark1, mark2, controlPointName);
} else {
Mark mark = baseDomainFactory.getOrCreateMark(controlPointName, markColor, markShape, markPattern);
MarkType type = resolveMarkTypeFromMetadata(controlPointMetadata, "Type");
String color = controlPointMetadata.get("Color");
String shape = controlPointMetadata.get("Shape");
String pattern = controlPointMetadata.get("Pattern");
Mark mark = baseDomainFactory.getOrCreateMark(controlPointName, type, color, shape, pattern);
domainControlPoint = mark;
}
controlPointCache.put(controlPoint, domainControlPoint);
@@ -161,6 +173,20 @@ public class DomainFactoryImpl implements DomainFactory {
return domainControlPoint;
}
}
private MarkType resolveMarkTypeFromMetadata(Map<String, String> controlPointMetadata, String typePropertyName) {
MarkType result = MarkType.BUOY;
String markType = controlPointMetadata.get(typePropertyName);
if(markType != null && !markType.isEmpty()) {
for(MarkType m: MarkType.values()) {
if(m.name().equalsIgnoreCase(markType)) {
result = m;
break;
}
}
}
return result;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Map<String, String> parseControlPointMetadata(ControlPoint controlPoint) {
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
encoding//src/com/sap/sailing/domain/base/impl/AbstractSpeedWithAbstractBearingImpl.java=UTF-8
encoding//src/com/sap/sailing/domain/tracking/impl/CompactGPSFixMovingImpl.java=UTF-8
@@ -5,6 +5,7 @@ import java.io.InputStream;
import java.io.Serializable;
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
@@ -26,7 +27,7 @@ public interface DomainFactory {
* in that case. Otherwise, a new {@link Mark} is created with <code>color</code> as its {@link Mark#getColor()}
* and <code>shape</code> as its {@link Mark#getShape()}.
*/
Mark getOrCreateMark(String id, String color, String shape, String pattern);
Mark getOrCreateMark(String id, MarkType type, String color, String shape, String pattern);
Gate createGate(Mark left, Mark right, String name);
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.MarkType;
/**
* A marks name is used as its ID which is only identifying the mark uniquely within a single race or course
@@ -12,4 +14,5 @@ public interface Mark extends WithID, ControlPoint, IsManagedByDomainFactory {
public String getColor();
public String getShape();
public String getPattern();
public MarkType getType();
}
@@ -20,7 +20,7 @@ public abstract class AbstractSpeedWithAbstractBearingImpl extends AbstractSpeed
@Override
public String toString() {
return super.toString()+" to "+getBearing().getDegrees()+"�";
return super.toString()+" to "+getBearing().getDegrees()+"°";
}
@Override
@@ -16,15 +16,16 @@ import java.util.concurrent.ConcurrentHashMap;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Gate;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
@@ -120,10 +121,10 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public Mark getOrCreateMark(String id, String color, String shape, String pattern) {
public Mark getOrCreateMark(String id, MarkType type, String color, String shape, String pattern) {
Mark result = markCache.get(id);
if (result == null) {
result = new MarkImpl(id, color, shape, pattern);
result = new MarkImpl(id, type, color, shape, pattern);
markCache.put(id, result);
}
return result;
@@ -3,8 +3,9 @@ package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import java.util.Collections;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class MarkImpl extends NamedImpl implements Mark {
@@ -13,30 +14,43 @@ public class MarkImpl extends NamedImpl implements Mark {
private final String color;
private final String shape;
private final String pattern;
private final MarkType type;
public MarkImpl(String name) {
super(name);
type = MarkType.BUOY;
color = null;
shape = null;
pattern = null;
}
public MarkImpl(String name, MarkType type) {
super(name);
this.type = type;
color = null;
shape = null;
pattern = null;
}
public MarkImpl(String name, String color) {
public MarkImpl(String name, MarkType type, String color) {
super(name);
this.type = type;
this.color = color;
shape = null;
pattern = null;
}
public MarkImpl(String name, String color, String shape) {
public MarkImpl(String name, MarkType type, String color, String shape) {
super(name);
this.type = type;
this.color = color;
this.shape = shape;
pattern = null;
}
public MarkImpl(String name, String color, String shape, String pattern) {
public MarkImpl(String name, MarkType type, String color, String shape, String pattern) {
super(name);
this.type = type;
this.color = color;
this.shape = shape;
this.pattern = pattern;
@@ -56,7 +70,7 @@ public class MarkImpl extends NamedImpl implements Mark {
@Override
public Mark resolve(DomainFactory domainFactory) {
Mark result = domainFactory.getOrCreateMark(getName(), color, shape, pattern);
Mark result = domainFactory.getOrCreateMark(getName(), type, color, shape, pattern);
return result;
}
@@ -74,4 +88,9 @@ public class MarkImpl extends NamedImpl implements Mark {
public String getPattern() {
return pattern;
}
@Override
public MarkType getType() {
return type;
}
}
@@ -190,6 +190,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
* as defined by <code>timePointBeforeManeuver</code> is extrapolated until <code>timePointAfterManeuver</code>,
* and the resulting extrapolated position's "windward distance" is computed to the competitor's actual position
* at that time. This distance is returned as the result of this method.
* @param maneuverTimePoint TODO
*/
Distance getManeuverLoss(TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver) throws NoWindException;
Distance getManeuverLoss(TimePoint timePointBeforeManeuver, TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) throws NoWindException;
}
@@ -56,7 +56,7 @@ public class CompactGPSFixMovingImpl extends CompactGPSFixImpl implements GPSFix
@Override
public String toString() {
return super.toString()+" to "+getBearing().getDegrees()+"�";
return super.toString()+" to "+getBearing().getDegrees()+"°";
}
@Override
public int hashCode() {
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
@@ -14,14 +13,12 @@ import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.confidence.ConfidenceBasedAverager;
import com.sap.sailing.domain.confidence.ConfidenceFactory;
import com.sap.sailing.domain.confidence.HasConfidence;
import com.sap.sailing.domain.confidence.Weigher;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
public class DynamicGPSFixMovingTrackImpl<ItemType> extends DynamicTrackImpl<ItemType, GPSFixMoving> {
@@ -53,13 +50,12 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends DynamicTrackImpl<Ite
NavigableSet<GPSFixMoving> fixesToUseForSpeedEstimation, Weigher<TimePoint> weigher) {
lockForRead();
try {
GPSFixMoving[] relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation);
List<GPSFixMoving> relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation);
List<SpeedWithConfidence<TimePoint>> speeds = new ArrayList<SpeedWithConfidence<TimePoint>>();
BearingWithConfidenceCluster<TimePoint> bearingCluster = new BearingWithConfidenceCluster<TimePoint>(
weigher);
if (relevantFixes.length != 0) {
BearingWithConfidenceCluster<TimePoint> bearingCluster = new BearingWithConfidenceCluster<TimePoint>(weigher);
if (!relevantFixes.isEmpty()) {
int i=0;
GPSFixMoving last = relevantFixes[i];
GPSFixMoving last = relevantFixes.get(i);
// add fix's own speed/bearing; this also works if only one "relevant" fix is found
SpeedWithConfidenceImpl<TimePoint> speedWithConfidence = new SpeedWithConfidenceImpl<TimePoint>(
last.getSpeed(),
@@ -67,9 +63,9 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends DynamicTrackImpl<Ite
speeds.add(speedWithConfidence);
bearingCluster.add(new BearingWithConfidenceImpl<TimePoint>(last.getSpeed().getBearing(), /* confidence */
0.9, last.getTimePoint()));
while (i<relevantFixes.length-1) {
while (i<relevantFixes.size()-1) {
// add to average the position and time difference
GPSFixMoving next = relevantFixes[++i];
GPSFixMoving next = relevantFixes.get(++i);
aggregateSpeedAndBearingFromLastToNext(speeds, bearingCluster, last, next);
// add to average the speed and bearing provided by the GPSFixMoving
SpeedWithConfidenceImpl<TimePoint> computedSpeedWithConfidence = new SpeedWithConfidenceImpl<TimePoint>(
@@ -98,105 +94,6 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends DynamicTrackImpl<Ite
}
}
/**
* Note that no corresponding re-definition of {@link #getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(GPSFix)} is
* necessary because the fixes affected are just the same.
*/
protected GPSFixMoving[] getFixesRelevantForSpeedEstimation(TimePoint at,
NavigableSet<GPSFixMoving> fixesToUseForSpeedEstimation) {
assertReadLock();
DummyGPSFixMoving atTimed = new DummyGPSFixMoving(at);
List<GPSFixMoving> relevantFixes = new LinkedList<GPSFixMoving>();
boolean beforeSetEmpty;
GPSFixMoving beforeSetLast = null;
lockForRead();
try {
NavigableSet<GPSFixMoving> beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */ false);
beforeSetEmpty = beforeSet.isEmpty(); // ask this while holding the lock
if (!beforeSetEmpty) {
beforeSetLast = beforeSet.last();
}
for (GPSFixMoving beforeFix : beforeSet.descendingSet()) {
if (at.asMillis() - beforeFix.getTimePoint().asMillis() > getMillisecondsOverWhichToAverage() / 2) {
break;
}
relevantFixes.add(0, beforeFix);
}
} finally {
unlockAfterRead();
}
boolean afterSetEmpty;
GPSFixMoving afterSetFirst = null;
lockForRead();
try {
NavigableSet<GPSFixMoving> afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */ true);
afterSetEmpty = afterSet.isEmpty(); // ask this while holding the lock
if (!afterSetEmpty) {
afterSetFirst = afterSet.first();
}
for (GPSFixMoving afterFix : afterSet) {
if (afterFix.getTimePoint().asMillis() - at.asMillis() > getMillisecondsOverWhichToAverage() / 2) {
break;
}
relevantFixes.add(afterFix);
}
} finally {
unlockAfterRead();
}
if (relevantFixes.isEmpty()) {
// find the fix closest to "at":
if (beforeSetEmpty) {
if (!afterSetEmpty) {
relevantFixes.add(afterSetFirst);
}
} else {
if (afterSetEmpty) {
relevantFixes.add(beforeSetLast);
} else {
GPSFixMoving beforeFix = beforeSetLast;
GPSFixMoving afterFix = afterSetFirst;
relevantFixes.add(at.asMillis() - beforeFix.getTimePoint().asMillis() <= afterFix.getTimePoint()
.asMillis() - at.asMillis() ? beforeFix : afterFix);
}
}
}
return relevantFixes.toArray(new GPSFixMoving[0]);
}
private class DummyGPSFixMoving extends DummyTimed implements GPSFixMoving {
private static final long serialVersionUID = 7135346050999824024L;
public DummyGPSFixMoving(TimePoint timePoint) {
super(timePoint);
}
@Override
public Position getPosition() {
return null;
}
@Override
public SpeedWithBearing getSpeed() {
return null;
}
@Override
public SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to) {
return null;
}
@Override
public boolean isValidityCached() {
return false;
}
@Override
public boolean isValid() {
return false;
}
@Override
public void invalidateCache() {
}
@Override
public void cacheValidity(boolean isValid) {
}
}
/**
* In addition to the base class implementation, we additionally have the speed and bearing as
* measured by the device. We use the device-measured speed and compare it with the speed computed
@@ -575,18 +575,16 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
NavigableSet<FixType> fixesToUseForSpeedEstimation, Weigher<TimePoint> weigher) {
lockForRead();
try {
GPSFix[] relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation);
List<FixType> relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation);
List<SpeedWithConfidence<TimePoint>> speeds = new ArrayList<SpeedWithConfidence<TimePoint>>();
BearingWithConfidenceCluster<TimePoint> bearingCluster = new BearingWithConfidenceCluster<TimePoint>(weigher);
if (relevantFixes.length != 0) {
int i=0;
GPSFix last = relevantFixes[i];
while (i<relevantFixes.length-1) {
FixType last = null;
for (FixType next : relevantFixes) {
if (last != null) {
// TODO bug #346: consider time difference between next.getTimepoint() and at to compute a confidence
GPSFix next = relevantFixes[++i];
aggregateSpeedAndBearingFromLastToNext(speeds, bearingCluster, last, next);
last = next;
}
last = next;
}
ConfidenceBasedAverager<Double, Speed, TimePoint> speedAverager = ConfidenceFactory.INSTANCE.createAverager(weigher);
HasConfidence<Double, Speed, TimePoint> speedWithConfidence = speedAverager.getAverage(speeds, at);
@@ -690,32 +688,45 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
return new Pair<TimePoint, TimePoint>(intervalStart, intervalEnd);
}
protected FixType createDummyGPSFix(TimePoint at) {
@SuppressWarnings("unchecked")
FixType result = (FixType) new DummyGPSFix(at);
return result;
}
/**
* A track that doesn't have {@link GPSFixMoving} fixes and therefore needs to compute the speed using the fix time
* and position differences needs at least two fixes to compute a result. If only one fix is found within the
* {@link #getMillisecondsOverWhichToAverage() averaging interval}, the closest existing fix later or earlier than
* <code>at</code> is used.
* and position differences. It therefore needs at least two fixes to compute a result. The algorithm always uses
* at least the closest existing fix later and the closet existing fix earlier than <code>at</code>, if available.
* <p>
*
* But even for a track with {@link GPSFixMoving} fixes this is a good algorithm because in case the speed changes
* significantly between fixes, it is important to know the next fix to understand and consider the trend.
*/
protected GPSFix[] getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet<FixType> fixesToUseForSpeedEstimation) {
protected List<FixType> getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet<FixType> fixesToUseForSpeedEstimation) {
lockForRead();
try {
DummyGPSFix atTimed = new DummyGPSFix(at);
List<GPSFix> relevantFixes = new LinkedList<GPSFix>();
@SuppressWarnings("unchecked")
NavigableSet<GPSFix> castFixesToUseForSpeedEstimation = (NavigableSet<GPSFix>) fixesToUseForSpeedEstimation;
NavigableSet<GPSFix> beforeSet = castFixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */false);
NavigableSet<GPSFix> afterSet = castFixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */true);
GPSFix beforeFix = null;
Iterator<GPSFix> beforeFixIter = beforeSet.descendingIterator();
FixType atTimed = createDummyGPSFix(at);
List<FixType> relevantFixes = new LinkedList<FixType>();
NavigableSet<FixType> beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */false);
NavigableSet<FixType> afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */true);
FixType beforeFix = null;
Iterator<FixType> beforeFixIter = beforeSet.descendingIterator();
boolean noBeforeFixUsedYet = true;
while (beforeFixIter.hasNext() &&
at.asMillis() - (beforeFix=beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2) {
(at.asMillis() - (beforeFix=beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2 || noBeforeFixUsedYet)) {
relevantFixes.add(0, beforeFix);
noBeforeFixUsedYet = false;
beforeFix = null; // mark the fix as used
}
GPSFix afterFix = null;
Iterator<GPSFix> afterFixIter = afterSet.iterator();
FixType afterFix = null;
Iterator<FixType> afterFixIter = afterSet.iterator();
boolean noAfterFixUsedYet = true;
while (afterFixIter.hasNext() &&
(afterFix=afterFixIter.next()).getTimePoint().asMillis() - at.asMillis() < getMillisecondsOverWhichToAverage() / 2) {
((afterFix=afterFixIter.next()).getTimePoint().asMillis() - at.asMillis() < getMillisecondsOverWhichToAverage() / 2 || noAfterFixUsedYet)) {
relevantFixes.add(afterFix);
noAfterFixUsedYet = false;
afterFix = null; // mark the fix as used
}
// now fill up relevantFixes until we have at least two fixes or we run out of fixes entirely (can't estimate speed
// with this type of fix on a track with less than two fixes)
@@ -755,7 +766,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
}
}
}
return relevantFixes.toArray(new GPSFix[0]);
return relevantFixes;
} finally {
unlockAfterRead();
}
@@ -1,7 +1,10 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
@@ -25,6 +28,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.util.impl.ArrayListNavigableSet;
/**
* Provides a convenient view on the tracked leg, projecting to a single competitor's performance.
@@ -581,7 +585,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
@Override
public Distance getManeuverLoss(TimePoint timePointBeforeManeuver,
TimePoint timePointAfterManeuver) throws NoWindException {
TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) throws NoWindException {
// FIXME bug 156: the timePointAfterManeuver is not a good one to select the outbound speed from:
// instead, the outbound speed should be chosen as the maximum speed reached after the acceleration phase
// after the maneuver, at least within some reasonable threshold like five times the approximate
@@ -590,38 +594,46 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
assert timePointBeforeManeuver != null;
assert timePointAfterManeuver != null;
Distance result;
long millisecondsOverWhichToAverageSpeed = getTrackedRace().getMillisecondsOverWhichToAverageSpeed();
TimePoint extrapolationBase = timePointBeforeManeuver.minus(millisecondsOverWhichToAverageSpeed);
final GPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(getCompetitor());
SpeedWithBearing speedBeforeManeuver = track.getEstimatedSpeed(extrapolationBase);
if (speedBeforeManeuver != null) {
TimePoint extrapolationTarget = timePointAfterManeuver.plus(millisecondsOverWhichToAverageSpeed);
SpeedWithBearing speedAfterManeuver = track.getEstimatedSpeed(extrapolationTarget);
// find the mean course between inbound and outbound course and project actual and extrapolated
// positions onto it:
Bearing middle = speedBeforeManeuver.getBearing().middle(speedAfterManeuver.getBearing());
Position positionAtBase = track.getEstimatedPosition(extrapolationBase, /* extrapolate */false);
Speed projectedSpeedBeforeManeuver = speedBeforeManeuver.projectTo(positionAtBase, middle);
double projectedSpeedBeforeManeuverInKnots = projectedSpeedBeforeManeuver.getKnots();
Speed projectedSpeedAfterManeuver = speedAfterManeuver.projectTo(positionAtBase, middle);
List<GPSFixMoving> fixes = getFixesToConsiderForManeuverLossAnalysis(timePointBeforeManeuver,
maneuverTimePoint, timePointAfterManeuver);
TimePoint timePointWhenSpeedStartedToDrop = fixes.get(0).getTimePoint();
SpeedWithBearing speedWhenSpeedStartedToDrop = track.getEstimatedSpeed(timePointWhenSpeedStartedToDrop);
if (speedWhenSpeedStartedToDrop != null) {
TimePoint timePointWhenSpeedLevelledOffAfterManeuver = fixes.get(fixes.size()-1).getTimePoint();
SpeedWithBearing speedAfterManeuver = track.getEstimatedSpeed(timePointWhenSpeedLevelledOffAfterManeuver);
// For upwind/downwind legs, find the mean course between inbound and outbound course and project actual and
// extrapolated positions onto it:
Bearing middle = null;
if (getTrackedLeg().isUpOrDownwindLeg(timePointWhenSpeedStartedToDrop)) {
middle = getProjectionAngleForUpwindDownwindManeuverLossAnalysis(timePointWhenSpeedStartedToDrop, speedWhenSpeedStartedToDrop,
speedAfterManeuver);
}
Position positionAtBase = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop, /* extrapolate */false);
Speed projectedSpeedWhenSpeedStartedToDrop = projectSpeedToMiddleIfUpwindDownwindElseToLegDirection(
timePointWhenSpeedStartedToDrop, middle, speedWhenSpeedStartedToDrop, positionAtBase);
double projectedSpeedBeforeManeuverInKnots = projectedSpeedWhenSpeedStartedToDrop.getKnots();
Speed projectedSpeedAfterManeuver = projectSpeedToMiddleIfUpwindDownwindElseToLegDirection(
timePointAfterManeuver, middle, speedAfterManeuver, positionAtBase);
double projectedSpeedAfterManeuverInKnots = projectedSpeedAfterManeuver.getKnots();
track.lockForRead();
try {
Iterator<GPSFixMoving> fixIter = track.getFixesIterator(timePointBeforeManeuver, /* inclusive */true);
GPSFixMoving fix;
TimePoint previousTimePoint = timePointBeforeManeuver;
result = Distance.NULL;
while (fixIter.hasNext() && !(fix = fixIter.next()).getTimePoint().after(timePointAfterManeuver)) {
Speed projectedSpeedAtFix = fix.getSpeed().projectTo(fix.getPosition(), middle);
final double estimatedSpeedWithoutManeuver = projectedSpeedBeforeManeuverInKnots +
(fix.getTimePoint().asMillis()-timePointBeforeManeuver.asMillis()) /
(timePointAfterManeuver.asMillis()-timePointBeforeManeuver.asMillis()) *
(projectedSpeedAfterManeuverInKnots - projectedSpeedBeforeManeuverInKnots);
Speed speedDifference = new KnotSpeedImpl(estimatedSpeedWithoutManeuver - projectedSpeedAtFix.getKnots());
result = result.add(speedDifference.travel(previousTimePoint, fix.getTimePoint()));
}
} finally {
track.unlockAfterRead();
// now determine the fixes along which to accumulate the maneuver loss:
TimePoint previousTimePoint = timePointWhenSpeedStartedToDrop;
result = Distance.NULL;
for (GPSFixMoving fix : fixes) {
// project onto average maneuver course for upwind/downwind and on leg direction otherwise
final SpeedWithBearing fixSpeed = track.getEstimatedSpeed(fix.getTimePoint());
final Position fixPosition = fix.getPosition();
Speed projectedSpeedAtFix = projectSpeedToMiddleIfUpwindDownwindElseToLegDirection(
timePointBeforeManeuver, middle, fixSpeed, fixPosition);
final double estimatedSpeedWithoutManeuverInKnots = (double) projectedSpeedBeforeManeuverInKnots
+ (double) (fix.getTimePoint().asMillis() - timePointWhenSpeedStartedToDrop.asMillis())
/ (double) (timePointWhenSpeedLevelledOffAfterManeuver.asMillis() - timePointWhenSpeedStartedToDrop.asMillis())
* (double) (projectedSpeedAfterManeuverInKnots - projectedSpeedBeforeManeuverInKnots);
Speed speedDifference = new KnotSpeedImpl(estimatedSpeedWithoutManeuverInKnots
- projectedSpeedAtFix.getKnots());
result = result.add(speedDifference.travel(previousTimePoint, fix.getTimePoint()));
previousTimePoint = fix.getTimePoint();
}
} else {
result = null;
@@ -629,4 +641,145 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
return result;
}
/**
* Fetches the set of fixes that encompass the maneuver. Usually, during a maneuver a boat loses speed over ground
* before it has reached the new tack and starts accelerating again, until the speed levels off. We assume that the
* <code>timePointBeforeManeuver</code> minus some excess time based on the approximate maneuver duration is the
* earliest time point to analyze. Starting there, we look for speed over ground minima between then and
* <code>timePointAfterManeuver</code> plus three times the approximate maneuver time as excess time. There may be
* multiple minima. We choose the one that has the best "fit" in terms of being close to the
* <code>maneuverTimePoint</code> and being low in terms of speed over ground. From that time point, the nearest
* maximum speeds over ground before and after are determined, and the fixes between them are returned.
*/
private List<GPSFixMoving> getFixesToConsiderForManeuverLossAnalysis(TimePoint timePointBeforeManeuver,
TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) {
final long EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds();
final long EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS = 3*EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS;
List<GPSFixMoving> fixes = new ArrayList<>();
NavigableSet<GPSFixMoving> maxima = new ArrayListNavigableSet<GPSFixMoving>(new TimedComparator());
NavigableSet<GPSFixMoving> minima = new ArrayListNavigableSet<GPSFixMoving>(new TimedComparator());
GPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(getCompetitor());
Speed lastSpeed = Speed.NULL;
Speed lastLastSpeed = Speed.NULL;
GPSFixMoving lastFix = null;
Speed minimumSpeed = new KnotSpeedImpl(Double.MAX_VALUE);
track.lockForRead();
try {
Iterator<GPSFixMoving> fixIter = track.getFixesIterator(
timePointBeforeManeuver.minus(EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS), /* inclusive */true);
GPSFixMoving fix;
// The timePointAfterManeuver is determined based on the geometric shape of the boat's trajectory, not on
// the speed development. To understand the full maneuver loss, we need to follow the boat speed until it levels off,
// but no further than some reasonable threshold because the wind may continue to pick up, letting the boat accelerate
// over a time much longer than accounted for by the maneuver.
while (fixIter.hasNext()) {
fix = fixIter.next();
final SpeedWithBearing estimatedSpeedAtFix = track.getEstimatedSpeed(fix.getTimePoint());
if (lastFix != null) {
if (lastSpeed.compareTo(lastLastSpeed) > 0 && lastSpeed.compareTo(estimatedSpeedAtFix) > 0) {
maxima.add(lastFix);
} else if (lastSpeed.compareTo(lastLastSpeed) < 0 && lastSpeed.compareTo(estimatedSpeedAtFix) < 0) {
minima.add(lastFix);
if (estimatedSpeedAtFix.compareTo(minimumSpeed) < 0) {
minimumSpeed = estimatedSpeedAtFix;
}
}
}
if (fix.getTimePoint().after(timePointAfterManeuver.plus(EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS))) {
break;
}
fixes.add(fix);
lastLastSpeed = lastSpeed;
lastSpeed = estimatedSpeedAtFix;
lastFix = fix;
}
} finally {
track.unlockAfterRead();
}
GPSFixMoving fixWithLowestSpeedOverGround = getBestFittingSpeedMinimumInManeuver(minima, minimumSpeed, maneuverTimePoint);
if (fixWithLowestSpeedOverGround == null) {
fixWithLowestSpeedOverGround = fixes.get(0);
}
// now remove all fixes before the last maximum before the fix with the lowest speed over ground during the maneuver if
// there was such a maximum; otherwise, leave all fixes from the maneuver start on in place.
final long MAX_SMOOTHENING_INTERVAL_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds();
GPSFixMoving lastMaxSpeedFixBeforeLowSpeed = maxima.lower(fixWithLowestSpeedOverGround);
// now check if there's a greater one that's only a little bit earlier
if (lastMaxSpeedFixBeforeLowSpeed != null) {
GPSFixMoving stillGreater;
while ((stillGreater=maxima.lower(lastMaxSpeedFixBeforeLowSpeed)) != null &&
track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo(
track.getEstimatedSpeed(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) > 0 &&
lastMaxSpeedFixBeforeLowSpeed.getTimePoint().asMillis()-
stillGreater.getTimePoint().asMillis() < MAX_SMOOTHENING_INTERVAL_MILLIS) {
lastMaxSpeedFixBeforeLowSpeed = stillGreater;
}
Iterator<GPSFixMoving> i = fixes.iterator();
while (i.hasNext() && i.next().getTimePoint().before(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) {
i.remove();
}
}
// now remove all fixes after the first maximum after the global minimum:
GPSFixMoving firstMaxSpeedFixAfterLowSpeed = maxima.higher(fixWithLowestSpeedOverGround);
if (firstMaxSpeedFixAfterLowSpeed != null) {
GPSFixMoving stillGreater;
while ((stillGreater=maxima.higher(firstMaxSpeedFixAfterLowSpeed)) != null &&
track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo(
track.getEstimatedSpeed(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) > 0 &&
stillGreater.getTimePoint().asMillis() - firstMaxSpeedFixAfterLowSpeed.getTimePoint().asMillis()
< MAX_SMOOTHENING_INTERVAL_MILLIS) {
firstMaxSpeedFixAfterLowSpeed = stillGreater;
}
ListIterator<GPSFixMoving> i = fixes.listIterator(fixes.size());
while (i.hasPrevious() && i.previous().getTimePoint().after(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) {
i.remove();
}
}
return fixes;
}
private GPSFixMoving getBestFittingSpeedMinimumInManeuver(NavigableSet<GPSFixMoving> minima, Speed minimumSpeed, TimePoint maneuverTimePoint) {
// Idea: being the approximate maneuver duration away from maneuverTimePoint is as bad as having twice the speed.
GPSFixMoving bestSpeedMinimum = null;
double lowestBadness = Double.MAX_VALUE;
final long approximateManeuverTimeInMillis = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds();
final GPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(getCompetitor());
for (GPSFixMoving minimum : minima) {
// best speedBadness can be 1 which represents the absolute speed minimum in the interval considered
double speedBadness = track.getEstimatedSpeed(minimum.getTimePoint()).getKnots() / minimumSpeed.getKnots();
// best timePointBadness can be 1 which is a minimum exactly at the maneuver time point
double timePointBadness = 1. + (double) Math.abs(minimum.getTimePoint().asMillis()-maneuverTimePoint.asMillis()) /
(double) approximateManeuverTimeInMillis;
final double totalBadness = speedBadness * timePointBadness;
if (totalBadness < lowestBadness) {
bestSpeedMinimum = minimum;
lowestBadness = totalBadness;
}
}
return bestSpeedMinimum;
}
protected Speed projectSpeedToMiddleIfUpwindDownwindElseToLegDirection(TimePoint at,
Bearing projectToForUpwindDownwind, final SpeedWithBearing speed, final Position position) throws NoWindException {
Speed projectedSpeedAtFix = getTrackedLeg().isUpOrDownwindLeg(at) ?
speed.projectTo(position, projectToForUpwindDownwind) :
getWindwardSpeed(speed, at);
return projectedSpeedAtFix;
}
protected Bearing getProjectionAngleForUpwindDownwindManeuverLossAnalysis(TimePoint timePointBeforeManeuver,
SpeedWithBearing speedBeforeManeuver, SpeedWithBearing speedAfterManeuver) {
Bearing middle;
middle = speedBeforeManeuver.getBearing().middle(speedAfterManeuver.getBearing());
Wind wind = getTrackedRace().getWind(getTrackedRace().getTrack(getCompetitor()).
getEstimatedPosition(timePointBeforeManeuver, /* extrapolate */ false), timePointBeforeManeuver);
// If the average course in the maneuver deviates from the wind's
final int MAX_DIFF_AVERAGE_COURSE_VERSUS_WIND_IN_DEGREES = 20;
if (Math.abs(wind.getBearing().getDifferenceTo(middle).getDegrees()) > MAX_DIFF_AVERAGE_COURSE_VERSUS_WIND_IN_DEGREES &&
Math.abs(wind.getFrom().getDifferenceTo(middle).getDegrees()) > MAX_DIFF_AVERAGE_COURSE_VERSUS_WIND_IN_DEGREES) {
middle = wind.getBearing();
}
return middle;
}
}
@@ -1895,15 +1895,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List<Pair<GPSFixMoving, CourseChange>> group,
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees,
long totalMilliseconds) throws NoWindException {
TimePoint maneuverTimePoint = new MillisecondsTimePoint(totalMilliseconds / group.size());
Position maneuverPosition = getTrack(competitor)
.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
MillisecondsTimePoint timePointBeforeManeuver = new MillisecondsTimePoint(group.get(0).getA().getTimePoint()
.asMillis()
- getApproximateManeuverDurationInMilliseconds() / 2);
MillisecondsTimePoint timePointAfterManeuver = new MillisecondsTimePoint(group.get(group.size() - 1).getA()
.getTimePoint().asMillis()
+ getApproximateManeuverDurationInMilliseconds() / 2);
TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, timePointAfterManeuver);
Position maneuverPosition = getTrack(competitor)
.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
Tack tackBeforeManeuver = getTack(maneuverPosition, timePointBeforeManeuver,
speedWithBearingOnApproximationAtBeginning.getBearing());
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
@@ -1922,7 +1922,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} else if (Math.abs(totalCourseChangeInDegrees) > PENALTY_CIRCLE_DEGREES_THRESHOLD) {
maneuverType = ManeuverType.PENALTY_CIRCLE;
if (legBeforeManeuver != null) {
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, timePointAfterManeuver);
}
} else {
if (tackBeforeManeuver != tackAfterManeuver) {
@@ -1935,13 +1935,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
case UPWIND:
maneuverType = ManeuverType.TACK;
if (legBeforeManeuver != null) {
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, timePointAfterManeuver);
}
break;
case DOWNWIND:
maneuverType = ManeuverType.JIBE;
if (legBeforeManeuver != null) {
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, timePointAfterManeuver);
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, timePointAfterManeuver);
}
break;
default:
@@ -1977,6 +1977,41 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
return maneuver;
}
/**
* Computes the maneuver time point as the time point along between maneuver start and end where the competitor's track has greatest change
* in course.
*/
private TimePoint computeManeuverTimepoint(Competitor competitor, MillisecondsTimePoint timePointBeforeManeuver,
MillisecondsTimePoint timePointAfterManeuver) {
TimePoint result = timePointBeforeManeuver;
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
GPSFixMoving lastFix = null;
double maxAngleSpeedInDegreesPerSecond = 0;
track.lockForRead();
try {
for (Iterator<GPSFixMoving> i=track.getFixesIterator(timePointBeforeManeuver, /* inclusive */ true); i.hasNext(); ) {
GPSFixMoving fix = i.next();
if (fix.getTimePoint().after(timePointAfterManeuver)) {
break;
}
if (lastFix != null) {
Bearing courseAtLastFix = track.getEstimatedSpeed(lastFix.getTimePoint()).getBearing();
Bearing courseAtFix = track.getEstimatedSpeed(fix.getTimePoint()).getBearing();
double angleSpeedInDegreesPerSecond = Math.abs((courseAtFix.getDifferenceTo(courseAtLastFix).getDegrees()) /
(double) (fix.getTimePoint().asMillis()-lastFix.getTimePoint().asMillis()));
if (angleSpeedInDegreesPerSecond > maxAngleSpeedInDegreesPerSecond) {
maxAngleSpeedInDegreesPerSecond = angleSpeedInDegreesPerSecond;
result = lastFix.getTimePoint();
}
}
lastFix = fix;
}
} finally {
track.unlockAfterRead();
}
return result;
}
/**
* Fetches the boat class-specific parameter
*/
@@ -24,8 +24,8 @@ public class ColorMap<T> {
baseColors[0] = new HSVColor(0.0f, 1.0f, 1.0f); // Red
baseColors[1] = new HSVColor(30.0f, 1.0f, 1.0f); // Orange
baseColors[2] = new HSVColor(45.f, 1.0f, 1.0f);
baseColors[3] = new HSVColor(120.0f, 1.0f, 1.0f); // Green
baseColors[4] = new HSVColor(180.0f, 1.0f, 1.0f); // Cyan
baseColors[3] = new HSVColor(120.0f, 1.0f, 0.8f); // Green
baseColors[4] = new HSVColor(200.0f, 1.0f, 1.0f); // Cyan
baseColors[5] = new HSVColor(240.0f, 1.0f, 1.0f); // Blue
baseColors[6] = new HSVColor(270.0f, 1.0f, 1.0f); // Pink
baseColors[7] = new HSVColor(285.0f, 1.0f, 1.0f);
@@ -63,7 +63,7 @@ public class ColorMap<T> {
int baseColorCount = baseColors.length;
int baseColorsIndex = index % baseColorCount;
int factor = index / (baseColorCount*3);
float decreaseStepSize = 0.1f;
float decreaseStepSize = 0.075f;
float brightnessDecrease = 0;
float saturationDecrease = 0;
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.client;
import com.google.gwt.i18n.client.NumberFormat;
import com.sap.sailing.domain.common.DetailType;
public class DetailTypeFormatter {
@@ -76,6 +77,16 @@ public class DetailTypeFormatter {
return stringMessages.averageJibeLossInMeters();
}
return null;
}
public static NumberFormat getNumberFormat(DetailType detailType) {
String decimalPlaces = "";
for (int i = 0; i < detailType.getPrecision(); i++) {
if (i == 0) {
decimalPlaces += ".";
}
decimalPlaces += "0";
}
return NumberFormat.getFormat("0" + decimalPlaces);
}
}
@@ -884,6 +884,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
markDTO.color = mark.getColor();
markDTO.shape = mark.getShape();
markDTO.pattern = mark.getPattern();
markDTO.type = mark.getType();
return markDTO;
}
@@ -2,11 +2,14 @@ package com.sap.sailing.gwt.ui.shared;
import java.util.Collections;
import com.sap.sailing.domain.common.MarkType;
public class MarkDTO extends ControlPointDTO {
public PositionDTO position;
public String color;
public String shape;
public String pattern;
public MarkType type;
MarkDTO() {}
@@ -37,6 +37,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor;
@@ -57,8 +58,8 @@ import com.sap.sailing.gwt.ui.shared.MultiCompetitorRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.components.Component;
/**
* ChartPanel is a GWT panel that can show one sort of competitor data (e.g. current speed over ground, windward distance to
* leader) for different races in a chart.
* ChartPanel is a GWT panel that can show one sort of competitor data (e.g. current speed over ground, windward
* distance to leader) for different races in a chart.
*
* When calling the constructor a chart is created that creates a final amount of series (so the maximum number of
* competitors cannot be changed in one chart) which are connected to competitors, when the sailing service returns the
@@ -68,8 +69,8 @@ import com.sap.sailing.gwt.ui.shared.components.Component;
* @author Benjamin Ebling (D056866), Axel Uhl (d043530)
*
*/
public abstract class AbstractChartPanel<SettingsType extends ChartSettings> extends RaceChart
implements CompetitorSelectionChangeListener, RequiresResize {
public abstract class AbstractChartPanel<SettingsType extends ChartSettings> extends RaceChart implements
CompetitorSelectionChangeListener, RequiresResize {
private static final int LINE_WIDTH = 1;
private MultiCompetitorRaceDataDTO chartData;
private boolean compactChart;
@@ -78,22 +79,18 @@ implements CompetitorSelectionChangeListener, RequiresResize {
private final Map<CompetitorDTO, Series> markPassingSeriesByCompetitor;
private final boolean allowTimeAdjust;
private long stepSize = 5000;
private DetailType dataToShow;
private DetailType selectedDetailType;
private final CompetitorSelectionProvider competitorSelectionProvider;
/**
* When the chart is not visible and the competitor selection changes, no loading is performed. However, in this case
* we need to remember that loading will be necessary as soon as the chart becomes visible.
*/
private boolean needToLoadWhenMadeVisible;
private Long timeOfEarliestRequestInMillis;
private Long timeOfLatestRequestInMillis;
public AbstractChartPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
CompetitorSelectionProvider competitorSelectionProvider, RaceSelectionProvider raceSelectionProvider,
Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages, ErrorReporter errorReporter, DetailType dataToShow,
boolean compactChart, boolean allowTimeAdjust) {
Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages,
ErrorReporter errorReporter, DetailType detailType, boolean compactChart, boolean allowTimeAdjust) {
super(sailingService, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
this.competitorSelectionProvider = competitorSelectionProvider;
this.dataToShow = dataToShow;
this.competitorSelectionProvider = competitorSelectionProvider;
this.compactChart = compactChart;
this.allowTimeAdjust = allowTimeAdjust;
dataSeriesByCompetitor = new HashMap<CompetitorDTO, Series>();
@@ -103,44 +100,45 @@ implements CompetitorSelectionChangeListener, RequiresResize {
noCompetitorsSelectedLabel = new Label(stringMessages.selectAtLeastOneCompetitor() + ".");
noCompetitorsSelectedLabel.setStyleName("abstractChartPanel-importantMessageOfChart");
chart = createChart(dataToShow);
chart = createChart();
setSelectedDetailType(detailType);
competitorSelectionProvider.addCompetitorSelectionChangeListener(this);
raceSelectionProvider.addRaceSelectionChangeListener(this);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (needToLoadWhenMadeVisible) {
loadData(/* showBusyIndicator */ true);
}
}
/**
* Creates a new chart for the given {@link DetailType} <code>dataToShow</code> and also
* clears the {@link #chartData}, the {@link #dataSeriesByCompetitor} and the {@link #markPassingSeriesByCompetitor}.
* @param dataToShow The detail type for the new chart.
* Creates a new chart for the given {@link DetailType} <code>dataToShow</code> and also clears the
* {@link #chartData}, the {@link #dataSeriesByCompetitor} and the {@link #markPassingSeriesByCompetitor}.
*
* @param detailType
* The detail type for the new chart.
* @return A chart for the given detail Type
*/
private Chart createChart(DetailType dataToShow) {
private Chart createChart() {
Chart chart = new Chart().setZoomType(Chart.ZoomType.X)
.setPersistent(true)
.setWidth100()
.setHeight100()
.setMarginLeft(65)
.setMarginRight(65)
.setBorderColor(new Color("#A6A6A6"))
.setBorderColor(new Color("#CACACA"))
.setBackgroundColor(new Color("#EBEBEB"))
.setBorderWidth(1)
.setBorderRadius(0)
.setPlotBorderWidth(0)
.setCredits(new Credits().setEnabled(false))
.setChartSubtitle(new ChartSubtitle().setText(stringMessages.clickAndDragToZoomIn()))
.setLinePlotOptions(new LinePlotOptions().setLineWidth(LINE_WIDTH).setMarker(new Marker().setEnabled(false).setHoverState(
.setLinePlotOptions(
new LinePlotOptions()
.setLineWidth(LINE_WIDTH)
.setMarker(
new Marker().setEnabled(false).setHoverState(
new Marker().setEnabled(true).setRadius(4))).setShadow(false)
.setHoverStateLineWidth(LINE_WIDTH));
ChartUtil.useCheckboxesToShowAndHide(chart);
chart.setChartTitle(new ChartTitle().setText(DetailTypeFormatter.format(dataToShow, stringMessages)));
if (allowTimeAdjust) {
chart.setClickEventHandler(new ChartClickEventHandler() {
@Override
@@ -155,240 +153,199 @@ implements CompetitorSelectionChangeListener, RequiresResize {
}
});
}
final String unit = getUnit();
if (!compactChart) {
chart.getYAxis().setAxisTitleText(DetailTypeFormatter.format(dataToShow, stringMessages) + " ["+unit+"]");
} else {
chart.getYAxis().setAxisTitleText("["+unit+"]");
}
chart.getXAxis().setType(Axis.Type.DATE_TIME).setMaxZoom(60 * 1000); // 1 minute
chart.getYAxis().setStartOnTick(false).setShowFirstLabel(false);
chart.getYAxis().setReversed((dataToShow == DetailType.WINDWARD_DISTANCE_TO_OVERALL_LEADER ||
dataToShow == DetailType.GAP_TO_LEADER_IN_SECONDS) ? true : false);
chart.getXAxis().setType(Axis.Type.DATE_TIME).setMaxZoom(60 * 1000) // 1 minute
.setAxisTitleText(stringMessages.time());
chart.getXAxis().setLabels(new XAxisLabels().setFormatter(new AxisLabelsFormatter() {
@Override
public String format(AxisLabelsData axisLabelsData) {
return dateFormatHoursMinutes.format(new Date(axisLabelsData.getValueAsLong()));
}
}));
String decimalPlaces = "";
for (int i = 0; i < dataToShow.getPrecision(); i++) {
if (i == 0) {
decimalPlaces += ".";
}
decimalPlaces += "0";
}
final NumberFormat numberFormat = NumberFormat.getFormat("0" + decimalPlaces);
chart.setToolTip(new ToolTip().setEnabled(true).setFormatter(new ToolTipFormatter() {
@Override
public String format(ToolTipData toolTipData) {
String seriesName = toolTipData.getSeriesName();
if (seriesName.equals(stringMessages.time())) {
return "<b>" + seriesName + ":</b> " + dateFormat.format(new Date(toolTipData.getXAsLong()))
+ "<br/>(" + stringMessages.clickChartToSetTime() + ")";
} else {
return "<b>" + seriesName
+ (toolTipData.getPointName() != null ? " " + toolTipData.getPointName() : "")
+ "</b><br/>" + dateFormat.format(new Date(toolTipData.getXAsLong())) + ": "
+ numberFormat.format(toolTipData.getYAsDouble()) + unit;
}
}
}));
if (compactChart) {
chart.setSpacingBottom(10).setSpacingLeft(10).setSpacingRight(10).setSpacingTop(2)
.setOption("legend/margin", 2)
.setOption("title/margin", 5)
.setChartSubtitle(null)
.getXAxis().setAxisTitleText(null);
.setOption("legend/margin", 2).setOption("title/margin", 5).setChartSubtitle(null).getXAxis()
.setAxisTitleText(null);
}
setChartData(null);
dataSeriesByCompetitor.clear();
markPassingSeriesByCompetitor.clear();
return chart;
}
protected abstract Component<SettingsType> getComponent();
/**
* Loads the needed data (data which isn't in the {@link #chartData cache}) for the {@link #getVisibleCompetitors()
* visible competitors} via
* Loads the needed data (data which isn't in the {@link #chartData cache}) for the
* {@link #getSelectedCompetitors() visible competitors} via
* {@link SailingServiceAsync#getCompetitorsRaceData(RaceIdentifier, List, long, DetailType, AsyncCallback)}. After
* loading is the method {@link #drawChartData()} called.<br />
* If no data needs to be {@link #needsDataLoading() loaded}, the no competitors selected label is displayed.
*
* @param showBusyIndicator If <code>true</code> is the busy indicator shown while loading the data from the server.
* @param showBusyIndicator
* If <code>true</code> is the busy indicator shown while loading the data from the server.
*/
protected void loadData(boolean showBusyIndicator) {
if (isVisible()) {
if (hasVisibleCompetitors()) {
needToLoadWhenMadeVisible = false; // we're loading it now
if (showBusyIndicator) {
setWidget(chart);
showLoading(stringMessages.loadingCompetitorData());
}
if (chartData == null || chartData.getDetailType() != getDataToShow()) {
chartData = new MultiCompetitorRaceDataDTO(getDataToShow());
}
// Date toDate = new Date(System.currentTimeMillis() - timer.getLivePlayDelayInMillis());
final ArrayList<Pair<Date, CompetitorDTO>> dataQuery = new ArrayList<Pair<Date, CompetitorDTO>>();
for (CompetitorDTO competitor : getVisibleCompetitors()) {
Date chartDataDateOfNewestData = chartData.getDateOfNewestData();
Date competitorDateOfNewestData = chartData.contains(competitor) ? chartData.getCompetitorData(
competitor).getDateOfNewestData() : null;
if (!chartData.contains(competitor)) {
dataQuery.add(new Pair<Date, CompetitorDTO>(new Date(0), competitor));
} else if (competitorDateOfNewestData.before(chartDataDateOfNewestData)
|| competitorDateOfNewestData.before(timeRangeWithZoomProvider.getToTime())) {
dataQuery.add(new Pair<Date, CompetitorDTO>(new Date(competitorDateOfNewestData.getTime()
+ getStepSize()), competitor));
}
}
GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(
sailingService, selectedRaceIdentifier, dataQuery, timeRangeWithZoomProvider.getToTime(), getStepSize(),
getDataToShow(), new AsyncCallback<MultiCompetitorRaceDataDTO>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
getStringMessages().failedToLoadRaceData() + ": " + caught.toString(),
timer.getPlayMode() == PlayModes.Live);
hideLoading();
}
@Override
public void onSuccess(MultiCompetitorRaceDataDTO result) {
if (result != null) {
for (CompetitorRaceDataDTO competitorData : result.getAllRaceData()) {
if (chartData.contains(competitorData.getCompetitor())) {
chartData.addCompetitorRaceData(competitorData);
chartData.setCompetitorMarkPassingsData(competitorData);
} else {
chartData.setCompetitorData(competitorData.getCompetitor(), competitorData);
}
}
}
chart.getXAxis().setMin(timeRangeWithZoomProvider.getFromTime().getTime());
chart.getXAxis().setMax(timeRangeWithZoomProvider.getToTime().getTime());
drawChartData();
chart.redraw();
hideLoading();
}
});
asyncActionsExecutor.execute(getCompetitorsRaceDataAction);
} else {
setWidget(noCompetitorsSelectedLabel);
private void updateChart(Date from, Date to, boolean append, boolean showBusyIndicator) {
if (hasSelectedCompetitors()) {
setWidget(chart);
if (showBusyIndicator) {
showLoading(stringMessages.loadingCompetitorData());
}
loadData(from, to, append);
} else {
// not visible; don't load data but remember that data needs to be loaded as soon as the chart becomes visible
needToLoadWhenMadeVisible = true;
setWidget(noCompetitorsSelectedLabel);
}
}
private void loadData(Date from, Date to, boolean append) {
boolean hasDetailTypeChanged = chartData == null || chartData.getDetailType() != selectedDetailType;
if (hasDetailTypeChanged) {
chartData = new MultiCompetitorRaceDataDTO(selectedDetailType);
}
// Date toDate = new Date(System.currentTimeMillis() - timer.getLivePlayDelayInMillis());
final ArrayList<Pair<Date, CompetitorDTO>> dataQuery = new ArrayList<Pair<Date, CompetitorDTO>>();
for (CompetitorDTO competitor : getSelectedCompetitors()) {
Date chartDataDateOfNewestData = chartData.getDateOfNewestData();
Date competitorDateOfNewestData = chartData.contains(competitor) ? chartData.getCompetitorData(competitor)
.getDateOfNewestData() : null;
if (!chartData.contains(competitor)) {
dataQuery.add(new Pair<Date, CompetitorDTO>(new Date(0), competitor));
} else if (competitorDateOfNewestData.before(chartDataDateOfNewestData)
|| competitorDateOfNewestData.before(timeRangeWithZoomProvider.getToTime())) {
dataQuery.add(new Pair<Date, CompetitorDTO>(new Date(competitorDateOfNewestData.getTime()
+ getStepSize()), competitor));
}
}
GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(sailingService,
selectedRaceIdentifier, dataQuery, timeRangeWithZoomProvider.getToTime(), getStepSize(),
getSelectedDetailType(), new AsyncCallback<MultiCompetitorRaceDataDTO>() {
@Override
public void onSuccess(MultiCompetitorRaceDataDTO result) {
if (result != null) {
for (CompetitorRaceDataDTO competitorData : result.getAllRaceData()) {
if (chartData.contains(competitorData.getCompetitor())) {
chartData.addCompetitorRaceData(competitorData);
chartData.setCompetitorMarkPassingsData(competitorData);
} else {
chartData.setCompetitorData(competitorData.getCompetitor(), competitorData);
}
}
}
hideLoading();
drawChartData();
}
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
getStringMessages().failedToLoadRaceData() + ": " + caught.toString(),
timer.getPlayMode() == PlayModes.Live);
hideLoading();
}
});
asyncActionsExecutor.execute(getCompetitorsRaceDataAction);
}
@Override
public void addedToSelection(CompetitorDTO competitor) {
loadData(true);
if (isVisible()) {
timeChanged(timer.getTime());
}
}
@Override
public void removedFromSelection(CompetitorDTO competitor) {
Series competitorSeries = getCompetitorSeries(competitor);
Series competitorSeries = dataSeriesByCompetitor.get(competitor);
if (competitorSeries != null) {
chart.removeSeries(competitorSeries);
chart.removeSeries(competitorSeries, isVisible());
}
Series competitorMarkPassingSeries = getCompetitorMarkPassingSeries(competitor);
Series competitorMarkPassingSeries = markPassingSeriesByCompetitor.get(competitor);
if (competitorMarkPassingSeries != null) {
chart.removeSeries(competitorMarkPassingSeries);
chart.removeSeries(competitorMarkPassingSeries, isVisible());
}
if (!hasVisibleCompetitors()) {
if (isVisible() && !hasSelectedCompetitors()) {
setWidget(noCompetitorsSelectedLabel);
}
}
/**
* Creates the series for all selected competitors if these aren't created yet.<br />
* Fills the series for the selected competitors with the data in {@link AbstractChartPanel#chartData}.<br />
* Removes series of competitors, which aren't selected and adds series for competitors, which are newly selected.<br />
* Also {@link #forceTimeLineUpdate() updates} the {@link #timeLineSeries} if {@link #timeLineNeedsUpdate} is <code>true</code>.<br /><br />
* Also {@link #forceTimeLineUpdate() updates} the {@link #timeLineSeries} if {@link #timeLineNeedsUpdate} is
* <code>true</code>.<br />
* <br />
*
* The data for all {@link #getVisibleCompetitors() visible competitors} needs to be filled before calling this method.
* The data for all {@link #getSelectedCompetitors() visible competitors} needs to be filled before calling this
* method.
*/
private synchronized void drawChartData() {
//Make sure the busy indicator is removed at this point, or plotting the data results in an exception
// Make sure the busy indicator is removed at this point, or plotting the data results in an exception
setWidget(chart);
Iterable<CompetitorDTO> competitors = competitorSelectionProvider.getAllCompetitors();
if (chartData != null && competitors != null && competitors.iterator().hasNext()) {
if (chartData != null) {
List<Series> chartSeries = Arrays.asList(chart.getSeries());
for (CompetitorDTO competitor : competitors) {
Series compSeries = getCompetitorSeries(competitor);
Series markSeries = getCompetitorMarkPassingSeries(competitor);
if (isCompetitorVisible(competitor)) {
CompetitorRaceDataDTO competitorData = chartData.getCompetitorData(competitor);
if (competitorData != null) {
Date toDate = new Date(System.currentTimeMillis() - timer.getLivePlayDelayInMillis());
List<Triple<String, Date, Double>> markPassingsData = competitorData.getMarkPassingsData();
List<Point> markPassingPoints = new ArrayList<Point>();
for (Triple<String, Date, Double> markPassingData : markPassingsData) {
if (markPassingData.getB() != null && markPassingData.getC() != null) {
if (markPassingData.getB().before(toDate)) {
Point markPassingPoint = new Point(markPassingData.getB().getTime(),
markPassingData.getC());
markPassingPoint.setName(markPassingData.getA());
markPassingPoints.add(markPassingPoint);
}
for (CompetitorDTO competitor : competitorSelectionProvider.getSelectedCompetitors()) {
Series competitorDataSeries = getOrCreateCompetitorDataSeries(competitor);
Series markPassingSeries = getOrCreateCompetitorMarkPassingSeries(competitor);
CompetitorRaceDataDTO competitorData = chartData.getCompetitorData(competitor);
if (competitorData != null) {
Date toDate = new Date(System.currentTimeMillis() - timer.getLivePlayDelayInMillis());
List<Triple<String, Date, Double>> markPassingsData = competitorData.getMarkPassingsData();
List<Point> markPassingPoints = new ArrayList<Point>();
for (Triple<String, Date, Double> markPassingData : markPassingsData) {
if (markPassingData.getB() != null && markPassingData.getC() != null) {
if (markPassingData.getB().before(toDate)) {
Point markPassingPoint = new Point(markPassingData.getB().getTime(),
markPassingData.getC());
markPassingPoint.setName(markPassingData.getA());
markPassingPoints.add(markPassingPoint);
}
}
markSeries.setPoints(markPassingPoints.toArray(new Point[0]));
Point[] compSeriesPoints = compSeries.getPoints();
Date dateOfNewestSeriesPoint = compSeriesPoints.length == 0 ? null :
new Date(compSeriesPoints[compSeriesPoints.length - 1].getX().longValue());
List<Pair<Date, Double>> raceData = dateOfNewestSeriesPoint == null ? competitorData
.getRaceData() : competitorData.getRaceDataAfterDate(dateOfNewestSeriesPoint);
for (Pair<Date, Double> data : raceData) {
if (data.getA() != null && data.getB() != null) {
if (data.getA().before(toDate)) {
Point competitorPoint = new Point(data.getA().getTime(), data.getB());
compSeries.addPoint(competitorPoint);
}
}
}
//Adding the series if chart doesn't contain it
if (!chartSeries.contains(compSeries)) {
chart.addSeries(compSeries);
chart.addSeries(markSeries);
}
}
} else {
//Removing the series if chart contains it
if (chartSeries.contains(compSeries)) {
chart.removeSeries(compSeries);
chart.removeSeries(markSeries);
markPassingSeries.setPoints(markPassingPoints.toArray(new Point[0]));
Point[] compSeriesPoints = competitorDataSeries.getPoints();
Date dateOfNewestSeriesPoint = compSeriesPoints.length == 0 ? null : new Date(
compSeriesPoints[compSeriesPoints.length - 1].getX().longValue());
List<Pair<Date, Double>> raceData = dateOfNewestSeriesPoint == null ? competitorData.getRaceData()
: competitorData.getRaceDataAfterDate(dateOfNewestSeriesPoint);
for (Pair<Date, Double> data : raceData) {
if (data.getA() != null && data.getB() != null) {
if (data.getA().before(toDate)) {
Point competitorPoint = new Point(data.getA().getTime(), data.getB());
competitorDataSeries.addPoint(competitorPoint);
}
}
}
// Adding the series if chart doesn't contain it
if (!chartSeries.contains(competitorDataSeries)) {
chart.addSeries(competitorDataSeries);
chart.addSeries(markPassingSeries);
}
}
}
}
if (!isZoomed) {
chart.getXAxis().setMin(timeRangeWithZoomProvider.getFromTime().getTime());
chart.getXAxis().setMax(timeRangeWithZoomProvider.getToTime().getTime());
}
chart.redraw();
}
private Iterable<CompetitorDTO> getVisibleCompetitors() {
private Iterable<CompetitorDTO> getSelectedCompetitors() {
return competitorSelectionProvider.getSelectedCompetitors();
}
private boolean hasVisibleCompetitors() {
return getVisibleCompetitors().iterator().hasNext();
}
private boolean isCompetitorVisible(CompetitorDTO competitor) {
return competitorSelectionProvider.isSelected(competitor);
private boolean hasSelectedCompetitors() {
return Util.size(getSelectedCompetitors()) > 0;
}
/**
@@ -396,56 +353,69 @@ implements CompetitorSelectionChangeListener, RequiresResize {
* @param competitor
* @return A series in the chart, that can be used to show the data of a specific competitor.
*/
private Series getCompetitorSeries(final CompetitorDTO competitor){
private Series getOrCreateCompetitorDataSeries(final CompetitorDTO competitor) {
Series result = dataSeriesByCompetitor.get(competitor);
if (result == null) {
result = chart.createSeries().setType(Series.Type.LINE).setName(competitor.name);
if (result == null) {
result = chart.createSeries().setType(Series.Type.LINE).setName(competitor.name);
result.setPlotOptions(new LinePlotOptions()
.setLineWidth(LINE_WIDTH)
.setMarker(new Marker().setEnabled(false).setHoverState(new Marker().setEnabled(true).setRadius(4)))
.setShadow(false).setHoverStateLineWidth(LINE_WIDTH)
.setColor(competitorSelectionProvider.getColor(competitor)).setSelected(true));
dataSeriesByCompetitor.put(competitor, result);
}
return result;
}
return result;
}
private String getUnit() {
String unit = "";
switch (getDataToShow()) {
private String getDetailTypeUnit() {
String detailTypeUnit = "";
switch (getSelectedDetailType()) {
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
unit = getStringMessages().currentSpeedOverGroundInKnotsUnit();
detailTypeUnit = getStringMessages().currentSpeedOverGroundInKnotsUnit();
break;
case DISTANCE_TRAVELED:
unit = getStringMessages().distanceInMetersUnit();
detailTypeUnit = getStringMessages().distanceInMetersUnit();
break;
case GAP_TO_LEADER_IN_SECONDS:
unit = getStringMessages().gapToLeaderInSecondsUnit();
detailTypeUnit = getStringMessages().gapToLeaderInSecondsUnit();
break;
case VELOCITY_MADE_GOOD_IN_KNOTS:
unit = getStringMessages().velocityMadeGoodInKnotsUnit();
detailTypeUnit = getStringMessages().velocityMadeGoodInKnotsUnit();
break;
case WINDWARD_DISTANCE_TO_OVERALL_LEADER:
unit = getStringMessages().windwardDistanceToGoInMetersUnit();
detailTypeUnit = getStringMessages().windwardDistanceToGoInMetersUnit();
}
return unit;
return detailTypeUnit;
}
/**
*
* @param competitor
* @return A series in the chart, that can be used to show the mark passings.
*/
private Series getCompetitorMarkPassingSeries(CompetitorDTO competitor){
private Series getOrCreateCompetitorMarkPassingSeries(CompetitorDTO competitor) {
Series result = markPassingSeriesByCompetitor.get(competitor);
if (result == null) {
result = chart.createSeries().setType(Series.Type.SCATTER).setName(stringMessages.markPassing()+" "+competitor.name);
result.setPlotOptions(new ScatterPlotOptions().setColor(competitorSelectionProvider.getColor(competitor)).setSelected(true));
result = chart.createSeries().setType(Series.Type.SCATTER)
.setName(stringMessages.markPassing() + " " + competitor.name);
result.setPlotOptions(new ScatterPlotOptions().setColor(competitorSelectionProvider.getColor(competitor))
.setSelected(true));
markPassingSeriesByCompetitor.put(competitor, result);
}
return result;
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if(visible) {
// Workaround for a highcharts bug:
// Set a chart title, overwrite the title, switch chart to invisible and visible again -> the old title appears
chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType, stringMessages)),
null);
}
}
/**
* Clears the whole chart and empties cached data.
*/
@@ -457,7 +427,7 @@ implements CompetitorSelectionChangeListener, RequiresResize {
}
public String getLocalizedShortName() {
return DetailTypeFormatter.format(getDataToShow(), getStringMessages());
return DetailTypeFormatter.format(getSelectedDetailType(), getStringMessages());
}
public Widget getEntryWidget() {
@@ -467,14 +437,15 @@ implements CompetitorSelectionChangeListener, RequiresResize {
public boolean hasSettings() {
return true;
}
public ChartSettings getAbstractSettings() {
return new ChartSettings(getStepSize());
}
/**
* Updates the settings known to be contained in {@link ChartSettings}. Subclasses have to update settings provided
* by subclasses thereof. Subclasses also need to call {@link #clearChart()} and {@link #loadData(boolean)}, if this method returns <code>true</code>;
* by subclasses thereof. Subclasses also need to call {@link #clearChart()} and {@link #updateChart(boolean)}, if
* this method returns <code>true</code>;
*
* @return <code>true</code> if the settings had been changed and a clearing and loading is needed.
*/
@@ -498,38 +469,68 @@ implements CompetitorSelectionChangeListener, RequiresResize {
protected void setStepSize(long stepSize) {
this.stepSize = stepSize;
}
protected DetailType getDataToShow() {
return this.dataToShow;
protected DetailType getSelectedDetailType() {
return this.selectedDetailType;
}
/**
* Updates the {@link #dataToShow} field, creates a new chart for the new <code>dataToShow</code> and clears the {@link #chartData}.<br />
* Doesn't {@link #loadData(boolean) load the data}.
* Updates the {@link #selectedDetailType} field, clears the chart for the new <code>selectedDetailType</code> and
* clears the {@link #chartData}.<br />
* Doesn't {@link #updateChart(boolean) load the data}.
*
* @return <code>true</code> if the data to show changed
* @return <code>true</code> if the detail type changed
*/
protected boolean setDataToShow(DetailType dataToShow) {
if (dataToShow != this.dataToShow) {
this.dataToShow = dataToShow;
setChartData(null);
chart = createChart(dataToShow);
setWidget(chart);
return true;
} else {
return false;
protected boolean setSelectedDetailType(DetailType newSelectedDetailType) {
boolean hasDetailTypeChanged = newSelectedDetailType != this.selectedDetailType;
if (hasDetailTypeChanged) {
this.selectedDetailType = newSelectedDetailType;
clearChart();
chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType, stringMessages)),
null);
final String unit = getDetailTypeUnit();
if (!compactChart) {
chart.getYAxis().setAxisTitleText(
DetailTypeFormatter.format(selectedDetailType, stringMessages) + " [" + unit + "]");
} else {
chart.getYAxis().setAxisTitleText("[" + unit + "]");
}
chart.getYAxis()
.setReversed(
(selectedDetailType == DetailType.WINDWARD_DISTANCE_TO_OVERALL_LEADER || selectedDetailType == DetailType.GAP_TO_LEADER_IN_SECONDS) ? true
: false);
final NumberFormat numberFormat = DetailTypeFormatter.getNumberFormat(selectedDetailType);
chart.setToolTip(new ToolTip().setEnabled(true).setFormatter(new ToolTipFormatter() {
@Override
public String format(ToolTipData toolTipData) {
String seriesName = toolTipData.getSeriesName();
if (seriesName.equals(stringMessages.time())) {
return "<b>" + seriesName + ":</b> " + dateFormat.format(new Date(toolTipData.getXAsLong()))
+ "<br/>(" + stringMessages.clickChartToSetTime() + ")";
} else {
return "<b>" + seriesName
+ (toolTipData.getPointName() != null ? " " + toolTipData.getPointName() : "")
+ "</b><br/>" + dateFormat.format(new Date(toolTipData.getXAsLong())) + ": "
+ numberFormat.format(toolTipData.getYAsDouble()) + " " + unit;
}
}
}));
}
return hasDetailTypeChanged;
}
protected MultiCompetitorRaceDataDTO getChartData() {
return chartData;
}
protected void setChartData(MultiCompetitorRaceDataDTO chartData) {
this.chartData = chartData;
}
@Override
public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
if (selectedRaces != null && !selectedRaces.isEmpty()) {
@@ -539,8 +540,8 @@ implements CompetitorSelectionChangeListener, RequiresResize {
}
clearChart();
if(selectedRaceIdentifier != null) {
loadData(true);
if (selectedRaceIdentifier != null) {
timeChanged(timer.getTime());
}
}
@@ -580,28 +581,56 @@ implements CompetitorSelectionChangeListener, RequiresResize {
}
}
}
return new Pair<Boolean, Boolean>(everyPassingInRange, twoPassingsInRangeBeforeError);
}
@Override
public void timeChanged(Date date) {
if (getChartData() != null) {
Date newestEvent = getChartData().getOldestDateOfNewestData();
if ((newestEvent == null || (newestEvent.before(date) && (date.getTime() - newestEvent.getTime()) >= getStepSize()))) {
loadData(false);
if (!isVisible()) {
return;
}
switch (timer.getPlayMode()) {
case Live: {
boolean showBusyIndicator = false;
// is date before first cache entry or is cache empty?
if (timeOfEarliestRequestInMillis == null || date.getTime() < timeOfEarliestRequestInMillis) {
updateChart(timeRangeWithZoomProvider.getFromTime(), date, /* append */true, showBusyIndicator);
} else if (date.getTime() > timeOfLatestRequestInMillis) {
updateChart(new Date(timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */
true, showBusyIndicator);
}
// otherwise the cache spans across date and so we don't need to load anything
break;
}
case Replay: {
boolean showBusyIndicator = true;
if (timeOfLatestRequestInMillis == null) {
// pure replay mode
updateChart(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime(), /* append */
false, showBusyIndicator);
} else {
// replay mode during live play
if (timeOfEarliestRequestInMillis == null || date.getTime() < timeOfEarliestRequestInMillis) {
updateChart(timeRangeWithZoomProvider.getFromTime(), date, /* append */true, showBusyIndicator);
} else if (date.getTime() > timeOfLatestRequestInMillis) {
updateChart(new Date(timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */
true, showBusyIndicator);
}
}
break;
}
}
}
@Override
public void onResize() {
if (getChartData() != null) {
chart.setSizeToMatchContainer();
// it's important here to recall the redraw method, otherwise the bug fix for wrong checkbox positions (nativeAdjustCheckboxPosition)
// in the BaseChart class would not be called
chart.redraw();
}
chart.setSizeToMatchContainer();
// it's important here to recall the redraw method, otherwise the bug fix for wrong checkbox positions
// (nativeAdjustCheckboxPosition)
// in the BaseChart class would not be called
chart.redraw();
}
@Override
@@ -4,16 +4,16 @@ import com.google.gwt.user.client.ui.DoubleBox;
import com.google.gwt.user.client.ui.FocusWidget;
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.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public abstract class AbstractChartSettingsComponent<SettingsType extends ChartSettings> implements SettingsDialogComponent<SettingsType> {
private DoubleBox stepSizeBox;
private final ChartSettings settings;
private final StringMessages stringMessages;
protected DoubleBox stepSizeBox;
protected final StringMessages stringMessages;
public AbstractChartSettingsComponent(ChartSettings settings, StringMessages stringMessages) {
this.settings = settings;
@@ -21,18 +21,14 @@ public abstract class AbstractChartSettingsComponent<SettingsType extends ChartS
}
@Override
public VerticalPanel getAdditionalWidget(DataEntryDialog<?> dialog) {
VerticalPanel panel = new VerticalPanel();
panel.add(new Label(getStringMessages().stepSizeInSeconds()));
public Widget getAdditionalWidget(DataEntryDialog<?> dialog) {
VerticalPanel mainPanel = new VerticalPanel();
mainPanel.add(new Label(stringMessages.stepSizeInSeconds()));
stepSizeBox = dialog.createDoubleBox(((double) settings.getStepSize()) / 1000, 5);
panel.add(stepSizeBox);
return panel;
mainPanel.add(stepSizeBox);
return mainPanel;
}
protected DoubleBox getStepSizeBox() {
return stepSizeBox;
}
@Override
public Validator<SettingsType> getValidator() {
return new Validator<SettingsType>() {
@@ -40,7 +36,7 @@ public abstract class AbstractChartSettingsComponent<SettingsType extends ChartS
public String getErrorMessage(SettingsType valueToValidate) {
String errorMessage = null;
if (valueToValidate.getStepSize() < 1) {
errorMessage = getStringMessages().stepSizeMustBeGreaterThanNull();
errorMessage = stringMessages.stepSizeMustBeGreaterThanNull();
}
return errorMessage;
}
@@ -52,14 +48,14 @@ public abstract class AbstractChartSettingsComponent<SettingsType extends ChartS
return stepSizeBox;
}
protected StringMessages getStringMessages() {
return stringMessages;
}
public ChartSettings getAbstractResult() {
Double valueInSeconds = getStepSizeBox().getValue();
Long value = valueInSeconds == null ? 0 : (long) (getStepSizeBox().getValue() * 1000);
Double valueInSeconds = stepSizeBox.getValue();
Long value = valueInSeconds == null ? 0 : (long) (stepSizeBox.getValue() * 1000);
return new ChartSettings(value);
}
public ChartSettings getSettings() {
return settings;
}
}
@@ -47,7 +47,7 @@ public class ChartPanel extends AbstractChartPanel<ChartSettings> implements Com
public void updateSettings(ChartSettings newSettings) {
updateSettingsOnly(newSettings);
clearChart();
loadData(true);
timeChanged(timer.getTime());
}
@Override
@@ -71,8 +71,11 @@ public class LeaderboardRankChart extends SimplePanel implements RequiresResize,
.setMarginRight(65)
.setWidth100()
.setHeight100()
.setBorderColor(new Color("#A6A6A6"))
.setBorderColor(new Color("#CACACA"))
.setBorderWidth(1)
.setBorderRadius(0)
.setBackgroundColor(new Color("#EBEBEB"))
.setPlotBorderWidth(0)
.setCredits(new Credits().setEnabled(false))
.setChartTitle(new ChartTitle().setText(stringMessages.rank()))
.setLinePlotOptions(new LinePlotOptions().setLineWidth(LINE_WIDTH).setMarker(
@@ -17,8 +17,8 @@ import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
* leader) for different races in a chart. The chart type can be selected from the settings.
*
* When calling the constructor a chart is created that creates a final amount of series (so the maximum number of
* competitors cannot be changed in one chart) which are connected to competitors, when the sailing service returns the
* data. So {@code seriesID, competitorID and markSeriesID} are linked with the index. So if u know for example the
* competitors cannot be changed in one chart) which are connected to competitors, when the SailingService returns the
* data. So {@code seriesID, competitorID and markSeriesID} are linked with the index. So if you know for example the
* seriesID-index, you can get the competitor by calling competitorID.get(index).
*
* @author Benjamin Ebling (D056866), Axel Uhl (d043530)
@@ -35,16 +35,15 @@ public class MultiChartPanel extends AbstractChartPanel<MultiChartSettings> impl
@Override
public SettingsDialogComponent<MultiChartSettings> getSettingsDialogComponent() {
return new MultiChartSettingsComponent(new MultiChartSettings(getAbstractSettings(), getDataToShow()), getStringMessages());
return new MultiChartSettingsComponent(new MultiChartSettings(getAbstractSettings(), getSelectedDetailType()), getStringMessages());
}
@Override
public void updateSettings(MultiChartSettings newSettings) {
boolean settingsChanged = updateSettingsOnly(newSettings);
boolean dataToShowChanged = setDataToShow(newSettings.getDataToShow());
if (dataToShowChanged || settingsChanged) {
clearChart();
loadData(true);
boolean selectedDetailTypeChanged = setSelectedDetailType(newSettings.getDetailType());
if (selectedDetailTypeChanged || settingsChanged) {
timeChanged(timer.getTime());
}
}
@@ -3,14 +3,14 @@ package com.sap.sailing.gwt.ui.shared.charts;
import com.sap.sailing.domain.common.DetailType;
public class MultiChartSettings extends ChartSettings {
private final DetailType dataToShow;
public MultiChartSettings(ChartSettings superResult, DetailType dataToShow) {
private final DetailType detailType;
public MultiChartSettings(ChartSettings superResult, DetailType detailType) {
super(superResult);
this.dataToShow = dataToShow;
this.detailType = detailType;
}
public DetailType getDataToShow() {
return dataToShow;
public DetailType getDetailType() {
return detailType;
}
}
@@ -1,8 +1,12 @@
package com.sap.sailing.gwt.ui.shared.charts;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.DetailTypeFormatter;
@@ -11,44 +15,60 @@ import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class MultiChartSettingsComponent extends AbstractChartSettingsComponent<MultiChartSettings> implements
SettingsDialogComponent<MultiChartSettings> {
private ListBox dataSelection;
private final DetailType initialDataToShow;
private ListBox chartTypeSelectionListBox;
private final DetailType initialDetailType;
private final List<DetailType> availableDetailsTypes;
public MultiChartSettingsComponent(MultiChartSettings settings, StringMessages stringMessages) {
super(settings, stringMessages);
this.initialDataToShow = settings.getDataToShow();
this.initialDetailType = settings.getDetailType();
availableDetailsTypes = new ArrayList<DetailType>();
availableDetailsTypes.add(DetailType.WINDWARD_DISTANCE_TO_OVERALL_LEADER);
availableDetailsTypes.add(DetailType.DISTANCE_TRAVELED);
availableDetailsTypes.add(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS);
availableDetailsTypes.add(DetailType.GAP_TO_LEADER_IN_SECONDS);
availableDetailsTypes.add(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
availableDetailsTypes.add(DetailType.RACE_RANK);
}
@Override
public VerticalPanel getAdditionalWidget(DataEntryDialog<?> dialog) {
VerticalPanel configPanel = super.getAdditionalWidget(dialog);
configPanel.setSpacing(5);
Label lblChart = new Label(getStringMessages().chooseChart());
configPanel.add(lblChart);
dataSelection = dialog.createListBox(/* isMultiSelect */ false);
public Widget getAdditionalWidget(DataEntryDialog<?> dialog) {
VerticalPanel mainPanel = new VerticalPanel();
mainPanel.setSpacing(5);
Label chartSelectionLabel = new Label(stringMessages.chooseChart());
mainPanel.add(chartSelectionLabel);
chartTypeSelectionListBox = dialog.createListBox(/* isMultiSelect */ false);
int i=0;
for (DetailType detailType : new DetailType[] { DetailType.WINDWARD_DISTANCE_TO_OVERALL_LEADER, DetailType.DISTANCE_TRAVELED,
DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, DetailType.GAP_TO_LEADER_IN_SECONDS, DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_RANK }) {
dataSelection.addItem(DetailTypeFormatter.format(detailType, getStringMessages()), detailType.toString());
if (detailType == initialDataToShow) {
dataSelection.setSelectedIndex(i);
for (DetailType detailType : availableDetailsTypes) {
chartTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType, stringMessages), detailType.name());
if (detailType == initialDetailType) {
chartTypeSelectionListBox.setSelectedIndex(i);
}
i++;
}
configPanel.add(dataSelection);
return configPanel;
mainPanel.add(chartTypeSelectionListBox);
mainPanel.add(new Label(stringMessages.stepSizeInSeconds()));
stepSizeBox = dialog.createDoubleBox(((double) getSettings().getStepSize()) / 1000, 5);
mainPanel.add(stepSizeBox);
return mainPanel;
}
@Override
public MultiChartSettings getResult() {
DetailType dataToShow = null;
for (DetailType dt : DetailType.values()){
if (dt.toString().equals(dataSelection.getValue(dataSelection.getSelectedIndex()))){
dataToShow = dt;
DetailType newDetailType = null;
int selectedIndex = chartTypeSelectionListBox.getSelectedIndex();
String selectedDetailType = chartTypeSelectionListBox.getValue(selectedIndex);
for (DetailType detailType : availableDetailsTypes){
if (detailType.name().equals(selectedDetailType)){
newDetailType = detailType;
break;
}
}
return new MultiChartSettings(getAbstractResult(), dataToShow);
return new MultiChartSettings(getAbstractResult(), newDetailType);
}
}
@@ -101,8 +101,11 @@ public class WindChart extends RaceChart implements Component<WindChartSettings>
.setMarginRight(65)
.setWidth100()
.setHeight100()
.setBorderColor(new Color("#A6A6A6"))
.setBorderColor(new Color("#CACACA"))
.setBorderWidth(1)
.setBorderRadius(0)
.setBackgroundColor(new Color("#EBEBEB"))
.setPlotBorderWidth(0)
.setCredits(new Credits().setEnabled(false))
.setChartTitle(new ChartTitle().setText(stringMessages.wind()))
.setChartSubtitle(new ChartSubtitle().setText(stringMessages.clickAndDragToZoomIn()))
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.ui.shared.racemap;
import com.google.gwt.maps.client.overlay.Icon;
import com.sap.sailing.domain.common.MarkType;
/**
* A descriptor class for display properties of a mark icon.
@@ -10,9 +11,11 @@ public class MarkIconDescriptor {
private final String color;
private final String shape;
private final String pattern;
private final MarkType type;
public MarkIconDescriptor(Icon icon, String color, String shape, String pattern) {
public MarkIconDescriptor(Icon icon, MarkType type, String color, String shape, String pattern) {
this.icon = icon;
this.type = type;
this.color = color;
this.shape = shape;
this.pattern = pattern;
@@ -33,14 +36,24 @@ public class MarkIconDescriptor {
public String getPattern() {
return pattern;
}
public MarkType getType() {
return type;
}
/**
* An utility method to check if the mark icon has compatible display properties.
* In case of null values at least one valid display property must exist and fit.
*/
public boolean isCompatible(String colorToCheck, String shapeToCheck, String patternToCheck) {
public boolean isCompatible(MarkType typeToCheck, String colorToCheck, String shapeToCheck, String patternToCheck) {
boolean result = false;
if((typeToCheck != null && type == null) || (typeToCheck == null && type != null)) {
result = false;
} else if(typeToCheck != null && type != null) {
result = type == typeToCheck;
}
if((colorToCheck != null && color == null) || (colorToCheck == null && color != null)) {
result = false;
} else if(colorToCheck != null && color != null) {
@@ -953,7 +953,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
protected Marker createCourseMarkMarker(final MarkDTO markDTO) {
MarkerOptions options = MarkerOptions.newInstance();
final Icon markIcon = raceMapImageManager.resolveMarkIcon(markDTO.color, markDTO.shape, markDTO.pattern);
final Icon markIcon = raceMapImageManager.resolveMarkIcon(markDTO.type, markDTO.color, markDTO.shape, markDTO.pattern);
if (markIcon != null) {
options.setIcon(markIcon);
}
@@ -12,6 +12,7 @@ import com.google.gwt.maps.client.geom.Size;
import com.google.gwt.maps.client.overlay.Icon;
import com.google.gwt.resources.client.ImageResource;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.impl.Util.Pair;
@@ -47,11 +48,11 @@ public class RaceMapImageManager {
expeditionWindIconTransformer = new ImageTransformer(resources.expeditionWindIcon());
}
public Icon resolveMarkIcon(String color, String shape, String pattern) {
public Icon resolveMarkIcon(MarkType type, String color, String shape, String pattern) {
Icon result = defaultCourseMarkIcon;
for (MarkIconDescriptor iconDescriptor: markIconDescriptors) {
if(iconDescriptor.isCompatible(color, shape, pattern)) {
if(iconDescriptor.isCompatible(type, color, shape, pattern)) {
result = iconDescriptor.getIcon();
break;
}
@@ -70,16 +71,22 @@ public class RaceMapImageManager {
defaultCourseMarkIcon.setIconSize(Size.newInstance(19, 28));
defaultCourseMarkIcon.setIconAnchor(Point.newInstance(6, 15));
createMarkIconDescriptor(resources.buoyRedIcon(), "red", null, null);
createMarkIconDescriptor(resources.buoyGreenIcon(), "green", null, null);
createMarkIconDescriptor(resources.buoyYellowIcon(), "yellow", null, null);
createMarkIconDescriptor(resources.buoyGreyIcon(), "grey", null, null);
createMarkIconDescriptor(resources.buoyWhiteIcon(), "white", null, null);
createMarkIconDescriptor(resources.buoyWhiteConeIcon(), "white", "conical", null);
createMarkIconDescriptor(resources.buoyBlackIcon(), "black", null, null);
createMarkIconDescriptor(resources.buoyBlackConeIcon(), "black", "conical", null);
createMarkIconDescriptor(resources.buoyDarkOrangeIcon(), "orange", null, null);
createMarkIconDescriptor(resources.buoyBlackFinishIcon(), "black", "cylinder", "checkered");
createMarkIconDescriptor(resources.buoyRedIcon(), MarkType.BUOY, "red", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyGreenIcon(), MarkType.BUOY, "green", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyYellowIcon(), MarkType.BUOY, "yellow", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyGreyIcon(), MarkType.BUOY, "grey", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyWhiteIcon(), MarkType.BUOY, "white", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyWhiteConeIcon(), MarkType.BUOY, "white", "conical", null, 6, 20);
createMarkIconDescriptor(resources.buoyBlackIcon(), MarkType.BUOY, "black", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyBlackConeIcon(), MarkType.BUOY, "black", "conical", null, 6, 20);
createMarkIconDescriptor(resources.buoyDarkOrangeIcon(), MarkType.BUOY, "orange", null, null, 6, 20);
createMarkIconDescriptor(resources.buoyBlackFinishIcon(), MarkType.BUOY, "black", "cylinder", "checkered", 6, 20);
createMarkIconDescriptor(resources.cameraBoatIcon(), MarkType.CAMERABOAT, null, null, null, 35, 20);
createMarkIconDescriptor(resources.umpireBoatIcon(), MarkType.UMPIREBOAT, null, null, null, 35, 20);
createMarkIconDescriptor(resources.startBoatIcon(), MarkType.STARTBOAT, null, null, null, 35, 20);
createMarkIconDescriptor(resources.landmarkIcon(), MarkType.LANDMARK, null, null, null, 6, 15);
Icon tackToStarboardIcon = Icon
.newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|00FF00|000000");
@@ -137,11 +144,12 @@ public class RaceMapImageManager {
}
}
private void createMarkIconDescriptor(ImageResource imgResource, String color, String shape, String pattern) {
private void createMarkIconDescriptor(ImageResource imgResource, MarkType type, String color, String shape, String pattern,
int anchorPointX, int anchorPointY) {
Icon icon = Icon.newInstance(imgResource.getSafeUri().asString());
icon.setIconSize(Size.newInstance(19, 28));
icon.setIconAnchor(Point.newInstance(6, 15));
MarkIconDescriptor markIconDescriptor = new MarkIconDescriptor(icon, color, shape, pattern);
//icon.setIconSize(Size.newInstance(19, 28));
icon.setIconAnchor(Point.newInstance(anchorPointX, anchorPointY));
MarkIconDescriptor markIconDescriptor = new MarkIconDescriptor(icon, type, color, shape, pattern);
markIconDescriptors.add(markIconDescriptor);
}
@@ -38,9 +38,17 @@ interface RaceMapResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/buoys/buoy_green.png")
ImageResource buoyGreenIcon();
@Source("com/sap/sailing/gwt/ui/client/images/buoys/landmark.png")
ImageResource landmarkIcon();
@Source("com/sap/sailing/gwt/ui/client/images/cameraBoat.png")
ImageResource cameraBoatIcon();
@Source("com/sap/sailing/gwt/ui/client/images/juryBoat.png")
ImageResource juryBoatIcon();
@Source("com/sap/sailing/gwt/ui/client/images/umpireBoat.png")
ImageResource umpireBoatIcon();
@Source("com/sap/sailing/gwt/ui/client/images/startBoat.png")
ImageResource startBoatIcon();
@Source("com/sap/sailing/gwt/ui/client/images/wind_combined.png")
ImageResource combinedWindIcon();
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,20 +1,83 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!DOCTYPE html>
<html>
<head>
<title>SAP Sailing Analytics - Extreme Sailing Series 2012</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="content-style-type" content="text/css" />
<link rel="shortcut icon" type="image/x-icon" href="/sap.ico" />
<link href="../../fontface.css" media="screen" rel="stylesheet" title="Default stylesheet" type="text/css" />
<link href="../main.css" media="screen" rel="stylesheet" title="Default stylesheet" type="text/css" />
<link href='../fontface.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link href='../site.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<title>Welcome to the Sailing Analytics Homepage</title>
</head>
<!-- start body -->
<body>
<h2>SAP Sailing Analytics - Extreme Sailing Series 2012</h2>
<p>
<a href="http://ess40-2012.sapsailing.com/gwt/Leaderboard.html?name=Extreme%2040%20Rio&showRaceDetails=true">Live Rio Leaderboard</a>
</p>
<p>
<a href="http://ess40-2012.sapsailing.com/gwt/Leaderboard.html?name=Extreme%20Sailing%20Series%202012%20Overall&showRaceDetails=true&displayName=Overall%20Standings&leaderboardGroupName=Extreme%20Sailing%20Series%202012&root=leaderboardGroupPanel">Live Overall Leaderboard</a>
</p>
<!-- = partial "info_message" -->
<!-- start yield_content -->
<div id='yield_content'>
<!-- start header -->
<!-- start logo and title panel _header -->
<div id='logoAndTitlePanel'>
<a class='sapLogo'>
<img alt='SAP Website' src='../images/sap-logo.png' />
</a>
</div>
<!-- end header -->
<!-- start contentWrapper -->
<div id='contentWrapper'>
<!-- start keyVisual -->
<div id='keyVisual'>
<img alt='Sailer' src='../images/key-visual.png' />
<div id='infoBox'>
<h1 id='pageHeadline'>Extreme Sailing Series 2012</h1>
<p>
Understanding what happens out on the race course isn't always easy.
To help solve this challenge and bring the excitement of sailing to the fans,
we have developed a leader board based on SAP analytics.
</p>
</div>
</div>
<!-- end keyVisual -->
<!-- start breadcrumb -->
<!-- = partial "breadcrumb" -->
<!-- end breadcrumb -->
<!-- start yield -->
<div id='mainContent'>
<!-- = partial "former_events" -->
<h2 class='articleHeadline'>Act 8 - Rio de Janeiro, Brazil</h2>
<div class='innerContent'>
<h3 class='articleSubheadline'>6th - 9th December</h3>
<div class='leaderBoardGroup'>
<a class='btn-cta' href='http://ess40-2012.sapsailing.com/gwt/Leaderboard.html?name=Extreme%2040%20Rio&amp;showRaceDetails=true'>
<span>Live Rio Act Leaderboard</span>
</a>
<div class='clearer'></div>
<a class='btn-cta' href='http://ess40-2012.sapsailing.com/gwt/Leaderboard.html?name=Extreme%20Sailing%20Series%202012%20Overall&amp;showRaceDetails=true&amp;displayName=Overall%20Standings&amp;leaderboardGroupName=Extreme%20Sailing%20Series%202012&amp;root=leaderboardGroupPanel'>
<span>Live Series Leaderboard</span>
</a>
</div>
</div>
</div>
<div id='spaceBar'></div>
<!-- end yield -->
</div>
<!-- end contentWrapper -->
</div>
<!-- end yield_content -->
<!-- start footer -->
<div id='footer'>
<ul class='regularNav'>
<li>
<a href='http://www12.sap.com/corporate-en/legal/impressum.epx' target='_blank'>
Legal Disclosure
</a>
<span>
|
</span>
</li>
<li>
<a href='http://www12.sap.com/corporate-en/our-company/legal/privacy.epx' target='_blank'>
Privacy
</a>
</li>
</ul>
</div>
<!-- end footer -->
</body>
<!-- end body -->
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

-84
View File
@@ -1,84 +0,0 @@
/* line 2, _reset.sass */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline; }
/* HTML5 display-role reset for older browsers */
/* line 23, _reset.sass */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block; }
/* line 27, _reset.sass */
body {
line-height: 1; }
/* line 30, _reset.sass */
ol, ul {
list-style: none; }
/* line 33, _reset.sass */
blockquote, q {
quotes: none; }
/* line 36, _reset.sass */
blockquote:before, blockquote:after,
q:before, q:after {
content: "";
content: none; }
/* line 41, _reset.sass */
table {
border-collapse: collapse;
border-spacing: 0; }
/* line 7, site.css.sass */
html {
height: 100%; }
/* line 10, site.css.sass */
body {
height: 100%;
color: #222222;
font-size: 14px;
font-family: OpenSansRegular, Arial, Verdana, sans-serif;
font-weight: normal;
margin: 0px;
padding: 0px;
line-height: 18px; }
/* line 27, site.css.sass */
ul {
font-size: 12px;
padding-left: 0px;
margin-bottom: 25px;
margin-left: 30px;
}
/* line 34, site.css.sass */
a {
text-decoration: none;
color: #0066b3;
}
/* line 38, site.css.sass */
a:hover {
text-decoration: underline;
}
+298
View File
@@ -0,0 +1,298 @@
/* line 2, _reset.sass */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
/* line 23, _reset.sass */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
/* line 27, _reset.sass */
body {
line-height: 1;
}
/* line 30, _reset.sass */
ol, ul {
list-style: none;
}
/* line 33, _reset.sass */
blockquote, q {
quotes: none;
}
/* line 36, _reset.sass */
blockquote:before, blockquote:after,
q:before, q:after {
content: "";
content: none;
}
/* line 41, _reset.sass */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* line 2, _default.sass */
html {
height: 100%;
}
/* line 5, _default.sass */
body {
height: 100%;
color: #222222;
font-size: 12px;
font-family: OpenSansRegular, Arial, Verdana, sans-serif;
font-weight: normal;
background-image: url(images/bg-image.jpg);
background-attachment: fixed;
background-color: #222222;
background-position: 50% 0%;
background-repeat: no-repeat;
margin: 0px;
padding: 0px;
line-height: 18px;
}
/* line 22, _default.sass */
ul {
font-size: 12px;
padding-left: 0px;
margin-bottom: 25px;
margin-left: 30px;
}
/* line 29, _default.sass */
a {
text-decoration: none;
color: #0066b3;
}
/* line 33, _default.sass */
a:hover {
text-decoration: underline;
}
/* line 38, _helpers.sass */
.clearer {
clear: both;
font-size: 1px;
height: 0px;
line-height: 0px;
margin: -1px 0 0;
width: 100%;
}
/* line 14, site.css.sass */
#infoMessage {
display: none;
color: white;
}
/* line 17, site.css.sass */
#infoMessage .center {
padding: 10px 0;
width: 950px;
margin: 0 auto;
}
/* line 22, site.css.sass */
#yield_content {
width: 950px;
margin: 0 auto;
min-height: 100%;
}
/* line 2, _key_visual.sass */
#yield_content #keyVisual {
height: 240px;
position: relative;
background: white;
}
/* line 7, _key_visual.sass */
#yield_content #keyVisual #infoBox {
width: 502px;
height: 80px;
background: #999999;
position: absolute;
right: 0;
bottom: 14px;
padding: 10px;
}
/* line 16, _key_visual.sass */
#yield_content #keyVisual #infoBox p {
color: white;
font-size: 12px;
}
/* line 21, _key_visual.sass */
#yield_content #keyVisual #infoBox #pageHeadline {
color: white;
margin: 0 0 10px 0;
font-size: 18px;
}
/* line 2, _breadcrumb.sass */
#yield_content #breadcrumb {
height: 23px;
background: #e5e5e5;
margin: 2px 0 8px;
padding: 0;
}
/* line 8, _breadcrumb.sass */
#yield_content #breadcrumb .breadcrumbList {
margin: 0;
padding: 0 10px;
float: left;
}
/* line 12, _breadcrumb.sass */
#yield_content #breadcrumb .breadcrumbList li {
height: 23px;
float: left;
background: url(images/breadcrumb-seperator.png) right center no-repeat;
margin: 0 10px 0 0;
}
/* line 17, _breadcrumb.sass */
#yield_content #breadcrumb .breadcrumbList li:last-child {
background: none;
}
/* line 19, _breadcrumb.sass */
#yield_content #breadcrumb .breadcrumbList li a {
display: block;
margin: 2px 10px 0 0;
}
/* line 3, _main_content.sass */
#yield_content #mainContent {
padding: 0 10px 10px 10px;
background: white;
*display: inline-block;
}
/* line 3, _helpers.sass */
#yield_content #mainContent:after {
content: " ";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
/* line 2, _former_events.sass */
#yield_content #mainContent #formerEvents {
margin: 0 0 20px 0;
font-size: 12px;
padding: 10px 0 0 0;
float: left;
}
/* line 8, _former_events.sass */
#yield_content #mainContent #formerEvents dt {
margin: 0 5px 0 0;
font-family: OpenSansBold, Arial, Verdana, sans-serif;
}
/* line 12, _former_events.sass */
#yield_content #mainContent #formerEvents dt, #yield_content #mainContent #formerEvents dd {
float: left;
}
/* line 14, _former_events.sass */
#yield_content #mainContent #formerEvents dd {
margin: 0 5px 0 0;
}
/* line 9, _main_content.sass */
#yield_content #mainContent .articleHeadline {
font-size: 18px;
font-family: OpenSansBold, Arial, Verdana, sans-serif;
color: #222222;
border-bottom: 2px solid #222222;
clear: both;
padding: 10px 0 5px;
margin: 0 0 15px;
}
/* line 19, _main_content.sass */
#yield_content #mainContent .articleSubheadline {
font-size: 14px;
font-family: OpenSansBold, Arial, Verdana, sans-serif;
color: #222222;
margin: 0 0 6px 0;
}
/* line 26, _main_content.sass */
#yield_content #mainContent h4 {
margin: 0 0 10px;
font-size: 12px;
font-family: OpenSansBold, Arial, Verdana, sans-serif;
}
/* line 33, _main_content.sass */
#yield_content #mainContent a.btn-cta {
background: url("images/btn-cta-orangeright.png") 100% 0% no-repeat;
display: block;
float: left;
line-height: 30px;
padding: 0px 25px 0px 0px;
text-decoration: none;
margin: 0 0 10px 0;
}
/* line 41, _main_content.sass */
#yield_content #mainContent a.btn-cta:hover {
background-position: 100% -30px;
text-decoration: none !important;
}
/* line 44, _main_content.sass */
#yield_content #mainContent a.btn-cta:hover span {
background-position: 0% -30px;
text-decoration: none !important;
}
/* line 48, _main_content.sass */
#yield_content #mainContent a.btn-cta span {
background: url("images/btn-cta-orangeleft.png") no-repeat transparent;
color: white;
cursor: pointer;
display: block;
float: left;
height: 30px;
line-height: 30px;
padding: 0px 0px 0px 17px;
text-decoration: none;
}
/* line 31, site.css.sass */
#yield_content #spaceBar {
height: 80px;
}
/* line 2, _footer.sass */
#footer {
color: white;
width: 940px;
height: 20px;
margin: -40px auto 0 auto;
background: url(images/bkg-footer1.png);
padding: 10px;
font-size: 11px;
}
/* line 11, _footer.sass */
#footer a {
color: white;
font-size: 11px;
}
/* line 16, _footer.sass */
#footer .regularNav {
margin: 0;
}
/* line 18, _footer.sass */
#footer .regularNav li {
display: inline;
}