Merge remote-tracking branch 'origin/simulator' into simulator

This commit is contained in:
Alexandru Dimofte committed 2012-04-19 11:06:17 +03:00
commit 09c3bfeb92
376 files changed
+81589 -3193

No files matched your search

+1 -1
View File
@@ -165,7 +165,7 @@ First, check out the branch you want to re-build:
- ensure that there is an ssh tunnel to localhost that
- "mvn clean install"
- rm -rf com.sap.sailing.gwt.ui/com.sap.sailing.gwt.ui.*; mvn -fae clean install 2>&1 | tee log
This will compile the sources, bundle them up in to OSGi bundle JAR
files, run all tests which required online access to the TracTrac
@@ -8,7 +8,10 @@ import java.io.IOException;
import java.text.ParseException;
import java.util.regex.Matcher;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
@@ -34,21 +37,21 @@ public class DeclinationImportTest extends AbstractDeclinationTest {
}
@Test
public void importSimpleDeclination() throws IOException, ParseException {
public void importSimpleDeclination() throws IOException, ParseException, ParserConfigurationException, SAXException {
Declination record = importer.importRecord(new DegreePosition(53, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("1920-05-27").getTime()));
assertEquals(-12.-41./60., record.getBearing().getDegrees(), 0.000000001);
assertEquals(0.+11./60., record.getAnnualChange().getDegrees(), 0.000000001);
assertEquals(-12.68941, record.getBearing().getDegrees(), 0.0001);
assertEquals(0.1863178, record.getAnnualChange().getDegrees(), 0.0001);
}
@Test
public void importSouthernHemisphereDeclination() throws IOException, ParseException {
public void importSouthernHemisphereDeclination() throws IOException, ParseException, ParserConfigurationException, SAXException {
long start = System.currentTimeMillis();
Declination record = importer.importRecord(new DegreePosition(-10, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()));
System.out.println("took "+(System.currentTimeMillis()-start)+"ms");
assertEquals(-9.-32./60., record.getBearing().getDegrees(), 0.000000001);
assertEquals(0.+10./60., record.getAnnualChange().getDegrees(), 0.000000001);
assertEquals(-9.54538, record.getBearing().getDegrees(), 0.0001);
assertEquals(0.17543, record.getAnnualChange().getDegrees(), 0.0001);
}
@Test
@@ -38,8 +38,8 @@ public class DeclinationServiceTest extends AbstractDeclinationTest {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2010-02-03").getTime()),
new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 30000);
assertNotNull(result);
assertEquals(-3.-27./60., result.getBearing().getDegrees(), 0.0000001);
assertEquals(0.+09./60., result.getAnnualChange().getDegrees(), 0.0000001);
assertEquals(-3.45941, result.getBearing().getDegrees(), 0.0001);
assertEquals(0.1553643, result.getAnnualChange().getDegrees(), 0.0001);
}
@Test
@@ -5,7 +5,10 @@ import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.text.ParseException;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
@@ -15,26 +18,26 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
public class SimpleDeclinationTest extends AbstractDeclinationTest {
@Test
public void testSimpleCorrection() throws IOException, ParseException {
public void testSimpleCorrection() throws IOException, ParseException, ParserConfigurationException, SAXException {
Declination record = importer.importRecord(new DegreePosition(53, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("1920-05-27").getTime()));
assertEquals(-12.-41./60., record.getBearing().getDegrees(), 0.000000001);
assertEquals(0.+11./60., record.getAnnualChange().getDegrees(), 0.000000001);
assertEquals(-12.68941, record.getBearing().getDegrees(), 0.0001);
assertEquals(0.1863178, record.getAnnualChange().getDegrees(), 0.0001);
TimePoint oneYearLater = new MillisecondsTimePoint(simpleDateFormat.parse("1921-05-27").getTime());
Bearing bearing = record.getBearingCorrectedTo(oneYearLater);
assertEquals(-12.-41./60.+11./60., bearing.getDegrees(), 0.000000001);
assertEquals(-12.68941+0.1863178, bearing.getDegrees(), 0.0001);
}
@Test
public void testBackwardCorrection() throws IOException, ParseException {
public void testBackwardCorrection() throws IOException, ParseException, ParserConfigurationException, SAXException {
Declination record = importer.importRecord(new DegreePosition(53, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("1920-05-27").getTime()));
assertEquals(-12.-41./60., record.getBearing().getDegrees(), 0.000000001);
assertEquals(0.+11./60., record.getAnnualChange().getDegrees(), 0.000000001);
assertEquals(-12.68941, record.getBearing().getDegrees(), 0.0001);
assertEquals(0.1863178, record.getAnnualChange().getDegrees(), 0.0001);
// 1920 is a leap year, so add one day, otherwise it wouldn't match the 365 day assumption
TimePoint oneYearEarlier = new MillisecondsTimePoint(simpleDateFormat.parse("1919-05-28").getTime());
Bearing bearing = record.getBearingCorrectedTo(oneYearEarlier);
assertEquals(-12.-41./60.-11./60., bearing.getDegrees(), 0.000000001);
assertEquals(-12.68941-0.1863178, bearing.getDegrees(), 0.0001);
}
}
File diff suppressed because it is too large. Load diff
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
public class DeclinationRecordImpl implements Declination {
private static final long serialVersionUID = 6918630656182340186L;
private final Position position;
private final TimePoint timePoint;
private final Bearing bearing;
@@ -14,6 +14,10 @@ import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Bearing;
@@ -102,7 +106,7 @@ public class DeclinationStore {
try {
declination = importer.importRecord(position, timePoint);
break;
} catch (IOException ioe) {
} catch (IOException | ParserConfigurationException | SAXException ioe) {
ioe.printStackTrace();
if (i<2) {
System.out.println("re-trying");
@@ -128,6 +132,9 @@ public class DeclinationStore {
NOAAImporter importer = new NOAAImporter();
for (int year = fromYear; year <= toYear; year++) {
QuadTree<Declination> storedDeclinations = getStoredDeclinations(year);
if (storedDeclinations == null) {
storedDeclinations = new QuadTree<>();
}
// append if file already exists
File fileForYear = new File(getResourceForYear(year));
Writer out;
@@ -144,8 +151,10 @@ public class DeclinationStore {
for (double lng = 0; lng < 180; lng += grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
if (DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
if (existingDeclinationRecord == null
|| DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord
.getPosition().getDistance(point), timePoint, existingDeclinationRecord
.getTimePoint()) > 0.1) {
// less than ~6 nautical miles and/or ~.6 months off
fetchAndAppendDeclination(timePoint, point, importer, out);
}
@@ -153,8 +162,10 @@ public class DeclinationStore {
for (double lng = -grid; lng > -180; lng -= grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
if (DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
if (existingDeclinationRecord == null
|| DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord
.getPosition().getDistance(point), timePoint, existingDeclinationRecord
.getTimePoint()) > 0.1) {
// less than ~6 nautical miles and/or ~.6 months off
fetchAndAppendDeclination(timePoint, point, importer, out);
}
@@ -1,18 +1,23 @@
package com.sap.sailing.declination.impl;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
@@ -25,7 +30,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
*
*/
public class NOAAImporter {
private static final String QUERY_URL = "http://www.ngdc.noaa.gov/geomagmodels/struts/calcDeclination";
private static final String QUERY_URL = "http://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination";
private static final String REGEXP_DECLINATION = "<p class=\"indent\"><b>Declination</b> = ([0-9]*)&deg; ([0-9]*)' *([EW])";
private static final String REGEXP_ANNUAL_CHANGE = "changing by *([0-9]*)&deg; *([0-9]*)' ([EW])/year *</p>";
@@ -46,69 +51,26 @@ public class NOAAImporter {
return annualChangePattern;
}
public Declination importRecord(Position position, TimePoint timePoint) throws IOException {
public Declination importRecord(Position position, TimePoint timePoint) throws IOException, ParserConfigurationException, SAXException {
Declination result = null;
URL url = new URL(QUERY_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
Date date = timePoint.asDate();
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
String content = "minLatStr=" + position.getLatDeg() + "&minLatHemisphere="
+ (position.getLatDeg() >= 0 ? "n" : "s") + "&minLonStr=" + position.getLngDeg() + "&minLonHemisphere="
+ (position.getLngDeg() >= 0 ? "e" : "w") + "&minYear=" + calendar.get(Calendar.YEAR) + "&minMonth="
+ (calendar.get(Calendar.MONTH) + 1) + "&minDay=" + calendar.get(Calendar.DAY_OF_MONTH);
out.writeBytes(content);
out.flush();
out.close();
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
int declinationDeg = 0;
int declinationMin = 0;
boolean foundDeclination = false;
int annualChangeDeg = 0;
int annualChangeMin = 0;
boolean foundAnnualChange = false;
StringBuilder page = new StringBuilder();
while ((!foundDeclination || !foundAnnualChange) && (line = in.readLine()) != null) {
page.append(line);
page.append('\n');
if (!foundDeclination) {
Matcher declinationMatcher = declinationPattern.matcher(line);
if (declinationMatcher.find()) {
foundDeclination = true;
declinationDeg = Integer.valueOf(declinationMatcher.group(1));
declinationMin = Integer.valueOf(declinationMatcher.group(2));
char ew = declinationMatcher.group(3).charAt(0);
if (ew == 'W') {
declinationDeg = -declinationDeg;
declinationMin = -declinationMin;
}
}
}
if (!foundAnnualChange) {
Matcher annualChangeMatcher = annualChangePattern.matcher(line);
if (annualChangeMatcher.find()) {
foundAnnualChange = true;
annualChangeDeg = Integer.valueOf(annualChangeMatcher.group(1));
annualChangeMin = Integer.valueOf(annualChangeMatcher.group(2));
char ew = annualChangeMatcher.group(3).charAt(0);
if (ew == 'W') {
annualChangeDeg = -annualChangeDeg;
annualChangeMin = -annualChangeMin;
}
}
}
}
in.close();
if (foundDeclination && foundAnnualChange) {
result = new DeclinationRecordImpl(position, timePoint, new DegreeBearingImpl(((double) declinationDeg)
+ ((double) declinationMin) / 60.), new DegreeBearingImpl(((double) annualChangeDeg)
+ ((double) annualChangeMin) / 60.));
}
URL url = new URL(QUERY_URL+"?lon1="+position.getLngDeg()+"&lat1="+position.getLatDeg()+"&startYear=" + calendar.get(Calendar.YEAR) + "&startMonth="
+ (calendar.get(Calendar.MONTH) + 1) + "&startDay=" + calendar.get(Calendar.DAY_OF_MONTH)+"&resultFormat=xml");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final InputStream inputStream = conn.getInputStream();
Document doc = builder.parse(inputStream);
Element maggridresultNode = (Element) doc.getFirstChild();
Element resultNode = (Element) maggridresultNode.getElementsByTagName("result").item(0);
String declination = resultNode.getElementsByTagName("declination").item(0).getTextContent().trim();
String declinationAnnualChangeInMinutes = resultNode.getElementsByTagName("declination_sv").item(0).getTextContent().trim();
inputStream.close();
double declinationAsDouble = declination.equals("nan") ? Double.NaN : Double.valueOf(declination);
double declinationAnnualChangeInMinutesAsDouble = declinationAnnualChangeInMinutes.equals("nan") ? Double.NaN : Double.valueOf(declinationAnnualChangeInMinutes);
result = new DeclinationRecordImpl(position, timePoint, new DegreeBearingImpl(declinationAsDouble),
new DegreeBearingImpl(declinationAnnualChangeInMinutesAsDouble/60.));
return result;
}
@@ -136,7 +98,7 @@ public class NOAAImporter {
result[0] = fetched;
result.notifyAll();
}
} catch (IOException e) {
} catch (IOException | ParserConfigurationException | SAXException e) {
throw new RuntimeException(e);
}
}
@@ -14,12 +14,12 @@ public interface Bearing {
Bearing add(Bearing diff);
/**
* Computes the bearing different that needs to be {@link #add(Bearing) added} to this bearing
* Computes the bearing difference that needs to be {@link #add(Bearing) added} to this bearing
* to result in <code>b</code>. The absolute number of degrees of the resulting bearing is
* guaranteed to be less or equal to 180. For example (simplifying bearings as the number
* of degrees they represent), <code>180.getDegreesTo(182)==2</code>, and
* <code>180.getDegreesTo(178)==-2</code>, and <code>10.getDegreesTo(-10)==-20</code>,
* and <code>350.getDegreesTo(10)==20</code>, and <code>10.getDegreesTo(350)==-20</code>
* of degrees they represent), <code>180.getDifferenceTo(182)==2</code>, and
* <code>180.getDifferenceTo(178)==-2</code>, and <code>10.getDifferenceTo(-10)==-20</code>,
* and <code>350.getDifferenceTo(10)==20</code>, and <code>10.getDifferenceTo(350)==-20</code>
*/
Bearing getDifferenceTo(Bearing b);
@@ -15,7 +15,7 @@ public enum DetailType implements Serializable {
WINDWARD_DISTANCE_TO_GO_IN_METERS(0), RACE_DISTANCE_TRAVELED(0), RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2), RACE_GAP_TO_LEADER_IN_SECONDS(0),
RACE_DISTANCE_TO_LEADER_IN_METERS(0),
WINDWARD_DISTANCE_TO_OVERALL_LEADER(0), HEAD_UP(0), BEAR_AWAY(0), TACK(0), JIBE(0), PENALTY_CIRCLE(0), MARK_PASSING(0), CURRENT_LEG(0),
DISPLAY_LEGS(0);
DISPLAY_LEGS(0), TIME_TRAVELED(0);
private int precision;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common;
public interface Named {
import java.io.Serializable;
public interface Named extends Serializable {
String getName();
}
@@ -97,6 +97,8 @@ public interface Placemark extends Serializable {
public class ByPopulationDistanceRatio implements Comparator<Placemark> {
private Position position;
private double populationFactor = 1.0;
private double distanceFactor = 1.0;
public ByPopulationDistanceRatio(Position position) {
this.position = position;
@@ -104,8 +106,8 @@ public interface Placemark extends Serializable {
@Override
public int compare(Placemark p1, Placemark p2) {
double r1 = p1.getPopulation() / p1.distanceFrom(position).getKilometers();
double r2 = p2.getPopulation() / p2.distanceFrom(position).getKilometers();
double r1 = (p1.getPopulation() * populationFactor) / (p1.distanceFrom(position).getKilometers() * distanceFactor);
double r2 = (p2.getPopulation() * populationFactor) / (p2.distanceFrom(position).getKilometers() * distanceFactor);
return r1 > r2 ? 1 : r1 < r2 ? -1 : 0;
}
@@ -1,8 +1,9 @@
package com.sap.sailing.domain.common;
import java.io.Serializable;
import java.util.Date;
public interface TimePoint extends Comparable<TimePoint> {
public interface TimePoint extends Comparable<TimePoint>, Serializable {
long asMillis();
Date asDate();
@@ -12,33 +12,58 @@ public enum WindSourceType {
/**
* Manually entered via a web form or received through a REST service call, e.g., from BeTomorrow's estimation
*/
WEB(true),
WEB(true, 0.9, /* useSpeed */ true),
/**
* Measured using wind sensors
*/
EXPEDITION(true),
EXPEDITION(true, 0.9, /* useSpeed */ true),
/**
* Estimates wind conditions by analyzing the boat tracks; may not have results for all time points, e.g.,
* because at a given time point all boats may sail on the same tack and hence no averaging between the
* two tacks is possible. This is the more likely to happen the smaller the fleet tracked is.
*/
TRACK_BASED_ESTIMATION(false),
TRACK_BASED_ESTIMATION(false, 0.5, /* useSpeed */ false),
/**
* Inferred from the race course layout if the course is known to have its first leg be an upwind leg
*/
COURSE_BASED(false);
COURSE_BASED(false, 0.3, /* useSpeed */ false),
/**
* Wind estimation combined from all other wind sources, using <code>TrackedRace.getWind(...)</code>, based on
* confidences
*/
COMBINED(false, 0.9, /* useSpeed */ true);
private final boolean canBeStored;
private WindSourceType(boolean canBeStored) {
private final double baseConfidence;
/**
* If <code>false</code>, the speeds of this wind source are not meaningful / defined. For example, the {@link #COURSE_BASED}
* wind source type doesn't provide any clues as to the wind speed, so it shouldn't be shown in charts nor used when
* combining wind sources into one.
*/
private final boolean useSpeed;
private WindSourceType(boolean canBeStored, double baseConfidence, boolean useSpeed) {
this.canBeStored = canBeStored;
this.baseConfidence = baseConfidence;
this.useSpeed = useSpeed;
}
public boolean canBeStored() {
return canBeStored;
}
public double getBaseConfidence() {
return baseConfidence;
}
public boolean useSpeed() {
return useSpeed;
}
}
@@ -6,7 +6,10 @@ import com.sap.sailing.domain.common.Bearing;
public class DegreeBearingImpl extends AbstractBearing implements Bearing {
private final double bearingDeg;
/**
*
* @param bearingDeg if a mount degrees over 360, then it will be mod 360 degrees
*/
public DegreeBearingImpl(double bearingDeg) {
super();
this.bearingDeg = bearingDeg - 360*(int) (bearingDeg/360.);
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.Named;
public class NamedImpl implements Named {
private static final long serialVersionUID = -4815125282671451300L;
private final String name;
public NamedImpl(String name) {
@@ -6,6 +6,16 @@ import java.util.Iterator;
import java.util.List;
public class Util {
/**
* Adds all elements from <code>what</code> to <code>addTo</code> and returns <code>addTo</code> for chained use.
*/
public static <T> Collection<T> addAll(Iterable<T> what, Collection<T> addTo) {
for (T t : what) {
addTo.add(t);
}
return addTo;
}
public static <T> int size(Iterable<T> i) {
if (i instanceof Collection<?>) {
return ((Collection<?>) i).size();
@@ -12,8 +12,10 @@ import com.sap.sailing.domain.common.WindSourceType;
public class WindSourceWithAdditionalID extends WindSourceImpl {
private static final long serialVersionUID = -142955860536561690L;
private final String id;
private String id;
WindSourceWithAdditionalID() {} // for GWT serialization
public WindSourceWithAdditionalID(WindSourceType type, String id) {
super(type);
this.id = id;
@@ -93,7 +93,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
@Override
public WindTrack loadWindTrack(Event event, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage) {
WindTrack result = new WindTrackImpl(millisecondsOverWhichToAverage);
WindTrack result = new WindTrackImpl(millisecondsOverWhichToAverage, windSource.getType().getBaseConfidence(), windSource.getType().useSpeed());
try {
BasicDBObject query = new BasicDBObject();
query.put(FieldNames.EVENT_NAME.name(), event.getName());
@@ -289,7 +289,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
WindTrack track = result.get(windSource);
if (track == null) {
track = new WindTrackImpl(millisecondsOverWhichToAverageWind);
track = new WindTrackImpl(millisecondsOverWhichToAverageWind, windSource.getType().getBaseConfidence(),
windSource.getType().useSpeed());
result.put(windSource, track);
}
track.add(wind);
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
*
*/
public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
private static final long serialVersionUID = -8933075542228571746L;
private final Map<String, Integer> carriedPointsByCompetitorName;
private final Map<String, Map<RaceInLeaderboard, MaxPointsReason>> maxPointsReasonsByCompetitorName;
private final Map<String, Map<RaceInLeaderboard, Integer>> correctedScoresByCompetitorName;
@@ -37,6 +38,8 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
* @author Axel Uhl (D043530)
*/
private class RaceInLeaderboardForDelayedCarriedPoints extends RaceInLeaderboardImpl {
private static final long serialVersionUID = -1243132535406059096L;
public RaceInLeaderboardForDelayedCarriedPoints(Leaderboard leaderboard, String name, boolean medalRace) {
super(leaderboard, name, medalRace);
}
@@ -79,7 +79,9 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
}
public DBCollection getWindTrackCollection() {
return database.getCollection(CollectionNames.WIND_TRACKS.name());
DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
result.ensureIndex(new BasicDBObject(FieldNames.EVENT_NAME.name(), null));
return result;
}
public DBObject storeWindTrackEntry(Event event, RaceDefinition race, WindSource windSource, Wind wind) {
@@ -40,14 +40,14 @@ public class MongoWindStoreImpl extends EmptyWindStore implements MongoWindStore
*/
@Override
public WindTrack getWindTrack(TrackedEvent trackedEvent, TrackedRace trackedRace, WindSource windSource,
long millisecondsOverWhichToAverage) {
long millisecondsOverWhichToAverage, long delayForWindEstimationCacheInvalidation) {
WindTrack result;
if (windSource.canBeStored()) {
result = domainObjectFactory.loadWindTrack(trackedEvent.getEvent(), trackedRace.getRace(), windSource,
millisecondsOverWhichToAverage);
result.addListener(new MongoWindListener(trackedEvent, trackedRace, windSource, mongoObjectFactory, db));
} else {
result = super.getWindTrack(trackedEvent, trackedRace, windSource, millisecondsOverWhichToAverage);
result = super.getWindTrack(trackedEvent, trackedRace, windSource, millisecondsOverWhichToAverage, delayForWindEstimationCacheInvalidation);
}
return result;
}
@@ -9,7 +9,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -100,7 +99,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testEndToEndScenarioWithInitMessages() throws IOException, InterruptedException, ParseException {
public void testEndToEndScenarioWithInitMessages() throws Exception {
String[] racesToTrack = new String[] { "4711", "4712" };
String scriptName = "/InitMessagesScript.txt";
setUpUsingScript(racesToTrack, scriptName);
@@ -128,7 +127,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testLongRaceLog() throws IOException, InterruptedException, ParseException {
public void testLongRaceLog() throws Exception {
String[] racesToTrack = new String[] { "W4702" };
String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt";
String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt";
@@ -180,7 +179,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testLongLogRaceNewConfig() throws UnknownHostException, InterruptedException, IOException, ParseException {
public void testLongLogRaceNewConfig() throws Exception {
String[] racesToTrack = new String[] { "W4702" };
String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt";
String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt";
@@ -232,7 +231,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testDuplicateCCGMessageAndWaypointUniqueness() throws IOException, InterruptedException, ParseException {
public void testDuplicateCCGMessageAndWaypointUniqueness() throws Exception {
String[] racesToTrack = new String[] { "W4702" };
setUpUsingScript(racesToTrack, "/DuplicateCCG.txt");
@@ -255,7 +254,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testRongRaceLogRACZero() throws UnknownHostException, InterruptedException, IOException, ParseException{
public void testRongRaceLogRACZero() throws Exception{
String[] racesToTrack = new String[] { "W4702" };
String scriptName2 = "/SailMasterDataInterfaceRACZero.txt";
setUpUsingScript(racesToTrack, scriptName2);
@@ -272,7 +271,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
@Test
public void testEndToEndWithSwissTimingData() throws IOException, InterruptedException, ParseException {
public void testEndToEndWithSwissTimingData() throws Exception {
String[] racesToTrack = new String[] { "W4702" };
String scriptName1 = "/SailMasterDataInterfaceRACandSTL.txt";
String scriptName2 = "/SailMasterDataInterface-ExampleAsText.txt";
@@ -289,8 +288,7 @@ public class EndToEndListeningStoreAndFowardTest {
}
}
private void setUpUsingScript(String[] racesToTrack, String... scriptNames) throws InterruptedException,
UnknownHostException, IOException, ParseException {
private void setUpUsingScript(String[] racesToTrack, String... scriptNames) throws Exception {
for (String raceToTrack : racesToTrack) {
RacesHandle raceHandle = racingEventService.addSwissTimingRace(raceToTrack, "localhost", CLIENT_PORT, /* canSendRequests */
false, emptyWindStore, -1);
@@ -12,6 +12,8 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.WindStore;
import difflib.PatchFailedException;
@@ -37,4 +39,8 @@ public interface DomainFactory {
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint waypoint, Competitor competitor);
void removeRace(String raceID);
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader);
}
@@ -19,7 +19,6 @@ import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.EventImpl;
@@ -35,9 +34,13 @@ import com.sap.sailing.domain.swisstimingadapter.Fix;
import com.sap.sailing.domain.swisstimingadapter.Mark;
import com.sap.sailing.domain.swisstimingadapter.MessageType;
import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
import com.sap.sailing.domain.swisstimingadapter.StartList;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import difflib.PatchFailedException;
@@ -84,14 +87,14 @@ public class DomainFactoryImpl implements DomainFactory {
olympicClassesByID.put("009", baseDomainFactory.getOrCreateBoatClass("49er", /* alwaysStartsUpwind */ true));
olympicClassesByID.put("007", baseDomainFactory.getOrCreateBoatClass("Star", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("010", baseDomainFactory.getOrCreateBoatClass("Elliott 6M", /* typicallyStartsUpwind */ true));
unknownBoatClass = new BoatClassImpl("Unknown", /* typicallyStartsUpwind */ true);
unknownBoatClass = baseDomainFactory.getOrCreateBoatClass("Unknown", /* typicallyStartsUpwind */ true);
}
@Override
public Event getOrCreateEvent(String raceID) {
Event result = raceIDToEventCache.get(raceID);
if (result == null) {
result = new EventImpl(raceID, null);
result = new EventImpl(raceID, getOrCreateBoatClassFromRaceID(raceID));
raceIDToEventCache.put(raceID, result);
}
return result;
@@ -250,4 +253,12 @@ public class DomainFactoryImpl implements DomainFactory {
return baseDomainFactory.getOrCreateNationality(nationalityName);
}
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader) {
return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, canSendRequests,
swissTimingFactory, domainFactory, windStore, messageLoader);
}
}
@@ -78,13 +78,17 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
this.raceID = raceID;
this.messageLoader = messageLoader;
this.windStore = windStore;
this.id = new Triple<String, String, Integer>(raceID, hostname, port);
this.id = createID(raceID, hostname, port);
connector.addSailMasterListener(raceID, this);
event = domainFactory.getOrCreateEvent(raceID);
setTrackedEvent(trackedEventRegistry.getOrCreateTrackedEvent(event));
connector.trackRace(raceID);
}
static Triple<String, String, Integer> createID(String raceID, String hostname, int port) {
return new Triple<String, String, Integer>(raceID, hostname, port);
}
@Override
public void stop() throws MalformedURLException, IOException, InterruptedException {
connector.removeSailMasterListener(raceID, this);
@@ -0,0 +1,46 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
import com.sap.sailing.domain.tracking.WindStore;
public class SwissTimingTrackingConnectivityParameters implements RaceTrackingConnectivityParameters {
private final String hostname;
private final int port;
private final String raceID;
private final boolean canSendRequests;
private final SwissTimingFactory swissTimingFactory;
private final DomainFactory domainFactory;
private final WindStore windStore;
private final RaceSpecificMessageLoader messageLoader;
public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader) {
super();
this.hostname = hostname;
this.port = port;
this.raceID = raceID;
this.canSendRequests = canSendRequests;
this.swissTimingFactory = swissTimingFactory;
this.domainFactory = domainFactory;
this.windStore = windStore;
this.messageLoader = messageLoader;
}
@Override
public RaceTracker createRaceTracker(TrackedEventRegistry trackedEventRegistry) throws Exception {
return swissTimingFactory.createRaceTracker(raceID, hostname, port, canSendRequests, windStore, messageLoader,
domainFactory, trackedEventRegistry);
}
@Override
public Object getTrackerID() {
return SwissTimingRaceTrackerImpl.createID(raceID, hostname, port);
}
}
@@ -16,5 +16,5 @@
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.206.73 -ea"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.206.73 -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
</launchConfiguration>
@@ -11,7 +11,9 @@ import java.util.Set;
import org.junit.Test;
import com.sap.sailing.domain.base.BearingWithConfidence;
import com.sap.sailing.domain.base.PositionWithConfidence;
import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.PositionWithConfidenceImpl;
@@ -23,6 +25,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
import com.sap.sailing.domain.confidence.ConfidenceBasedAverager;
import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
import com.sap.sailing.domain.confidence.ConfidenceFactory;
import com.sap.sailing.domain.confidence.HasConfidence;
import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable;
@@ -31,6 +34,7 @@ import com.sap.sailing.domain.confidence.Weigher;
import com.sap.sailing.domain.confidence.impl.ScalableDoubleWithConfidence;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.BearingWithConfidenceCluster;
import com.sap.sailing.domain.tracking.impl.ScalableWind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
@@ -114,16 +118,76 @@ public class ConfidenceTest {
}
}
@Test
public void testBearingClusterSplittingWithDifferentBearingsOrdering() {
TimePoint timePoint = new MillisecondsTimePoint(1308839544250l);
BearingWithConfidenceCluster<TimePoint> clusterA = new BearingWithConfidenceCluster<TimePoint>(ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher(
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
/* milliseconds over which to average */ 30000l, /* minimum confidence */ 0.0000000001));
for (String a : new String[] {
"87.0@0.3561978879735175",
"286.8716453147824@0.7507926558478147",
"282.55627120464703@0.7643492902545556",
"286.8605698949788@0.7483842322506868",
"291.5836361697427@0.7491852169449421",
"297.0631828865192@0.7488453128197485",
"283.6400613098378@0.7488453128197485",
"279.95201024864554@0.7298408190555351",
"279.77216720379766@0.7283443881177472",
"283.75770067567913@0.7491852169449421",
"284.30394138063696@0.7488453128197485",
"285.5253164529858@0.7491852169449421"
}) {
BearingWithConfidence<TimePoint> bearingWithConfidence = parseBearingWithConfidence(a);
clusterA.add(bearingWithConfidence);
}
BearingWithConfidenceCluster<TimePoint>[] splitResultA = clusterA.splitInTwo(45.0, timePoint);
BearingWithConfidenceCluster<TimePoint> clusterB = new BearingWithConfidenceCluster<TimePoint>(ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher(
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
/* milliseconds over which to average */ 30000l, /* minimum confidence */ 0.0000000001));
for (String b : new String[] {
"282.55627120464703@0.7643492902545556",
"286.8716453147824@0.7507926558478147",
"286.8605698949788@0.7483842322506868",
"285.5253164529858@0.7491852169449421",
"283.75770067567913@0.7491852169449421",
"283.6400613098378@0.7488453128197485",
"279.95201024864554@0.7298408190555351",
"291.5836361697427@0.7491852169449421",
"297.0631828865192@0.7488453128197485",
"279.77216720379766@0.7283443881177472",
"284.30394138063696@0.7488453128197485",
"87.0@0.3561978879735175"
}) {
BearingWithConfidence<TimePoint> bearingWithConfidence = parseBearingWithConfidence(b);
clusterB.add(bearingWithConfidence);
}
BearingWithConfidenceCluster<TimePoint>[] splitResultB = clusterB.splitInTwo(45.0, timePoint);
assertEquals(11, Math.max(splitResultA[0].size(), splitResultA[1].size()));
assertEquals(1, Math.min(splitResultA[0].size(), splitResultA[1].size()));
assertEquals(11, Math.max(splitResultB[0].size(), splitResultB[1].size()));
assertEquals(1, Math.min(splitResultB[0].size(), splitResultB[1].size()));
assertEquals((splitResultA[0].size()<splitResultA[1].size()?splitResultA[0]:splitResultA[1]).getAverage(timePoint).getObject().getDegrees(),
(splitResultB[0].size()<splitResultB[1].size()?splitResultB[0]:splitResultB[1]).getAverage(timePoint).getObject().getDegrees(), 0.0000001);
}
private BearingWithConfidence<TimePoint> parseBearingWithConfidence(String a) {
String[] bearingAndConfidence = a.split("@");
double degBearing = Double.valueOf(bearingAndConfidence[0]);
double confidence = Double.valueOf(bearingAndConfidence[1]);
return new BearingWithConfidenceImpl<TimePoint>(new DegreeBearingImpl(degBearing), confidence, new MillisecondsTimePoint(1308839544250l));
}
@Test
public void testLinearWeigherHalfTime() {
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000);
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000);
double confidence = w.getConfidence(new MillisecondsTimePoint(1000), new MillisecondsTimePoint(0));
assertEquals(0.5, confidence, 0.00000001);
}
@Test
public void testLinearWeigherZeroTimeDifference() {
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000);
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000);
double confidence = w.getConfidence(new MillisecondsTimePoint(1000), new MillisecondsTimePoint(1000));
assertEquals(1.0, confidence, 0.00000001);
}
@@ -143,14 +207,76 @@ public class ConfidenceTest {
assertNull(average);
}
@Test
public void testAveragingWithTwoWindsOneOfWhichHasUseSpeedFalse() {
WindWithConfidence<TimePoint> d1 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(0, 0),
new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(0), /* useSpeed */ true);
WindWithConfidence<TimePoint> d2 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ false);
ConfidenceBasedWindAverager<TimePoint> averager = ConfidenceFactory.INSTANCE
.createWindAverager(ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000));
List<WindWithConfidence<TimePoint>> list = Arrays.asList(d1, d2);
HasConfidence<ScalableWind, Wind, TimePoint> average = averager.getAverage(list, new MillisecondsTimePoint(10));
assertEquals(0.5, average.getObject().getPosition().getLatDeg(), 0.00000001);
assertEquals(0.0, average.getObject().getPosition().getLngDeg(), 0.00000001);
assertEquals(10, average.getObject().getTimePoint().asMillis());
assertEquals(10, average.getObject().getKnots(), 0.00000001);
assertEquals(135, average.getObject().getBearing().getDegrees(), 0.00000001);
}
@Test
public void testAveragingOneWindWithUseSpeedFalse() {
WindWithConfidence<TimePoint> d1 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ false);
ConfidenceBasedWindAverager<TimePoint> averager = ConfidenceFactory.INSTANCE
.createWindAverager(ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000));
List<WindWithConfidence<TimePoint>> list = Arrays.asList(d1);
HasConfidence<ScalableWind, Wind, TimePoint> average = averager.getAverage(list, new MillisecondsTimePoint(20));
assertEquals(1.0, average.getObject().getPosition().getLatDeg(), 0.00000001);
assertEquals(0.0, average.getObject().getPosition().getLngDeg(), 0.00000001);
assertEquals(20, average.getObject().getTimePoint().asMillis());
assertEquals(10, average.getObject().getKnots(), 0.00000001);
assertEquals(180, average.getObject().getBearing().getDegrees(), 0.00000001);
}
@Test
public void testAveragingWithFourWindsTwoOfWhichHaveUseSpeedFalse() {
WindWithConfidence<TimePoint> d1 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ true);
WindWithConfidence<TimePoint> d2 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ true);
WindWithConfidence<TimePoint> d3 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(30, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ false);
WindWithConfidence<TimePoint> d4 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(40, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ false);
ConfidenceBasedWindAverager<TimePoint> averager = ConfidenceFactory.INSTANCE
.createWindAverager(ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000));
List<WindWithConfidence<TimePoint>> list = Arrays.asList(d1, d2, d3, d4);
HasConfidence<ScalableWind, Wind, TimePoint> average = averager.getAverage(list, new MillisecondsTimePoint(20));
assertEquals(1.0, average.getObject().getPosition().getLatDeg(), 0.00000001);
assertEquals(0.0, average.getObject().getPosition().getLngDeg(), 0.00000001);
assertEquals(20, average.getObject().getTimePoint().asMillis());
assertEquals(15, average.getObject().getKnots(), 0.00000001);
assertEquals(180, average.getObject().getBearing().getDegrees(), 0.00000001);
}
@Test
public void testAveragingWithTwoWinds() {
WindWithConfidence<TimePoint> d1 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(0),
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))), /* confidence */ 0.5, /* relativeTo */ new MillisecondsTimePoint(0));
WindWithConfidence<TimePoint> d2 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0), new MillisecondsTimePoint(20),
new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(180))), /* confidence */ 0.5, /* relativeTo */ new MillisecondsTimePoint(20));
ConfidenceBasedAverager<ScalableWind, Wind, TimePoint> averager = ConfidenceFactory.INSTANCE
.createAverager(ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000));
WindWithConfidence<TimePoint> d1 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(0, 0),
new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(0), /* useSpeed */ true);
WindWithConfidence<TimePoint> d2 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0),
new MillisecondsTimePoint(20), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(180))), /* confidence */
0.5, /* relativeTo */new MillisecondsTimePoint(20), /* useSpeed */ true);
ConfidenceBasedWindAverager<TimePoint> averager = ConfidenceFactory.INSTANCE
.createWindAverager(ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000));
List<WindWithConfidence<TimePoint>> list = Arrays.asList(d1, d2);
HasConfidence<ScalableWind, Wind, TimePoint> average = averager.getAverage(list, new MillisecondsTimePoint(10));
assertEquals(0.5, average.getObject().getPosition().getLatDeg(), 0.00000001);
@@ -1,71 +0,0 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
public class LongRangeRegattaGeocoderTest extends AbstractManeuverDetectionTestCase {
public LongRangeRegattaGeocoderTest() throws MalformedURLException, URISyntaxException {
super();
}
@Before
public void setUp() throws URISyntaxException, IOException, InterruptedException {
super.setUp();
super.setUp("event_20110609_KielerWoch",
/* raceId */"357c700a-9d9a-11e0-85be-406186cbf87c", new ReceiverType[] { ReceiverType.MARKPASSINGS,
ReceiverType.RACESTARTFINISH, ReceiverType.RACECOURSE });
fixApproximateMarkPositionsForGeocoder(getTrackedRace());
dateFormat = new SimpleDateFormat("MM/dd/yyyy-HH:mm:ss");
}
private void fixApproximateMarkPositionsForGeocoder(DynamicTrackedRace race) {
TimePoint epoch = new MillisecondsTimePoint(0l);
TimePoint now = MillisecondsTimePoint.now();
Map<String, Position> buoyPositions = new HashMap<String, Position>();
buoyPositions.put("K Start (left)", new DegreePosition(5.90906782829855, -55.16166687011719));
buoyPositions.put("K Start (right)", new DegreePosition(5.939801840526332, -55.17402648925781));
buoyPositions.put("K Mark1", new DegreePosition(8.233237111274565, -31.640625));
buoyPositions.put("K Mark4 (right)", new DegreePosition(13.63083009512624, -21.8243408203125));
buoyPositions.put("K Mark4 (left)", new DegreePosition(13.870080100685891, -21.9122314453125));
buoyPositions.put("K Finish (left)", new DegreePosition(14.694198629294522, -17.39307403564453));
buoyPositions.put("K Finish (right)", new DegreePosition(14.693866535193942, -17.371788024902344));
for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
for (Buoy buoy : w.getBuoys()) {
race.getOrCreateTrack(buoy).addGPSFix(new GPSFixImpl(buoyPositions.get(buoy.getName()), epoch));
race.getOrCreateTrack(buoy).addGPSFix(new GPSFixImpl(buoyPositions.get(buoy.getName()), now));
}
}
}
@Test
public void testSetupOK() throws ParseException, NoWindException {
assertNotNull(getTrackedRace());
Pair<Placemark, Placemark> placemarks = getTrackedRace().getStartFinishPlacemarks();
assertNotNull(placemarks.getA());
assertNotNull(placemarks.getB());
}
}
@@ -80,7 +80,13 @@ public class ManeuverDetectionOnMdM2011SemifinalTest extends OnlineTracTracBased
c.set(2011, 10-1, 30, 13, 34, 00);
assertManeuver(maneuvers, ManeuverType.TACK, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
c.set(2011, 10-1, 30, 13, 35, 46);
assertManeuver(maneuvers, ManeuverType.JIBE, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
// tolerance for the JIBE maneuver around 13:35:46 needs to be a bit greater than for the others because depending on coincidental
// Douglas-Peucker approximation, the DP point after the jibe may either be detected at 13:35:51 which will still make it inside
// the bounds for grouping DP points into one maneuver, or the DP point is put to 13:35:54 which then is one second too late
// to be grouped into the same maneuver as the DP point before the jibe which is at 13:35:45. In that case, the jibe is detected
// at 13:35:54 with a much smaller jibing angle while the maneuver before which otherwise is grouped into the jibe then is
// only a bearing away by a lot.
assertManeuver(maneuvers, ManeuverType.JIBE, Tack.STARBOARD, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 10000);
c.set(2011, 10-1, 30, 13, 36, 49);
assertManeuver(maneuvers, ManeuverType.JIBE, Tack.PORT, new MillisecondsTimePoint(c.getTime()), /* tolerance in milliseconds */ 3000);
}
@@ -21,6 +21,7 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
this.started = started;
this.competitor = competitor;
this.raceDefinition = new RaceDefinition() {
private static final long serialVersionUID = 6812543850545870357L;
@Override
public String getName() {
return null;
@@ -17,6 +17,7 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
competitors = new HashSet<Competitor>();
competitors.add(competitor);
this.raceDefinition = new RaceDefinition() {
private static final long serialVersionUID = 8878878939443845646L;
@Override
public String getName() {
return null;
@@ -49,25 +49,25 @@ public class MultipleClassesInEventTest {
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
kiwotest2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
kiwotest3 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
weym470may112014_2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
assertEquals("STG", kiwotest1.getEvent().getBoatClass().getName());
assertEquals("505", kiwotest2.getEvent().getBoatClass().getName());
@@ -90,7 +90,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
setStoredDataLoaded(false);
ArrayList<Receiver> receivers = new ArrayList<Receiver>();
for (Receiver r : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE,
new DynamicRaceDefinitionSet() {
/* startOfTracking */ null, /* endOfTracking */ null, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
@@ -94,11 +94,11 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
receivers.add(receiver);
for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers(
new DynamicTrackedEventImpl(DomainFactory.INSTANCE.getOrCreateEvent(getEvent())),
getEvent(), EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
getEvent(), EmptyWindStore.INSTANCE, /* startOfTracking */ null, /* endOfTracking */ null,
new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {}
}, ReceiverType.RACECOURSE,
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
}, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
receivers.add(r);
}
addListenersForStoredDataAndStartController(receivers);
@@ -76,8 +76,8 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
public void raceRemoved(TrackedRace trackedRace) {
}
});
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE,
new DynamicRaceDefinitionSet() {
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), /* start of tracking */ null,
/* end of tracking */ null, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
@@ -87,7 +87,7 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
}
}
addListenersForStoredDataAndStartController(domainFactory.getUpdateReceivers(trackedEvent, getEvent(),
EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
/* start of tracking */ null, /* end of tracking */ null, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
@@ -46,6 +46,8 @@ public abstract class TrackBasedTest {
/**
* Creates a simple two-lap upwind-downwind course for a race/event with given name and boat class name with the
* competitors specified. The marks are laid out such that the upwind/downwind leg detection should be alright.
* Wind is coming from the north. A single wind fix with bearing 180deg (from=0deg) is added to the {@link WindSourceType#WEB}
* wind track using <code>timePointForFixes</code> as time point.
*
* @param timePointForFixes
* a wind fix will be inserted into the {@link WindSourceType#WEB} wind track which is aligned with the
@@ -73,7 +75,8 @@ public abstract class TrackBasedTest {
Course course = new CourseImpl(raceName, waypoints);
RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors);
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(trackedEvent, race, EmptyWindStore.INSTANCE,
/* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000);
/* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000,
/* delay for wind estimation cache invalidation */ 0);
// in this simplified artificial course, the top mark is exactly north of the right leeward gate
DegreePosition topPosition = new DegreePosition(54.48, 10.24);
trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.234451), new MillisecondsTimePoint(0)));
@@ -82,9 +85,8 @@ public abstract class TrackBasedTest {
trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.234451), MillisecondsTimePoint.now()));
trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.24), MillisecondsTimePoint.now()));
trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, MillisecondsTimePoint.now()));
trackedRace.getOrCreateWindTrack(new WindSourceImpl(WindSourceType.WEB)).add(
new WindImpl(topPosition, timePointForFixes,
new KnotSpeedWithBearingImpl(/* speedInKnots */14.7, new DegreeBearingImpl(180))));
trackedRace.recordWind(new WindImpl(topPosition, timePointForFixes, new KnotSpeedWithBearingImpl(
/* speedInKnots */14.7, new DegreeBearingImpl(180))), new WindSourceImpl(WindSourceType.WEB));
return trackedRace;
}
@@ -45,7 +45,7 @@ public class UnicodeCharactersInCompetitorNamesTest {
"http://" + TracTracConnectionConstants.HOST_NAME + "/events/event_20110609_KielerWoch/clientparams.php?event=event_20110609_KielerWoch&race=5b08a9ee-9933-11e0-85be-406186cbf87c"),
tractracTunnel ? new URI("tcp://"+tractracTunnelHost+":"+TracTracConnectionConstants.PORT_TUNNEL_LIVE) : new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_LIVE),
tractracTunnel ? new URI("tcp://"+tractracTunnelHost+":"+TracTracConnectionConstants.PORT_TUNNEL_STORED) : new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_STORED),
EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedEventRegistry());
Iterable<Competitor> competitors = fourtyninerYellow_2.getRacesHandle().getRaces().iterator().next().getCompetitors();
for (Competitor competitor : competitors) {
@@ -38,15 +38,20 @@ 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.NauticalMileDistance;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest {
private List<Competitor> competitors;
@@ -92,11 +97,60 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
}
private void setBearingForCompetitor(Competitor competitor, TimePoint timePoint, double bearingDeg) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> hungersTrack = getTrackedRace().getTrack(competitor);
hungersTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), timePoint,
DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrackedRace().getTrack(competitor);
competitorTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), timePoint,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(bearingDeg))));
}
/**
* Uses competitors on upwind leg to cause a valid wind estimation. Additionally, for the same time point, adds a
* wind fix to the {@link WindSourceType#WEB} wind track that is slightly different from the expected estimation
* result. Then, asks the tracked race for the combined wind direction. The expected outcome is that the larger
* the minimal cluster size (boats on the same tack going upwind), the more confident the wind estimation is,
* and subsequently the more it is considered in averaging between the {@link WindSourceType#WEB} wind track
* and the estimation wind track.
*/
@Test
public void testWindEstimationPreferringLargerClusters() throws NoWindException {
initRace(4, new int[] { 1, 1, 1, 1 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = new MillisecondsTimePoint(new GregorianCalendar(2012, 03, 14).getTime());
getTrackedRace().recordWind(new WindImpl(null, now, new KnotSpeedWithBearingImpl(/* speedInKnots */ 12, new DegreeBearingImpl(180.))),
new WindSourceImpl(WindSourceType.WEB));
// produces estimated bearing of 170deg; result should be averaged between 107 (estimation) and 180 (web) deg
setBearingForCompetitor(competitors.get(0), now, 305);
setBearingForCompetitor(competitors.get(1), now, 35);
WindWithConfidence<Pair<Position, TimePoint>> combinedWindDirectionMinClusterSizeOne = getTrackedRace()
.getWindWithConfidence(/* position */null, now);
final double combinedDegreesMinClusterSizeOne = combinedWindDirectionMinClusterSizeOne.getObject().getBearing().getDegrees();
assertTrue(combinedDegreesMinClusterSizeOne > 170 && combinedDegreesMinClusterSizeOne < 180);
// now produce a minimum cluster size of 2, raising the estimation's confidence
setBearingForCompetitor(competitors.get(2), now, 305);
setBearingForCompetitor(competitors.get(3), now, 35);
WindWithConfidence<Pair<Position, TimePoint>> combinedWindDirectionMinClusterSizeTwo = getTrackedRace()
.getWindWithConfidence(/* position */null, now);
final double combinedDegreesNow = combinedWindDirectionMinClusterSizeTwo.getObject().getBearing().getDegrees();
assertTrue(combinedDegreesNow > 170 && combinedDegreesNow < 180);
// we expect the combined direction now to be closer to the estimation as compared to before because the estimation is more confident
// since the minimum cluster size is 2 instead of 1
assertTrue(combinedDegreesNow < combinedDegreesMinClusterSizeOne);
}
@Test
public void testCombinedWindTrack() throws NoWindException {
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 315);
setBearingForCompetitor(competitors.get(1), now, 45); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
setBearingForCompetitor(competitors.get(3), now, 225); // on the same tack, should give no read-out
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(/* position */ null, now);
// less precision because downwind estimation has less confidence
assertEquals(180., estimatedWindDirection.getBearing().getDegrees(), 0.00000001);
CombinedWindTrackImpl combinedTrack = new CombinedWindTrackImpl(getTrackedRace(), WindSourceType.COMBINED.getBaseConfidence());
Wind combinedWindDirection = combinedTrack.getAveragedWind(/* position */ null, now);
assertEquals(180., combinedWindDirection.getBearing().getDegrees(), 0.00001);
}
/**
* See <a href="http://sapcoe-app01.pironet-ndh.com/show_bug.cgi?id=166">bug #166</a>
*/
@@ -104,18 +158,23 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
public void testWindEstimationCacheInvalidationAfterLegTypeChange() throws NoWindException {
TimePoint fixTime = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime());
TimePoint checkTime = new MillisecondsTimePoint(fixTime.asMillis()+60000); // one minute later
initRace(2, new int[] { 1, 1 }, fixTime);
initRace(4, new int[] { 1, 1, 1, 1 }, fixTime);
getTrackedRace().setRaceIsKnownToStartUpwind(false); // use only WEB wind to determine leg type
TimePoint now = checkTime;
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 50);
setBearingForCompetitor(competitors.get(2), now, 140);
setBearingForCompetitor(competitors.get(3), now, 230);
TrackedLeg firstLeg = getTrackedRace().getTrackedLeg(getTrackedRace().getRace().getCourse().getLegs().iterator().next());
assertEquals(LegType.UPWIND, firstLeg.getLegType(new MillisecondsTimePoint(MillisecondsTimePoint.now().asMillis())));
final Map<TimePoint, Wind> cachedFixes = new HashMap<TimePoint, Wind>();
final Map<TimePoint, WindWithConfidence<TimePoint>> cachedFixes = new HashMap<TimePoint, WindWithConfidence<TimePoint>>();
TrackBasedEstimationWindTrackImpl track = new TrackBasedEstimationWindTrackImpl(
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000) {
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000, WindSourceType.TRACK_BASED_ESTIMATION.getBaseConfidence(),
/* delay for cache invalidation in milliseconds */ 0l) {
private static final long serialVersionUID = -4540785297605915273L;
@Override
protected void cache(TimePoint timePoint, Wind fix) {
protected void cache(TimePoint timePoint, WindWithConfidence<TimePoint> fix) {
super.cache(timePoint, fix);
if (fix != null) {
cachedFixes.put(timePoint, fix);
@@ -126,7 +185,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
assertNotNull(estimatedWindDirection);
assertEquals(185., estimatedWindDirection.getBearing().getDegrees(), 0.00000001);
assertFalse(cachedFixes.isEmpty());
assertEquals(185., cachedFixes.values().iterator().next().getBearing().getDegrees(), 0.00000001);
assertEquals(185., cachedFixes.values().iterator().next().getObject().getBearing().getDegrees(), 0.00000001);
// now invert leg's type by moving the top mark along the wind from the leeward gate:
Iterator<Waypoint> waypointsIter = getTrackedRace().getRace().getCourse().getWaypoints().iterator();
Waypoint leewardMark = waypointsIter.next();
@@ -140,7 +199,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
assertEquals(LegType.DOWNWIND, firstLeg.getLegType(fixTime));
Wind estimatedWindDirectionDownwind = track.getAveragedWind(/* position */ null, checkTime);
assertNotNull(estimatedWindDirectionDownwind);
assertEquals(5., estimatedWindDirectionDownwind.getBearing().getDegrees(), 0.00000001);
assertEquals(185., estimatedWindDirectionDownwind.getBearing().getDegrees(), 0.00000001);
}
@Test
@@ -151,11 +210,14 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
TimePoint now = checkTime;
setBearingForCompetitor(competitors.get(0), checkTime, 320);
setBearingForCompetitor(competitors.get(1), checkTime, 50);
final Map<TimePoint, Wind> cachedFixes = new HashMap<TimePoint, Wind>();
final Map<TimePoint, WindWithConfidence<TimePoint>> cachedFixes = new HashMap<TimePoint, WindWithConfidence<TimePoint>>();
TrackBasedEstimationWindTrackImpl track = new TrackBasedEstimationWindTrackImpl(
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000) {
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000, WindSourceType.TRACK_BASED_ESTIMATION.getBaseConfidence(),
/* delay for cache invalidation in milliseconds */ 0l) {
private static final long serialVersionUID = -2264781053554744932L;
@Override
protected void cache(TimePoint timePoint, Wind fix) {
protected void cache(TimePoint timePoint, WindWithConfidence<TimePoint> fix) {
super.cache(timePoint, fix);
if (fix != null) {
cachedFixes.put(timePoint, fix);
@@ -166,15 +228,15 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
assertNotNull(estimatedWindDirection);
assertEquals(185., estimatedWindDirection.getBearing().getDegrees(), 0.00000001);
assertFalse(cachedFixes.isEmpty());
assertEquals(185., cachedFixes.values().iterator().next().getBearing().getDegrees(), 0.00000001);
assertEquals(185., cachedFixes.values().iterator().next().getObject().getBearing().getDegrees(), 0.00000001);
// now clear set of cached fixes, ask again and ensure nothing is cached again:
cachedFixes.clear();
Wind estimatedWindDirectionCached = track.getAveragedWind(/* position */ null, checkTime);
assertTrue(cachedFixes.isEmpty());
assertNotNull(estimatedWindDirectionCached);
assertEquals(185., estimatedWindDirectionCached.getBearing().getDegrees(), 0.00000001);
// now add a GPS fix and make sure the cache is invalidated
now = MillisecondsTimePoint.now();
// now add a GPS fix and make sure the cache is invalidated by adding it inside the averaging interval
now = new MillisecondsTimePoint(checkTime.asMillis() + track.getMillisecondsOverWhichToAverageWind()/2);
setBearingForCompetitor(competitors.get(0), now, 330);
Wind estimatedWindDirectionNew = track.getAveragedWind(/* position */ null, checkTime);
assertFalse(cachedFixes.isEmpty());
@@ -260,9 +322,9 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
initRace(4, new int[] { 1, 1, 2, 2 }, new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
MillisecondsTimePoint now = MillisecondsTimePoint.now();
setBearingForCompetitor(competitors.get(0), now, 315);
setBearingForCompetitor(competitors.get(1), now, 50); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(1), now, 45); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(2), now, 135);
setBearingForCompetitor(competitors.get(3), now, 220); // on the same tack, should give no read-out
setBearingForCompetitor(competitors.get(3), now, 225); // on the same tack, should give no read-out
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(/* position */ null, now);
assertEquals(180., estimatedWindDirection.getBearing().getDegrees(), 0.00000001);
}
@@ -58,7 +58,8 @@ public class WindEstimationOnKielerWoche505Race2DataTest extends OnlineTracTracB
// hence have a direction change.
TimePoint middle = new MillisecondsTimePoint(1308839492322l);
TrackBasedEstimationWindTrackImpl estimatedWindTrack = new TrackBasedEstimationWindTrackImpl(getTrackedRace(),
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND);
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, WindSourceType.TRACK_BASED_ESTIMATION.getBaseConfidence(),
/* delay for cache invalidation in milliseconds */ 0l);
Wind estimatedWindDirection = getTrackedRace().getEstimatedWindDirection(/* position */ null, middle);
assertNotNull(estimatedWindDirection);
Wind estimationBasedOnTrack = estimatedWindTrack.getAveragedWind(null, middle);
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -29,7 +30,7 @@ public class WindTest {
*/
@Test
public void testAveragingWind() throws InterruptedException {
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS);
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true);
TimePoint t1 = MillisecondsTimePoint.now();
TimePoint t2 = new MillisecondsTimePoint(t1.asMillis()+10);
TimePoint middle = new MillisecondsTimePoint((t1.asMillis()+t2.asMillis())/2);
@@ -44,7 +45,7 @@ public class WindTest {
@Test
public void testMultipleWindFixesWithSameTimestampInSameWindTrack() {
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS);
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true);
TimePoint now = MillisecondsTimePoint.now();
DegreePosition pos1 = new DegreePosition(0, 0);
DegreePosition pos2 = new DegreePosition(1, 1);
@@ -65,7 +66,7 @@ public class WindTest {
@Test
public void testEmptyTrackYieldsNullAsWindEstimate() {
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS);
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true);
assertNull(track.getAveragedWind(new DegreePosition(0, 0), MillisecondsTimePoint.now()));
}
@@ -76,15 +77,15 @@ public class WindTest {
*/
@Test
public void testAveragingOfSparseWindTrack() {
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS);
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(0)));
Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0)));
Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0)));
Wind wind3 = new WindImpl(pos, new MillisecondsTimePoint(2000), new KnotSpeedWithBearingImpl(30, new DegreeBearingImpl(0)));
Wind wind4 = new WindImpl(pos, new MillisecondsTimePoint(10000), new KnotSpeedWithBearingImpl(40, new DegreeBearingImpl(0)));
Wind wind5 = new WindImpl(pos, new MillisecondsTimePoint(30000), new KnotSpeedWithBearingImpl(50, new DegreeBearingImpl(0)));
Wind wind6 = new WindImpl(pos, new MillisecondsTimePoint(40000), new KnotSpeedWithBearingImpl(60, new DegreeBearingImpl(0)));
Wind wind7 = new WindImpl(pos, new MillisecondsTimePoint(50000), new KnotSpeedWithBearingImpl(70, new DegreeBearingImpl(0)));
Wind wind3 = new WindImpl(pos, new MillisecondsTimePoint(2000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0)));
Wind wind4 = new WindImpl(pos, new MillisecondsTimePoint(10000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0)));
Wind wind5 = new WindImpl(pos, new MillisecondsTimePoint(30000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0)));
Wind wind6 = new WindImpl(pos, new MillisecondsTimePoint(40000), new KnotSpeedWithBearingImpl(130, new DegreeBearingImpl(0)));
Wind wind7 = new WindImpl(pos, new MillisecondsTimePoint(50000), new KnotSpeedWithBearingImpl(170, new DegreeBearingImpl(0)));
track.add(wind1);
track.add(wind2);
track.add(wind3);
@@ -94,18 +95,22 @@ public class WindTest {
track.add(wind7);
// interval does bearely reach 20's burst because 0 has 0 length and 1000..30000 has 29000 length
assertEquals((10+20+30+40+50)/5, track.getAveragedWind(pos, new MillisecondsTimePoint(1)).getKnots(), 0.00000001);
assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1)).getKnots(), 0.00000001);
// interval uses the two fixes to the left (0, 1000)=1000 and three to the right (2000, 10000, 30000)=28000
assertEquals((10+20+30+40+50)/5, track.getAveragedWind(pos, new MillisecondsTimePoint(1001)).getKnots(), 0.00000001);
assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1001)).getKnots(), 0.00000001);
// in the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000)=10000, so 20000ms worth of wind
assertEquals((10+20+30+40+50+60)/6, track.getAveragedWind(pos, new MillisecondsTimePoint(11000)).getKnots(), 0.00000001);
final double averageFor20000 = track.getAveragedWind(pos, new MillisecondsTimePoint(20000)).getKnots();
// value is hard to predict exactly because time difference-based confidences rate fixes closer to 20000ms higher than those further away
assertEquals(35, averageFor20000, 5);
// right of the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000, 50000)=20000
assertEquals((10+20+30+40+50+60+70)/7, track.getAveragedWind(pos, new MillisecondsTimePoint(20500)).getKnots(), 0.00000001);
final double averageFor20500 = track.getAveragedWind(pos, new MillisecondsTimePoint(20500)).getKnots();
assertEquals(37, averageFor20500, 5);
assertTrue(averageFor20500 > averageFor20000);
}
@Test
public void testSingleElementWindTrack() {
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS);
WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123)));
track.add(wind);
@@ -116,7 +121,7 @@ public class WindTest {
@Test
public void testSingleElementExtrapolation() {
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */);
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123)));
track.add(wind);
@@ -128,7 +133,7 @@ public class WindTest {
@Test
public void testSingleElementExtrapolationBeyondThreshold() {
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */);
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123)));
track.add(wind);
@@ -141,20 +146,20 @@ public class WindTest {
@Test
public void testTwoElementWindTrackSameBearing() {
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */);
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(100)));
track.add(wind1);
Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(100)));
track.add(wind2);
Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(2000));
Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(500));
assertEquals(15, estimate.getKnots(), 0.000000001);
assertEquals(100, estimate.getBearing().getDegrees(), 0.00000001);
}
@Test
public void testTwoElementWindTrackDifferentBearing() {
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */);
WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true);
DegreePosition pos = new DegreePosition(0, 0);
Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(110)));
track.add(wind1);
@@ -186,7 +191,7 @@ public class WindTest {
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(85)));
Wind wind3 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T17:31:40").getTime()),
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(80)));
WindTrack track = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000);
WindTrack track = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true);
track.add(wind1);
track.add(wind2);
track.add(wind3);
@@ -12,7 +12,6 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Tack;
@@ -40,7 +39,8 @@ import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
public class MockedTrackedRace implements DynamicTrackedRace {
private final WindTrack windTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000);
private static final long serialVersionUID = 5827912985564121181L;
private final WindTrack windTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true);
public WindTrack getWindTrack() {
return windTrack;
@@ -272,9 +272,13 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public DynamicTrackedEvent getTrackedEvent() {
return new DynamicTrackedEvent() {
private static final long serialVersionUID = 2651590861333064588L;
@Override
public Event getEvent() {
return new Event() {
private static final long serialVersionUID = -4908774269425170811L;
@Override
public String getName() {
return "A Mocked Test Event";
@@ -421,12 +425,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
}
@Override
public Pair<Placemark, Placemark> getStartFinishPlacemarks() {
// TODO Auto-generated method stub
return null;
}
@Override
public RaceIdentifier getRaceIdentifier() {
// TODO Auto-generated method stub
@@ -483,4 +481,57 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public WindWithConfidence<TimePoint> getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<WindSource> getWindSourcesToExclude() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setWindSourcesToExclude(Iterable<WindSource> windSourcesToExclude) {
// TODO Auto-generated method stub
}
@Override
public TimePoint getEndOfTracking() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getTimePointOfOldestEvent() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) {
// TODO Auto-generated method stub
}
@Override
public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) {
// TODO Auto-generated method stub
}
@Override
public Iterable<TimePoint> getStartTimesOfTrackedLegs() {
// TODO Auto-generated method stub
return null;
}
}
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedEvent;
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -100,17 +101,26 @@ public interface DomainFactory {
*
* The link to the {@link RaceDefinition} is created in the {@link DomainFactory} when the
* {@link RaceCourseReceiver} creates the {@link TrackedRace} object. Starting then, the {@link DomainFactory} will
* respond with the {@link RaceDefinition} when its {@link DomainFactory#getRaces(Event)} is called with the TracTrac
* {@link Event} as argument that is used for its tracking.
* respond with the {@link RaceDefinition} when its {@link DomainFactory#getRaces(Event)} is called with the
* TracTrac {@link Event} as argument that is used for its tracking.
* <p>
*
* @param startOfTracking
* if <code>null</code>, all stored data from the "beginning of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this start time.
* @param endOfTracking
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
* @param windStore
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
* available but loses track of the wind, e.g., during server restarts.
* @param trackedEventRegistry TODO
*/
TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore, TrackedEventRegistry trackedEventRegistry) throws MalformedURLException,
FileNotFoundException, URISyntaxException;
TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, TrackedEventRegistry trackedEventRegistry)
throws MalformedURLException, FileNotFoundException, URISyntaxException;
BoatClass getOrCreateBoatClass(CompetitorClass competitorClass);
@@ -125,12 +135,20 @@ public interface DomainFactory {
* must have been created before through
* {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)} because otherwise the link to the
* {@link Event} can't be established
* @param startOfTracking
* if <code>null</code>, all stored data from the "beginning of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this start time.
* @param endOfTracking
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has to
* provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
* @param tokenToRetrieveAssociatedRace
* used to update the set of{@link RaceDefinition}s received by the
* {@link RaceCourseReceiver} created by this call
*/
Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent, Event tractracEvent, WindStore windStore,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent, Event tractracEvent, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
/**
* Creates a {@link RaceDefinition} from a TracTrac {@link Race} and a domain {@link Course} definition. The
@@ -165,7 +183,7 @@ public interface DomainFactory {
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint passed, com.sap.sailing.domain.base.Competitor competitor);
Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent, Event tractracEvent, WindStore windStore,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types);
TimePoint startOfTracking, TimePoint endOfTracking, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types);
DynamicTrackedRace trackRace(DynamicTrackedEvent trackedEvent, RaceDefinition raceDefinition, WindStore windStore,
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
@@ -186,8 +204,7 @@ public interface DomainFactory {
*/
void updateCourseWaypoints(Course courseToUpdate, List<ControlPoint> controlPoints) throws PatchFailedException;
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI,
String storedDataURI);
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI);
/**
* Fetch the race definition for <code>race</code>. If the race definition hasn't been created yet, the call blocks
@@ -203,7 +220,9 @@ public interface DomainFactory {
RaceDefinition getAndWaitForRaceDefinition(Race race, long timeoutInMilliseconds);
Pair<List<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(Race race);
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore);
/**
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
* {@link com.sap.sailing.domain.base.Event} and, if a {@link TrackedRace} for the corresponding
@@ -214,5 +233,5 @@ public interface DomainFactory {
* will have to create a new one.
*/
void removeRace(Event tractracEvent, Race tractracRace, TrackedEventRegistry trackedEventRegistry);
}
@@ -44,7 +44,7 @@ public class RaceRecord {
TimePoint tp = null;
if (trackingstarttime != null) {
try {
tp = new MillisecondsTimePoint(DateParser.parse(trackingstarttime).getTime());
tp = new MillisecondsTimePoint(DateParser.parseUTC(trackingstarttime).getTime());
} catch (InvalidDateException e) {
logger.warning("Unable to parse trackingstarttime of race "+name+": "+trackingstarttime+". Leaving null.");
}
@@ -53,7 +53,7 @@ public class RaceRecord {
tp = null;
if (trackingendtime != null) {
try {
tp = new MillisecondsTimePoint(DateParser.parse(trackingendtime).getTime());
tp = new MillisecondsTimePoint(DateParser.parseUTC(trackingendtime).getTime());
} catch (InvalidDateException e) {
logger.warning("Unable to parse trackingendtime of race "+name+": "+trackingendtime+". Leaving null.");
}
@@ -62,7 +62,7 @@ public class RaceRecord {
tp = null;
if (racestarttime != null) {
try {
tp = new MillisecondsTimePoint(DateParser.parse(racestarttime).getTime());
tp = new MillisecondsTimePoint(DateParser.parseUTC(racestarttime).getTime());
} catch (InvalidDateException e) {
logger.warning("Unable to parse racestarttime of race "+name+": "+racestarttime+". Leaving null.");
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tractracadapter;
/**
* Configuration parameters that can be used to connect to a TracTrac event / race.
*
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedEvent;
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -61,6 +62,7 @@ import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.sap.sailing.util.WeakIdentityHashMap;
import com.tractrac.clientmodule.CompetitorClass;
import com.tractrac.clientmodule.ControlPoint;
import com.tractrac.clientmodule.Race;
@@ -91,6 +93,14 @@ public class DomainFactoryImpl implements DomainFactory {
private final Map<Pair<String, String>, com.sap.sailing.domain.base.Event> eventCache =
new HashMap<Pair<String, String>, com.sap.sailing.domain.base.Event>();
/**
* A cache based on weak references to the TracTrac event, allowing for quick Event lookup as long as the
* TracTrac event remains referenced. This is intended to reduce the number of times the dominant boat
* class needs to be determined for an event. Synchronization for additions / removals is tied to the
* synchronization for {@link #eventCache}.
*/
private final WeakIdentityHashMap<com.tractrac.clientmodule.Event, Event> weakEventCache = new WeakIdentityHashMap<>();
private final Map<Race, RaceDefinition> raceCache = new HashMap<Race, RaceDefinition>();
private final Set<String> mayStartWithNoUpwindLeg;
@@ -253,24 +263,33 @@ public class DomainFactoryImpl implements DomainFactory {
// This means that currently it is permissible to assume that we'll get at most one
// boat class per TracTrac event. Generally, however, we have to assume that
// one TracTrac event may map to multiple domain Event objects with one BoatClass each
Collection<CompetitorClass> competitorClassList = new ArrayList<CompetitorClass>();
for (com.tractrac.clientmodule.Competitor c : event.getCompetitorList()) {
competitorClassList.add(c.getCompetitorClass());
}
BoatClass boatClass = getDominantBoatClass(competitorClassList);
Pair<String, String> key = new Pair<String, String>(event.getName(), boatClass==null?null:boatClass.getName());
Event result = eventCache.get(key);
// try a quick look-up in the weak cache using the TracTrac event as key; only if that delivers no result,
// compute the dominant boat class which requires a lot more effort
Event result = weakEventCache.get(event);
if (result == null) {
result = new EventImpl(event.getName(), boatClass);
eventCache.put(key, result);
Collection<CompetitorClass> competitorClassList = new ArrayList<CompetitorClass>();
for (com.tractrac.clientmodule.Competitor c : event.getCompetitorList()) {
competitorClassList.add(c.getCompetitorClass());
}
BoatClass boatClass = getDominantBoatClass(competitorClassList);
Pair<String, String> key = new Pair<String, String>(event.getName(), boatClass == null ? null
: boatClass.getName());
result = eventCache.get(key);
if (result == null) {
result = new EventImpl(event.getName(), boatClass);
eventCache.put(key, result);
weakEventCache.put(event, result);
}
}
return result;
}
}
@Override
public Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent, com.tractrac.clientmodule.Event tractracEvent,
WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types) {
public Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent,
com.tractrac.clientmodule.Event tractracEvent, WindStore windStore, TimePoint startOfTracking,
TimePoint endOfTracking, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types) {
Collection<Receiver> result = new ArrayList<Receiver>();
for (ReceiverType type : types) {
switch (type) {
@@ -281,7 +300,7 @@ public class DomainFactoryImpl implements DomainFactory {
break;
case MARKPOSITIONS:
result.add(new MarkPositionReceiver(
trackedEvent, tractracEvent, this));
trackedEvent, tractracEvent, startOfTracking, endOfTracking, this));
break;
case RAWPOSITIONS:
result.add(new RawPositionReceiver(
@@ -302,10 +321,10 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public Iterable<Receiver> getUpdateReceivers(DynamicTrackedEvent trackedEvent,
com.tractrac.clientmodule.Event tractracEvent, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return getUpdateReceivers(trackedEvent, tractracEvent, windStore, raceDefinitionSetToUpdate, ReceiverType.RACECOURSE,
ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH,
ReceiverType.RAWPOSITIONS);
com.tractrac.clientmodule.Event tractracEvent, TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return getUpdateReceivers(trackedEvent, tractracEvent, windStore, startOfTracking, endOfTracking,
raceDefinitionSetToUpdate, ReceiverType.RACECOURSE, ReceiverType.MARKPASSINGS,
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS);
}
@Override
@@ -336,6 +355,7 @@ public class DomainFactoryImpl implements DomainFactory {
event.removeRace(raceDefinition);
if (oldSize > 0 && Util.size(event.getAllRaces()) == 0) {
eventCache.remove(key);
weakEventCache.remove(tractracEvent);
}
TrackedEvent trackedEvent = trackedEventRegistry.getTrackedEvent(event);
if (trackedEvent != null) {
@@ -450,10 +470,11 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore,
TrackedEventRegistry trackedEventRegistry) throws MalformedURLException, FileNotFoundException,
URISyntaxException {
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, windStore, trackedEventRegistry);
public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, TrackedEventRegistry trackedEventRegistry)
throws MalformedURLException, FileNotFoundException, URISyntaxException {
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, startOfTracking, endOfTracking,
windStore, trackedEventRegistry);
}
@Override
@@ -473,4 +494,10 @@ public class DomainFactoryImpl implements DomainFactory {
return new TracTracConfigurationImpl(name, jsonURL, liveDataURI, storedDataURI);
}
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore) {
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, startOfTracking, endOfTracking, windStore, this);
}
}
@@ -9,6 +9,7 @@ import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
@@ -42,9 +43,36 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue<ControlPoint
private static final Logger logger = Logger.getLogger(MarkPositionReceiver.class.getName());
private int received;
public MarkPositionReceiver(final DynamicTrackedEvent trackedEvent, com.tractrac.clientmodule.Event tractracEvent, final DomainFactory domainFactory) {
/**
* if <code>null</code>, all stored data from the "beginning of time" will be loaded that the event has to provide,
* particularly for the mark positions which are stored per event, not per race; otherwise, particularly the mark
* position loading will be constrained to this start time.
*/
private final TimePoint startOfTracking;
/**
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has to provide,
* particularly for the mark positions which are stored per event, not per race; otherwise, particularly the mark
* position loading will be constrained to this end time.
*/
private final TimePoint endOfTracking;
/**
* @param startOfTracking
* if <code>null</code>, all stored data from the "beginning of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this start time.
* @param endOfTracking
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has to
* provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
*/
public MarkPositionReceiver(final DynamicTrackedEvent trackedEvent, com.tractrac.clientmodule.Event tractracEvent,
TimePoint startOfTracking, TimePoint endOfTracking, final DomainFactory domainFactory) {
super(domainFactory, tractracEvent, trackedEvent);
this.startOfTracking = startOfTracking;
this.endOfTracking = endOfTracking;
// assumption: there is currently only one race per TracTrac Event object
if (tractracEvent.getRaceList().isEmpty()) {
throw new IllegalArgumentException("Can't receive mark positions from event "+tractracEvent.getName()+" that has no race");
@@ -66,7 +94,8 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue<ControlPoint
public void gotData(ControlPoint controlPoint, ControlPointPositionData record, boolean isLiveData) {
enqueue(new Triple<ControlPoint, ControlPointPositionData, Boolean>(controlPoint, record, isLiveData));
}
}, /* fromTime */0l, /* toTime */Long.MAX_VALUE);
}, /* fromTime */ startOfTracking == null ? 0l : startOfTracking.asMillis(),
/* toTime */ endOfTracking == null ? Long.MAX_VALUE : endOfTracking.asMillis());
result.add(controlPointListener);
setAndStartThread(new Thread(this, getClass().getName()));
return result;
@@ -61,9 +61,16 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue<Ra
System.out.print("StartStop");
DynamicTrackedRace trackedRace = getTrackedRace(event.getA());
if (trackedRace != null) {
MillisecondsTimePoint start = new MillisecondsTimePoint(event.getB().getStartTime());
if (trackedRace.getStart() == null || !trackedRace.getStart().equals(start)) {
trackedRace.setStartTimeReceived(start);
StartStopTimesData startStopTimesData = event.getB();
if(startStopTimesData != null) {
if(startStopTimesData.getStartTime() > 0) {
MillisecondsTimePoint startOfTracking = new MillisecondsTimePoint(startStopTimesData.getStartTime());
trackedRace.setStartOfTrackingReceived(startOfTracking);
}
if(startStopTimesData.getStopTime() > 0) {
MillisecondsTimePoint endOfTracking = new MillisecondsTimePoint(startStopTimesData.getStopTime());
trackedRace.setEndOfTrackingReceived(endOfTracking);
}
}
} else {
logger.warning("Couldn't find tracked race for race " + event.getA().getName()
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConnectivityParameters {
private final URL paramURL;
private final URI liveURI;
private final URI storedURI;
private final TimePoint startOfTracking;
private final TimePoint endOfTracking;
private final WindStore windStore;
private final DomainFactory domainFactory;
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore, DomainFactory domainFactory) {
super();
this.paramURL = paramURL;
this.liveURI = liveURI;
this.storedURI = storedURI;
this.startOfTracking = startOfTracking;
this.endOfTracking = endOfTracking;
this.windStore = windStore;
this.domainFactory = domainFactory;
}
@Override
public RaceTracker createRaceTracker(TrackedEventRegistry trackedEventRegistry) throws MalformedURLException,
FileNotFoundException, URISyntaxException {
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, startOfTracking,
endOfTracking, windStore, trackedEventRegistry);
return tracker;
}
@Override
public Util.Triple<URL, URI, URI> getTrackerID() {
return TracTracRaceTrackerImpl.createID(paramURL, liveURI, storedURI);
}
}
@@ -19,6 +19,8 @@ import com.tractrac.clientmodule.data.ICallbackData;
public class RawPositionReceiver extends AbstractReceiverWithQueue<RaceCompetitor, CompetitorPositionRawData, Boolean> {
private static final Logger logger = Logger.getLogger(RawPositionReceiver.class.getName());
private int received;
public RawPositionReceiver(DynamicTrackedEvent trackedEvent, com.tractrac.clientmodule.Event tractracEvent, DomainFactory domainFactory) {
super(domainFactory, tractracEvent, trackedEvent);
}
@@ -46,7 +48,12 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue<RaceCompetito
@Override
protected void handleEvent(Triple<RaceCompetitor, CompetitorPositionRawData, Boolean> event) {
System.out.print("P");
if (received++ % 1000 == 0) {
System.out.print("P");
if ((received / 1000 + 1) % 80 == 0) {
System.out.println();
}
}
Race race = event.getA().getRace();
DynamicTrackedRace trackedRace = getTrackedRace(race);
if (trackedRace != null) {
@@ -43,4 +43,5 @@ public class TracTracConfigurationImpl implements TracTracConfiguration {
return "<Exception during TracTracConfiguration.toString(): "+e.getMessage()+">";
}
}
}
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl;
@@ -75,28 +76,39 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
* <p>
*
* A race tracker uses the <code>paramURL</code> for the TracTrac Java client to register for push data about one
* race. The {@link DomainFactory} is asked to retrieve an existing or create a new {@link com.sap.sailing.domain.base.Event}
* based on the TracTrac event. The {@link RaceDefinition} for the race, however, isn't created until the {@link Course} has been
* received. Therefore, the {@link RaceCourseReceiver} will create the {@link RaceDefinition} and will add it to the
* {@link com.sap.sailing.domain.base.Event}.
* race. The {@link DomainFactory} is asked to retrieve an existing or create a new
* {@link com.sap.sailing.domain.base.Event} based on the TracTrac event. The {@link RaceDefinition} for the race,
* however, isn't created until the {@link Course} has been received. Therefore, the {@link RaceCourseReceiver} will
* create the {@link RaceDefinition} and will add it to the {@link com.sap.sailing.domain.base.Event}.
* <p>
*
* The link to the {@link RaceDefinition} is created in the {@link DomainFactory} when the
* {@link RaceCourseReceiver} creates the {@link TrackedRace} object. Starting then, the {@link DomainFactory} will
* respond with the {@link RaceDefinition} when its {@link DomainFactory#getRaces(Event)} is called with the TracTrac
* {@link Event} as argument that is used for its tracking.
* respond with the {@link RaceDefinition} when its {@link DomainFactory#getRaces(Event)} is called with the
* TracTrac {@link Event} as argument that is used for its tracking.
* <p>
*
* @param startOfTracking
* if <code>null</code>, all stored data from the "beginning of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this start time.
* @param endOfTracking
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has to
* provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
* @param windStore
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
* available but loses track of the wind, e.g., during server restarts.
* @param trackedEventRegistry used to create the {@link TrackedEvent} for the domain event
* @param trackedEventRegistry
* used to create the {@link TrackedEvent} for the domain event
*/
protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
WindStore windStore, TrackedEventRegistry trackedEventRegistry) throws URISyntaxException,
MalformedURLException, FileNotFoundException {
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore,
TrackedEventRegistry trackedEventRegistry) throws URISyntaxException, MalformedURLException,
FileNotFoundException {
super();
urls = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
urls = createID(paramURL, liveURI, storedURI);
this.races = new HashSet<RaceDefinition>();
this.windStore = windStore;
this.domainFactory = domainFactory;
@@ -123,7 +135,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
setTrackedEvent(trackedEventRegistry.getOrCreateTrackedEvent(domainEvent));
receivers = new HashSet<Receiver>();
Set<TypeController> typeControllers = new HashSet<TypeController>();
for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedEvent(), tractracEvent, windStore, this)) {
for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedEvent(), tractracEvent, startOfTracking,
endOfTracking, windStore, this)) {
receivers.add(receiver);
for (TypeController typeController : receiver.getTypeControllersAndStart()) {
typeControllers.add(typeController);
@@ -131,6 +144,10 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
}
addListenersForStoredDataAndStartController(typeControllers);
}
static Triple<URL, URI, URI> createID(URL paramURL, URI liveURI, URI storedURI) {
return new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
}
/**
* Control points may get added late in the race. If they don't have a tracker installed, their position
@@ -6,6 +6,7 @@ import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
@@ -68,4 +69,10 @@ public class TracTracTrackedLegImpl implements TrackedLeg {
return null;
}
@Override
public Bearing getLegBearing(TimePoint at) {
// TODO Auto-generated method stub
return null;
}
}
@@ -53,7 +53,7 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor {
}
@Override
public long getTimeInMilliSeconds(TimePoint timePoint) {
public Long getTimeInMilliSeconds(TimePoint timePoint) {
return getMarkResults(timePoint).getLegtime();
}
@@ -19,6 +19,7 @@ import com.tractrac.ResultAPI.PerLegResult;
import com.tractrac.ResultAPI.ResultGenerator;
public class TracTracTrackedRaceImpl extends DynamicTrackedRaceImpl implements DynamicTrackedRace {
private static final long serialVersionUID = 4406299109870965123L;
/**
* Note, that a result generator may cover multiple regattas in multiple classes. When results are
@@ -18,5 +18,4 @@ Export-Package: com.sap.sailing.domain.base,
Bundle-ActivationPolicy: lazy
Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.domain.common,
com.sap.sailing.geocoding;bundle-version="1.0.0",
org.json.simple;bundle-version="1.1.0"
@@ -39,4 +39,16 @@ public interface BoatClass extends Named {
boolean typicallyStartsUpwind();
Distance getHullLength();
/**
* Downwind leg-based wind estimations are inherently less confident than upwind leg-based estimations because
* jibing angles vary more greatly from boat to boat than tacking angles.
*
* @param numberOfBoatsInSmallestCluster the larger the number of boats, the more confident the estimate is considered to be
* ("wisdom of the crowds"). The minimum confidence for just one boat in the smallest cluster is still guaranteed to be
* greater than zero.
*/
double getDownwindWindEstimationConfidence(int numberOfBoatsInSmallestCluster);
double getUpwindWindEstimationConfidence(int numberOfBoatsInSmallestCluster);
}
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.base;
import java.io.Serializable;
import com.sap.sailing.domain.common.TimePoint;
public interface Timed {
public interface Timed extends Serializable {
TimePoint getTimePoint();
}
@@ -1,10 +1,12 @@
package com.sap.sailing.domain.base;
import java.io.Serializable;
public interface WithID {
/**
* Something that uniquely identifies this object beyond his name
*/
Object getId();
Serializable getId();
}
@@ -7,6 +7,8 @@ import java.util.Date;
import com.sap.sailing.domain.common.TimePoint;
public abstract class AbstractTimePoint implements TimePoint {
private static final long serialVersionUID = 8825508619301420378L;
private static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
public static Comparator<TimePoint> TIMEPOINT_COMPARATOR = new Comparator<TimePoint>() {
@@ -5,6 +5,8 @@ import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class BoatClassImpl extends NamedImpl implements BoatClass {
private static final long serialVersionUID = 7194912853476256420L;
/**
* 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
@@ -12,15 +14,25 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
*/
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_UPWIND = 60.;
private static final double MINIMUM_ANGLE_BETWEEN_DIFFERENT_TACKS_DOWNWIND = 25.;
private static final Distance MAXIMUM_DISTANCE_FOR_COURSE_APPROXIMATION = new MeterDistance(3);
/**
* Upwind course-based wind estimations are pretty confident for most boat classes.
*/
private static final double UPWIND_WIND_ESTIMATION_CONFIDENCE = .9;
/**
* Downwind estimations are less confident than upwind because jibing angles vary more.
*/
private static final double DOWNWIND_WIND_ESTIMATION_CONFIDENCE = .5;
private final long approximateManeuverDurationInMilliseconds;
private final boolean typicallyStartsUpwind;
private final boolean typicallyStartsUpwind;
private final Distance hullLength;
@@ -65,4 +77,26 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
public Distance getHullLength() {
return hullLength;
}
@Override
public double getDownwindWindEstimationConfidence(int numberOfBoatsInSmallestCluster) {
// Even for up to a million boats in the smallest cluster, the multiplier is still less than one.
// The multiplier is 1/1000000 in case there is only one boat and approaches 1.0 for many boats.
return DOWNWIND_WIND_ESTIMATION_CONFIDENCE * getConfidenceMultiplierForClusterSize(numberOfBoatsInSmallestCluster);
}
@Override
public double getUpwindWindEstimationConfidence(int numberOfBoatsInSmallestCluster) {
// Even for up to a million boats in the smallest cluster, the multiplier is still less than one.
// The multiplier is 1/1000000 in case there is only one boat and approaches 1.0 for many boats.
return UPWIND_WIND_ESTIMATION_CONFIDENCE * getConfidenceMultiplierForClusterSize(numberOfBoatsInSmallestCluster);
}
/**
* Even for up to a million boats in the smallest cluster, the multiplier is still less than one.
* The multiplier is 1/1000000 in case there is only one boat and approaches 1.0 for many boats.
*/
private double getConfidenceMultiplierForClusterSize(int numberOfBoatsInSmallestCluster) {
return 1.0 + 1.0/1000000.0 - 1.0/(double) numberOfBoatsInSmallestCluster;
}
}
@@ -5,6 +5,7 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class BoatImpl extends NamedImpl implements Boat {
private static final long serialVersionUID = 3489730487528955788L;
private final BoatClass boatClass;
private final String sailID;
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class BuoyImpl extends NamedImpl implements Buoy {
private static final long serialVersionUID = 1900673146064411979L;
public BuoyImpl(String name) {
super(name);
@@ -1,16 +1,19 @@
package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class CompetitorImpl extends NamedImpl implements Competitor {
private static final long serialVersionUID = 294603681016643157L;
private final Team team;
private final Boat boat;
private final Object id;
private final Serializable id;
public CompetitorImpl(Object id, String name, Team team, Boat boat) {
public CompetitorImpl(Serializable id, String name, Team team, Boat boat) {
super(name);
this.id = id;
this.team = team;
@@ -18,7 +21,7 @@ public class CompetitorImpl extends NamedImpl implements Competitor {
}
@Override
public Object getId() {
public Serializable getId() {
return id;
}
@@ -27,6 +27,8 @@ import difflib.Patch;
import difflib.PatchFailedException;
public class CourseImpl extends NamedImpl implements Course {
private static final long serialVersionUID = -4280487649617132403L;
private static final Logger logger = Logger.getLogger(CourseImpl.class.getName());
private final List<Waypoint> waypoints;
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class EventImpl extends NamedImpl implements Event {
private static final long serialVersionUID = 6509564189552478869L;
private final Set<RaceDefinition> races;
private final BoatClass boatClass;
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Gate;
public class GateImpl implements Gate {
private static final long serialVersionUID = 2807354812133070574L;
private final Buoy left;
private final Buoy right;
private final String name;
@@ -28,4 +28,8 @@ public class HasConfidenceImpl<ValueType, BaseType, RelativeTo> implements HasCo
return object;
}
@Override
public String toString() {
return ""+getObject()+"@"+getConfidence();
}
}
@@ -4,6 +4,7 @@ import java.util.Date;
public class MillisecondsTimePoint extends AbstractTimePoint {
private static final long serialVersionUID = -1021748860232043166L;
private final long millis;
private Date date;
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.common.CountryCode;
import com.sap.sailing.domain.common.CountryCodeFactory;
public class NationalityImpl implements Nationality {
private static final long serialVersionUID = 238906193483424259L;
private final String threeLetterIOCAcronym;
public NationalityImpl(String name, String threeLetterIOCAcronym) {
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class PersonImpl extends NamedImpl implements Person {
private static final long serialVersionUID = -2104903799224233508L;
private final Nationality nationality;
private final Date dateOfBirth;
private final String description;
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
private static final long serialVersionUID = -1900955198751393727L;
private final Course course;
private final Iterable<Competitor> competitors;
private final BoatClass boatClass;
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class TeamImpl extends NamedImpl implements Team {
private static final long serialVersionUID = 4646922280429210183L;
private final Iterable<? extends Person> sailors;
private final Person coach;
@@ -5,6 +5,7 @@ import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Waypoint;
public class WaypointImpl implements Waypoint {
private static final long serialVersionUID = 1600863368078653897L;
private final ControlPoint controlPoint;
private static int idCounter = 1;
private final int id;
@@ -34,9 +35,6 @@ public class WaypointImpl implements Waypoint {
return getControlPoint().getBuoys();
}
/**
* Note that a waypoint is not compared by its identity but by the control point it represents.
*/
@Override
public Integer getId() {
return id;
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.confidence;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.ScalableWind;
public interface ConfidenceBasedWindAverager<RelativeTo> extends ConfidenceBasedAverager<ScalableWind, Wind, RelativeTo>{
@Override
WindWithConfidence<RelativeTo> getAverage(
Iterable<? extends HasConfidenceAndIsScalable<ScalableWind, Wind, RelativeTo>> values, RelativeTo at);
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.confidence;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.confidence.impl.ConfidenceBasedAveragerFactoryImpl;
import com.sap.sailing.domain.tracking.WindWithConfidence;
public interface ConfidenceFactory {
ConfidenceFactory INSTANCE = new ConfidenceBasedAveragerFactoryImpl();
@@ -15,7 +16,16 @@ public interface ConfidenceFactory {
*/
<ValueType, BaseType, RelativeTo> ConfidenceBasedAverager<ValueType, BaseType, RelativeTo> createAverager(Weigher<RelativeTo> weigher);
/**
* Produces a specialized averaged which can deal with the special case that {@link WindWithConfidence} objects
* have an internal <code>useSpeed</code> flag which may, when set to <code>false</code> suppress the consideration
* of the wind fix's speed (not the bearing) in computing the average. For this to work, the averager has to maintain
* a separate confidence sum for the speed values considered.
*/
<RelativeTo> ConfidenceBasedWindAverager<RelativeTo> createWindAverager(Weigher<RelativeTo> weigher);
<RelativeTo> Weigher<RelativeTo> createConstantWeigher(double constantConfidence);
/**
* Creates a weigher for time points. With increasing time difference the weight/confidence decreases exponentially.
* It is halved every <code>halfConfidenceAfterMilliseconds</code> milliseconds.
@@ -28,5 +38,5 @@ public interface ConfidenceFactory {
*/
Weigher<TimePoint> createExponentialTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds, double minimumConfidence);
Weigher<TimePoint> createLinearTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds);
Weigher<TimePoint> createHyperbolicTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds);
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.confidence.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.confidence.ConfidenceBasedAverager;
import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
import com.sap.sailing.domain.confidence.ConfidenceFactory;
import com.sap.sailing.domain.confidence.Weigher;
@@ -20,9 +21,14 @@ public class ConfidenceBasedAveragerFactoryImpl implements ConfidenceFactory {
public <ValueType, BaseType, RelativeTo> ConfidenceBasedAverager<ValueType, BaseType, RelativeTo> createAverager(Weigher<RelativeTo> weigher) {
return new ConfidenceBasedAveragerImpl<ValueType, BaseType, RelativeTo>(weigher);
}
@Override
public <RelativeTo> ConfidenceBasedWindAverager<RelativeTo> createWindAverager(Weigher<RelativeTo> weigher) {
return new ConfidenceBasedWindAveragerImpl<RelativeTo>(weigher);
}
@Override
public Weigher<TimePoint> createLinearTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds) {
public Weigher<TimePoint> createHyperbolicTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds) {
return new HyperbolicTimeDifferenceWeigher(halfConfidenceAfterMilliseconds);
}
@@ -52,7 +52,7 @@ public class ConfidenceBasedAveragerImpl<ValueType, BaseType, RelativeTo> implem
ScalableValue<ValueType, BaseType> numerator = null;
double confidenceSum = 0;
for (HasConfidenceAndIsScalable<ValueType, BaseType, RelativeTo> next : values) {
double relativeWeight = (weigher == null ? 1.0 : weigher.getConfidence(next.getRelativeTo(), at)) * next.getConfidence();
double relativeWeight = (getWeigher() == null ? 1.0 : getWeigher().getConfidence(next.getRelativeTo(), at)) * next.getConfidence();
ScalableValue<ValueType, BaseType> weightedNext = next.getScalableValue().multiply(relativeWeight);
if (numerator == null) {
numerator = weightedNext;
@@ -67,4 +67,8 @@ public class ConfidenceBasedAveragerImpl<ValueType, BaseType, RelativeTo> implem
return new HasConfidenceImpl<ValueType, BaseType, RelativeTo>(result, newConfidence, at);
}
}
protected Weigher<RelativeTo> getWeigher() {
return weigher;
}
}
@@ -0,0 +1,79 @@
package com.sap.sailing.domain.confidence.impl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable;
import com.sap.sailing.domain.confidence.Weigher;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.ScalableWind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
/**
* In order to enable the aggregation of {@link Wind} objects whose {@link WindSource}'s {@link WindSourceType} suggests
* {@link WindSourceType#useSpeed() not to use their speed values}, we keep track separately of the confidence sum
* of the {@link Speed} objects in this averager.<p>
*
* If a {@link WindSource}'s {@link WindSourceType} suggests {@link WindSourceType#useSpeed() not to use their speed
* values}, the separate confidence sum for the speed values is not touched, and the separate speed sum is not
* incremented. When dividing by the confidence sum eventually, the separate speed sum is divided separately by the
* separate speed confidence sum, and a new result object is constructed using the speed obtained this way.
* @author Axel Uhl (d043530)
*/
public class ConfidenceBasedWindAveragerImpl<RelativeTo> extends
ConfidenceBasedAveragerImpl<ScalableWind, Wind, RelativeTo> implements ConfidenceBasedWindAverager<RelativeTo> {
public ConfidenceBasedWindAveragerImpl(Weigher<RelativeTo> weigher) {
super(weigher);
}
@Override
public WindWithConfidence<RelativeTo> getAverage(
Iterable<? extends HasConfidenceAndIsScalable<ScalableWind, Wind, RelativeTo>> values, RelativeTo at) {
boolean atLeastOneFixWasMarkedToUseSpeed = false;
if (values == null || Util.isEmpty(values)) {
return null;
} else {
ScalableWind numerator = null;
double confidenceSum = 0;
double speedConfidenceSum = 0;
double knotSum = 0;
for (HasConfidenceAndIsScalable<ScalableWind, Wind, RelativeTo> next : values) {
double relativeWeight = (getWeigher() == null ? 1.0 : getWeigher().getConfidence(next.getRelativeTo(), at)) * next.getConfidence();
ScalableWind weightedNext = next.getScalableValue().multiply(relativeWeight).getValue();
if (numerator == null) {
numerator = weightedNext;
} else {
numerator = numerator.add(weightedNext);
}
confidenceSum += relativeWeight;
// handle speed (without bearing) separately:
if (weightedNext.useSpeed()) {
atLeastOneFixWasMarkedToUseSpeed = true;
speedConfidenceSum += relativeWeight;
knotSum += weightedNext.divide(1.0).getKnots();
}
}
// TODO consider greater variance to reduce the confidence
double newConfidence = confidenceSum / Util.size(values);
Wind preResult = numerator.divide(confidenceSum);
// if only values with useSpeed=false were aggregated, use the original result, otherwise compute
// separate speed average:
Wind result;
if (!atLeastOneFixWasMarkedToUseSpeed) {
result = preResult;
} else {
result = new WindImpl(preResult.getPosition(), preResult.getTimePoint(), new KnotSpeedWithBearingImpl(
knotSum / speedConfidenceSum, preResult.getBearing()));
}
return new WindWithConfidenceImpl<RelativeTo>(result, newConfidence, at, atLeastOneFixWasMarkedToUseSpeed);
}
}
}
@@ -0,0 +1,29 @@
package com.sap.sailing.domain.confidence.impl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.confidence.ConfidenceFactory;
import com.sap.sailing.domain.confidence.Weigher;
/**
* A weigher that uses a {@link Position} and a {@link TimePoint} to compute a confidence based on
* time and space distance.<p>
*
* <b>Note</b>: The current implementation only considers the time axis.
*
* @author Axel Uhl (d043530)
*
*/
public class PositionAndTimePointWeigher implements Weigher<Pair<Position, TimePoint>> {
private final Weigher<TimePoint> weigher;
public PositionAndTimePointWeigher(long halfConfidenceAfterMilliseconds) {
weigher = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(halfConfidenceAfterMilliseconds);
}
@Override
public double getConfidence(Pair<Position, TimePoint> fix, Pair<Position, TimePoint> request) {
return weigher.getConfidence(fix.getB(), request.getB());
}
}
@@ -229,4 +229,6 @@ public interface Leaderboard extends Named {
void updateIsMedalRace(String raceName, boolean isMedalRace);
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule);
Competitor getCompetitorByIdAsString(String idAsString);
}
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
public class LeaderboardGroupImpl implements LeaderboardGroup {
private static final long serialVersionUID = 2035927369446736934L;
private String name;
private String description;
private List<Leaderboard> leaderboards;
@@ -23,6 +23,7 @@ import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
public class LeaderboardImpl implements Named, Leaderboard {
private static final long serialVersionUID = -328091952760083438L;
private final List<RaceInLeaderboard> races;
private final SettableScoreCorrection scoreCorrection;
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
@@ -175,6 +176,16 @@ public class LeaderboardImpl implements Named, Leaderboard {
}
return null;
}
@Override
public Competitor getCompetitorByIdAsString(String idAsString) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getId().toString().equals(idAsString)) {
return competitor;
}
}
return null;
}
@Override
public SettableScoreCorrection getScoreCorrection() {
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.leaderboard.RaceInLeaderboard;
import com.sap.sailing.domain.tracking.TrackedRace;
public class RaceInLeaderboardImpl implements RaceInLeaderboard {
private static final long serialVersionUID = -7801617988982540470L;
private TrackedRace trackedRace;
private final Leaderboard leaderboard;
private boolean medalRace;
@@ -12,6 +12,11 @@ public interface DynamicTrackedEvent extends TrackedEvent {
*/
DynamicTrackedRace getExistingTrackedRace(RaceDefinition race);
/**
* @param raceDefinitionSetToUpdate
* may be <code>null</code> which means that no update will be fired to any
* {@link DynamicRaceDefinitionSet}.
*/
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, WindStore windStore,
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
@@ -38,6 +38,18 @@ public interface DynamicTrackedRace extends TrackedRace {
* significantly off.
*/
void setStartTimeReceived(TimePoint start);
/** Sets the start of tracking as received from the tracking infrastructure.
* This isn't necessarily what {@link #getStartOfTracking()} will deliver because we might consider other values to
* calculate the start of tracking.
*/
void setStartOfTrackingReceived(TimePoint startOfTrackingReceived);
/** Sets the end of tracking as received from the tracking infrastructure.
* This isn't necessarily what {@link #getEndOfTracking()} will deliver because we might consider other values to
* calculate the end of tracking.
*/
void setEndOfTrackingReceived(TimePoint endOfTrackingReceived);
void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed);
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import com.sap.sailing.domain.common.Position;
public interface Positioned {
public interface Positioned extends Serializable {
Position getPosition();
}
@@ -0,0 +1,27 @@
package com.sap.sailing.domain.tracking;
/**
* Different tracking providers require different sets of arguments to start tracking a race.
* This interface represents the functionality required to start tracking a race, agnostic of the
* particular parameters and ways of launching the connector. Use {@link #createRaceTracker()} to
* create the tracker that starts tracking the race identified by these tracking parameters.
*
* @author Axel Uhl (d043530)
*
*/
public interface RaceTrackingConnectivityParameters {
/**
* Starts a {@link RaceTracker} using the connectivity parameters provided by this object.
*/
RaceTracker createRaceTracker(TrackedEventRegistry trackedEventRegistry) throws Exception;
/**
* Deliver an ID object equal to that of the {@link RaceTracker#getID()} delivered by the {@link RaceTracker}
* that will be created from these parameters by calling {@link #createRaceTracker(TrackedEventRegistry)}.
*/
Object getTrackerID();
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.Iterator;
import com.sap.sailing.domain.base.Timed;
@@ -16,7 +17,7 @@ import com.sap.sailing.domain.common.TimePoint;
*
* @author Axel Uhl (d043530)
*/
public interface Track<FixType extends Timed> {
public interface Track<FixType extends Timed> extends Serializable {
/**
* Callers must synchronize on this object before iterating the result if they have to expect concurrent
* modifications.
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Event;
@@ -21,7 +23,7 @@ import com.sap.sailing.domain.common.TimePoint;
* @author Axel Uhl (D043530)
*
*/
public interface TrackedEvent {
public interface TrackedEvent extends Serializable {
Event getEvent();
Iterable<TrackedRace> getTrackedRaces();
@@ -4,6 +4,7 @@ import java.util.LinkedHashMap;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
@@ -38,4 +39,6 @@ public interface TrackedLeg {
*/
LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint);
Bearing getLegBearing(TimePoint at);
}
@@ -18,11 +18,11 @@ public interface TrackedLegOfCompetitor {
/**
* How much time did the {@link #getCompetitor competitor} spend in this {@link #getLeg() leg} at
* <code>timePoint</code>? If the competitor hasn't started the leg yet at <code>timePoint</code>, 0 is returned. If the
* competitor has finished the leg already at <code>timePoint</code>, the time it took the competitor to
* complete the leg is returned.
* <code>timePoint</code>? If the competitor hasn't started the leg yet at <code>timePoint</code>, <code>null</code>
* is returned. If the competitor has finished the leg already at <code>timePoint</code>, the time it took the
* competitor to complete the leg is returned.
*/
long getTimeInMilliSeconds(TimePoint timePoint);
Long getTimeInMilliSeconds(TimePoint timePoint);
/**
* The distance over ground traveled by the competitor in this leg up to <code>timePoint</code>. If
@@ -44,6 +44,16 @@ public interface TrackedLegOfCompetitor {
*/
Distance getWindwardDistanceToGo(TimePoint timePoint) throws NoWindException;
/**
* Computes an approximation for the average velocity made good (windward / leeward speed) of this leg's competitor at
* <code>timePoint</code>. If the competitor hasn't started the leg yet, <code>null</code> is returned. If the competitor
* has already finished the leg, the average over the whole leg is computed, otherwise the average for the time interval
* from the start of the leg up to <code>timePoint</code>.<p>
*
* The approximation uses the wind direction of <code>timePoint</code> at the middle between start and end waypoint or of
* the time point when the competitor completed the leg if that was before <code>timePoint</code>. Note that this does not
* account for changing winds during the leg.
*/
Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException;
/**
@@ -108,7 +118,9 @@ public interface TrackedLegOfCompetitor {
boolean hasFinishedLeg(TimePoint timePoint);
/**
* Returns <code>null</code> in case this leg's competitor hasn't started the leg yet.
* Returns <code>null</code> in case this leg's competitor hasn't started the leg yet. If in the leg at
* <code>timePoint</code>, returns the VMG value for this time point. If the competitor has already finished the
* leg, the VMG at the time when the competitor finished the leg is returned.
*/
Speed getVelocityMadeGood(TimePoint timePoint) throws NoWindException;
@@ -117,6 +129,11 @@ public interface TrackedLegOfCompetitor {
*/
Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException;
/**
* Returns <code>null</code> in case this leg's competitor hasn't started the leg yet. If in the leg at
* <code>timePoint</code>, returns the current speed over ground for this time point. If the competitor has already
* finished the leg, the speed over ground at the time the competitor finished the leg is returned.
*/
SpeedWithBearing getSpeedOverGround(TimePoint at);
/**
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.List;
import java.util.NavigableSet;
import java.util.SortedSet;
@@ -14,7 +15,6 @@ import com.sap.sailing.domain.base.impl.DouglasPeucker;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Tack;
@@ -38,7 +38,7 @@ import com.sap.sailing.domain.common.impl.Util.Pair;
* @author Axel Uhl (d043530)
*
*/
public interface TrackedRace {
public interface TrackedRace extends Serializable {
final long MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS = 30000;
RaceDefinition getRace();
@@ -46,15 +46,9 @@ public interface TrackedRace {
RaceIdentifier getRaceIdentifier();
/**
* @return A pair of placemarks, where A is the start placemark and B is the finish placemark.<br />
* The returning pair is never <code>null</code>, but A and/or B can be <code>null</code>.
*/
Pair<Placemark, Placemark> getStartFinishPlacemarks();
/**
* Computes the estimated start time for this race. When there are no {@link MarkPassing}s for the first mark, the
* start time received from the tracking infrastructure is used. This is also used if there are mark passings for
* the first mark and the start time is less than
* Computes the estimated start time for this race (not to be confused with the {@link #getStartOfTracking()} time point
* which is expected to be before the race start time). When there are no {@link MarkPassing}s for the first mark, <code>null</code>
* is returned. If there are mark passings for the first mark and the start time is less than
* {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} before the first mark passing for the
* first mark. Otherwise, the first mark passing for the first mark minus
* {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} is returned as the race start time.
@@ -69,7 +63,14 @@ public interface TrackedRace {
* no boat passed the finish line yet.
*/
TimePoint getAssumedEnd();
/**
* Returns a list of the start times of all legs as far as we know them
* The leg time of the first leg is equal to #{@link #getStart()}
* All other leg times are equal to the first mark passing of the leg
*/
Iterable<TimePoint> getStartTimesOfTrackedLegs();
/**
* Shorthand for <code>{@link #getStart()}.{@link TimePoint#compareTo(TimePoint) compareTo(at)} &lt;= 0</code>
*/
@@ -111,7 +112,11 @@ public interface TrackedRace {
* If the competitor hasn't passed the start waypoint yet, <code>null</code> is
* returned because the competitor was not yet on any leg at that point in time. If
* the time point happens to be after the last fix received from that competitor,
* the last known leg for that competitor is returned.
* the last known leg for that competitor is returned. If the time point is after the
* competitor's mark passing for the finish line, <code>null</code> is returned.
* For all legs except the last, if the time point equals a mark passing time point
* of the leg's starting waypoint, that leg is returned. For the time point of
* the mark passing for the finish line, the last leg is returned.
*/
TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at);
@@ -183,9 +188,8 @@ public interface TrackedRace {
/**
* Obtains estimated interpolated wind information for a given position and time point. The information is taken
* from all wind sources available except for those listed in <code>windSourcesToExclude</code>, with preferences
* controlled by the {@link #getWindSource() current wind source} which can be selected using {@link #setWindSource},
* and by the order of the {@link WindSource} literals.
* from all wind sources available except for those listed in <code>windSourcesToExclude</code>, using the confidences
* of the wind values provided by the various sources during averaging.
*/
Wind getWind(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude);
@@ -209,14 +213,30 @@ public interface TrackedRace {
*/
void waitForNextUpdate(int sinceUpdate) throws InterruptedException;
/**
* Time stamp of the start of the actual tracking.
* The value can be null (e.g. if we have not received any signal from the tracking infrastructure)
*/
TimePoint getStartOfTracking();
/**
* Time stamp of the end of the actual tracking.
* The value can be null (e.g. if we have not received any signal from the tracking infrastructure)
*/
TimePoint getEndOfTracking();
/**
* Regardless of the order in which events were received, this method returns the latest time point contained by any of
* the events received and processed.
*/
TimePoint getTimePointOfNewestEvent();
/**
* Regardless of the order in which events were received, this method returns the oldest time point contained by any of
* the events received and processed.
*/
TimePoint getTimePointOfOldestEvent();
/**
* @return the mark passings for <code>competitor</code> in this race received so far; the mark passing objects are
* returned such that their {@link MarkPassing#getWaypoint() waypoints} are ordered in the same way they are ordered
@@ -315,6 +335,36 @@ public interface TrackedRace {
Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint);
Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) throws NoWindException;
/**
* Calls {@link #getWindWithConfidence(Position, TimePoint, Iterable)} and excludes those wind sources listed in
* {@link #getWindSourcesToExclude}.
*/
WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at);
/**
* Lists those wind sources which by default are not considered in {@link #getWind(Position, TimePoint)} and
* {@link #getWindWithConfidence(Position, TimePoint)}.
*/
Iterable<WindSource> getWindSourcesToExclude();
WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude);
/**
* Loops over this tracked race's wind sources and from each asks its averaged wind for the position <code>p</code>
* and time point <code>at</code>, using the particular wind source's averaging interval. The confidences delivered
* by each wind source are used during computing the averaged result across the wind sources. The result has the averaged
* confidence attached.
*/
WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at,
Iterable<WindSource> windSourcesToExclude);
/**
* Same as {@link #getEstimatedWindDirection(Position, TimePoint)}, but propagates the confidence of the wind
* estimation, relative to the <code>timePoint</code> for which the request is made, in the result.
*/
WindWithConfidence<TimePoint> getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint);
/**
* After the call returns, {@link #getWindSourcesToExclude()} returns an iterable that equals <code>windSourcesToExclude</code>
*/
void setWindSourcesToExclude(Iterable<WindSource> windSourcesToExclude);
}
@@ -14,7 +14,8 @@ import com.sap.sailing.domain.common.WindSource;
*
*/
public interface WindStore {
WindTrack getWindTrack(TrackedEvent trackedEvent, TrackedRace trackedRace, WindSource windSource, long millisecondsOverWhichToAverage);
WindTrack getWindTrack(TrackedEvent trackedEvent, TrackedRace trackedRace, WindSource windSource,
long millisecondsOverWhichToAverage, long delayForWindEstimationCacheInvalidation);
/**
* Loads all wind tracks known to this wind store that pertain to the tracked race / event specified.
@@ -15,4 +15,5 @@ import com.sap.sailing.domain.tracking.impl.ScalableWind;
* <code>Pair&lt;TimePoint, Position&gt;</code>
*/
public interface WindWithConfidence<RelativeTo> extends HasConfidenceAndIsScalable<ScalableWind, Wind, RelativeTo> {
boolean useSpeed();
}
@@ -8,6 +8,8 @@ import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.tracking.GPSFix;
public abstract class AbstractGPSFixImpl implements GPSFix {
private static final long serialVersionUID = 9037068515469957639L;
@Override
public int hashCode() {
final int prime = 31;
@@ -117,7 +117,7 @@ public class BearingWithConfidenceCluster<RelativeTo> {
private double getConfidenceScaledDifference(BearingWithConfidence<RelativeTo> bearingWithConfidence1,
BearingWithConfidence<RelativeTo> bearingWithConfidence2) {
return bearingWithConfidence1.getObject().getDifferenceTo(bearingWithConfidence2.getObject()).getDegrees() *
return Math.abs(bearingWithConfidence1.getObject().getDifferenceTo(bearingWithConfidence2.getObject()).getDegrees()) *
bearingWithConfidence1.getConfidence() * bearingWithConfidence2.getConfidence();
}
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
/**
* Delivers what {@link TrackedRace#getWind(Position, TimePoint)} delivers, as a navigable set.
*
* @author Axel Uhl (d043530)
*
*/
public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
public CombinedWindAsNavigableSet(WindTrack track, TrackedRace trackedRace, long resolutionInMilliseconds) {
super(track, trackedRace, resolutionInMilliseconds);
}
public CombinedWindAsNavigableSet(WindTrack track, TrackedRace trackedRace,
TimePoint from, TimePoint to, long resolutionInMilliseconds) {
super(track, trackedRace, from, to, resolutionInMilliseconds);
}
@Override
protected Wind getWind(Position p, TimePoint timePoint) {
return getTrackedRace().getWind(p, timePoint);
}
@Override
protected NavigableSet<Wind> createSubset(WindTrack track, TrackedRace trackedRace, TimePoint from, TimePoint to) {
return new CombinedWindAsNavigableSet(track, trackedRace, from, to, getResolutionInMilliseconds());
}
/**
* Time point up to and including which the GPS fixes are considered in the race's tracks. Returns the value of
* {@link #to} unless it is <code>null</code>. In this case, the time point of the
* {@link TrackedRace#getAssumedEnd() assumed end of race},
* {@link #ceilingToResolution(Wind) ceiled to the resolution of this set} will be returned instead. If no valid
* time of a newest event can be obtained from the race, <code>MillisecondsTimePoint(1)</code> is returned instead.
*/
protected TimePoint getTo() {
return getToInternal() == null ? getTrackedRace().getAssumedEnd() == null ? new MillisecondsTimePoint(1)
: ceilingToResolution(getTrackedRace().getAssumedEnd()) : getToInternal();
}
}
@@ -0,0 +1,41 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl.EstimatedWindFixesAsNavigableSet;
/**
* A wind track that delivers the result of
* {@link TrackedRace#getWind(com.sap.sailing.domain.common.Position, com.sap.sailing.domain.common.TimePoint)},
* using a {@link CombinedWindAsNavigableSet} as its internal raw fixes collection. Resolution of raw fixes is set to 10s.
*
* @author Axel Uhl (d043530)
*
*/
public class CombinedWindTrackImpl extends VirtualWindTrackImpl {
private static final long serialVersionUID = -5019721956924343447L;
private CombinedWindAsNavigableSet virtualInternalRawFixes;
public CombinedWindTrackImpl(TrackedRace trackedRace, double baseConfidence) {
super(trackedRace, /* millisecondsOverWhichToAverage not used, see overridden method */ -1, baseConfidence,
/* useSpeed */ WindSourceType.COMBINED.useSpeed());
virtualInternalRawFixes = new CombinedWindAsNavigableSet(this, trackedRace, /* resolutionInMilliseconds */ 10000l);
}
@Override
protected CombinedWindAsNavigableSet getInternalRawFixes() {
return virtualInternalRawFixes;
}
/**
* The combined wind source already averages the wind from each wind source considered. There is no use in again
* averaging over a longer period of time. Therefore, we set the averaging interval to two times the resolution of
* the {@link EstimatedWindFixesAsNavigableSet virtual fixes collection} plus two milliseconds, so that at most one
* fix before and one fix after the time point requested will be used.
*/
@Override
public long getMillisecondsOverWhichToAverageWind() {
return 2*getInternalRawFixes().getResolutionInMilliseconds()+2;
}
}
Loaded 100 of 376 files, more files were not shown because too many files have changed in this diff. Show more