mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-27 22:37:15 +00:00
removed old mark passing test stuff that Martin Hanysz once started with; it got obsoleted by Nick's work
Change-Id: I8f679a1c39ab1986a6b6e41dc6a65a7bdb5bb912
This commit is contained in:
1 parent
a87cbdc8f0
commit
b5e39cabb4
3 files changed
-1173
No files matched your search
-731
@@ -1,731 +0,0 @@
|
||||
package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
|
||||
import com.sap.sailing.domain.tractracadapter.ReceiverType;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
/**
|
||||
* Tests the mark passing algorithm by using the data provided by an {@link OnlineTracTracBasedTest}. Subclasses may
|
||||
* implement different mark passing detection algorithms.
|
||||
*
|
||||
* @author Martin Hanysz
|
||||
*
|
||||
*/
|
||||
public abstract class MartinAbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
/**
|
||||
* How many milliseconds may the given and computed mark passings drift apart before being counted as a miss.
|
||||
*/
|
||||
private static final int TIMEDELTA_TOLERANCE = 10000;
|
||||
|
||||
/**
|
||||
* Reload the test data from the web?
|
||||
*/
|
||||
private boolean forceReload = false;
|
||||
|
||||
public MartinAbstractMarkPassingTest() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setUp() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
super.setUp();
|
||||
String raceID = "event_20110609_KielerWoch-505_Race_2";
|
||||
if (!loadData(raceID) && !forceReload) {
|
||||
System.out.println("Downloading new data from the web.");
|
||||
this.setUp("event_20110609_KielerWoch",
|
||||
/* raceId */raceID, /* liveUri */ null, /* storedUri */ null,
|
||||
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS,
|
||||
ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS });
|
||||
OnlineTracTracBasedTest.fixApproximateMarkPositionsForWindReadOut(getTrackedRace(),
|
||||
new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()));
|
||||
getTrackedRace().recordWind(
|
||||
new WindImpl(/* position */null, MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12,
|
||||
new DegreeBearingImpl(65))), new WindSourceImpl(WindSourceType.WEB));
|
||||
saveData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads stored data for the given raceID or returns false if no data is present.
|
||||
*
|
||||
* @param raceID
|
||||
* - ID of the race to load from disk
|
||||
* @return true if data was loaded, false if not
|
||||
*/
|
||||
private boolean loadData(String raceID) {
|
||||
String path = null;
|
||||
File file = new File("resources/");
|
||||
if (file.exists() && file.isDirectory()) {
|
||||
for (String fileName : file.list()) {
|
||||
if (fileName.endsWith(".data") && fileName.contains(raceID)) {
|
||||
path = "resources/" + fileName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path == null)
|
||||
return false;
|
||||
FileInputStream fs = null;
|
||||
ObjectInputStream os = null;
|
||||
Object obj = null;
|
||||
|
||||
try {
|
||||
System.out.print("Loading cached data for raceID " + raceID + "...");
|
||||
fs = new FileInputStream(path);
|
||||
os = new ObjectInputStream(fs);
|
||||
obj = os.readObject();
|
||||
System.out.println("done!");
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fs != null) {
|
||||
try {
|
||||
fs.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj != null && obj instanceof DynamicTrackedRaceImpl) {
|
||||
setTrackedRace((DynamicTrackedRaceImpl) obj);
|
||||
setRace(getTrackedRace().getRace());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current result of getTrackedRace to disk for future reuse.
|
||||
*/
|
||||
private void saveData() {
|
||||
DynamicTrackedRace trackedRace = getTrackedRace();
|
||||
String racePath = "resources/" + trackedRace.getRace().getId() + ".data";
|
||||
FileOutputStream fs = null;
|
||||
ObjectOutputStream os = null;
|
||||
try {
|
||||
System.out.println("Caching data for raceID " + trackedRace.getRace().getId());
|
||||
File f = new File(racePath);
|
||||
f.createNewFile();
|
||||
fs = new FileOutputStream(f);
|
||||
os = new ObjectOutputStream(fs);
|
||||
os.writeObject(trackedRace);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fs != null) {
|
||||
try {
|
||||
fs.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testMarkPassings() {
|
||||
List<MarkPassing> markPassings = computeAllMarkPassings();
|
||||
// compare computed mark passings to given ones
|
||||
int overallMisses = compareCalculatedAndGivenPassings(markPassings, true);
|
||||
assertEquals("Calculation returned less mark passings than the test data contains", 0, overallMisses);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the actual mark passing detection algorithm by handing a sequence of {@link GPSFix}es of each
|
||||
* {@link Competitor} into the {@link MartinAbstractMarkPassingTest#computeMarkPassings(Competitor, GPSFixMoving)}
|
||||
* method. For every competitor, sequences of mark passings for each waypoint of the course can be calculated. One
|
||||
* sequence may contain multiple possible mark passings for the same waypoint that may represent the same passing of
|
||||
* that specific waypoint. The list of sequences returned in the result should contain one sequence for each time
|
||||
* the waypoint has to be passed to complete the course.
|
||||
*
|
||||
* @return a {@link Map} from each {@link Competitor} to a {@link Map} from each {@link Waypoint} to a {@link List}
|
||||
* of mark passing sequences
|
||||
*/
|
||||
protected List<MarkPassing> computeAllMarkPassings() {
|
||||
ArrayList<MarkPassing> markPassings = new ArrayList<MarkPassing>();
|
||||
|
||||
// To imitate GPSFixes that are received over time, we hand the GPSFixes to the detection algorithm in
|
||||
// chronological order
|
||||
HashMap<Competitor, Iterator<GPSFixMoving>> fixesIterators = new HashMap<Competitor, Iterator<GPSFixMoving>>();
|
||||
HashMap<Competitor, GPSFixMoving> nextFixes = new HashMap<Competitor, GPSFixMoving>();
|
||||
|
||||
// get all the GPSFix iterators
|
||||
for (Competitor competitor : getRace().getCompetitors()) {
|
||||
try {
|
||||
getTrackedRace().getTrack(competitor).lockForRead();
|
||||
// from the start of tracking or "the dawn of time"
|
||||
TimePoint start = getTrackedRace().getStartOfTracking() != null ? getTrackedRace().getStartOfTracking()
|
||||
: new MillisecondsTimePoint(0);
|
||||
fixesIterators.put(competitor, getTrackedRace().getTrack(competitor).getRawFixesIterator(start, true));
|
||||
} finally {
|
||||
getTrackedRace().getTrack(competitor).unlockAfterRead();
|
||||
}
|
||||
}
|
||||
|
||||
// initially fill the next fix of each competitor to hand into the mark passing algorithm
|
||||
for (Entry<Competitor, Iterator<GPSFixMoving>> entry : fixesIterators.entrySet()) {
|
||||
if (entry.getValue().hasNext())
|
||||
nextFixes.put(entry.getKey(), entry.getValue().next());
|
||||
}
|
||||
|
||||
while (!nextFixes.isEmpty()) {
|
||||
// Find the chronologically first unhandled fix.
|
||||
Entry<Competitor, GPSFixMoving> firstUnhandledFix = null;
|
||||
for (Entry<Competitor, GPSFixMoving> entry : nextFixes.entrySet()) {
|
||||
if (firstUnhandledFix == null
|
||||
|| entry.getValue().getTimePoint().compareTo(firstUnhandledFix.getValue().getTimePoint()) < 0) {
|
||||
firstUnhandledFix = entry;
|
||||
}
|
||||
}
|
||||
if (firstUnhandledFix != null) {
|
||||
// compute a possible mark passing
|
||||
MarkPassing computedPassing = computeMarkPassings(firstUnhandledFix.getKey(),
|
||||
firstUnhandledFix.getValue());
|
||||
// replace the fix we just handled by the next fix of the competitor's track
|
||||
if (fixesIterators.get(firstUnhandledFix.getKey()).hasNext()) {
|
||||
nextFixes.put(firstUnhandledFix.getKey(), fixesIterators.get(firstUnhandledFix.getKey()).next());
|
||||
} else {
|
||||
// If no further fixes can be obtained from the iterator, remove the competitor from the nextFixes
|
||||
// map.
|
||||
// This ensures the while loop eventually terminates after all fixes have been analyzed.
|
||||
nextFixes.remove(firstUnhandledFix.getKey());
|
||||
}
|
||||
firstUnhandledFix = null;
|
||||
if (computedPassing != null) {
|
||||
// A new mark passing has been detected.
|
||||
// Find out if it overwrites a previous mark passing or is a new one.
|
||||
MarkPassing latestPassingOfCompetitor = null;
|
||||
int index = -1;
|
||||
for (int i = 0; i < markPassings.size(); i++) {
|
||||
MarkPassing p = markPassings.get(i);
|
||||
if (p.getCompetitor().equals(computedPassing.getCompetitor())) {
|
||||
latestPassingOfCompetitor = p;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (latestPassingOfCompetitor != null
|
||||
&& latestPassingOfCompetitor.getWaypoint().equals(computedPassing.getWaypoint())) {
|
||||
// Latest passing of the competitor was at the same waypoint -> replace it with the new one
|
||||
markPassings.set(index, computedPassing);
|
||||
} else {
|
||||
markPassings.add(computedPassing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return markPassings;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the actual mark passing detection algorithm. It is supposed to analyze if the given
|
||||
* {@link Competitor} passed a {@link Mark} at or before the given {@link GPSFixMoving}. If a {@link MarkPassing}
|
||||
* for a previously passed {@link Waypoint} is detected, it must only return a non-null result, if the returned
|
||||
* {@link MarkPassing} should overwrite the previously returned one.
|
||||
*
|
||||
* @param competitor
|
||||
* - the {@link Competitor} the given {@link GPSFixMoving} belongs to
|
||||
* @param fix
|
||||
* - the {@link GPSFixMoving} representing the latest known position of the given {@link Competitor}
|
||||
* @return a {@link List} of mark passings
|
||||
*/
|
||||
abstract MarkPassing computeMarkPassings(Competitor competitor, GPSFixMoving fix);
|
||||
|
||||
/**
|
||||
* Compares the computed mark passings to the given ones. For each given sequence of mark passings, the one with the
|
||||
* timestamp closest to the given data is selected.
|
||||
*
|
||||
* @param markPassings
|
||||
* - a {@link Map} from each {@link Competitor} to a {@link Map} from each {@link Waypoint} to a
|
||||
* {@link List} of mark passing sequences
|
||||
* @return how many mark passings were missed or detected way too early or too late
|
||||
*/
|
||||
protected int compareCalculatedAndGivenPassings(List<MarkPassing> markPassings, boolean printDebug) {
|
||||
int overallMisses = 0;
|
||||
Iterable<Competitor> competitors = getRace().getCompetitors();
|
||||
for (Competitor c : competitors) {
|
||||
int misses = 0;
|
||||
NavigableSet<MarkPassing> givenPassingsForCompetitor = getTrackedRace().getMarkPassings(c);
|
||||
List<MarkPassing> calculatedPassingsForCompetitor = new ArrayList<MarkPassing>();
|
||||
for (MarkPassing p : markPassings) {
|
||||
if (p.getCompetitor().equals(c)) {
|
||||
calculatedPassingsForCompetitor.add(p);
|
||||
}
|
||||
}
|
||||
if (printDebug)
|
||||
System.out.println("Competitor is " + c.getName());
|
||||
// Simultaneously iterate over given and computed mark passings and calculate the timedelta
|
||||
ListIterator<MarkPassing> calculatedPassingsIt = calculatedPassingsForCompetitor.listIterator();
|
||||
for (MarkPassing givenPassing : givenPassingsForCompetitor) {
|
||||
Waypoint passedWaypoint = givenPassing.getWaypoint();
|
||||
MarkPassing calculatedPassing;
|
||||
if (calculatedPassingsIt.hasNext()) {
|
||||
calculatedPassing = calculatedPassingsIt.next();
|
||||
} else {
|
||||
calculatedPassing = null;
|
||||
}
|
||||
|
||||
if (calculatedPassing != null && calculatedPassing.getWaypoint().equals(passedWaypoint)) {
|
||||
long timedelta = calculatedPassing.getTimePoint().asMillis()
|
||||
- givenPassing.getTimePoint().asMillis();
|
||||
String waypointType = isGate(passedWaypoint) ? "Gate" : "Buoy";
|
||||
if (Math.abs(timedelta) < TIMEDELTA_TOLERANCE) {
|
||||
// counts as detected
|
||||
if (printDebug)
|
||||
System.out.println("\tTimedelta of calculated mark passing for waypoint "
|
||||
+ passedWaypoint.getName() + " (" + waypointType + "): " + timedelta + "ms, "
|
||||
+ calculatedPassing.getTimePoint().asDate() + "(computed) vs. "
|
||||
+ givenPassing.getTimePoint().asDate() + "(given)");
|
||||
} else {
|
||||
// timedelta too huge, counts as missed
|
||||
misses++;
|
||||
overallMisses++;
|
||||
if (printDebug)
|
||||
System.out.println("\tPassings of waypoint " + passedWaypoint.getName() + " ("
|
||||
+ waypointType + ") are detected way off (" + timedelta + "ms, "
|
||||
+ calculatedPassing.getTimePoint().asDate() + ")");
|
||||
}
|
||||
} else {
|
||||
// mark passing for given waypoint
|
||||
misses++;
|
||||
overallMisses++;
|
||||
if (printDebug)
|
||||
System.out.println("\tNo passings of waypoint " + passedWaypoint.getName());
|
||||
if (calculatedPassingsIt.hasPrevious()) {
|
||||
// rewind the iterator to the previous entry
|
||||
calculatedPassingsIt.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (printDebug)
|
||||
System.out.println("\t" + misses + " waypoints were missed.");
|
||||
}
|
||||
if (printDebug)
|
||||
System.out.println("Overall, " + overallMisses + " mark passings were missed.");
|
||||
return overallMisses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous {@link Waypoint} of a given {@link Waypoint}.
|
||||
*
|
||||
* @param waypoint
|
||||
* - the {@link Waypoint} to obtain the predecessor of
|
||||
* @return a {@link Waypoint} that has to be passed before the given {@link Waypoint} of the {@link Course}, or null
|
||||
* if none
|
||||
*/
|
||||
protected Waypoint getPreviousWaypoint(Waypoint waypoint) {
|
||||
Course course = getRace().getCourse();
|
||||
try {
|
||||
course.lockForRead();
|
||||
int i = course.getIndexOfWaypoint(waypoint);
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
if (course.getIndexOfWaypoint(w) == i - 1) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next {@link Waypoint} after the given {@link Waypoint}.
|
||||
*
|
||||
* @param waypoint
|
||||
* - the {@link Waypoint} to obtain the successor of
|
||||
* @return a {@link Waypoint} that has to be passed after the given {@link Waypoint} of the {@link Course}, or null
|
||||
* if none
|
||||
*/
|
||||
protected Waypoint getNextWaypoint(Waypoint waypoint) {
|
||||
Course course = getRace().getCourse();
|
||||
try {
|
||||
course.lockForRead();
|
||||
int i = course.getIndexOfWaypoint(waypoint);
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
if (course.getIndexOfWaypoint(w) == i + 1) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the passing side of any given {@link Mark}. For single buoys this returns
|
||||
* {@link Waypoint#getPassingInstructions()}, for gates, the passing side of the {@link Waypoint}'s first
|
||||
* {@link Mark} is calculated and depending on the given {@link Mark} is returned unchanged (m = Mark 1) or inverted
|
||||
* (m = Mark 2). Since the calculation of the passing side uses the {@link DynamicGPSFixTrack} of the
|
||||
* {@link Waypoint}'s {@link Mark}s, a {@link TimePoint} has to be specified.
|
||||
*
|
||||
* @param waypoint
|
||||
* - the {@link Waypoint} to get the passing side of
|
||||
* @param m
|
||||
* - the {@link Mark} of the {@link Waypoint} to get the passing side of
|
||||
* @param timePoint
|
||||
* - the {@link TimePoint} to get the passing side at
|
||||
* @return the {@link NauticSide} the given {@link Mark} of the given {@link WayPoint} has to be passed on at the
|
||||
* given {@link TimePoint}
|
||||
*/
|
||||
protected PassingInstruction getPassingInstructionOfMark(Waypoint waypoint, Mark m, TimePoint timePoint) {
|
||||
if (isGate(waypoint)) {
|
||||
PassingInstruction mark1PassingInstruction = getPassingInstructionForMark1OfGate(waypoint, timePoint);
|
||||
if (m.equals(waypoint.getMarks().iterator().next())) {
|
||||
// m is mark 1 of gate
|
||||
return mark1PassingInstruction;
|
||||
} else if (mark1PassingInstruction.equals(PassingInstruction.Starboard)) {
|
||||
// m is mark 2 of gate -> opposite PassingInstruction of mark 1
|
||||
return PassingInstruction.Port;
|
||||
} else {
|
||||
return PassingInstruction.Starboard;
|
||||
}
|
||||
} else {
|
||||
// waypoint is not a gate
|
||||
return waypoint.getPassingInstructions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the passing side of {@link Mark} 1 of the given {@link Waypoint} at the given {@link TimePoint}.
|
||||
*
|
||||
* @param waypoint
|
||||
* - the {@link Waypoint} to calculate the passing side for
|
||||
* @param timePoint
|
||||
* - the {@link TimePoint} to calculate the passing side at
|
||||
* @return the {@link NauticSide} the {@link Mark} 1 of the given {@link Waypoint} has to be passed on
|
||||
*/
|
||||
protected PassingInstruction getPassingInstructionForMark1OfGate(Waypoint waypoint, TimePoint timePoint) {
|
||||
if (!isGate(waypoint)) {
|
||||
return null;
|
||||
}
|
||||
// calculate the passing side of the first mark in the waypoints marks
|
||||
Position mark1Pos = null;
|
||||
Iterator<Mark> markIt = waypoint.getMarks().iterator();
|
||||
DynamicGPSFixTrack<Mark, GPSFix> mark1Track = getTrackedRace().getOrCreateTrack(markIt.next());
|
||||
try {
|
||||
mark1Track.lockForRead();
|
||||
mark1Pos = mark1Track.getFirstRawFixAtOrAfter(timePoint).getPosition();
|
||||
} finally {
|
||||
mark1Track.unlockAfterRead();
|
||||
}
|
||||
|
||||
Position mark2Pos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> mark2Track = getTrackedRace().getOrCreateTrack(markIt.next());
|
||||
try {
|
||||
mark2Track.lockForRead();
|
||||
mark2Pos = mark2Track.getFirstRawFixAtOrAfter(timePoint).getPosition();
|
||||
} finally {
|
||||
mark2Track.unlockAfterRead();
|
||||
}
|
||||
|
||||
if (getRace().getCourse().getFirstWaypoint().equals(waypoint)) {
|
||||
// first waypoint of the course, racing direction is towards next waypoint
|
||||
Position nextWpPos = getTrackedRace().getApproximatePosition(getNextWaypoint(waypoint), timePoint);
|
||||
Bearing mark1ToNextWp = mark1Pos.getBearingGreatCircle(nextWpPos);
|
||||
Bearing mark2ToNextWp = mark2Pos.getBearingGreatCircle(nextWpPos);
|
||||
if (mark1ToNextWp.getDifferenceTo(mark2ToNextWp).getDegrees() > 0) {
|
||||
return PassingInstruction.Starboard;
|
||||
} else {
|
||||
return PassingInstruction.Port;
|
||||
}
|
||||
} else {
|
||||
// racing direction is from previous waypoint
|
||||
Position prevWpPos = getTrackedRace().getApproximatePosition(getPreviousWaypoint(waypoint), timePoint);
|
||||
Bearing prevWpToMark1 = prevWpPos.getBearingGreatCircle(mark1Pos);
|
||||
Bearing prevWpToMark2 = prevWpPos.getBearingGreatCircle(mark2Pos);
|
||||
if (prevWpToMark1.getDifferenceTo(prevWpToMark2).getDegrees() > 0) {
|
||||
return PassingInstruction.Port;
|
||||
} else {
|
||||
return PassingInstruction.Starboard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a {@link Waypoint} has exactly 2 {@link Mark Marks}.
|
||||
*
|
||||
* @param wp
|
||||
* the {@link Waypoint} to check
|
||||
* @return <code>true</code> if {@link Waypoint#getMarks()} returns an {@link Iterable} with exactly 2 elements.
|
||||
*/
|
||||
protected boolean isGate(Waypoint wp) {
|
||||
Iterator<Mark> it = wp.getMarks().iterator();
|
||||
if (it != null && it.hasNext()) {
|
||||
it.next();
|
||||
if (it.hasNext()) {
|
||||
it.next();
|
||||
if (!it.hasNext()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the passing {@link Bearing} of the given {@link Mark} which belongs to the given {@link Waypoint} at
|
||||
* the given {@link TimePoint}.
|
||||
*
|
||||
* @param waypoint
|
||||
* - the {@link Waypoint} the given {@link Mark} belongs to
|
||||
* @param mark
|
||||
* - the {@link Mark} to calculate the passing {@link Bearing} for
|
||||
* @param time
|
||||
* - the {@link TimePoint} at which to calculate the passing {@link Bearing}
|
||||
* @return a {@link Bearing} that represents the line a boat has to pass in order to round the given {@link Mark}
|
||||
*/
|
||||
protected Bearing getPassingBearing(Waypoint waypoint, Mark mark, TimePoint time) {
|
||||
Course course = getRace().getCourse();
|
||||
Bearing passingBearing = null;
|
||||
|
||||
Position markPos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> markTrack = getTrackedRace().getOrCreateTrack(mark);
|
||||
try {
|
||||
markTrack.lockForRead();
|
||||
markPos = markTrack.getFirstRawFixAtOrAfter(time).getPosition();
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
}
|
||||
|
||||
if (waypoint.equals(course.getFirstWaypoint()) || waypoint.equals(course.getLastWaypoint())) {
|
||||
// passing a line
|
||||
Mark otherMark = null;
|
||||
for (Mark m : waypoint.getMarks()) {
|
||||
if (!m.equals(mark)) {
|
||||
otherMark = m;
|
||||
}
|
||||
}
|
||||
Position otherMarkPos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> otherMarkTrack = getTrackedRace().getOrCreateTrack(otherMark);
|
||||
try {
|
||||
otherMarkTrack.lockForRead();
|
||||
otherMarkPos = otherMarkTrack.getFirstRawFixAtOrAfter(time).getPosition();
|
||||
} finally {
|
||||
otherMarkTrack.unlockAfterRead();
|
||||
}
|
||||
passingBearing = markPos.getBearingGreatCircle(otherMarkPos);
|
||||
} else {
|
||||
// passing a single buoy
|
||||
|
||||
Bearing bearingToNextWp = null;
|
||||
Bearing bearingfromPrevWp = null;
|
||||
Bearing bearingDiff = null;
|
||||
|
||||
// calculate bearings to next and from previous waypoint
|
||||
if (!course.getLastWaypoint().equals(waypoint)) {
|
||||
// not last waypoint of the race
|
||||
Waypoint nextWp = getNextWaypoint(waypoint);
|
||||
Position nextWaypointPos = getTrackedRace().getApproximatePosition(nextWp, time);
|
||||
|
||||
bearingToNextWp = markPos.getBearingGreatCircle(nextWaypointPos);
|
||||
}
|
||||
if (!course.getFirstWaypoint().equals(waypoint)) {
|
||||
// not first waypoint of the course
|
||||
Waypoint prevWp = getPreviousWaypoint(waypoint);
|
||||
Position prevWaypointPos = getTrackedRace().getApproximatePosition(prevWp, time);
|
||||
|
||||
bearingfromPrevWp = prevWaypointPos.getBearingGreatCircle(markPos);
|
||||
}
|
||||
|
||||
// depending on passing side, set the bearing difference
|
||||
PassingInstruction passingInstruction = getPassingInstructionOfMark(waypoint, mark, time);
|
||||
if (passingInstruction != null && passingInstruction.equals(PassingInstruction.Starboard)) {
|
||||
bearingDiff = new DegreeBearingImpl(-90);
|
||||
} else {
|
||||
bearingDiff = new DegreeBearingImpl(90);
|
||||
}
|
||||
|
||||
// calculate the passing bearing
|
||||
if (course.getFirstWaypoint().equals(waypoint)) {
|
||||
passingBearing = bearingToNextWp.add(bearingDiff);
|
||||
} else if (course.getLastWaypoint().equals(waypoint)) {
|
||||
passingBearing = bearingfromPrevWp.add(bearingDiff);
|
||||
} else {
|
||||
passingBearing = bearingToNextWp.add(bearingDiff).middle(bearingfromPrevWp.add(bearingDiff));
|
||||
}
|
||||
}
|
||||
return passingBearing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@link Entry} with the best {@link MarkPassing} of the key set. A {@link MarkPassing} is better than
|
||||
* another if:
|
||||
* <ol>
|
||||
* <li>it is on the correct side of the mark and the other one is not</li>
|
||||
* <li>it is closer to the mark than the other one</li>
|
||||
* <li>its TimePoint is smaller (it happened earlier) than the other one</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param possiblePassings
|
||||
* @return
|
||||
*/
|
||||
protected Entry<MarkPassing, Position> findBestMarkPassing(Map<MarkPassing, Position> passings) {
|
||||
if (passings.size() == 1) {
|
||||
return passings.entrySet().iterator().next();
|
||||
} else if (passings.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<MarkPassing, Position> possibleResults = new HashMap<MarkPassing, Position>();
|
||||
// check if mark was passed on correct side
|
||||
for (Entry<MarkPassing, Position> entry : passings.entrySet()) {
|
||||
// find out on which side of the mark was passed
|
||||
MarkPassing passing = entry.getKey();
|
||||
Position passPos = entry.getValue();
|
||||
Position markPos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> markTrack = getTrackedRace().getOrCreateTrack(
|
||||
passing.getWaypoint().getMarks().iterator().next());
|
||||
try {
|
||||
markTrack.lockForRead();
|
||||
markPos = markTrack.getLastFixAtOrBefore(passing.getTimePoint()).getPosition();
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
}
|
||||
Bearing bearingDiff = markPos.getBearingGreatCircle(passPos).getDifferenceTo(
|
||||
getPassingBearing(passing.getWaypoint(), passing.getWaypoint().getMarks().iterator().next(),
|
||||
passing.getTimePoint()));
|
||||
if (Math.abs(bearingDiff.getDegrees()) < 90) {
|
||||
// passed on correct side of mark
|
||||
possibleResults.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
if (possibleResults.size() > 0) {
|
||||
// All possible passings are on correct side, pass them to the distance test.
|
||||
// If all passings were on the wrong side, passings is not overwritten and they are still passed to the
|
||||
// distance test.
|
||||
passings.putAll(possibleResults);
|
||||
}
|
||||
possibleResults.clear();
|
||||
|
||||
// find the mark passing with the smallest distance to the mark
|
||||
for (Entry<MarkPassing, Position> entry : passings.entrySet()) {
|
||||
// find out how far away the mark was passed
|
||||
MarkPassing passing = entry.getKey();
|
||||
Position passPos = entry.getValue();
|
||||
Position markPos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> markTrack = getTrackedRace().getOrCreateTrack(
|
||||
passing.getWaypoint().getMarks().iterator().next());
|
||||
try {
|
||||
markTrack.lockForRead();
|
||||
markPos = markTrack.getLastFixAtOrBefore(passing.getTimePoint()).getPosition();
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
}
|
||||
if (possibleResults.isEmpty()) {
|
||||
possibleResults.put(passing, passPos);
|
||||
} else {
|
||||
Distance shortestDistance = markPos
|
||||
.getDistance(possibleResults.entrySet().iterator().next().getValue());
|
||||
Distance newDistance = markPos.getDistance(passPos);
|
||||
if (shortestDistance.compareTo(newDistance) == 0) {
|
||||
// same distance, add to possible results
|
||||
possibleResults.put(passing, passPos);
|
||||
} else if (shortestDistance.compareTo(newDistance) > 0) {
|
||||
// shorter distance, remove all other possible results and add this one
|
||||
possibleResults.clear();
|
||||
possibleResults.put(passing, passPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (possibleResults.size() == 1) {
|
||||
// only one possible result left, we're done
|
||||
return possibleResults.entrySet().iterator().next();
|
||||
} else if (possibleResults.size() > 1) {
|
||||
// all possible passings have the same distance to the mark, pass them to the timing test
|
||||
passings.putAll(possibleResults);
|
||||
}
|
||||
possibleResults.clear();
|
||||
|
||||
// find the mark passing with the smallest timestamp
|
||||
for (Entry<MarkPassing, Position> entry : passings.entrySet()) {
|
||||
// find out how far away the mark was passed
|
||||
MarkPassing passing = entry.getKey();
|
||||
Position passPos = entry.getValue();
|
||||
if (possibleResults.isEmpty()) {
|
||||
possibleResults.put(passing, passPos);
|
||||
} else {
|
||||
TimePoint smallestTime = possibleResults.entrySet().iterator().next().getKey().getTimePoint();
|
||||
TimePoint newTime = passing.getTimePoint();
|
||||
if (smallestTime.compareTo(newTime) == 0) {
|
||||
// same timestamp, add to possible results
|
||||
possibleResults.put(passing, passPos);
|
||||
} else if (smallestTime.compareTo(newTime) > 0) {
|
||||
// smaller timestamp, remove all other possible results and add this one
|
||||
possibleResults.clear();
|
||||
possibleResults.put(passing, passPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (possibleResults.size() > 0) {
|
||||
// all possible passings have the same timestamp, just return the first one
|
||||
return possibleResults.entrySet().iterator().next();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
-249
@@ -1,249 +0,0 @@
|
||||
package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
|
||||
/**
|
||||
* This class tests a mark passing detection algorithm that is based purely on bearing analyses
|
||||
* from the {@link Course}'s {@link Mark}s to the GPS fixes of the {@link Competitor}'s boats.
|
||||
* It uses a "hot zone" around the {@link Mark}s to avoid unnecessary testing of {@link Mark}s
|
||||
* too far away from the boat.
|
||||
*
|
||||
* @author Martin Hanysz
|
||||
*
|
||||
*/
|
||||
public class MartinMarkPassingBearingBasedTest extends MartinAbstractMarkPassingTest {
|
||||
|
||||
/**
|
||||
* Controls the size of the hot zone around buoys. It is multiplied with the boat length.
|
||||
*/
|
||||
private static final double HOT_ZONE_TO_BOAT_LENGHT_RATIO = 10.0;
|
||||
|
||||
private Map<Competitor, Entry<MarkPassing, Position>> lastCompetitorPassings;
|
||||
|
||||
public MartinMarkPassingBearingBasedTest() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
lastCompetitorPassings = new HashMap<Competitor, Entry<MarkPassing, Position>>();
|
||||
}
|
||||
|
||||
@Override
|
||||
MarkPassing computeMarkPassings(Competitor competitor, GPSFixMoving fix) {
|
||||
Map<MarkPassing, Position> possibleMarkPassings = new HashMap<MarkPassing, Position>();
|
||||
|
||||
// find out which waypoints to check (last waypoint passed and the next)
|
||||
List<Waypoint> waypointsToCheck = new ArrayList<Waypoint>();
|
||||
Course course = getRace().getCourse();
|
||||
try {
|
||||
course.lockForRead();
|
||||
if (!lastCompetitorPassings.containsKey(competitor)) {
|
||||
// no previous passings, competitor needs to pass 1st waypoint
|
||||
waypointsToCheck.add(course.getFirstWaypoint());
|
||||
} else {
|
||||
// previous passing, competitor either passes same waypoint again or the next waypoint
|
||||
waypointsToCheck.add(lastCompetitorPassings.get(competitor).getKey().getWaypoint());
|
||||
Waypoint next = getNextWaypoint(lastCompetitorPassings.get(competitor).getKey().getWaypoint());
|
||||
if (next != null) {
|
||||
waypointsToCheck.add(next);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
|
||||
for (Waypoint wp : waypointsToCheck) {
|
||||
MarkPassing passing = null;
|
||||
if (isGate(wp)) {
|
||||
if (wp.equals(course.getFirstWaypoint()) ||
|
||||
wp.equals(course.getLastWaypoint())) {
|
||||
// start or finish line
|
||||
passing = getLinePassing(wp, fix, competitor);
|
||||
} else {
|
||||
// waypoint is a gate, either pass mark 1 or mark 2 of the gate
|
||||
Iterator<Mark> markIterator = wp.getMarks().iterator();
|
||||
while (passing == null && markIterator.hasNext()) {
|
||||
passing = getBuoyPassing(wp, markIterator.next(), fix, competitor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// waypoint is single buoy
|
||||
passing = getBuoyPassing(wp, wp.getMarks().iterator().next(), fix, competitor);
|
||||
}
|
||||
|
||||
if (passing != null) {
|
||||
// detected a passing
|
||||
possibleMarkPassings.put(passing, fix.getPosition());
|
||||
}
|
||||
}
|
||||
Entry<MarkPassing, Position> result = null;
|
||||
if (possibleMarkPassings.size() > 0) {
|
||||
Entry<MarkPassing, Position> lastPassing = lastCompetitorPassings.get(competitor);
|
||||
Entry<MarkPassing, Position> possiblePassing = possibleMarkPassings.entrySet().iterator().next();
|
||||
if (lastPassing != null &&
|
||||
lastPassing.getKey().getWaypoint().equals(possiblePassing.getKey().getWaypoint())) {
|
||||
// previous passing detected & passed same waypoint again
|
||||
possibleMarkPassings.put(lastPassing.getKey(), lastPassing.getValue());
|
||||
}
|
||||
result = findBestMarkPassing(possibleMarkPassings);
|
||||
}
|
||||
if (result != null) {
|
||||
lastCompetitorPassings.put(competitor, result);
|
||||
return result.getKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given {@link Waypoint} was passed with the given {@link GPSFixMoving} of the given {@link Competitor}.
|
||||
* @param waypoint - the {@link Waypoint} to check
|
||||
* @param mark - the mark of the given {@link Waypoint} to be checked
|
||||
* @param fix - the {@link GPSFixMoving} to check
|
||||
* @param competitor - the {@link Competitor} the given fix belongs to
|
||||
* @return a {@link MarkPassing} if the fix represents a passing of the given {@link Waypoint}, null if not
|
||||
*/
|
||||
private MarkPassing getBuoyPassing(Waypoint waypoint, Mark mark, GPSFixMoving fix, Competitor competitor) {
|
||||
if (waypoint == null || getTrackedRace().getStartOfRace().compareTo(fix.getTimePoint()) > 0) {
|
||||
// no waypoint or race not started yet
|
||||
return null;
|
||||
}
|
||||
|
||||
// get position of given mark
|
||||
Position markPos = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> markTrack = getTrackedRace().getOrCreateTrack(mark);
|
||||
try {
|
||||
markTrack.lockForRead();
|
||||
markPos = markTrack.getFirstRawFixAtOrAfter(fix.getTimePoint()).getPosition();
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
}
|
||||
|
||||
// check if boat is in "hot zone"
|
||||
// boat in "hot zone" <=> distance from boat to mark < boat length * x
|
||||
if (fix.getPosition().getDistance(markPos).compareTo(competitor.getBoat().getBoatClass().getHullLength().scale(HOT_ZONE_TO_BOAT_LENGHT_RATIO)) > 0) {
|
||||
// not in "hot zone"
|
||||
return null;
|
||||
}
|
||||
|
||||
// calculate mark passing bearing for waypoint
|
||||
Bearing markToBoatBearing = markPos.getBearingGreatCircle(fix.getPosition());
|
||||
Bearing passingBearing = getPassingBearing(waypoint, mark, fix.getTimePoint());
|
||||
PassingInstruction passingInstruction = getPassingInstructionOfMark(waypoint, mark, fix.getTimePoint());
|
||||
double passingBearingDelta = markToBoatBearing.getDifferenceTo(passingBearing).getDegrees();
|
||||
if (passingInstruction == null ||
|
||||
(passingInstruction.equals(PassingInstruction.Port) &&
|
||||
passingBearingDelta > 0)) {
|
||||
// markToBoatBearing is smaller than passing bearing -> passed on port
|
||||
return DomainFactory.INSTANCE.createMarkPassing(fix.getTimePoint(), waypoint, competitor);
|
||||
} else if ( passingInstruction.equals(PassingInstruction.Starboard) &&
|
||||
passingBearingDelta < 0) {
|
||||
// markToBoatBearing is greater than passing bearing -> passed on stb
|
||||
return DomainFactory.INSTANCE.createMarkPassing(fix.getTimePoint(), waypoint, competitor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the line represented by the given {@link Waypoint} was passed with the given {@link GPSFixMoving} of the given {@link Competitor}.
|
||||
* @param waypoint - the {@link Waypoint} to check
|
||||
* @param fix - the {@link GPSFixMoving} to check
|
||||
* @param competitor - the {@link Competitor} the given fix belongs to
|
||||
* @return a {@link MarkPassing} if the fix represents a passing of the line of the given {@link Waypoint}, null if not
|
||||
*/
|
||||
private MarkPassing getLinePassing(Waypoint waypoint, GPSFixMoving fix, Competitor competitor) {
|
||||
if (waypoint == null || !isGate(waypoint)) {
|
||||
// no waypoint, waypoint not a gate, or race not started yet
|
||||
return null;
|
||||
}
|
||||
|
||||
/* A boat passes the line, if the bearing from the boat to mark 1 of the gate equals
|
||||
* the bearing from mark 2 of the gate to mark 1 of the gate AND the bearing from the boat
|
||||
* to mark 2 of the gate equals the bearing from mark 1 of the gate to mark 2 of the gate.
|
||||
*/
|
||||
// get tracks of both marks
|
||||
Iterator<Mark> markIt = waypoint.getMarks().iterator();
|
||||
DynamicGPSFixTrack<Mark, GPSFix> mark1Track = null;
|
||||
Mark mark1 = null;
|
||||
DynamicGPSFixTrack<Mark, GPSFix> mark2Track = null;
|
||||
Mark mark2 = null;
|
||||
if (markIt.hasNext()) {
|
||||
mark1 = markIt.next();
|
||||
mark1Track = getTrackedRace().getOrCreateTrack(mark1);
|
||||
if (markIt.hasNext())
|
||||
mark2 = markIt.next();
|
||||
mark2Track = getTrackedRace().getOrCreateTrack(mark2);
|
||||
}
|
||||
|
||||
// get positions for both marks of the gate
|
||||
Position mark1Pos = null;
|
||||
Position mark2Pos = null;
|
||||
try {
|
||||
mark1Track.lockForRead();
|
||||
try {
|
||||
mark2Track.lockForRead();
|
||||
mark1Pos = mark1Track.getFirstRawFixAtOrAfter(fix.getTimePoint()).getPosition();
|
||||
mark2Pos = mark2Track.getFirstRawFixAtOrAfter(fix.getTimePoint()).getPosition();
|
||||
} finally {
|
||||
mark2Track.unlockAfterRead();
|
||||
}
|
||||
} finally {
|
||||
mark1Track.unlockAfterRead();
|
||||
}
|
||||
|
||||
// calculate if boat is in "hot zone" of gate
|
||||
// in "hot zone" <=> distance from any mark of gate to boat < length of line between marks
|
||||
Distance lineLength = mark1Pos.getDistance(mark2Pos);
|
||||
if ( mark1Pos.getDistance(fix.getPosition()).compareTo(lineLength) > 0 &&
|
||||
mark2Pos.getDistance(fix.getPosition()).compareTo(lineLength) > 0) {
|
||||
// not in "hot zone"
|
||||
return null;
|
||||
}
|
||||
|
||||
// calculate bearings from boat to 1st and 2nd mark, from 1st mark to 2nd mark, and from 2nd mark to 1st mark
|
||||
Bearing boatToMark1Bearing = fix.getPosition().getBearingGreatCircle(mark1Pos);
|
||||
Bearing mark1ToMark2Bearing = mark1Pos.getBearingGreatCircle(mark2Pos);
|
||||
Bearing boatToMark2Bearing = fix.getPosition().getBearingGreatCircle(mark2Pos);
|
||||
Bearing mark2ToMark1Bearing = mark2Pos.getBearingGreatCircle(mark1Pos);
|
||||
|
||||
PassingInstruction PassingInstructionOfMark1OfGate = getPassingInstructionForMark1OfGate(waypoint, fix.getTimePoint());
|
||||
double mark1PassingBearingDelta = boatToMark1Bearing.getDifferenceTo(mark2ToMark1Bearing).getDegrees();
|
||||
double mark2PassingBearingDelta = boatToMark2Bearing.getDifferenceTo(mark1ToMark2Bearing).getDegrees();
|
||||
//Mark closestMark = mark1;
|
||||
if (mark2 != null && fix.getPosition().getDistance(mark1Pos).compareTo(fix.getPosition().getDistance(mark2Pos)) < 0) {
|
||||
//closestMark = mark2;
|
||||
|
||||
}
|
||||
|
||||
if ( PassingInstructionOfMark1OfGate.equals(PassingInstruction.Starboard) &&
|
||||
mark1PassingBearingDelta < 0 &&
|
||||
mark2PassingBearingDelta > 0) {
|
||||
// gate passed if mark1 had to be passed on stb
|
||||
return DomainFactory.INSTANCE.createMarkPassing(fix.getTimePoint(), waypoint,competitor);
|
||||
} else if ( PassingInstructionOfMark1OfGate.equals(PassingInstruction.Port) &&
|
||||
mark1PassingBearingDelta > 0 &&
|
||||
mark2PassingBearingDelta < 0) {
|
||||
// gate passed if mark1 had to be passed on port
|
||||
return DomainFactory.INSTANCE.createMarkPassing(fix.getTimePoint(), waypoint, competitor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.markpassingcalculation.splining.HermiteCurve;
|
||||
import com.sap.sailing.domain.markpassingcalculation.splining.StraightLine;
|
||||
import com.sap.sailing.domain.markpassingcalculation.splining.Vector2D;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* This class tests a mark passing detection algorithm that interpolates the boats' positions between
|
||||
* their GPS fixes using Hermite Splines.
|
||||
* @author Martin Hanysz
|
||||
*
|
||||
*/
|
||||
public class MartinMarkPassingSplineBasedTest extends MartinAbstractMarkPassingTest {
|
||||
|
||||
// a factor to scale the tangents calculated from boat speed and bearing
|
||||
private static double TANGENT_SCALING_FACTOR = 100;
|
||||
private Map<Competitor, Entry<MarkPassing, Position>> lastCompetitorPassings;
|
||||
|
||||
public MartinMarkPassingSplineBasedTest() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
lastCompetitorPassings = new HashMap<Competitor, Entry<MarkPassing, Position>>();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setTangentScaling() {
|
||||
TANGENT_SCALING_FACTOR = 100.0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTangentScaling() {
|
||||
Map<Double, Integer> results = new HashMap<Double, Integer>();
|
||||
double s = 100.0;
|
||||
double div = 10.0;
|
||||
while (s > 0.000000001) {
|
||||
MartinMarkPassingSplineBasedTest.TANGENT_SCALING_FACTOR = s;
|
||||
List<MarkPassing> markPassings = computeAllMarkPassings();
|
||||
// compare computed mark passings to given ones
|
||||
int overallMisses = compareCalculatedAndGivenPassings(markPassings, false);
|
||||
results.put(s, overallMisses);
|
||||
s /= div;
|
||||
}
|
||||
boolean success = false;
|
||||
for (Entry<Double, Integer> e : results.entrySet()) {
|
||||
System.out.println("Scaling: " + e.getKey() + ", Misses: " + e.getValue());
|
||||
if (e.getValue() == 0) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
assertTrue("No setting for the scaling factor resulted in 0 missed waypoints.", success);
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.sap.sailing.domain.test.AbstractMarkPassingTest#computeMarkPassings()
|
||||
*/
|
||||
@Override
|
||||
MarkPassing computeMarkPassings(Competitor competitor, GPSFixMoving fix) {
|
||||
// interpolate the course of the competitor from the previous fix to the given fix
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrackedRace().getTrack(competitor);
|
||||
GPSFixMoving prevFix = null;
|
||||
try {
|
||||
competitorTrack.lockForRead();
|
||||
prevFix = competitorTrack.getLastRawFixBefore(fix.getTimePoint());
|
||||
} finally {
|
||||
competitorTrack.unlockAfterRead();
|
||||
}
|
||||
if (prevFix == null) {
|
||||
return null;
|
||||
}
|
||||
Vector2D point1 = new Vector2D(prevFix.getPosition());
|
||||
Vector2D point2 = new Vector2D(fix.getPosition());
|
||||
Vector2D tangent1 = new Vector2D(prevFix.getSpeed().getBearing(), prevFix.getSpeed().getKnots() * TANGENT_SCALING_FACTOR /* abh�ngig von Bootsklasse, zeitl. Abstand zw. Fixes */);
|
||||
Vector2D tangent2 = new Vector2D(fix.getSpeed().getBearing(), fix.getSpeed().getKnots() * TANGENT_SCALING_FACTOR);
|
||||
HermiteCurve interpolatedCourse = new HermiteCurve(point1, point2, tangent1, tangent2);
|
||||
|
||||
// find out which waypoints to check (last waypoint passed and the next)
|
||||
List<Waypoint> waypointsToCheck = new ArrayList<Waypoint>();
|
||||
Course raceCourse = getRace().getCourse();
|
||||
try {
|
||||
raceCourse.lockForRead();
|
||||
if (!lastCompetitorPassings.containsKey(competitor)) {
|
||||
// no previous passings, competitor needs to pass 1st waypoint
|
||||
waypointsToCheck.add(raceCourse.getFirstWaypoint());
|
||||
} else {
|
||||
waypointsToCheck.add(lastCompetitorPassings.get(competitor).getKey().getWaypoint());
|
||||
Waypoint next = getNextWaypoint(lastCompetitorPassings.get(competitor).getKey().getWaypoint());
|
||||
if (next != null) {
|
||||
waypointsToCheck.add(next);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
raceCourse.unlockAfterRead();
|
||||
}
|
||||
|
||||
// check if the interpolated course passes one of the waypoints to check
|
||||
Map<MarkPassing, Position> possiblePassings = new HashMap<MarkPassing, Position>();
|
||||
for (Waypoint wp : waypointsToCheck) {
|
||||
StraightLine passingLine = null;
|
||||
List<Position> markPositions = new ArrayList<Position>();
|
||||
for (Mark m : wp.getMarks()) {
|
||||
DynamicGPSFixTrack<Mark, GPSFix> markTrack = getTrackedRace().getOrCreateTrack(m);
|
||||
try {
|
||||
markTrack.lockForRead();
|
||||
markPositions.add(markTrack.getLastFixAtOrBefore(fix.getTimePoint()).getPosition());
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
Vector2D mark1Pos = new Vector2D(markPositions.get(0));
|
||||
Map<Vector2D, Double> intersections = null;
|
||||
//Mark passedMark = wp.getMarks().iterator().next();
|
||||
if (isGate(wp)) {
|
||||
if (wp.equals(raceCourse.getFirstWaypoint()) || wp.equals(raceCourse.getLastWaypoint())) {
|
||||
// passing start or finish line
|
||||
Vector2D mark2Pos = new Vector2D(markPositions.get(1));
|
||||
passingLine = new StraightLine(mark1Pos, mark2Pos.subtract(mark1Pos));
|
||||
intersections = interpolatedCourse.intersectWith(passingLine);
|
||||
} else {
|
||||
// passing either mark of the gate
|
||||
for (Mark mark : wp.getMarks()) {
|
||||
Bearing passingBearing = getPassingBearing(wp, mark, fix.getTimePoint());
|
||||
Vector2D passingVector = new Vector2D(passingBearing, 1.0);
|
||||
passingLine = new StraightLine(mark1Pos, passingVector);
|
||||
intersections = interpolatedCourse.intersectWith(passingLine);
|
||||
if (intersections.size() > 0) {
|
||||
// already found a passing of a mark
|
||||
//passedMark = mark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// passing single buoy
|
||||
Bearing passingBearing = getPassingBearing(wp, wp.getMarks().iterator().next(), fix.getTimePoint());
|
||||
Vector2D passingVector = new Vector2D(passingBearing, 1.0);
|
||||
passingLine = new StraightLine(mark1Pos, passingVector);
|
||||
intersections = interpolatedCourse.intersectWith(passingLine);
|
||||
}
|
||||
for (Entry<Vector2D, Double> entry : intersections.entrySet()) {
|
||||
TimePoint time = fix.getTimePoint();
|
||||
TimePoint prevTime = prevFix.getTimePoint();
|
||||
// t_pass = t_fix - (t_prev - t_fix) * interpolationParameterOfIntersection
|
||||
// If the intersection is at 75% of the interpolated course, t_pass is at 75% between t_prev and t_fix.
|
||||
// This cast truncates the milliseconds, which is acceptable at the moment.
|
||||
TimePoint timePoint = time.minus((long) ((prevTime.asMillis() - time.asMillis()) * entry.getValue()));
|
||||
possiblePassings.put(DomainFactory.INSTANCE.createMarkPassing(timePoint, wp, competitor), new DegreePosition(entry.getKey().y(), entry.getKey().x()));
|
||||
}
|
||||
}
|
||||
Entry<MarkPassing, Position> result = null;
|
||||
if (possiblePassings.size() > 0) {
|
||||
Entry<MarkPassing, Position> lastPassing = lastCompetitorPassings.get(competitor);
|
||||
Entry<MarkPassing, Position> possiblePassing = possiblePassings.entrySet().iterator().next();
|
||||
if (lastPassing != null &&
|
||||
lastPassing.getKey().getWaypoint().equals(possiblePassing.getKey().getWaypoint())) {
|
||||
// previous passing detected & passed same waypoint again
|
||||
possiblePassings.put(lastPassing.getKey(), lastPassing.getValue());
|
||||
}
|
||||
result = findBestMarkPassing(possiblePassings);
|
||||
}
|
||||
if (result != null) {
|
||||
lastCompetitorPassings.put(competitor, result);
|
||||
return result.getKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user