mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-04 19:33:52 +00:00
Merge remote-tracking branch 'origin/simulator' into simulator
This commit is contained in:
commit
09c3bfeb92
376 files changed
+81589
-3193
No files matched your search
@@ -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
|
||||
|
||||
+9
-6
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+11
-8
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+68403
File diff suppressed because it is too large.
Load diff
+1
@@ -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;
|
||||
|
||||
+16
-5
@@ -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);
|
||||
}
|
||||
|
||||
+27
-65
@@ -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]*)° ([0-9]*)' *([EW])";
|
||||
private static final String REGEXP_ANNUAL_CHANGE = "changing by *([0-9]*)° *([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);
|
||||
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+30
-5
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+4
-1
@@ -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.);
|
||||
|
||||
+1
@@ -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();
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
|
||||
+3
-1
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+7
-9
@@ -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);
|
||||
|
||||
+6
@@ -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);
|
||||
}
|
||||
+14
-3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -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);
|
||||
|
||||
+46
@@ -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>
|
||||
+134
-8
@@ -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);
|
||||
|
||||
-71
@@ -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());
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+4
-4
@@ -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());
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
}
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+3
-3
@@ -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) {
|
||||
}
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+78
-16
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+59
-8
@@ -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;
|
||||
}
|
||||
}
|
||||
+31
-12
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
+3
-3
@@ -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
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.tractracadapter;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration parameters that can be used to connect to a TracTrac event / race.
|
||||
*
|
||||
|
||||
+47
-20
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+32
-3
@@ -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;
|
||||
|
||||
+10
-3
@@ -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()
|
||||
|
||||
+51
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+8
-1
@@ -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) {
|
||||
|
||||
+1
@@ -43,4 +43,5 @@ public class TracTracConfigurationImpl implements TracTracConfiguration {
|
||||
return "<Exception during TracTracConfiguration.toString(): "+e.getMessage()+">";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+28
-11
@@ -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
|
||||
|
||||
+7
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeInMilliSeconds(TimePoint timePoint) {
|
||||
public Long getTimeInMilliSeconds(TimePoint timePoint) {
|
||||
return getMarkResults(timePoint).getLegtime();
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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();
|
||||
|
||||
}
|
||||
+2
@@ -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>() {
|
||||
|
||||
+36
-2
@@ -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);
|
||||
|
||||
+6
-3
@@ -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;
|
||||
|
||||
+4
@@ -28,4 +28,8 @@ public class HasConfidenceImpl<ValueType, BaseType, RelativeTo> implements HasCo
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ""+getObject()+"@"+getConfidence();
|
||||
}
|
||||
}
|
||||
+1
@@ -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;
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
Executable
+11
@@ -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);
|
||||
}
|
||||
+11
-1
@@ -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);
|
||||
}
|
||||
+7
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+79
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
+1
@@ -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;
|
||||
|
||||
+11
@@ -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() {
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
+12
@@ -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();
|
||||
}
|
||||
+27
@@ -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);
|
||||
|
||||
}
|
||||
+22
-5
@@ -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)} <= 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.
|
||||
|
||||
+1
@@ -15,4 +15,5 @@ import com.sap.sailing.domain.tracking.impl.ScalableWind;
|
||||
* <code>Pair<TimePoint, Position></code>
|
||||
*/
|
||||
public interface WindWithConfidence<RelativeTo> extends HasConfidenceAndIsScalable<ScalableWind, Wind, RelativeTo> {
|
||||
boolean useSpeed();
|
||||
}
|
||||
+2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CombinedWindAsNavigableSet.java
Executable
+51
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+41
@@ -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
Reference in new issue
Block a user