Merge branch 'gwt' into gwtracemapdouglas

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceMapPanel.java
This commit is contained in:
Fabian Schwarz-Fritz committed 2011-11-29 10:44:05 +01:00
commit 46608a237c
27 files changed
+513 -82

No files matched your search

@@ -7,6 +7,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import org.junit.Before;
@@ -19,7 +20,6 @@ import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MeterDistance;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -53,18 +53,18 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends KielWeek2011
Iterator<MarkPassing> hassosMarkPassingsIter = hassosMarkPassings.iterator();
TimePoint startMarkPassing = hassosMarkPassingsIter.next().getTimePoint();
TimePoint firstWindwardMarkPassing = hassosMarkPassingsIter.next().getTimePoint();
GPSFix[] firstLegFineApproximation = dp.approximate(new MeterDistance(20), startMarkPassing, firstWindwardMarkPassing);
List<GPSFixMoving> firstLegFineApproximation = dp.approximate(new MeterDistance(20), startMarkPassing, firstWindwardMarkPassing);
assertNotNull(firstLegFineApproximation);
assertEquals(9, firstLegFineApproximation.length);
GPSFix[] firstLegCoarseApproximation = dp.approximate(new MeterDistance(50), startMarkPassing, firstWindwardMarkPassing);
assertEquals(9, firstLegFineApproximation.size());
List<GPSFixMoving> firstLegCoarseApproximation = dp.approximate(new MeterDistance(50), startMarkPassing, firstWindwardMarkPassing);
assertNotNull(firstLegCoarseApproximation);
assertEquals(4, firstLegCoarseApproximation.length);
assertEquals(4, firstLegCoarseApproximation.size());
TimePoint leewardGatePassing = hassosMarkPassingsIter.next().getTimePoint();
GPSFix[] secondLegFineApproximation = dp.approximate(new MeterDistance(20), firstWindwardMarkPassing, leewardGatePassing);
List<GPSFixMoving> secondLegFineApproximation = dp.approximate(new MeterDistance(20), firstWindwardMarkPassing, leewardGatePassing);
assertNotNull(secondLegFineApproximation);
assertEquals(7, secondLegFineApproximation.length);
GPSFix[] secondLegCoarseApproximation = dp.approximate(new MeterDistance(50), firstWindwardMarkPassing, leewardGatePassing);
assertEquals(7, secondLegFineApproximation.size());
List<GPSFixMoving> secondLegCoarseApproximation = dp.approximate(new MeterDistance(50), firstWindwardMarkPassing, leewardGatePassing);
assertNotNull(secondLegCoarseApproximation);
assertEquals(3, secondLegCoarseApproximation.length);
assertEquals(3, secondLegCoarseApproximation.size());
}
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.test.mock;
import java.util.List;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.BoatClass;
@@ -405,4 +406,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
// TODO Auto-generated method stub
return null;
}
}
@@ -1,4 +1,16 @@
package com.sap.sailing.domain.base;
public interface BoatClass extends Named {
long getApproximateManeuverDurationInMilliseconds();
/**
* If the averaged courses over ground differ by at least this degree angle, a maneuver will
* be assumed. Note that this should be much less than the tack angle because averaging may
* span across the actual maneuver.
*/
double getManeuverDegreeAngleThreshold();
double getMinimumAngleBetweenDifferentTacksDownwind();
double getMinimumAngleBetweenDifferentTacksUpwind();
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.tracking.GPSFix;
/**
* A course change happens at a position at a time and may change the course over ground as well as the speed over ground.
*
* @author Axel Uhl (d043530)
*
*/
public interface CourseChange extends GPSFix {
double getCourseChangeInDegrees();
double getSpeedChangeInKnots();
/**
* @return the side ({@link Tack#STARBOARD} or {@link Tack#PORT} or <code>null</code>) to which the direction changed
*/
Tack to();
}
@@ -2,6 +2,12 @@ package com.sap.sailing.domain.base;
import com.sap.sailing.domain.base.impl.NauticalMileDistance;
/**
* A distance which can be converted to various units of measure. Can be negative.
*
* @author Axel Uhl (d043530)
*
*/
public interface Distance extends Comparable<Distance> {
static final Distance NULL = new NauticalMileDistance(0);
@@ -22,8 +28,9 @@ public interface Distance extends Comparable<Distance> {
Distance scale(double factor);
/**
* Computes the (undirected) average speed one has traveled at when passing
* this distance in the number of milliseconds specified.
* Computes the (undirected) average speed one has traveled at when passing this distance in the number of
* milliseconds specified. Sign-sensitive, meaning, e.g., that if this distance is negative and the time is positive
* then the resulting speed will be negative.
*/
Speed inTime(long milliseconds);
}
@@ -3,6 +3,12 @@ package com.sap.sailing.domain.base;
import com.sap.sailing.domain.base.impl.DegreeBearingImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
/**
* A speed, convertable in various units of measure. Can be negative.
*
* @author Axel Uhl (d043530)
*
*/
public interface Speed extends Comparable<Speed> {
final static Speed NULL = new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0));
@@ -16,7 +22,8 @@ public interface Speed extends Comparable<Speed> {
/**
* Traveling at this speed starting at time <code>from</code> until time </code>to</code>, how far have we traveled?
* If <code>to</code> is before </code>from</code>, the speed will be applied in reverse.
* If <code>to</code> is before </code>from</code>, the speed will be applied in reverse. If this speed has a negative
* amount then so will the resulting distance.
*/
Distance travel(TimePoint from, TimePoint to);
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.base;
public interface SpeedWithBearing extends Speed {
Bearing getBearing();
@@ -9,4 +10,6 @@ public interface SpeedWithBearing extends Speed {
* applied in reverse.
*/
Position travelTo(Position pos, TimePoint from, TimePoint to);
SpeedWithBearing applyCourseChange(CourseChange courseChange);
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
@@ -37,4 +38,33 @@ public abstract class AbstractSpeedWithBearingImpl extends AbstractSpeedImpl imp
return super.equals(object) && object instanceof SpeedWithBearing
&& getBearing().equals(((SpeedWithBearing) object).getBearing());
}
public static CourseChange getCourseChangeRequiredToReach(SpeedWithBearing from, SpeedWithBearing to, Position position, TimePoint timePoint) {
double courseChangeInDegrees = to.getBearing().getDegrees() - from.getBearing().getDegrees();
if (courseChangeInDegrees < -180.) {
courseChangeInDegrees += 360.;
} else if (courseChangeInDegrees > 180.) {
courseChangeInDegrees -= 360.;
}
double speedChangeInKnots = to.getKnots() - from.getKnots();
return new CourseChangeImpl(courseChangeInDegrees, speedChangeInKnots, position, timePoint);
}
@Override
public SpeedWithBearing applyCourseChange(CourseChange courseChange) {
return applyCourseChange(this, courseChange);
}
public static SpeedWithBearing applyCourseChange(SpeedWithBearing from, CourseChange courseChange) {
double newBearingDeg = from.getBearing().getDegrees() + courseChange.getCourseChangeInDegrees();
if (newBearingDeg < 0) {
newBearingDeg += 360;
} else if (newBearingDeg > 360) {
newBearingDeg -= 360;
}
Bearing newBearing = new DegreeBearingImpl(newBearingDeg);
double newSpeedInKnots = from.getKnots()+courseChange.getSpeedChangeInKnots();
return new KnotSpeedWithBearingImpl(newSpeedInKnots, newBearing);
}
}
@@ -3,9 +3,42 @@ package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.BoatClass;
public class BoatClassImpl extends NamedImpl implements BoatClass {
/**
* If the averaged courses over ground differ by at least this degree angle, a maneuver will
* be assumed. Note that this should be much less than the tack angle because averaging may
* span across the actual maneuver.
*/
private static final double MANEUVER_DEGREE_ANGLE_THRESHOLD = /* minimumDegreeDifference */ 30.;
private static final double MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_UPWIND = 45.;
private static final double MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_DOWNWIND = 15.;
private final long approximateManeuverDurationInMilliseconds;
public BoatClassImpl(String name) {
super(name);
approximateManeuverDurationInMilliseconds = 10000;
}
@Override
public long getApproximateManeuverDurationInMilliseconds() {
return approximateManeuverDurationInMilliseconds;
}
@Override
public double getManeuverDegreeAngleThreshold() {
return MANEUVER_DEGREE_ANGLE_THRESHOLD;
}
@Override
public double getMinimumAngleBetweenDifferentTacksUpwind() {
return MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_UPWIND;
}
@Override
public double getMinimumAngleBetweenDifferentTacksDownwind() {
return MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_DOWNWIND;
}
}
@@ -0,0 +1,34 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.Tack;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
public class CourseChangeImpl extends GPSFixImpl implements CourseChange {
private final double courseChangeInDegrees;
private final double speedChangeInKnots;
public CourseChangeImpl(double courseChangeInDegrees, double speedChangeInKnots, Position position, TimePoint timePoint) {
super(position, timePoint);
this.courseChangeInDegrees = courseChangeInDegrees;
this.speedChangeInKnots = speedChangeInKnots;
}
@Override
public double getCourseChangeInDegrees() {
return courseChangeInDegrees;
}
@Override
public double getSpeedChangeInKnots() {
return speedChangeInKnots;
}
@Override
public Tack to() {
return getCourseChangeInDegrees() > 0 ? Tack.STARBOARD : getCourseChangeInDegrees() < 0 ? Tack.PORT : null;
}
}
@@ -1,6 +1,9 @@
package com.sap.sailing.domain.base.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.Distance;
@@ -56,26 +59,32 @@ public class DouglasPeucker<ItemType, FixType extends GPSFix> {
* the maximum distance of any fix on the {@link #track} to the approximation is less than
* <code>maxDistance</code>.
*/
public GPSFix[] approximate(Distance maxDistance, TimePoint from, TimePoint to) {
GPSFix[] resultWithoutFirstFix = approximateWithoutFirst(maxDistance, from, to);
GPSFix[] result = new GPSFix[resultWithoutFirstFix.length+1];
result[0] = track.getFirstFixAtOrAfter(from);
System.arraycopy(resultWithoutFirstFix, 0, result, 1, resultWithoutFirstFix.length);
public List<FixType> approximate(Distance maxDistance, TimePoint from, TimePoint to) {
List<FixType> resultWithoutFirstFix = approximateWithoutFirst(maxDistance, from, to);
List<FixType> result = new ArrayList<FixType>(resultWithoutFirstFix.size()+1);
result.add(track.getFirstFixAtOrAfter(from));
for (FixType f : resultWithoutFirstFix) {
result.add(f);
}
return result;
}
private GPSFix[] approximateWithoutFirst(Distance maxDistance, TimePoint from, TimePoint to) {
GPSFix[] result;
private List<FixType> approximateWithoutFirst(Distance maxDistance, TimePoint from, TimePoint to) {
List<FixType> result;
Pair<GPSFix, Distance> fixAndDistance = getFixWithGreatestCrossTrackErrorInInterval(from, to);
if (fixAndDistance.getB().compareTo(maxDistance) < 0) {
// reached desired accuracy for interval from..to
result = new GPSFix[] { track.getLastFixAtOrBefore(to) };
result = Collections.singletonList(track.getLastFixAtOrBefore(to));
} else {
GPSFix[] left = approximateWithoutFirst(maxDistance, from, fixAndDistance.getA().getTimePoint());
GPSFix[] right = approximateWithoutFirst(maxDistance, fixAndDistance.getA().getTimePoint(), to);
result = new GPSFix[left.length+right.length];
System.arraycopy(left, 0, result, 0, left.length);
System.arraycopy(right, 0, result, left.length, right.length);
List<FixType> left = approximateWithoutFirst(maxDistance, from, fixAndDistance.getA().getTimePoint());
List<FixType> right = approximateWithoutFirst(maxDistance, fixAndDistance.getA().getTimePoint(), to);
result = new ArrayList<FixType>(left.size()+right.size());
for (FixType fixFromLeft : left) {
result.add(fixFromLeft);
}
for (FixType fixFromRight : right) {
result.add(fixFromRight);
}
}
return result;
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
@@ -23,6 +24,11 @@ public class KilometersPerHourSpeedWithBearingImpl extends KilometersPerHourSpee
return pos.translateGreatCircle(getBearing(), this.travel(from, to));
}
@Override
public SpeedWithBearing applyCourseChange(CourseChange courseChange) {
return AbstractSpeedWithBearingImpl.applyCourseChange(this, courseChange);
}
@Override
public String toString() {
return super.toString()+" to "+getBearing().getDegrees()+"°";
@@ -1,6 +1,13 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Moving;
import com.sap.sailing.domain.base.SpeedWithBearing;
public interface GPSFixMoving extends GPSFix, Moving {
/**
* Computes the minimal (in terms of bearing change) course and speed change required to reach the
* target speed and bearing specified.
*/
CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing);
}
@@ -0,0 +1,24 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.Timed;
public interface Maneuver extends Timed, Positioned {
enum Type { HEAD_UP, BEAR_AWAY, TACK, JIBE, PENALTY_CIRCLE }
Type getType();
SpeedWithBearing getSpeedWithBearingBefore();
SpeedWithBearing getSpeedWithBearingAfter();
double getDirectionChangeInDegrees();
/**
* This assumes that a maneuver usually slows a boat down. This method tries to find out how long
* after the {@link #getTimePoint() time of the maneuver} it took for the boat to reach a roughly
* constant speed again.
*/
long getTimeInMillisecondsUntilBackToConstantSpeed();
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tracking;
import java.util.List;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.Buoy;
@@ -12,6 +13,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Tack;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.DouglasPeucker;
/**
* Live tracking data of a single race. The race follows a defined {@link Course} with a sequence of {@link Leg}s. The
@@ -206,4 +208,11 @@ public interface TrackedRace {
TrackedEvent getTrackedEvent();
Wind getDirectionFromStartToNextMark(TimePoint at);
/**
* Uses a {@link DouglasPeucker Douglas-Peucker} algorithm to approximate this track's fixes starting at
* time <code>from</code> until time point <code>to</code> such that the maximum distance between the
* track's fixes and the approximation is at most <code>maxDistance</code>.
*/
List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
}
@@ -1,11 +1,13 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.impl.AbstractBearing;
import com.sap.sailing.domain.base.impl.AbstractSpeedImpl;
import com.sap.sailing.domain.base.impl.AbstractSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.GPSFixMoving;
/**
@@ -37,6 +39,11 @@ public class CompactGPSFixMovingImpl extends CompactGPSFixImpl implements GPSFix
return pos.translateGreatCircle(getBearing(), this.travel(from, to));
}
@Override
public SpeedWithBearing applyCourseChange(CourseChange courseChange) {
return AbstractSpeedWithBearingImpl.applyCourseChange(this, courseChange);
}
@Override
public String toString() {
return super.toString()+" to "+getBearing().getDegrees()+"°";
@@ -80,6 +87,11 @@ public class CompactGPSFixMovingImpl extends CompactGPSFixImpl implements GPSFix
return new CompactSpeedWithBearing();
}
@Override
public CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing) {
return AbstractSpeedWithBearingImpl.getCourseChangeRequiredToReach(getSpeed(), targetSpeedWithBearing, getPosition(), getTimePoint());
}
@Override
public String toString() {
return super.toString() + " with " + getSpeed();
@@ -5,6 +5,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.Speed;
import com.sap.sailing.domain.base.SpeedWithBearing;
@@ -121,6 +122,10 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends DynamicTrackImpl<Ite
public SpeedWithBearing getSpeed() {
return null;
}
@Override
public CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing) {
return null;
}
}
@@ -280,4 +280,5 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
public DynamicTrackedEvent getTrackedEvent() {
return (DynamicTrackedEvent) super.getTrackedEvent();
}
}
@@ -1,8 +1,10 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.impl.AbstractSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.GPSFixMoving;
public class GPSFixMovingImpl extends GPSFixImpl implements GPSFixMoving {
@@ -32,4 +34,9 @@ public class GPSFixMovingImpl extends GPSFixImpl implements GPSFixMoving {
public boolean equals(Object other) {
return super.equals(other) && other instanceof GPSFixMoving && getSpeed().equals(((GPSFixMoving) other).getSpeed());
}
@Override
public CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing) {
return AbstractSpeedWithBearingImpl.getCourseChangeRequiredToReach(getSpeed(), targetSpeedWithBearing, getPosition(), getTimePoint());
}
}
@@ -0,0 +1,64 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.tracking.Maneuver;
public class ManeuverImpl implements Maneuver {
private final Type type;
private final Position position;
private final TimePoint timePoint;
private final SpeedWithBearing speedWithBearingBefore;
private final SpeedWithBearing speedWithBearingAfter;
private final long timeInMillisecondsUntilBackToConstantSpeed;
private final double directionChangeInDegrees;
public ManeuverImpl(Type type, Position position, TimePoint timePoint, SpeedWithBearing speedWithBearingBefore,
SpeedWithBearing speedWithBearingAfter, long timeInMillisecondsUntilBackToConstantSpeed, double directionChangeInDegrees) {
super();
this.type = type;
this.position = position;
this.timePoint = timePoint;
this.speedWithBearingBefore = speedWithBearingBefore;
this.speedWithBearingAfter = speedWithBearingAfter;
this.timeInMillisecondsUntilBackToConstantSpeed = timeInMillisecondsUntilBackToConstantSpeed;
this.directionChangeInDegrees = directionChangeInDegrees;
}
@Override
public long getTimeInMillisecondsUntilBackToConstantSpeed() {
return timeInMillisecondsUntilBackToConstantSpeed;
}
@Override
public Type getType() {
return type;
}
@Override
public Position getPosition() {
return position;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public SpeedWithBearing getSpeedWithBearingBefore() {
return speedWithBearingBefore;
}
@Override
public SpeedWithBearing getSpeedWithBearingAfter() {
return speedWithBearingAfter;
}
@Override
public double getDirectionChangeInDegrees() {
return directionChangeInDegrees;
}
}
@@ -12,8 +12,10 @@ import java.util.concurrent.ConcurrentSkipListSet;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.CourseListener;
import com.sap.sailing.domain.base.Distance;
import com.sap.sailing.domain.base.Leg;
@@ -23,11 +25,13 @@ import com.sap.sailing.domain.base.Tack;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.DegreeBearingImpl;
import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.NoWindError;
import com.sap.sailing.domain.tracking.NoWindException;
@@ -43,19 +47,8 @@ import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.util.Util;
public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
/**
* If the averaged courses over ground differ by at least this degree angle, a maneuver will
* be assumed. Note that this should be much less than the tack angle because averaging may
* span across the actual maneuver.
*/
private static final double MANEUVER_DEGREE_ANGLE_THRESHOLD = /* minimumDegreeDifference */ 30.;
private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName());
// TODO make typical tack/jibe angles and the respective clustering thresholds a boat class parameter
private static final double MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_UPWIND = 45.;
private static final double MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_DOWNWIND = 15.;
// TODO observe the race course; if it changes, update leg structures; consider fine-grained update events that tell what changed
private final RaceDefinition race;
@@ -562,7 +555,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
LegType legType = trackedLeg.getLegType(timePoint);
if (legType != LegType.REACHING) {
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
if (!track.hasDirectionChange(timePoint, MANEUVER_DEGREE_ANGLE_THRESHOLD)) {
if (!track.hasDirectionChange(timePoint, getManeuverDegreeAngleThreshold())) {
Bearing bearing = track.getEstimatedSpeed(timePoint).getBearing();
BearingCluster bearingClusters = bearings.get(legType);
bearingClusters.add(bearing);
@@ -571,12 +564,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
}
}
Bearing upwindAverage = null;
BearingCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo(MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_UPWIND);
BearingCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo(getMinimumAngleBetweenDifferentTacksUpwind());
if (!bearingClustersUpwind[0].isEmpty() && !bearingClustersUpwind[1].isEmpty()) {
upwindAverage = bearingClustersUpwind[0].getAverage().middle(bearingClustersUpwind[1].getAverage());
}
Bearing downwindAverage = null;
BearingCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND).splitInTwo(MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_DOWNWIND);
BearingCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND).splitInTwo(getMinimumAngleBetweenDifferentTacksDownwind());
if (!bearingClustersDownwind[0].isEmpty() && !bearingClustersDownwind[1].isEmpty()) {
downwindAverage = bearingClustersDownwind[0].getAverage().middle(bearingClustersDownwind[1].getAverage());
}
@@ -633,4 +626,64 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
public String toString() {
return "TrackedRace for "+getRace();
}
@Override
public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
DouglasPeucker<Competitor, GPSFixMoving> douglasPeucker = new DouglasPeucker<Competitor, GPSFixMoving>(getTrack(competitor));
return douglasPeucker.approximate(maxDistance, from, to);
}
/**
* Tries to detect a maneuver on the <code>competitor</code>'s track around a given time point. The time period is
* taken from the {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is
* detected, an empty list is returned. Maneuvers can only be expected to be detected if at least two fixes are
* provided in <code>approximatedFixesToAnalyze</code> that have their {@link GPSFixMoving#getSpeed() speed and
* bearing} propertly set.
*
* @return an empty list if no maneuver is detected for <code>competitor</code> between <code>from</code> and
* <code>to</code>, or else the list of maneuvers detected.
*/
private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatedFixesToAnalyze) {
List<Maneuver> result = new ArrayList<Maneuver>();
if (approximatedFixesToAnalyze.size() > 2) {
List<CourseChange> courseChangeSequenceInSameDirection = new ArrayList<CourseChange>();
Iterator<GPSFixMoving> iter = approximatedFixesToAnalyze.iterator();
GPSFixMoving current = iter.next();
while (iter.hasNext()) {
GPSFixMoving previous = current;
current = iter.next();
CourseChange courseChange = previous.getCourseChangeRequiredToReach(current.getSpeed());
if (courseChangeSequenceInSameDirection.isEmpty() || courseChangeSequenceInSameDirection.get(0).to() == courseChange.to()) {
courseChangeSequenceInSameDirection.add(courseChange);
} else {
// course change in different direction; cluster the course changes in same direction so far, then start new list
List<Maneuver> maneuver = summarizeDirectionChangesIntoManeuvers(courseChangeSequenceInSameDirection);
}
}
}
// TODO first group fixes into sequences of direction changes in the same direction
// TODO implement detectManeuvers
return result;
}
private List<Maneuver> summarizeDirectionChangesIntoManeuvers(List<CourseChange> courseChangeSequenceInSameDirection) {
// TODO Auto-generated method stub
return null;
}
/**
* Fetches the boat class-specific parameter
*/
private double getManeuverDegreeAngleThreshold() {
return getRace().getBoatClass().getManeuverDegreeAngleThreshold();
}
private double getMinimumAngleBetweenDifferentTacksDownwind() {
return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksDownwind();
}
private double getMinimumAngleBetweenDifferentTacksUpwind() {
return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksUpwind();
}
}
@@ -8,6 +8,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Bearing;
import com.sap.sailing.domain.base.CourseChange;
import com.sap.sailing.domain.base.Distance;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.Speed;
@@ -249,6 +250,10 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
public double getBeaufort() {
return 0;
}
@Override
public SpeedWithBearing applyCourseChange(CourseChange courseChange) {
return null;
}
}
@Override
@@ -33,8 +33,9 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
private static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4;
private final RaceTreeView raceTree;
// private final RaceTreeView raceTree;
private final TrackedEventsComposite trackedEventsComposite;
private final StringConstants stringConstants;
private final SailingServiceAsync sailingService;
@@ -190,9 +191,13 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
VerticalPanel labelAndTreePanel = new VerticalPanel();
Label lblTrackedRaceConnected = new Label(stringConstants.trackedRaceConnectedToSelectedRaceName());
labelAndTreePanel.add(lblTrackedRaceConnected);
raceTree = new RaceTreeView(stringConstants, /* multiselection */ false);
labelAndTreePanel.add(raceTree);
raceTree.addRaceSelectionChangeListener(this);
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, adminConsole,
stringConstants, /* multiselection */ false);
// raceTree = new RaceTreeView(stringConstants, /* multiselection */ false);
labelAndTreePanel.add(trackedEventsComposite);
trackedEventsComposite.addRaceSelectionChangeListener(this);
selectedLeaderboardPanel.add(labelAndTreePanel);
VerticalPanel buttonPanelForSelectedLeaderboardDetails = new VerticalPanel();
@@ -284,7 +289,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
@Override
public void onSuccess(Void arg0) {
raceTree.clearSelection();
trackedEventsComposite.clearSelection();
}
});
}
@@ -442,7 +447,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
columnRemoveButton.setEnabled(false);
columnMoveUpButton.setEnabled(false);
columnMoveDownButton.setEnabled(false);
raceTree.clearSelection();
trackedEventsComposite.clearSelection();
unlinkRaceColumnFromTrackedRaceButton.setEnabled(false);
}
}
@@ -464,14 +469,14 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
if (eventAndRaceName != null) {
selectRaceInTree(eventAndRaceName.getA(), eventAndRaceName.getB());
} else {
raceTree.clearSelection();
trackedEventsComposite.clearSelection();
}
}
});
}
private void selectRaceInTree(String eventName, String raceName) {
raceTree.selectRaceByName(eventName, raceName);
trackedEventsComposite.selectRaceByName(eventName, raceName);
}
private String getSelectedRaceColumnName() {
@@ -661,7 +666,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
@Override
public void fillEvents(List<EventDAO> result) {
raceTree.fillEvents(result);
trackedEventsComposite.fillEvents(result);
}
private void addNewLeaderboard() {
@@ -750,7 +755,7 @@ public class LeaderboardConfigPanel extends FormPanel implements EventDisplayer,
+ " of event " + selectedRace.getA().name + " to race column named "
+ getSelectedRaceColumnName() + " of leaderboard " + getSelectedLeaderboardName()
+ ": " + t.getMessage());
raceTree.clearSelection();
trackedEventsComposite.clearSelection();
}
@Override
@@ -766,7 +766,7 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe
Map<CompetitorDAO, Date> to = new HashMap<CompetitorDAO, Date>();
to.put(competitorDAO, fixes.get(competitorDAO).get(lastShownFix.get(competitorDAO)).timepoint);
sailingService.getDouglasPoints(event.name, race.name, from, to, /* epsilon/meters */5,
new AsyncCallback<Map<CompetitorDAO, List<GPSFixDAO>>>() {
to, /* epsilon/meters */ 3, new AsyncCallback<Map<CompetitorDAO, List<GPSFixDAO>>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage());
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.client;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -10,6 +11,8 @@ import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.text.client.DateTimeFormatRenderer;
@@ -24,6 +27,7 @@ import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.Label;
@@ -54,6 +58,7 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
private final ErrorReporter errorReporter;
private final IntegerBox portIntegerbox;
private final TextBox hostnameTextbox;
private final TextBox filterEventsTextbox;
private final ListDataProvider<SwissTimingRaceRecordDAO> raceList;
private final CellTable<SwissTimingRaceRecordDAO> raceTable;
private final Map<String, SwissTimingConfigurationDAO> previousConfigurations;
@@ -61,7 +66,8 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
private final TrackedEventsComposite trackedEventsComposite;
private final EventRefresher eventRefresher;
private final CheckBox canSendRequestsCheckbox;
private DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_LONG));
private final DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_LONG));
private final List<SwissTimingRaceRecordDAO> availableSwissTimingRaces = new ArrayList<SwissTimingRaceRecordDAO>();
public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
EventRefresher eventRefresher, StringConstants stringConstants) {
@@ -176,7 +182,40 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
trackedRacesPanel.setWidth("100%");
trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel);
trackedRacesCaptionPanel.setStyleName("bold");
// text box for filtering the cell table
HorizontalPanel filterPanel = new HorizontalPanel();
filterPanel.setSpacing(5);
racesPanel.add(filterPanel);
Label lblFilterEvents = new Label("Filter races by name:");
filterPanel.add(lblFilterEvents);
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
filterEventsTextbox = new TextBox();
filterEventsTextbox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
String text = filterEventsTextbox.getText();
raceList.getList().clear();
if(text == null || text.isEmpty()) {
raceList.getList().addAll(availableSwissTimingRaces);
} else {
String textAsUppercase = text.toUpperCase();
for(SwissTimingRaceRecordDAO dao: availableSwissTimingRaces) {
if(dao.ID != null) {
if(dao.ID.toUpperCase().contains(textAsUppercase))
raceList.getList().add(dao);
}
}
}
}
});
filterPanel.add(filterEventsTextbox);
HorizontalPanel racesHorizontalPanel = new HorizontalPanel();
racesPanel.add(racesHorizontalPanel);
@@ -214,9 +253,6 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
declinationCheckbox.setValue(true);
trackPanel.add(declinationCheckbox);
// trackedRacesTreeView = new RaceTreeView(stringConstants, /* multiselection */ true);
// trackedRacesPanel.add(trackedRacesTreeView);
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher,
stringConstants, /* multiselection */ true);
trackedRacesPanel.add(trackedEventsComposite);
@@ -295,10 +331,15 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
@Override
public void onSuccess(final List<SwissTimingRaceRecordDAO> result) {
availableSwissTimingRaces.clear();
if (result != null)
availableSwissTimingRaces.addAll(result);
raceList.getList().clear();
if (result != null) {
raceList.getList().addAll(result);
}
raceList.getList().addAll(availableSwissTimingRaces);
filterEventsTextbox.setText(null);
// store a successful configuration in the database for later retrieval
final String configName = hostname+":"+port;
sailingService.storeSwissTimingConfiguration(configName, hostname, port, canSendRequests,
@@ -359,7 +400,6 @@ public class SwissTimingEventManagementPanel extends FormPanel implements EventD
@Override
public void fillEvents(List<EventDAO> result) {
// trackedRacesTreeView.fillEvents(result);
trackedEventsComposite.fillEvents(result);
}
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.client;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -63,16 +64,17 @@ public class TracTracEventManagementPanel extends FormPanel implements EventDisp
private final IntegerBox livePortIntegerbox;
private final TextBox hostnameTextbox;
private final TextBox eventNameTextbox;
private final TextBox filterEventsTextbox;
private final ListDataProvider<TracTracRaceRecordDAO> raceList;
private final CellTable<TracTracRaceRecordDAO> raceTable;
private final Map<String, TracTracConfigurationDAO> previousConfigurations;
private final ListBox previousConfigurationsComboBox;
private final Grid grid;
// private final RaceTreeView trackedRacesTreeView;
private final TrackedEventsComposite trackedEventsComposite;
private final EventRefresher eventRefresher;
private DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_LONG));
private final DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_LONG));
private final List<TracTracRaceRecordDAO> availableTracTracRaces = new ArrayList<TracTracRaceRecordDAO>();
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
EventRefresher eventRefresher, StringConstants stringConstants) {
this.sailingService = sailingService;
@@ -273,13 +275,46 @@ public class TracTracEventManagementPanel extends FormPanel implements EventDisp
trackedRacesPanel.setWidth("100%");
trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel);
trackedRacesCaptionPanel.setStyleName("bold");
// text box for filtering the cell table
HorizontalPanel filterPanel = new HorizontalPanel();
filterPanel.setSpacing(5);
racesPanel.add(filterPanel);
Label lblFilterEvents = new Label("Filter races by name:");
filterPanel.add(lblFilterEvents);
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
filterEventsTextbox = new TextBox();
filterEventsTextbox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
String text = filterEventsTextbox.getText();
raceList.getList().clear();
if(text == null || text.isEmpty()) {
raceList.getList().addAll(availableTracTracRaces);
} else {
String textAsUppercase = text.toUpperCase();
for(TracTracRaceRecordDAO dao: availableTracTracRaces) {
if(dao.name != null) {
if(dao.name.toUpperCase().contains(textAsUppercase))
raceList.getList().add(dao);
}
}
}
}
});
filterPanel.add(filterEventsTextbox);
HorizontalPanel racesHorizontalPanel = new HorizontalPanel();
racesPanel.add(racesHorizontalPanel);
VerticalPanel trackPanel = new VerticalPanel();
trackPanel.setStyleName("paddedPanel");
raceNameColumn.setSortable(true);
raceStartTrackingColumn.setSortable(true);
@@ -312,9 +347,6 @@ public class TracTracEventManagementPanel extends FormPanel implements EventDisp
declinationCheckbox.setValue(true);
trackPanel.add(declinationCheckbox);
// trackedRacesTreeView = new RaceTreeView(stringConstants, /* multiselection */ true);
// trackedRacesPanel.add(trackedRacesTreeView);
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher,
stringConstants, /* multiselection */ true);
trackedRacesPanel.add(trackedEventsComposite);
@@ -411,10 +443,15 @@ public class TracTracEventManagementPanel extends FormPanel implements EventDisp
@Override
public void onSuccess(final Pair<String, List<TracTracRaceRecordDAO>> result) {
availableTracTracRaces.clear();
if (result.getB() != null)
availableTracTracRaces.addAll(result.getB());
raceList.getList().clear();
if (result.getB() != null) {
raceList.getList().addAll(result.getB());
}
raceList.getList().addAll(availableTracTracRaces);
filterEventsTextbox.setText(null);
// store a successful configuration in the database for later retrieval
sailingService.storeTracTracConfiguration(result.getA(), jsonURL, liveDataURI, storedDataURI,
new AsyncCallback<Void>() {
@@ -40,7 +40,6 @@ import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.DegreeBearingImpl;
import com.sap.sailing.domain.base.impl.DegreePosition;
import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
@@ -1117,28 +1116,23 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
Map<CompetitorDAO, Date> from, Map<CompetitorDAO, Date> to,
double meters) {
Map<CompetitorDAO, List<GPSFixDAO>> result = new HashMap<CompetitorDAO, List<GPSFixDAO>>();
Event event = getService().getEventByName(eventName);
RaceDefinition race = getRaceByName(event, raceName);
TrackedRace trackedRace = getService().getOrCreateTrackedEvent(event).getTrackedRace(race);
TrackedRace trackedRace = getTrackedRace(eventName, raceName);
MeterDistance maxDistance = new MeterDistance(meters);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
CompetitorDAO competitorDAO = getCompetitorDAO(competitor);
if (from.containsKey(competitorDAO)) {
// get Track of competitor
GPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack = getTrackedRace(eventName, raceName).getTrack(
competitor);
DouglasPeucker<Competitor, GPSFixMoving> douglas = new DouglasPeucker<Competitor, GPSFixMoving>(
gpsFixTrack);
GPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack = trackedRace.getTrack(competitor);
// Distance for DouglasPeucker
TimePoint timePointFrom = new MillisecondsTimePoint(from.get(competitorDAO));
TimePoint timePointTo = new MillisecondsTimePoint(to.get(competitorDAO));
GPSFix[] gpsFixDouglas = douglas.approximate(maxDistance, timePointFrom, timePointTo);
List<GPSFixMoving> gpsFixApproximation = trackedRace.approximate(competitor, maxDistance, timePointFrom, timePointTo);
List<GPSFixDAO> gpsFixDouglasList = new ArrayList<GPSFixDAO>();
for (int i = 0; i < gpsFixDouglas.length; i++) {
GPSFix fix = gpsFixDouglas[i];
for (int i = 0; i < gpsFixApproximation.size(); i++) {
GPSFix fix = gpsFixApproximation.get(i);
SpeedWithBearing speedWithBearing;
if (i<gpsFixDouglas.length-1) {
GPSFix next = gpsFixDouglas[i+1];
if (i<gpsFixApproximation.size()-1) {
GPSFix next = gpsFixApproximation.get(i+1);
Bearing bearing = fix.getPosition().getBearingGreatCircle(next.getPosition());
Speed speed = fix.getPosition().getDistance(next.getPosition()).inTime(
next.getTimePoint().asMillis()-fix.getTimePoint().asMillis());