Merge remote-tracking branch 'origin/sprint4_master' into AuditRefactors

# Conflicts:
#	visualiser/src/main/java/seng302/VisualiserInput.java
main
Fan-Wu Yang 9 years ago
commit 22e5996e64

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinCommonCompilerArguments">
<option name="languageVersion" value="1.1" />
<option name="apiVersion" value="1.1" />
</component>
</project>

@ -161,4 +161,4 @@
</plugin>
</plugins>
</reporting>
</project>
</project>

@ -3,27 +3,21 @@ package seng302;
import javafx.application.Application;
import javafx.stage.Stage;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.DataInput.PolarParser;
import seng302.DataInput.XMLReader;
import seng302.Model.Event;
import seng302.Model.Polars;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class App extends Application {
@ -39,25 +33,32 @@ public class App extends Application {
@Override
public void start(Stage primaryStage) {
try {
Polars boatPolars = PolarParser.parse("polars/acc_polars.csv");
String regattaXML = readFile("mockXML/regattaTest.xml", StandardCharsets.UTF_8);
String raceXML = readFile("mockXML/raceTest.xml", StandardCharsets.UTF_8);
String boatXML = readFile("mockXML/boatTest.xml", StandardCharsets.UTF_8);
Event raceEvent = new Event(raceXML, regattaXML, boatXML);
Event raceEvent = new Event(raceXML, regattaXML, boatXML, boatPolars);
raceEvent.start();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (TransformerException e) {
} catch (Exception e) {
//Catch all exceptions, print, and exit.
e.printStackTrace();
System.exit(1);
}
}
/**
* Reads the Initial Race XML files that are necessary to run the mock.
* @param path path of the XML
* @param encoding encoding of the xml
* @return
* @throws IOException No file etc
* @throws ParserConfigurationException Issue with the XML formatting
* @throws SAXException Issue with XML formatting
* @throws TransformerException Issue with the XML format
*/
private String readFile(String path, Charset encoding) throws IOException, ParserConfigurationException, SAXException, TransformerException {
InputSource fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(path));

@ -6,9 +6,44 @@ package seng302;
*/
public class Constants {
public static final int NMToMetersConversion = 1852; // 1 nautical mile = 1852 meters
/**
* Multiply by this factor to convert nautical miles to meters.
* <br>
* Divide by this factor to convert meters to nautical miles.
* <br>
* 1 nautical mile = 1852 meters.
*/
public static final int NMToMetersConversion = 1852;
/**
* Multiply by this factor to convert Knots to millimeters per second.
* <br>
* Divide by this factor to convert millimeters per second to Knots.
* <br>
* 1 knot = 514.444 millimeters per second.
*/
public static final double KnotsToMMPerSecond = 514.444;
/**
* The race pre-start time, in milliseconds. 3 minutes.
*/
public static final long RacePreStartTime = 3 * 60 * 1000;
/**
* The race preparatory time, in milliseconds. 1 minutes.
*/
public static final long RacePreparatoryTime = 1 * 60 * 1000;
/**
* The number of milliseconds in one hour.
*/
public static long OneHourMilliseconds = 1 * 60 * 60 * 1000;
public static final int PRE_RACE_WAIT_TIME = 18000;
public static final int TEST_VELOCITIES[] = new int[] { 30, 15, 64, 52, 25, 24 };
}

@ -6,7 +6,7 @@ import seng302.Model.Mark;
import java.util.Map;
/**
* Created by cbt24 on 10/05/17.
* Boats Data
*/
public interface BoatDataSource {
Map<Integer, Boat> getBoats();

@ -6,6 +6,7 @@ import org.xml.sax.SAXException;
import seng302.Model.Boat;
import seng302.Model.GPSCoordinate;
import seng302.Model.Mark;
import seng302.Model.Polars;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
@ -13,38 +14,46 @@ import java.util.HashMap;
import java.util.Map;
/**
* Created by cbt24 on 10/05/17.
* Xml Reader class for Boat XML used for the race
*/
public class BoatXMLReader extends XMLReader implements BoatDataSource {
private final Map<Integer, Boat> boatMap = new HashMap<>();
private final Map<Integer, Mark> markerMap = new HashMap<>();
public BoatXMLReader(String filePath) throws ParserConfigurationException, SAXException, IOException {
this(filePath, true);
}
/**
* COnstructor for Boat XML
* Polars table to assign to each boat.
*/
Polars boatPolars;
/**
* Constructor for Boat XML
*
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @param filePath Name/path of file to read. Read as a resource.
* @param boatPolars polars used by the boats
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
if (read) {
read();
}
this.boatPolars = boatPolars;
read();
}
/**
* Read the XML
*/
public void read() {
readSettings();
readShapes();
readBoats();
}
/**
* Read the Boats
*/
private void readBoats() {
Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0);
for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) {
@ -69,6 +78,11 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
}
/**
* Checks if the node (XMl data node) is a Yacht or not
* @param boatNode Node from the XML
* @return Whether the node is a yacht node or not
*/
private boolean isYachtNode(Node boatNode) {
return boatNode.getAttributes().getNamedItem("Type").getTextContent().toLowerCase().equals("yacht");
}
@ -85,16 +99,28 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
else readMark(boatNode, sourceID, name);
}
/**
* Read a Yacht Node
* @param boatNode Node to be read
* @param sourceID Source ID of the Yacht
* @param name Name of the Boat
*/
private void readYacht(Node boatNode, int sourceID, String name) {
String shortName = boatNode.getAttributes().getNamedItem("ShortName").getTextContent();
if (exists(boatNode, "Country")) {
String country = boatNode.getAttributes().getNamedItem("Country").getTextContent();
boatMap.put(sourceID, new Boat(sourceID, name, country));
boatMap.put(sourceID, new Boat(sourceID, name, country, this.boatPolars));
} else {
boatMap.put(sourceID, new Boat(sourceID, name, shortName));
boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars));
}
}
/**
* Read Marker Boats
* @param boatNode Node to be read
* @param sourceID Source ID of the boat
* @param name Name of the Marker Boat
*/
private void readMark(Node boatNode, int sourceID, String name) {
Node nCoord = ((Element)boatNode).getElementsByTagName("GPSposition").item(0);
double x = Double.parseDouble(nCoord.getAttributes().getNamedItem("X").getTextContent());
@ -103,11 +129,19 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
markerMap.put(sourceID, mark);
}
/**
* Get the boats that are going to participate in this race
* @return Dictionary of boats that are to participate in this race indexed by SourceID
*/
@Override
public Map<Integer, Boat> getBoats() {
return boatMap;
}
/**
* Get the marker BOats that are participating in this race
* @return Dictionary of the Markers BOats that are in this race indexed by their Source ID.
*/
@Override
public Map<Integer, Mark> getMarkerBoats() {
return markerMap;

@ -0,0 +1,109 @@
package seng302.DataInput;
/**
* Created by hba56 on 10/05/17.
*/
import seng302.Exceptions.InvalidPolarFileException;
import seng302.Model.Bearing;
import seng302.Model.Polars;
import java.io.*;
import java.util.ArrayList;
/**
* Responsible for parsing a polar data file, and creating a Polar data object.
*/
public class PolarParser {
/**
* Given a filename, this function parses it and generates a Polar object, which can be queried for polar information.
* @param filename The filename to load and read data from (loaded as a resource).
* @return A Polar table containing data from the given file.
*/
public static Polars parse(String filename) throws InvalidPolarFileException {
//Temporary table to return later.
Polars polarTable = new Polars();
//Open the file for reading.
InputStream fileStream = PolarParser.class.getClassLoader().getResourceAsStream(filename);
if (fileStream == null) {
throw new InvalidPolarFileException("Could not open polar data file: " + filename);
}
//Wrap it with buffered input stream to set encoding and buffer.
InputStreamReader in = null;
try {
in = new InputStreamReader(fileStream, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InvalidPolarFileException("Unsupported encoding: UTF-8", e);
}
BufferedReader inputStream = new BufferedReader(in);
//We expect the polar data file to have the column headings:
// Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7
//and to have 7 rows of data.
//Angles are expected to be in degrees, and velocities in knots.
//We read data rows, and split them into arrays of elements.
ArrayList<String[]> dataRows = new ArrayList<>(7);
try {
//Heading row.
//We skip the heading row by reading it.
String headingRow = inputStream.readLine();
//Data rows.
while (inputStream.ready()) {
//Read line.
String dataRow = inputStream.readLine();
//Split line.
String[] dataElements = dataRow.split(",");
//Add to collection.
dataRows.add(dataElements);
}
} catch (IOException e) {
throw new InvalidPolarFileException("Could not read from polar data file: " + filename, e);
}
//Finished reading in data, now we need to construct polar rows and table from it.
//For each row...
int rowNumber = 0;
for (String[] row : dataRows) {
//For each pair of columns (the pair is angle, speed).
//We start at column 1 since column 0 is the wind speed column.
for (int i = 1; i < row.length; i += 2) {
//Add angle+speed=velocity estimate to polar table.
try {
//Add the polar value to the polar table
double windSpeedKnots = Double.parseDouble(row[0]);
double angleDegrees = Double.parseDouble(row[i]);
Bearing angle = Bearing.fromDegrees(angleDegrees);
double boatSpeedKnots = Double.parseDouble(row[i + 1]);
polarTable.addEstimate(windSpeedKnots, angle, boatSpeedKnots);
} catch (NumberFormatException e) {
throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e);
}
}
//Increment row number.
rowNumber++;
}
return polarTable;
}
}

@ -1,6 +1,5 @@
package seng302.DataInput;
;
import seng302.Model.Boat;
import seng302.Model.CompoundMark;
import seng302.Model.GPSCoordinate;
@ -10,7 +9,7 @@ import java.time.ZonedDateTime;
import java.util.List;
/**
* Created by connortaylorbrown on 19/04/17.
* Data Class for a Race
*/
public interface RaceDataSource {
List<Boat> getBoats();

@ -16,7 +16,7 @@ import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* Created by jjg64 on 21/04/17.
* XML Reader that reads in the race data required for this race
*/
public class RaceXMLReader extends XMLReader implements RaceDataSource {
private static final double COORDINATEPADDING = 0.000;
@ -38,6 +38,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
/**
* Constructor for Streamed Race XML
* @param filePath path of the file
* @param boatData data for boats in race
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
@ -53,6 +54,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
/**
* Constructor for Streamed Race XML
* @param filePath file path to read
* @param boatData data of the boats in race
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
@ -101,6 +103,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
}
/**
* Reads in the participants for htis race
*/
private void readParticipants() {
Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0);
nParticipants.getChildNodes().getLength();
@ -164,6 +169,11 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
return marker;
}
/**
* Gets a mark from an Element
* @param mark Element the mark is suppose to be part of
* @return a Mark that existed in the element
*/
private Mark getMark(Element mark) {
int sourceID = Integer.parseInt(mark.getAttribute("SourceID"));
return marks.get(sourceID);
@ -204,8 +214,12 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
lastCompoundMark = currentCompoundMark;
legName = getCompoundMarkName(getCompoundMarkID(markXML));
}
}
/**
* Reads the boundary limits of the course
*/
private void readCourseLimit() {
Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) {

@ -18,7 +18,7 @@ import java.io.StringReader;
import java.io.StringWriter;
/**
* Created by fwy13 on 26/03/2017.
* Base Reader for XML Files
*/
public abstract class XMLReader {
@ -27,9 +27,9 @@ public abstract class XMLReader {
/**
* Read in XML file
* @param filePath filepath for XML file
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException If a document builder cannot be created.
* @throws IOException If any IO errors occur while parsing the XML file.
* @throws SAXException If any parse error occurs while parsing.
*/
public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException {
@ -48,6 +48,11 @@ public abstract class XMLReader {
doc.getDocumentElement().normalize();
}
/**
* Alternate constructor
* @param xmlFile
* @param isWholeFile
*/
public XMLReader(String xmlFile, Boolean isWholeFile) {
}
@ -84,6 +89,12 @@ public abstract class XMLReader {
return node.getAttributes().getNamedItem(attribute) != null;
}
/**
* Get the contents of the XML FILe.
* @param document
* @return
* @throws TransformerException
*/
public static String getContents(Document document) throws TransformerException {
DOMSource source = new DOMSource(document);

@ -0,0 +1,28 @@
package seng302.Exceptions;
/**
* Created by f123 on 10-May-17.
*/
/**
* An exception thrown when we cannot parse a polar data file.
*/
public class InvalidPolarFileException extends RuntimeException {
/**
* Constructs the exception with a given message.
* @param message Message to store.
*/
public InvalidPolarFileException(String message) {
super(message);
}
/**
* Constructs the exception with a given message and cause.
* @param message Message to store.
* @param cause Cause to store.
*/
public InvalidPolarFileException(String message, Throwable cause) {
super(message, cause);
}
}

@ -49,6 +49,7 @@ public class MockOutput implements Runnable
private String regattaXml;
private String boatsXml;
private boolean stop = false; //whether or not hte thread keeps running
/**
* Ctor.
@ -158,18 +159,25 @@ public class MockOutput implements Runnable
this.messagesToSendQueue.add(messagesToSendBuffer);
}
/**
* Sending loop of the Server
*/
public void run() {
try {
while (true){
while (!stop){
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
mockSocket = serverSocket.accept();
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
while(boatsXml == null || regattaXml == null || raceXml == null) {
if (boatsXml == null || regattaXml == null || raceXml == null){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
parseXMLString(raceXml, XMLMessage.XMLTypeRace);
@ -205,18 +213,30 @@ public class MockOutput implements Runnable
}
}
public void stop(){
stop = true;
}
/**
* Sets the Race XML to send
* @param raceXml XML to send to the CLient
*/
public void setRaceXml(String raceXml) {
this.raceXml = raceXml;
}
/**
* Sets the Regatta XMl to send
* @param regattaXml XML to send to CLient
*/
public void setRegattaXml(String regattaXml) {
this.regattaXml = regattaXml;
}
/**
* Sets the Boats XML to send
* @param boatsXml XMl to send to the CLient
*/
public void setBoatsXml(String boatsXml) {
this.boatsXml = boatsXml;
}

@ -0,0 +1,133 @@
package seng302.Model;
/**
* This represents an angle.
* Has functions to return angle as either degrees or radians.
*/
public class Angle implements Comparable<Angle> {
/**
* The angle stored in this object.
* Degrees.
*/
private double degrees;
/**
* Ctor.
* Don't use this.
* This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Angle object.
*
* @param degrees The value, in degrees, to initialize this Angle object with.
*/
protected Angle(double degrees) {
this.degrees = degrees;
}
/**
* Constructs an Angle object from an angle value in degrees.
* @param degrees Angle value in degrees.
* @return Angle object.
*/
public static Angle fromDegrees(double degrees) {
Angle angle = new Angle(degrees);
return angle;
}
/**
* Constructs an Angle object from an angle value in radians.
* @param radians Angle value in radians.
* @return Angle object.
*/
public static Angle fromRadians(double radians) {
return Angle.fromDegrees(Math.toDegrees(radians));
}
/**
* Returns the value of this Angle object, in degrees.
* @return The value of this Angle object, in degrees.
*/
public double degrees() {
return this.degrees;
}
public void setDegrees(double degrees) {
this.degrees = degrees;
}
/**
* Returns the value of this Angle object, in radians.
* @return The value of this Angle object, in radians.
*/
public double radians() {
return Math.toRadians(this.degrees);
}
/**
* Returns true if two Angle objects have equal values.
* @param obj Other angle object to compare.
* @return True if they are equal, false otherwise.
*/
@Override
public boolean equals(Object obj) {
//Cast other side.
Angle other = (Angle) obj;
//Compare values.
if (this.degrees() == other.degrees()) {
return true;
} else {
return false;
}
}
/**
* Returns an int describing the ordering between this angle object, and another.
* @param o Other angle to compare to.
* @return {@literal int < 0} if this angle is less than the other angle, {@literal int > 0} if this angle is greater than the other angle, and {@literal int = 0} if this angle is equal to the other angle,
*/
@Override
public int compareTo(Angle o) {
if (this.degrees() < o.degrees()) {
return -1;
} else if (this.degrees() > o.degrees()) {
return 1;
} else {
return 0;
}
}
/**
* Converts an angle to an angle in a given periodic interval (e.g., degrees have a periodic interval of 360, radians have a periodic interval of 2Pi) of [lowerBound, upperBound).
* @param angle The angle to convert.
* @param lowerBound The lower bound of the interval.
* @param upperBound The upper bound of the interval.
* @param period The period of the interval.
* @return The angle in the desired periodic interval.
*/
public static double toPeriodicInterval(double angle, double lowerBound, double upperBound, double period) {
while (angle >= upperBound) {
//Too large.
angle -= period;
}
while (angle < lowerBound) {
//Too small.
angle += period;
}
return angle;
}
}

@ -0,0 +1,68 @@
package seng302.Model;
/**
* Represents an azimuth.
* If treated as an absolute azimuth this is the angle between north and a target point.
* If treated as a relative azimuth, this is the angle between from one target point to the other.
* It has the interval [-180, 180) degrees, and clockwise is positive.
*/
public class Azimuth extends Angle{
/**
* Ctor.
* This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Azimuth object.
*
* @param degrees The value, in degrees, to initialize this Azimuth object with.
*/
protected Azimuth(double degrees) {
super(degrees);
}
/**
* Converts an angle in degrees into an angle in degrees in the correct interval for an azimuth - [-180, 180).
* E.g., converts -183 to 177, or converts 250 to -110, or converts 180 to -180.
* @param degrees Degree value to convert.
* @return Degree value in interval [-180, 180).
*/
public static double toAzimuthInterval(double degrees) {
return Angle.toPeriodicInterval(degrees, -180d, 180d, 360d);
}
/**
* Constructs an Azimuth object from an angle value in degrees.
* @param degrees Azimuth value in degrees.
* @return Azimuth object.
*/
public static Azimuth fromDegrees(double degrees) {
//Ensure the angle is in the correct interval.
double degreesInInterval = Azimuth.toAzimuthInterval(degrees);
Azimuth azimuth = new Azimuth(degreesInInterval);
return azimuth;
}
/**
* Constructs an Azimuth object from an angle value in radians.
* @param radians Azimuth value in radians.
* @return Azimuth object.
*/
public static Azimuth fromRadians(double radians) {
return Azimuth.fromDegrees(Math.toDegrees(radians));
}
/**
* Constructs an Azimuth object from a Bearing object.
* @param bearing Bearing object to read value from.
* @return Azimuth object.
*/
public static Azimuth fromBearing(Bearing bearing) {
return Azimuth.fromDegrees(bearing.degrees());
}
}

@ -0,0 +1,66 @@
package seng302.Model;
/**
* Represents a bearing. Also known as a heading.
* If treated as an absolute bearing this is the angle between north and a target point.
* If treated as a relative bearing, this is the angle between from one target point to the other.
* Has the interval [0, 360) degrees, and clockwise is positive.
*/
public class Bearing extends Angle {
/**
* Ctor.
* This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct a Bearing object.
*
* @param degrees The value, in degrees, to initialize this Bearing object with.
*/
protected Bearing(double degrees) {
super(degrees);
}
/**
* Converts an angle in degrees into an angle in degrees in the correct interval for a bearing - [0, 360).
* E.g., converts -183 to 177, or converts 425 to 65.
* @param degrees Degree value to convert.
* @return Degree value in interval [0, 360).
*/
public static double toBearingInterval(double degrees) {
return Angle.toPeriodicInterval(degrees, -0d, 360d, 360d);
}
/**
* Constructs a Bearing object from an angle value in degrees.
* @param degrees Bearing value in degrees.
* @return Bearing object.
*/
public static Bearing fromDegrees(double degrees) {
//Ensure the angle is in the correct interval.
double degreesInInterval = Bearing.toBearingInterval(degrees);
Bearing bearing = new Bearing(degreesInInterval);
return bearing;
}
/**
* Constructs a Bearing object from an angle value in radians.
* @param radians Bearing value in radians.
* @return Bearing object.
*/
public static Bearing fromRadians(double radians) {
return Bearing.fromDegrees(Math.toDegrees(radians));
}
/**
* Constructs a Bearing object from an Azimuth object.
* @param azimuth Azimuth object to read value from.
* @return Bearing object.
*/
public static Bearing fromAzimuth(Azimuth azimuth) {
return Bearing.fromDegrees(azimuth.degrees());
}
}

@ -1,154 +1,445 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import seng302.Constants;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
/**
* Created by esa46 on 1/05/17.
* Boat Model that is used to store information on the boats that are running in the race.
*/
public class Boat {
/**
* The name of the boat/team.
*/
private String name;
private double velocity;
private double scaledVelocity;
/**
* The current speed of the boat, in knots.
* TODO knots
*/
private double currentSpeed;
/**
* The current bearing/heading of the boat.
*/
private Bearing bearing;
/**
* The current position of the boat.
*/
private GPSCoordinate currentPosition;
/**
* The country or team abbreviation of the boat.
*/
private String country;
/**
* The source ID of the boat.
* This uniquely identifies an entity during a race.
*/
private int sourceID;
/**
* The leg of the race that the boat is currently on.
*/
private Leg currentLeg;
/**
* The distance, in meters, that the boat has travelled in the current leg.
* TODO meters
*/
private double distanceTravelledInLeg;
private GPSCoordinate currentPosition;
/**
* The time, in milliseconds, that has elapsed during the current leg.
* TODO milliseconds
*/
private long timeElapsedInCurrentLeg;
/**
* The timestamp, in milliseconds, of when the boat finished the race.
* Is -1 if it hasn't finished.
* TODO milliseconds
*/
private long timeFinished = -1;
private boolean started = false;
private double heading;
/**
* Boat initialiser which keeps all of the information of the boat.
* The current status of the boat.
*/
private BoatStatusEnum status;
/**
* This stores a boat's polars table.
* Can be used to calculate VMG.
*/
private Polars polars;
/**
* This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds.
* TODO milliseconds
*/
private long timeSinceTackChange = 0;
/**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
*
* @param sourceID id of boat
* @param name Name of the Boat.
* @param country nam abbreviation
* @param sourceID The id of the boat
* @param name The name of the Boat.
* @param country The abbreviation or country code for the boat.
* @param polars The polars table to use for this boat.
*/
public Boat(int sourceID, String name, String country) {
this.country = this.country;
public Boat(int sourceID, String name, String country, Polars polars) {
this.country = country;
this.name = name;
this.sourceID = sourceID;
this.polars = polars;
this.bearing = Bearing.fromDegrees(0d);
this.status = BoatStatusEnum.UNDEFINED;
}
/**
* Calculates the azimuth of the travel via map coordinates of the raceMarkers
*
* @return the direction that the boat is heading towards in degrees (-180 to 180).
* Calculate the bearing of the boat to its next marker.
* @return The bearing to the next marker.
*/
public double calculateAzimuth() {
public Bearing calculateBearingToNextMarker() {
GeodeticCalculator calc = new GeodeticCalculator();
GPSCoordinate start = currentLeg.getStartCompoundMark().getAverageGPSCoordinate();
GPSCoordinate end = currentLeg.getEndCompoundMark().getAverageGPSCoordinate();
//Get the start and end points.
GPSCoordinate currentPosition = this.getCurrentPosition();
GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();
calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude());
calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude());
//Calculate bearing.
Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition);
return calc.getAzimuth();
return bearing;
}
/**
* Calculate the heding depending on the calculated azimuth value
* @return The azimuth value which is greater than 0
* Calculates the distance between the boat and its target marker in nautical miles.
* @return The distance (in nautical miles) between the boat and its target marker.
*/
public double calculateHeading() {
double azimuth = this.calculateAzimuth();
public double calculateDistanceToNextMarker() {
//Get start and end markers.
GPSCoordinate startPosition = this.getCurrentPosition();
if (azimuth >= 0) {
return azimuth;
} else {
return azimuth + 360;
//When boats finish, their "current leg" doesn't have an end marker.
if (this.getCurrentLeg().getEndCompoundMark() == null) {
return 0d;
}
GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();
//Calculate distance.
double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker);
return distanceNauticalMiles;
}
/**
* Returns the name of the boat/team.
* @return Name of the boat/team.
*/
public String getName() {
return name;
}
/**
* Sets the name of the boat/team.
* @param name Name of the boat/team.
*/
public void setName(String name) {
this.name = name;
}
public double getVelocity() {
return velocity;
}
public void setVelocity(double velocity) {
this.velocity = velocity;
/**
* Returns the current speed of the boat, in knots.
* @return The current speed of the boat, in knots.
*/
public double getCurrentSpeed() {
return currentSpeed;
}
public double getScaledVelocity() {
return scaledVelocity;
/**
* Sets the speed of the boat, in knots.
* @param currentSpeed The new speed of the boat, in knots.
*/
public void setCurrentSpeed(double currentSpeed) {
this.currentSpeed = currentSpeed;
}
public void setScaledVelocity(double scaledVelocity) {
this.scaledVelocity = scaledVelocity;
}
/**
* Gets the country/team abbreviation of the boat.
* @return The country/team abbreviation of the boat.
*/
public String getCountry() {
return country;
}
/**
* Sets the country/team abbreviation of the boat.
* @param country The new country/team abbreviation for the boat.
*/
public void setCountry(String country) {
this.country = country;
}
/**
* Returns the source ID of the boat.
* @return The source ID of the boat.
*/
public int getSourceID() {
return sourceID;
}
/**
* Sets the source ID of the boat.
* @param sourceID The new source ID for the boat.
*/
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
/**
* Returns the current leg of the race the boat is in.
* @return The current leg of the race the boat is in.
*/
public Leg getCurrentLeg() {
return currentLeg;
}
/**
* Sets the current leg of the race the boat is in.
* Clears time elapsed in current leg and distance travelled in current leg.
* @param currentLeg The new leg of the race the boat is in.
*/
public void setCurrentLeg(Leg currentLeg) {
this.currentLeg = currentLeg;
this.setTimeElapsedInCurrentLeg(0);
this.setDistanceTravelledInLeg(0);
}
/**
* Returns the distance, in meters, the boat has travelled in the current leg.
* @return The distance, in meters, the boat has travelled in the current leg.
*/
public double getDistanceTravelledInLeg() {
return distanceTravelledInLeg;
}
/**
* Sets the distance, in meters, the boat has travelled in the current leg.
* @param distanceTravelledInLeg The distance, in meters, the boat has travelled in the current leg.
*/
public void setDistanceTravelledInLeg(double distanceTravelledInLeg) {
this.distanceTravelledInLeg = distanceTravelledInLeg;
}
/**
* Returns the current position of the boat.
* @return The current position of the boat.
*/
public GPSCoordinate getCurrentPosition() {
return currentPosition;
}
/**
* Sets the current position of the boat.
* @param currentPosition The new position for the boat.
*/
public void setCurrentPosition(GPSCoordinate currentPosition) {
this.currentPosition = currentPosition;
}
/**
* Gets the timestamp, in milliseconds, at which the boat finished the race.
* @return The timestamp, in milliseconds, at which the boat finished the race.
*/
public long getTimeFinished() {
return timeFinished;
}
/**
* Sets the timestamp, in milliseconds, at which the boat finished the race.
* @param timeFinished The timestamp, in milliseconds, at which the boat finished the race.
*/
public void setTimeFinished(long timeFinished) {
this.timeFinished = timeFinished;
}
public boolean isStarted() {
return started;
/**
* Returns the current bearing of the boat.
* @return The current bearing of the boat.
*/
public Bearing getBearing() {
return bearing;
}
public void setStarted(boolean started) {
this.started = started;
/**
* Sets the current bearing of the boat.
* @param bearing The new bearing of the boat.
*/
public void setBearing(Bearing bearing) {
this.bearing = bearing;
}
public double getHeading() {
return heading;
/**
* Returns the polars table for this boat.
* @return The polars table for this boat.
*/
public Polars getPolars() {
return polars;
}
public void setHeading(double heading) {
this.heading = heading;
/**
* Sets the polars table for this boat.
* @param polars The new polars table for this boat.
*/
public void setPolars(Polars polars) {
this.polars = polars;
}
/**
* Returns the time since the boat changed its tack, in milliseconds.
* @return Time since the boat changed its tack, in milliseconds.
*/
public long getTimeSinceTackChange() {
return timeSinceTackChange;
}
/**
* Sets the time since the boat changed it's tack, in milliseconds.
* @param timeSinceTackChange Time since the boat changed its tack, in milliseconds.
*/
public void setTimeSinceTackChange(long timeSinceTackChange) {
this.timeSinceTackChange = timeSinceTackChange;
}
/**
* Returns the time, in milliseconds, that has elapsed since the boat started the current leg.
* @return The time, in milliseconds, that has elapsed since the boat started the current leg.
*/
public long getTimeElapsedInCurrentLeg() {
return timeElapsedInCurrentLeg;
}
/**
* Sets the time, in milliseconds, that has elapsed since the boat started the current leg.
* @param timeElapsedInCurrentLeg The new time, in milliseconds, that has elapsed since the boat started the current leg.
*/
public void setTimeElapsedInCurrentLeg(long timeElapsedInCurrentLeg) {
this.timeElapsedInCurrentLeg = timeElapsedInCurrentLeg;
}
/**
* Returns the status of the boat.
* @return The sttus of the boat.
*/
public BoatStatusEnum getStatus() {
return status;
}
/**
* Sets the status of the boat.
* @param status The new status of the boat.
*/
public void setStatus(BoatStatusEnum status) {
this.status = status;
}
/**
* Moves the boat meters forward in the direction that it is facing
* @param meters The number of meters to move forward.
* @param milliseconds The number of milliseconds to advance the boat's timers by.
*/
public void moveForwards(double meters, long milliseconds) {
//Update the boat's time since last tack.
this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds);
//Update the time into the current leg.
this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds);
//Update the distance into the current leg.
this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters);
//Updates the current position of the boat.
GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing()));
this.setCurrentPosition(newPosition);
}
/**
* Sets the boats speed and bearing to those in the given VMG.
* @param newVMG The new VMG to use for the boat - contains speed and bearing.
*/
public void setVMG(VMG newVMG) {
this.setBearing(newVMG.getBearing());
this.setCurrentSpeed(newVMG.getSpeed());
this.setTimeSinceTackChange(0);
}
/**
* Calculates the number of nautical miles the boat will travel in a given time slice.
* E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles.
* @param timeSlice The timeslice to use.
* @return The distance travelled, in nautical miles, over the given timeslice.
*/
public double calculateNauticalMilesTravelled(long timeSlice) {
//The proportion of one hour the current timeslice is.
//This will be a low fractional number, so we need to go from long -> double.
double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds;
//Calculates the distance travelled, in nautical miles, in the current timeslice.
//distanceTravelledNM = speed (nm p hr) * time taken to update loop
double distanceTravelledNM = this.getCurrentSpeed() * hourProportion;
return distanceTravelledNM;
}
/**
* Calculates the number of meters the boat will travel in a given time slice.
* E.g., in 53 milliseconds a boat may travel 0.02 meters.
* @param timeSlice The timeslice to use.
* @return The distance travelled, in meters, over the given timeslice.
*/
public double calculateMetersTravelled(long timeSlice) {
//Calculate the distance travelled, in nautical miles.
double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice);
//Convert to meters.
double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion;
return distanceTravelledMeters;
}
}

@ -1,61 +1,110 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import java.awt.geom.Point2D;
/**
* Created by esa46 on 29/03/17.
* Represents a compound mark - that is, either one or two individual marks which form a single compound mark.
*/
public class CompoundMark extends Marker{
public class CompoundMark {
private GPSCoordinate averageGPSCoordinate;
/**
* The first mark in the compound mark.
*/
private Mark mark1;
private Mark mark2 = null;
/**
* The second mark in the compound mark.
*/
private Mark mark2;
/**
* The average coordinate of the compound mark.
*/
private GPSCoordinate averageGPSCoordinate;
/**
* Constructs a compound mark from a single mark.
* @param mark1 The individual mark that comprises this compound mark.
*/
public CompoundMark(Mark mark1) {
super(mark1.getPosition());
this.mark1 = mark1;
this.averageGPSCoordinate = calculateAverage();
}
/**
* Constructs a compound mark from a pair of marks.
* @param mark1 The first individual mark that comprises this compound mark.
* @param mark2 The second individual mark that comprises this compound mark.
*/
public CompoundMark(Mark mark1, Mark mark2) {
super(mark1.getPosition(), mark2.getPosition());
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
}
public Mark getMark1Source() { return mark1; }
public Mark getMark2Source() { return mark2; }
/**
* Returns the first mark of the compound mark.
* @return The first mark of the compound mark.
*/
public Mark getMark1() {
return mark1;
}
/**
* Returns the second mark of the compound mark.
* @return The second mark of the compound mark.
*/
public Mark getMark2() {
return mark2;
}
public GPSCoordinate getMark1() {
/**
* Returns the position of the first mark in the compound mark.
* @return The position of the first mark in the compound mark.
*/
public GPSCoordinate getMark1Position() {
return mark1.getPosition();
}
public GPSCoordinate getMark2() {
/**
* Returns the position of the second mark in the compound mark.
* @return The position of the second mark in the compound mark.
*/
public GPSCoordinate getMark2Position() {
return mark2.getPosition();
}
/**
* Returns the average coordinate of the compound mark.
* @return The average coordinate of the compound mark.
*/
public GPSCoordinate getAverageGPSCoordinate() {
return averageGPSCoordinate;
}
/**
* Calculates the average coordinate of the compound mark.
* @return The average coordinate of the compound mark.
*/
private GPSCoordinate calculateAverage() {
if(mark2 != null) {
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude());
calc.setDestinationGeographicPoint(mark2.getPosition().getLongitude(), mark2.getPosition().getLatitude());
double azimuth = calc.getAzimuth();
double distance = calc.getOrthodromicDistance();
GeodeticCalculator middleCalc = new GeodeticCalculator();
middleCalc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude());
middleCalc.setDirection(azimuth, distance / 2);
Point2D middlePoint = middleCalc.getDestinationGeographicPoint();
return new GPSCoordinate(middlePoint.getY(), middlePoint.getX());
} else return mark1.getPosition();
//If the compound mark only contains one mark, the average is simply the first mark's position.
if (this.mark2 == null) {
return this.getMark1Position();
}
//Otherwise, calculate the average of both marks.
GPSCoordinate averageCoordinate = GPSCoordinate.calculateAverageCoordinate(this.getMark1Position(), this.getMark2Position());
return averageCoordinate;
}
}

@ -14,20 +14,22 @@ import java.time.format.DateTimeFormatter;
/**
* Created by esa46 on 21/04/17.
* A Race Event, this holds all of the race's information as well as handling the connection to its clients.
*/
public class Event {
String raceXML;
String regattaXML;
String boatXML;
Polars boatPolars;
MockOutput mockOutput;
public Event(String raceXML, String regattaXML, String boatXML) {
public Event(String raceXML, String regattaXML, String boatXML, Polars boatPolars) {
this.raceXML = getRaceXMLAtCurrentTime(raceXML);
this.boatXML = boatXML;
this.regattaXML = regattaXML;
this.boatPolars = boatPolars;
try {
mockOutput = new MockOutput();
new Thread(mockOutput).start();
@ -42,7 +44,7 @@ public class Event {
public void start() {
try {
sendXMLs();
Race newRace = new Race(new RaceXMLReader(this.raceXML, new BoatXMLReader(boatXML)), mockOutput);
Race newRace = new Race(new RaceXMLReader(this.raceXML, new BoatXMLReader(boatXML, this.boatPolars)), mockOutput);
new Thread((newRace)).start();
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {

@ -1,18 +1,34 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import org.opengis.geometry.DirectPosition;
import seng302.Constants;
import java.awt.geom.Point2D;
import java.util.Comparator;
import java.util.List;
/**
* GPS Coordinate for the world map.
* GPS Coordinate for the world map, containing a longitude and latitude.
* Created by esa46 on 15/03/17.
*/
public class GPSCoordinate {
/**
* The latitude of the coordinate.
*/
private double latitude;
/**
* The longitude of the coordinate.
*/
private double longitude;
/**
* Constructor Method
*
* @param latitude latitude the coordinate is located at.
* Constructs a GPSCoordinate from a latitude and longitude value.
* @param latitude Latitude the coordinate is located at.
* @param longitude Longitude that the coordinate is located at.
*/
public GPSCoordinate(double latitude, double longitude) {
@ -20,6 +36,8 @@ public class GPSCoordinate {
this.longitude = longitude;
}
/**
* Gets the Latitude that the Coordinate is at.
*
@ -68,5 +86,211 @@ public class GPSCoordinate {
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
/**
* Calculates min and max values and passed it to calculate if coordinate is in the boundary
* @param coordinate coordinate of interest
* @param boundary List of points which make a boundry
* @return true if coordinate is in the boundary
*/
public static boolean isInsideBoundary(GPSCoordinate coordinate, List<GPSCoordinate> boundary) {
int length = boundary.size();
boolean inside = false;
// Check if inside using ray casting algorithm
for (int i = 0, j = length - 1; i < length; j = i++) {
if (intersects(boundary.get(i), boundary.get(j), coordinate)) {
inside = !inside;
}
}
return inside;
}
/**
* Calculates if the coordinate is in the boundary
* @param coordinate coordinate of interest
* @param boundary List of points which make a boundary
* @param minValues max GPS
* @param maxValues min GPS
* @return true if coordinate is in the boundary
*/
public static boolean isInsideBoundary(GPSCoordinate coordinate, List<GPSCoordinate> boundary, GPSCoordinate minValues, GPSCoordinate maxValues) {
double minLat = minValues.getLatitude();
double minLon = minValues.getLongitude();
double maxLat = maxValues.getLatitude();
double maxLon = maxValues.getLongitude();
double coordinateLat = coordinate.getLatitude();
double coordinateLon = coordinate.getLongitude();
// End computation early
if (coordinateLat <= minLat || coordinateLat >= maxLat || coordinateLon <= minLon || coordinateLon >= maxLon) {
return false;
} else {
return isInsideBoundary(coordinate, boundary);
}
}
/**
* Helper function to find if a point is in a boundary
* @param boundaryA The first coordinate of the boundary.
* @param boundaryB The second coordinate of the bounary.
* @param coordinate The coordinate to test.
* @return true if a line from the point intersects the two boundary points
*/
private static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) {
double boundaryALat = boundaryA.getLatitude();
double boundaryALon = boundaryA.getLongitude();
double boundaryBLat = boundaryB.getLatitude();
double boundaryBLon = boundaryB.getLongitude();
double coordinateLat = coordinate.getLatitude();
double coordinateLon = coordinate.getLongitude();
if (boundaryALat > boundaryBLat) {
return intersects(boundaryB, boundaryA, coordinate);
}
if (coordinateLat == boundaryALat || coordinateLat == boundaryBLat) {
// Move coordinate off intersection line
coordinateLat += 1e-9;
}
if (coordinateLat > boundaryBLat || coordinateLat < boundaryALat || coordinateLon > Double.max(boundaryALon, boundaryBLon)) {
return false;
}
if (coordinateLon < Double.min(boundaryALon, boundaryBLon)) {
return true;
}
double aRatio = (coordinateLat - boundaryALat) / (coordinateLon - boundaryALon);
double bRatio = (coordinateLat - boundaryBLat) / (coordinateLon - boundaryBLon);
return aRatio >= bRatio;
}
/**
* Calculates the azimuth between two points.
* @param start The starting point.
* @param end The ending point.
* @return The azimuth from the start point to the end point.
*/
public static Azimuth calculateAzimuth(GPSCoordinate start, GPSCoordinate end) {
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude());
calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude());
return Azimuth.fromDegrees(calc.getAzimuth());
}
/**
* Calculates the bearing between two points.
* @param start The starting point.
* @param end The ending point.
* @return The Bearing from the start point to the end point.
*/
public static Bearing calculateBearing(GPSCoordinate start, GPSCoordinate end) {
//Calculates the azimuth between the two points.
Azimuth azimuth = GPSCoordinate.calculateAzimuth(start, end);
//And converts it into a bearing.
return Bearing.fromAzimuth(azimuth);
}
/**
* Calculates the distance, in meters, between two points.
* Note: all other distance calculations (e.g., nautical miles between two points) should use this, and convert from meters to their desired unit.
* @param start The starting point.
* @param end The ending point.
* @return The distance, in meters, between the two given points.
*/
public static double calculateDistanceMeters(GPSCoordinate start, GPSCoordinate end) {
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude());
calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude());
double distanceMeters = calc.getOrthodromicDistance();
return distanceMeters;
}
/**
* Calculates the distance, in nautical miles, between two points.
* @param start The starting point.
* @param end The ending point.
* @return The distance, in nautical miles, between the two given points.
*/
public static double calculateDistanceNauticalMiles(GPSCoordinate start, GPSCoordinate end) {
//Find distance in meters.
double distanceMeters = GPSCoordinate.calculateDistanceMeters(start, end);
//Convert to nautical miles.
double distanceNauticalMiles = distanceMeters / Constants.NMToMetersConversion;
return distanceNauticalMiles;
}
/**
* Calculates the GPS position an entity will be at, given a starting position, distance (in meters), and an azimuth.
*
* @param oldCoordinates GPS coordinates of the entity's starting position.
* @param distanceMeters The distance in meters.
* @param azimuth The entity's current azimuth.
* @return The entity's new coordinate.
*/
public static GPSCoordinate calculateNewPosition(GPSCoordinate oldCoordinates, double distanceMeters, Azimuth azimuth) {
GeodeticCalculator calc = new GeodeticCalculator();
//Set starting position.
calc.setStartingGeographicPoint(oldCoordinates.getLongitude(), oldCoordinates.getLatitude());
//Set direction.
calc.setDirection(azimuth.degrees(), distanceMeters);
//Get the destination.
Point2D destinationPoint = calc.getDestinationGeographicPoint();
return new GPSCoordinate(destinationPoint.getY(), destinationPoint.getX());
}
/**
* Calculates the average coordinate of two coordinates.
* @param point1 The first coordinate to average.
* @param point2 The second coordinate to average.
* @return The average of the two coordinates.
*/
public static GPSCoordinate calculateAverageCoordinate(GPSCoordinate point1, GPSCoordinate point2) {
//Calculate distance between them.
double distanceMeters = GPSCoordinate.calculateDistanceMeters(point1, point2);
//We want the average, so get half the distance between points.
distanceMeters = distanceMeters / 2d;
//Calculate azimuth between them.
Azimuth azimuth = GPSCoordinate.calculateAzimuth(point1, point2);
//Calculate the middle coordinate.
GPSCoordinate middleCoordinate = GPSCoordinate.calculateNewPosition(point1, distanceMeters, azimuth);
return middleCoordinate;
}
}

@ -1,102 +1,127 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import seng302.Constants;
/**
* Created by cbt24 on 6/03/17.
* Leg of the race, this is what each part of the race is divided into, from mark to mark.
*/
public class Leg {
private String name; //nautical miles
private double distance;
private Marker startCompoundMark;
private Marker endCompoundMark;
/**
* The name of the leg.
*/
private String name;
/**
* The distance of the leg, in nautical miles.
*/
private double distanceNauticalMiles;
/**
* The starting marker of the leg.
*/
private CompoundMark startCompoundMark;
/**
* The ending marking of the leg.
*/
private CompoundMark endCompoundMark;
/**
* The leg number within a race.
*/
private int legNumber;
/**
* Leg Initialiser
* Constructs a leg from a name, start marker, end marker, and leg number.
*
* @param name Name of the Leg
* @param start marker
* @param end marker
* @param number Leg's position in race
* @param name Name of the Leg.
* @param start Starting marker of the leg.
* @param end Ending marker of the leg.
* @param number Leg's position within the race.
*/
public Leg(String name, Marker start, Marker end, int number) {
public Leg(String name, CompoundMark start, CompoundMark end, int number) {
this.name = name;
this.startCompoundMark = start;
this.endCompoundMark = end;
this.legNumber = number;
calculateDistance();
this.calculateLegDistance();
}
/**
* Construction Method
* Constructs a leg from a name and leg number.
* This is currently used for constructing "dummy" DNF and Finish legs.
*
* @param name Name of the Leg
* @param number leg number
* @param name Name of the leg.
* @param number Leg's position within the race.
*/
public Leg(String name, int number) {
this.name = name;
this.legNumber = number;
}
/**
* Returns the name of the Leg
*
* @return Returns the name of the Leg
* Returns the name of the Leg.
* @return The name of the Leg.
*/
public String getName() {
return name;
}
/**
* Get the distance in nautical miles
*
* @return Returns the total distance of the leg.
* Get the distance in nautical miles.
* @return The total distance of the leg.
*/
public double getDistance() {
return distance;
public double getDistanceNauticalMiles() {
return distanceNauticalMiles;
}
/**
* Returns the leg number that the leg exists in the Race
*
* @return Returns the Leg
* Returns the leg number of the leg within a race.
* @return The leg number of the leg within a race
*/
public int getLegNumber() {
return legNumber;
}
public Marker getStartCompoundMark() {
/**
* Returns the starting marker of the leg.
* @return The starting marker of the leg.
*/
public CompoundMark getStartCompoundMark() {
return startCompoundMark;
}
public void setStartCompoundMark(Marker startCompoundMark) {
this.startCompoundMark = startCompoundMark;
}
public Marker getEndCompoundMark() {
/**
* Returns the ending marker of the leg.
* @return The ending marker of the leg.
*/
public CompoundMark getEndCompoundMark() {
return endCompoundMark;
}
public void setEndCompoundMark(Marker endCompoundMark) {
this.endCompoundMark = endCompoundMark;
}
/**
* Calculates the distance that the legs are in nautical miles (1.852 km).
* Calculates the distance of the leg, in nautical miles.
*/
public void calculateDistance() {
public void calculateLegDistance() {
GeodeticCalculator calc = new GeodeticCalculator();
//Load start and end of leg
//Gets the start and end coordinates.
GPSCoordinate startMarker = this.startCompoundMark.getAverageGPSCoordinate();
GPSCoordinate endMarker = this.endCompoundMark.getAverageGPSCoordinate();
calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude());
calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude());
this.distance = calc.getOrthodromicDistance() / Constants.NMToMetersConversion;
//Calculates the distance between markers.
double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startMarker, endMarker);
this.distanceNauticalMiles = distanceNauticalMiles;
}

@ -1,28 +1,64 @@
package seng302.Model;
/**
* Created by cbt24 on 10/05/17.
* Represents an individual mark.
* Has a source ID, name, and position.
*/
public class Mark {
/**
* The source ID of the mark.
*/
private int sourceID;
/**
* The name of the mark.
*/
private String name;
/**
* The position of the mark.
*/
private GPSCoordinate position;
/**
* Constructs a mark with a given source ID, name, and position.
* @param sourceID The source ID of the mark.
* @param name The name of the mark.
* @param position The position of the mark.
*/
public Mark(int sourceID, String name, GPSCoordinate position) {
this.sourceID = sourceID;
this.name = name;
this.position = position;
}
/**
* Returns the name of the mark.
* @return The name of the mark.
*/
public String getName() {
return name;
}
/**
* Returns the source ID of the mark.
* @return The source ID of the mark.
*/
public int getSourceID() {
return sourceID;
}
/**
* Returns the position of the mark.
* @return The position of the mark.
*/
public GPSCoordinate getPosition() {
return position;
}
}

@ -1,74 +0,0 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import java.awt.geom.Point2D;
/**
* Created by esa46 on 29/03/17.
*/
public class Marker {
private GPSCoordinate averageGPSCoordinate;
private GPSCoordinate mark1;
private GPSCoordinate mark2;
private String name;
private boolean doubleMarker = false;
public Marker(GPSCoordinate mark1) {
this.mark1 = mark1;
this.mark2 = mark1;
this.averageGPSCoordinate = calculateAverage();
}
public Marker(GPSCoordinate mark1, GPSCoordinate mark2) {
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
}
public Marker(String name, GPSCoordinate mark1, GPSCoordinate mark2) {
this.name = name;
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
}
public GPSCoordinate getMark1() {
return mark1;
}
public GPSCoordinate getMark2() {
return mark2;
}
public GPSCoordinate getAverageGPSCoordinate() {
return averageGPSCoordinate;
}
public String getName() {
return name;
}
private GPSCoordinate calculateAverage() {
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude());
calc.setDestinationGeographicPoint(mark2.getLongitude(), mark2.getLatitude());
double azimuth = calc.getAzimuth();
double distance = calc.getOrthodromicDistance();
GeodeticCalculator middleCalc = new GeodeticCalculator();
middleCalc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude());
middleCalc.setDirection(azimuth, distance / 2);
Point2D middlePoint = middleCalc.getDestinationGeographicPoint();
return new GPSCoordinate(middlePoint.getY(), middlePoint.getX());
}
}

@ -0,0 +1,394 @@
package seng302.Model;
import javafx.util.Pair;
import java.util.*;
/**
* Created by hba56 on 10/05/17.
*/
/**
* Encapsulates an entire polar table. Has a function to calculate VMG.
*/
public class Polars {
/**
* Internal store of data. Maps {@literal Pair<windSpeed, windAngle>} to boatSpeed.
*/
private Map<Pair<Double, Bearing>, Double> polarValues = new HashMap<>();
/**
* Stores a list of angles from the polar table - this is used during the calculateVMG function.
* Maps between windSpeed and a list of angles for that wind speed.
*/
private HashMap<Double, List<Bearing>> polarAngles = new HashMap<>();
/**
* Ctor.
*/
public Polars() {
}
/**
* Adds an estimated velocity to the polar table object, for a given (windSpeed, windAngle) pair. That is, stores a mapping from (windSpeed, windAngle) to (boatVelocity).
* Note: an estimate means given a specific wind speed of trueWindSpeed, if the boat travels relativeWindAngle degrees towards the wind, it will move at boatSpeed knots. E.g., trueWindSpeed = 20kn, relativeWindAngle = 45 degrees, boatSpeed = 25kn. If the boat travels towards the wind, plus or minus 45 degrees either side, it will move at 25kn.
* @param trueWindSpeed The true wind speed of the estimate.
* @param relativeWindAngle The relative wind angle between the wind direction + 180 degrees and the boat's direction of the estimate.
* @param boatSpeed The boat speed of the estimate.
*/
public void addEstimate(double trueWindSpeed, Bearing relativeWindAngle, double boatSpeed) {
//We also add the same values with a complementary angle (e.g., angle = 50, complement = 360 - 50 = 310). This is because the data file contains angles [0, 180), but we need [0, 360).
//Create the array to store angles for this wind speed if it doesn't exist.
if (!this.polarAngles.containsKey(trueWindSpeed)) {
this.polarAngles.put(trueWindSpeed, new ArrayList<>());
}
//Add estimate to map.
Pair<Double, Bearing> newKeyPositive = new Pair<>(trueWindSpeed, relativeWindAngle);
this.polarValues.put(newKeyPositive, boatSpeed);
//Get the "negative" bearing - that is, the equivalent bearing between [180, 360).
Bearing negativeBearing = Bearing.fromDegrees(360d - relativeWindAngle.degrees());
//Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0.
if (!negativeBearing.equals(relativeWindAngle)) {
Pair<Double, Bearing> newKeyNegative = new Pair<>(trueWindSpeed, negativeBearing);
this.polarValues.put(newKeyNegative, boatSpeed);
}
//Add angle to angle list. Don't add if it already contains them.
if (!this.polarAngles.get(trueWindSpeed).contains(relativeWindAngle)) {
this.polarAngles.get(trueWindSpeed).add(relativeWindAngle);
}
if (!this.polarAngles.get(trueWindSpeed).contains(negativeBearing)) {
this.polarAngles.get(trueWindSpeed).add(negativeBearing);
}
}
/**
* Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds.
* <br>
* If you don't care about bearing bounds, simply pass in lower = 0, upper = 359.9.
* <br>
* Passing in lower = 0, upper = 0, or lower = 0, upper = 360 will both be treated the same as lower = 0, upper = 359.99999.
* <br><br>
* The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound].
* <br><br>
* If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that {@literal VMGAngle >= lower OR VMGAngle <= upper} (e.g., {@literal [70, 55] means angle >= 70, OR angle =< 55}).
* <br><br>
* Returns a VMG with 0 speed and 0 bearing if there are no VMGs with {@literal velocity > 0} in the acceptable bearing bounds.
* @param trueWindAngle The current true wind angle.
* @param trueWindSpeed The current true wind speed. Knots.
* @param destinationAngle The angle between the boat and the destination point.
* @param bearingLowerBound The lowest bearing (angle) that the boat may travel on.
* @param bearingUpperBound The highest bearing (angle) that the boat may travel on.
* @return The VMG.
*/
public VMG calculateVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing destinationAngle, Bearing bearingLowerBound, Bearing bearingUpperBound) {
//Sorts polar angles.
for (List<Bearing> angles : this.polarAngles.values()) {
angles.sort(null);
}
//If the user enters [0, 360] for their bounds, there won't be any accepted angles, as Bearing(360) turn into Bearing(0) (it has the interval [0, 360)).
//So if both bearing bounds are zero, we assume that the user wanted [0, 360) for the interval.
//So, we give them Bearing(359.99999) as the upper bound.
if ((bearingLowerBound.degrees() == 0d) && (bearingUpperBound.degrees() == 0d)) {
bearingUpperBound = Bearing.fromDegrees(359.99999d);
}
//If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55] the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle =< 55), instead of (VMGAngle >= 70 AND VMGAngle =< 55).
boolean flippedInterval = false;
if (bearingLowerBound.degrees() > bearingUpperBound.degrees()) {
flippedInterval = true;
}
//We need to find the upper and lower wind speeds from the Polars table, for a given current wind speed (e.g., current wind speed is 11kn, therefore lower = 8kn, upper = 12kn).
double polarWindSpeedLowerBound = 0d;
double polarWindSpeedUpperBound = 9999999d;//Start this off with a value larger than any in the Polars table so that it actually works.
//This indicates whether or not we've managed to find a wind speed larger than the current wind speed (the upper bound) in the Polars table (in cases where the current wind speed is larger than any in the file we will never find an upper bound).
boolean foundUpperBoundWindSpeed = false;
boolean foundLowerBoundWindSpeed = false;
for (Pair<Double, Bearing> key : this.polarValues.keySet()) {
//The key is Pair<windSpeed, windAngle>, so pair.key is windSpeed.
double currentPolarSpeed = key.getKey();
//Lower bound.
if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) {
polarWindSpeedLowerBound = currentPolarSpeed;
foundLowerBoundWindSpeed = true;
}
//Upper bound.
if ((currentPolarSpeed < polarWindSpeedUpperBound) && (currentPolarSpeed > trueWindSpeed)) {
polarWindSpeedUpperBound = currentPolarSpeed;
foundUpperBoundWindSpeed = true;
}
}
//Find the angle with the best VMG.
//We need to find the VMGs for both lower and upper bound wind speeds, and interpolate between them.
List<VMG> vmgs = new ArrayList<>();
//Put wind speed bounds we found above into a list.
List<Double> windSpeedBounds = new ArrayList<>(2);
if (foundLowerBoundWindSpeed) {
windSpeedBounds.add(polarWindSpeedLowerBound);
}
if (foundUpperBoundWindSpeed) {
windSpeedBounds.add(polarWindSpeedUpperBound);
}
//Calculate VMG for any wind speed bounds we found.
for (double polarWindSpeed : windSpeedBounds) {
//The list of polar angles for this wind speed.
List<Bearing> polarAngles = this.polarAngles.get(polarWindSpeed);
double bestVMGVelocity = 0;
double bestVMGSpeed = 0;
Bearing bestVMGAngle = Bearing.fromDegrees(0d);
//Calculate the VMG for all possible angles at this wind speed.
for (double angleDegree = 0; angleDegree < 360; angleDegree += 0.1) {
Bearing angle = Bearing.fromDegrees(angleDegree);
//This is the true bearing of the boat, if it went at the angle against the wind.
//For angle < 90 OR angle > 270, it means that the boat is going into the wind (tacking).
//For angle > 90 AND angle < 270, it means that the boat is actually going with the wind (gybing).
double trueBoatBearingDegrees = trueWindAngle.degrees() + angle.degrees() + 180d;
Bearing trueBoatBearing = Bearing.fromDegrees(trueBoatBearingDegrees);
//Check that the boat's bearing would actually be acceptable.
//We continue (skip to next iteration) if it is outside of the interval.
if (flippedInterval) {
//Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true.
if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) & (trueBoatBearing.degrees() > bearingUpperBound.degrees())) {
continue;
}
} else {
//Bearing must be inside [lower, upper].
if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) || (trueBoatBearing.degrees() > bearingUpperBound.degrees())) {
continue;
}
}
//Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them.
//Check which pair of adjacent angles the angle is between.
boolean foundAdjacentAngles = false;
Bearing lowerBound = Bearing.fromDegrees(0d);
Bearing upperBound = Bearing.fromDegrees(0d);
for (int i = 0; i < polarAngles.size() - 1; i++) {
Bearing currentAngle = polarAngles.get(i);
Bearing nextAngle = polarAngles.get(i + 1);
//Check that angle is in interval [lower, upper).
if ((angle.degrees() >= currentAngle.degrees()) && (angle.degrees() < nextAngle.degrees())) {
foundAdjacentAngles = true;
lowerBound = currentAngle;
upperBound = nextAngle;
break;
}
}
if (!foundAdjacentAngles) {
//If we never found the interval, then it must be the "last" interval, between the i'th and 0'th values - angles are periodic, so they wrap around.
lowerBound = polarAngles.get(polarAngles.size() - 1);
upperBound = polarAngles.get(0);
}
//Calculate how far between those points the angle is.
//This is how far between the lower and upper bounds the angle is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper).
double interpolationScalar = calculatePeriodicLinearInterpolateScalar(lowerBound.degrees(), upperBound.degrees(), 360, angle.degrees());
//Get the estimated boat speeds for the lower and upper angles.
Pair<Double, Bearing> lowerKey = new Pair<>(polarWindSpeed, lowerBound);
Pair<Double, Bearing> upperKey = new Pair<>(polarWindSpeed, upperBound);
double lowerSpeed = this.polarValues.get(lowerKey);
double upperSpeed = this.polarValues.get(upperKey);
//Calculate the speed at the interpolated angle.
double interpolatedSpeed = calculateLinearInterpolation(lowerSpeed, upperSpeed, interpolationScalar);
//This is the delta angle between the boat's true bearing and the destination.
double angleBetweenDestAndTackDegrees = trueBoatBearing.degrees() - destinationAngle.degrees();
Bearing angleBetweenDestAndTack = Bearing.fromDegrees(angleBetweenDestAndTackDegrees);
//This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity).
double interpolatedVelocity = Math.cos(angleBetweenDestAndTack.radians()) * interpolatedSpeed;
//Check that the velocity is better, if so, update our best VMG so far, for this wind speed.
if (interpolatedVelocity > bestVMGVelocity) {
bestVMGVelocity = interpolatedVelocity;
bestVMGSpeed = interpolatedSpeed;
bestVMGAngle = trueBoatBearing;
}
}
//Angle iteration loop is finished.
//Create the VMG, and add to list.
VMG vmg = new VMG(bestVMGSpeed, bestVMGAngle);
vmgs.add(vmg);
}
//If we never found an upper bound for the wind speed, we will only have one VMG (for the lower bound), so we can't interpolate/extrapolate anything.
if (!foundUpperBoundWindSpeed) {
return vmgs.get(0);
} else {
//We may have more than one VMG. If we found an upper and lower bound we will have two, if we only found an upper bound (e.g., wind speed = 2kn, upper = 4kn, lower = n/a) we will only have one VMG, but must interpolate between that and a new VMG with 0kn speed.
//We do a simple linear interpolation.
VMG vmg1 = vmgs.get(0);
VMG vmg2;
if (vmgs.size() > 1) {
//If we have a second VMG use it.
vmg2 = vmgs.get(1);
} else {
//Otherwise create a VMG with zero speed, but the same angle. This is what our VMG would be with 0 knot wind speed (boats don't move at 0 knots).
//We also need to swap them around, as vmg1 needs to be the vmg for the lower bound wind speed, and vmg2 is the upper bound wind speed.
vmg2 = vmg1;
vmg1 = new VMG(0, vmg1.getBearing());
}
//Get the interpolation scalar for the current wind speed.
double interpolationScalar = calculateLinearInterpolateScalar(polarWindSpeedLowerBound, polarWindSpeedUpperBound, trueWindSpeed);
//We then calculate the interpolated VMG speed and angle using the interpolation scalar.
double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar);
double interpolatedAngleDegrees = calculateLinearInterpolation(vmg1.getBearing().degrees(), vmg2.getBearing().degrees(), interpolationScalar);
Bearing interpolatedAngle = Bearing.fromDegrees(interpolatedAngleDegrees);
//Return the interpolated VMG.
return new VMG(interpolatedSpeed, interpolatedAngle);
}
}
/**
* Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333.
* Also assumes that the bounds are periodic - e.g., for angles a lower bound of 350deg and upper bound of 5deg is in interval of 15 degrees.
* @param lowerBound The lower bound to interpolate between.
* @param upperBound The upper bound to interpolate between.
* @param value The value that sits between the lower and upper bounds.
* @param period The period of the bounds (e.g., for angles, they have a period of 360 degrees).
* @return The interpolation scalar for the value between two bounds.
*/
public static double calculatePeriodicLinearInterpolateScalar(double lowerBound, double upperBound, double period, double value) {
//This is the "distance" between the value and its lower bound.
//I.e., L----V-----------U
// <----> is lowerDelta.
double lowerDelta = value - lowerBound;
//This is the "distance" between the upper and lower bound.
//I.e., L----V-----------U
// <----------------> is intervalDelta.
//This can potentially be negative if we have, e.g., lower = 340deg, upper = 0deg, delta = -340deg.
double intervalDelta = upperBound - lowerBound;
//If it _is_ negative, modulo it to make it positive.
//E.g., -340deg = +20deg.
while (intervalDelta < 0) {
intervalDelta += period;
}
//This is how far between the lower and upper bounds the value is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper).
double interpolationScalar = lowerDelta / intervalDelta;
return interpolationScalar;
}
/**
* Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333.
* Assumes that the upper bound is larger than the lower bound.
* @param lowerBound The lower bound to interpolate between.
* @param upperBound The upper bound to interpolate between.
* @param value The value that sits between the lower and upper bounds.
* @return The interpolation scalar for the value between two bounds.
*/
public static double calculateLinearInterpolateScalar(double lowerBound, double upperBound, double value) {
//This is the "distance" between the value and its lower bound.
//I.e., L----V-----------U
// <----> is lowerDelta.
double lowerDelta = value - lowerBound;
//This is the "distance" between the upper and lower bound.
//I.e., L----V-----------U
// <----------------> is intervalDelta.
double intervalDelta = upperBound - lowerBound;
//This is how far between the lower and upper bounds the value is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper).
double interpolationScalar = lowerDelta / intervalDelta;
return interpolationScalar;
}
/**
* Does a linear interpolation between two bounds, using an interpolation scalar - i.e., value = lower + (scalar * delta).
* @param lowerBound Lower bound to interpolate from.
* @param upperBound Upper bound to interpolate to.
* @param interpolationScalar Interpolation scalar - the proportion the target value sits between the two bounds.
* @return The interpolated value.
*/
public static double calculateLinearInterpolation(double lowerBound, double upperBound, double interpolationScalar) {
//Get the delta between upper and lower bounds.
double boundDelta = upperBound - lowerBound;
//Calculate the speed at the interpolated angle.
double interpolatedValue = lowerBound + (boundDelta * interpolationScalar);
return interpolatedValue;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,50 @@
package seng302.Model;
/**
* Created by f123 on 10-May-17.
*/
/**
* This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component.
*/
public class VMG {
/**
* Speed component of the VMG, in knots.
*/
private double speed;
/**
* Bearing component of the VMG.
*/
private Bearing bearing;
/**
* Ctor. Creates a VMG object with a given speed and bearing (that is, a velocity).
* @param speed Speed component of the VMG.
* @param bearing Bearing component of the VMG.
*/
public VMG(double speed, Bearing bearing) {
this.speed = speed;
this.bearing = bearing;
}
/**
* Returns the speed component of this VMG object, measured in knots.
* @return Speed component of this VMG object.
*/
public double getSpeed() {
return speed;
}
/**
* Returns the bearing component of this VMG object.
* @return Bearing component of this VMG object.
*/
public Bearing getBearing() {
return bearing;
}
}

@ -0,0 +1,8 @@
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20
20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24
25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30
30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32
1 Tws Twa0 Bsp0 Twa1 Bsp1 UpTwa UpBsp Twa2 Bsp2 Twa3 Bsp3 Twa4 Bsp4 Twa5 Bsp5 Twa6 Bsp6 DnTwa DnBsp Twa7 Bsp7
2 4 0 0 30 4 45 8 60 9 75 10 90 10 115 10 145 10 155 10 175 4
3 8 0 0 30 7 43 10 60 11 75 11 90 11 115 12 145 12 153 12 175 10
4 12 0 0 30 11 43 14.4 60 16 75 20 90 23 115 24 145 23 153 21.6 175 14
5 16 0 0 30 12 42 19.2 60 25 75 27 90 31 115 32 145 30 153 28.8 175 20
6 20 0 0 30 13 41 24 60 29 75 37 90 39 115 40 145 38 153 36 175 24
7 25 0 0 30 15 40 30 60 38 75 44 90 49 115 50 145 49 151 47 175 30
8 30 0 0 30 15 42 30 60 37 75 42 90 48 115 49 145 48 150 46 175 32

@ -5,13 +5,15 @@ import org.junit.Test;
import org.xml.sax.SAXException;
import seng302.Model.Boat;
import seng302.Model.Mark;
import seng302.Model.Polars;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.testng.Assert.*;
import static org.junit.Assert.assertEquals;
//import static org.testng.Assert.*;
/**
* Created by cbt24 on 10/05/17.
@ -24,7 +26,7 @@ public class BoatXMLReaderTest {
@Before
public void setUp() {
try {
boatData = new BoatXMLReader("mockXML/boatTest.xml");
boatData = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
boats = new ArrayList<>(boatData.getBoats().values());
marks = new ArrayList<>(boatData.getMarkerBoats().values());
} catch (ParserConfigurationException e) {
@ -55,4 +57,4 @@ public class BoatXMLReaderTest {
assertEquals(names[i], marks.get(i).getName());
}
}
}
}

@ -0,0 +1,38 @@
package seng302.DataInput;
import org.testng.annotations.Test;
import seng302.Exceptions.InvalidPolarFileException;
import seng302.Model.Polars;
import java.io.File;
import static org.testng.Assert.*;
/**
* Created by f123 on 10-May-17.
*/
public class PolarParserTest {
@Test
/**
* Tests if we can parse a polar data file (stored in a string), and create a polar table.
*/
public void testParse() throws Exception {
try {
//Parse data file.
Polars polars = PolarParser.parse("polars/acc_polars.csv");
//If the parse function didn't through, it worked.
assertTrue(true);
}
catch (InvalidPolarFileException e) {
assertTrue(false);
}
}
}

@ -1,10 +1,10 @@
package seng302.Model;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.*;
import static org.junit.Assert.assertEquals;
/**
* Created by esa46 on 22/03/17.
@ -12,85 +12,92 @@ import static junit.framework.TestCase.*;
public class BoatTest {
private GPSCoordinate ORIGIN_COORDS = new GPSCoordinate(0, 0);
private Boat TEST_BOAT = new Boat(1, "Test", "tt");
private GPSCoordinate ORIGIN_COORDS;
private Boat TEST_BOAT;
@Before
public void setUp() {
ORIGIN_COORDS = new GPSCoordinate(0, 0);
TEST_BOAT = new Boat(1, "Test", "tt", new Polars());
TEST_BOAT.setCurrentPosition(ORIGIN_COORDS);
}
@Test
public void calculateDueNorthAzimuthReturns0() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(50, 0));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateAzimuth(), 0, 1e-8);
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8);
}
@Test
public void calculateDueSouthAzimuthReturns180() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(-50, 0));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateAzimuth(), 180, 1e-8);
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8);
}
@Test
public void calculateDueEastAzimuthReturns90() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(0, 50));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateAzimuth(), 90, 1e-8);
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8);
}
@Test
public void calculateDueWestAzimuthReturnsNegative90() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(0, -50));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateAzimuth(), -90, 1e-8);
assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -90, 1e-8);
}
@Test
public void calculateDueNorthHeadingReturns0() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(50, 0));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateHeading(), 0, 1e-8);
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 0, 1e-8);
}
@Test
public void calculateDueEastHeadingReturns90() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(0, 50));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateHeading(), 90, 1e-8);
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 90, 1e-8);
}
@Test
public void calculateDueSouthHeadingReturns180() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(-50, 0));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateHeading(), 180, 1e-8);
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 180, 1e-8);
}
@Test
public void calculateDueWestHeadingReturns270() {
Marker startMarker = new Marker(ORIGIN_COORDS);
Marker endMarker = new Marker(new GPSCoordinate(0, -50));
CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS));
CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50)));
Leg start = new Leg("Start", startMarker, endMarker, 0);
TEST_BOAT.setCurrentLeg(start);
assertEquals(TEST_BOAT.calculateHeading(), 270, 1e-8);
assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 270, 1e-8);
}
}

@ -3,8 +3,9 @@ package seng302.Model;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.testng.AssertJUnit.assertEquals;
//import static org.testng.AssertJUnit.assertEquals;
/**
* Created by esa46 on 29/03/17.
@ -16,7 +17,7 @@ public class CompoundMarkTest {
@Test
public void averageOfSingleMarkAtOriginIsSingleMark() {
Marker testMark = new Marker(ORIGIN_COORD);
CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD));
assertTrue(testMark.getAverageGPSCoordinate().equals(ORIGIN_COORD));
}
@ -25,7 +26,7 @@ public class CompoundMarkTest {
public void averageOfSingleMarkIsSingleMark() {
GPSCoordinate testCoord = new GPSCoordinate(20, 25);
Marker testMark = new Marker(testCoord);
CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord));
assertTrue(testMark.getAverageGPSCoordinate().equals(testCoord));
}
@ -34,15 +35,16 @@ public class CompoundMarkTest {
public void averageLatOfTwoMarksIsAccurate() {
GPSCoordinate testCoord = new GPSCoordinate(0.001, 0);
Marker testMark = new Marker(ORIGIN_COORD, testCoord);
CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord));
assertEquals(testMark.getAverageGPSCoordinate(), new GPSCoordinate(0.0005, 0));
}
@Test
public void averageLongOfTwoMarksIsAccurate() {
GPSCoordinate testCoord = new GPSCoordinate(0, 10);
Marker testMark = new Marker(ORIGIN_COORD, testCoord);
CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord));
assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(0, 5)));
}
@ -52,7 +54,9 @@ public class CompoundMarkTest {
GPSCoordinate testCoord1 = new GPSCoordinate(0.0, 30);
GPSCoordinate testCoord2 = new GPSCoordinate(0.001, 60);
Marker testMark = new Marker(testCoord1, testCoord2);
CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord1), new Mark(2, "test origin 2", testCoord2));
assertEquals(testMark.getAverageGPSCoordinate().getLatitude(), 0.00051776, 1e-8);
assertEquals(testMark.getAverageGPSCoordinate().getLongitude(), 45.000000, 1e-8);
}

@ -0,0 +1,106 @@
package seng302.Model;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.TestCase.assertTrue;
import static junit.framework.TestCase.assertFalse;
/**
* Created by jjg64 on 11/05/17.
*/
public class GPSCoordinateTest {
List<GPSCoordinate> boundary;
@Before
public void init() {
boundary = new ArrayList<>();
}
/**
* -------
* | |
* | * |
* -------
*/
@Test
public void insideSquareTest() {
boundary.add(new GPSCoordinate(0, 0));
boundary.add(new GPSCoordinate(10, 0));
boundary.add(new GPSCoordinate(10, 10));
boundary.add(new GPSCoordinate(0, 10));
GPSCoordinate coordinate = new GPSCoordinate(2, 8);
boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary);
assertTrue(inside);
}
/**
* -------
* | |
* * | |
* -------
*/
@Test
public void outsideSquareTest() {
boundary.add(new GPSCoordinate(0, 0));
boundary.add(new GPSCoordinate(10, 0));
boundary.add(new GPSCoordinate(10, 10));
boundary.add(new GPSCoordinate(0, 10));
GPSCoordinate coordinate = new GPSCoordinate(-2, 8);
boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary);
assertFalse(inside);
}
@Test
public void insideShapeWithObtuseAnglesTest() {
boundary.add(new GPSCoordinate(0, 0));
boundary.add(new GPSCoordinate(4, 4));
boundary.add(new GPSCoordinate(7, 2));
boundary.add(new GPSCoordinate(9, 5));
boundary.add(new GPSCoordinate(10, 10));
boundary.add(new GPSCoordinate(6, 6));
boundary.add(new GPSCoordinate(2, 10));
GPSCoordinate coordinate = new GPSCoordinate(5, 5);
boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary);
assertTrue(inside);
}
@Test
public void outsideShapeWithObtuseAnglesTest() {
boundary.add(new GPSCoordinate(0, 0));
boundary.add(new GPSCoordinate(4, 4));
boundary.add(new GPSCoordinate(7, 2));
boundary.add(new GPSCoordinate(9, 5));
boundary.add(new GPSCoordinate(10, 10));
boundary.add(new GPSCoordinate(6, 6));
boundary.add(new GPSCoordinate(2, 10));
GPSCoordinate coordinate = new GPSCoordinate(4, 3);
boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary);
assertFalse(inside);
}
/**
* -------
* | |
* * | |
* -------
*/
@Test
public void earlyTerminationTest() {
boundary.add(new GPSCoordinate(0, 0));
boundary.add(new GPSCoordinate(10, 0));
boundary.add(new GPSCoordinate(10, 10));
boundary.add(new GPSCoordinate(0, 10));
GPSCoordinate coordinate = new GPSCoordinate(-2, 8);
boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary, new GPSCoordinate(0, 0), new GPSCoordinate(10, 10));
assertFalse(inside);
}
}

@ -14,7 +14,7 @@ import static junit.framework.TestCase.assertEquals;
*/
public class LegTest {
private Marker ORIGIN_Compound_MARKER = new Marker(new GPSCoordinate(0, 0));
private CompoundMark ORIGIN_COMPOUND_MARKER = new CompoundMark(new Mark(1, "test mark1", new GPSCoordinate(0, 0)));
@Test
public void calculateDistanceHandles5nmNorth() {
@ -22,9 +22,9 @@ public class LegTest {
calc.setStartingGeographicPoint(0, 0);
calc.setDirection(0, 5 * Constants.NMToMetersConversion);
Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0);
assertEquals(test.getDistance(), 5, 1e-8);
CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_COMPOUND_MARKER, endMarker, 0);
assertEquals(test.getDistanceNauticalMiles(), 5, 1e-8);
}
@Test
@ -33,9 +33,9 @@ public class LegTest {
calc.setStartingGeographicPoint(0, 0);
calc.setDirection(90, 12 * Constants.NMToMetersConversion);
Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0);
assertEquals(test.getDistance(), 12, 1e-8);
CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_COMPOUND_MARKER, endMarker, 0);
assertEquals(test.getDistanceNauticalMiles(), 12, 1e-8);
}
@Test
@ -44,9 +44,9 @@ public class LegTest {
calc.setStartingGeographicPoint(0, 0);
calc.setDirection(180, 0.5 * Constants.NMToMetersConversion);
Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0);
assertEquals(test.getDistance(), 0.5, 1e-8);
CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_COMPOUND_MARKER, endMarker, 0);
assertEquals(test.getDistanceNauticalMiles(), 0.5, 1e-8);
}
@Test
@ -55,23 +55,23 @@ public class LegTest {
calc.setStartingGeographicPoint(0, 0);
calc.setDirection(-90, 0.1 * Constants.NMToMetersConversion);
Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0);
assertEquals(test.getDistance(), 0.1, 1e-8);
CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint());
Leg test = new Leg("Test", ORIGIN_COMPOUND_MARKER, endMarker, 0);
assertEquals(test.getDistanceNauticalMiles(), 0.1, 1e-8);
}
@Test
public void calculateDistanceHandlesZeroDifference() {
Leg test = new Leg("Test", ORIGIN_Compound_MARKER, ORIGIN_Compound_MARKER, 0);
assertEquals(test.getDistance(), 0, 1e-8);
Leg test = new Leg("Test", ORIGIN_COMPOUND_MARKER, ORIGIN_COMPOUND_MARKER, 0);
assertEquals(test.getDistanceNauticalMiles(), 0, 1e-8);
}
private Marker getEndMarker(Point2D point) {
private CompoundMark getEndMarker(Point2D point) {
GPSCoordinate coords = new GPSCoordinate(point.getY(), point.getX());
return new Marker(coords);
return new CompoundMark(new Mark(3, "test mark3", coords));
}
}

@ -0,0 +1,261 @@
package seng302.Model;
import org.junit.Before;
import org.junit.Test;
import seng302.DataInput.PolarParser;
import seng302.Exceptions.InvalidPolarFileException;
import static org.testng.Assert.*;
/**
* Created by f123 on 10-May-17.
*/
public class PolarsTest {
private Polars polars = null;
private double angleEpsilon = 2;
private double speedEpsilon = 0.5;
/**
* Creates the Polars object for the tests.
*/
@Before
public void setUp() {
//Read data.
try {
//Parse data file.
polars = PolarParser.parse("polars/acc_polars.csv");
}
catch (InvalidPolarFileException e) {
assertTrue(false);
}
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG1() {
//Test 1.
//This test has a wind speed that is between two values from the table (12kn, 16kn, this is 15.9kn).
Bearing windAngle1 = Bearing.fromDegrees(31.5);
Bearing destAngle1 = Bearing.fromDegrees(65.32);
double windSpeed1 = 15.9;//knots
Bearing vmgAngle1 = Bearing.fromDegrees(72.4);
double vmgSpeed1 = 30.4;
VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9));
Bearing calcVMGAngle1 = calcVMG1.getBearing();
double calcVMGSpeed1 = calcVMG1.getSpeed();
assertEquals(calcVMGAngle1.degrees(), vmgAngle1.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed1, vmgSpeed1, speedEpsilon);
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG2() {
//Test 2.
//This test has a wind speed much larger than any in the table (max from table is 30kn, this is 40kn).
Bearing windAngle2 = Bearing.fromDegrees(200);
Bearing destAngle2 = Bearing.fromDegrees(35);
double windSpeed2 = 40;//knots
Bearing vmgAngle2 = Bearing.fromDegrees(69);
double vmgSpeed2 = 32.8;
VMG calcVMG2 = polars.calculateVMG(windAngle2, windSpeed2, destAngle2, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9));
Bearing calcVMGAngle2 = calcVMG2.getBearing();
double calcVMGSpeed2 = calcVMG2.getSpeed();
assertEquals(calcVMGAngle2.degrees(), vmgAngle2.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon);
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG3() {
//Test 3.
//This test has a wind speed lower than any non-zero values from the table (table has 0kn, 4kn, this is 2kn).
Bearing windAngle3 = Bearing.fromDegrees(345);
Bearing destAngle3 = Bearing.fromDegrees(199);
double windSpeed3 = 2;//knots
Bearing vmgAngle3 = Bearing.fromDegrees(222);
double vmgSpeed3 = 4.4;
VMG calcVMG3 = polars.calculateVMG(windAngle3, windSpeed3, destAngle3, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9));
Bearing calcVMGAngle3 = calcVMG3.getBearing();
double calcVMGSpeed3 = calcVMG3.getSpeed();
assertEquals(calcVMGAngle3.degrees(), vmgAngle3.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon);
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG4() {
//Test 4.
//This test has a wind speed of 0.
Bearing windAngle4 = Bearing.fromDegrees(5);
Bearing destAngle4 = Bearing.fromDegrees(100);
double windSpeed4 = 0;//knots
Bearing vmgAngle4 = Bearing.fromDegrees(100);
double vmgSpeed4 = 0;
VMG calcVMG4 = polars.calculateVMG(windAngle4, windSpeed4, destAngle4, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9));
Bearing calcVMGAngle4 = calcVMG4.getBearing();
double calcVMGSpeed4 = calcVMG4.getSpeed();
assertEquals(calcVMGAngle4.degrees(), vmgAngle4.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon);
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG5() {
//Test 5.
//This test has a bearing bound of [55, 70), which only contains a suboptimal VMG.
Bearing windAngle5 = Bearing.fromDegrees(5);
Bearing destAngle5 = Bearing.fromDegrees(100);
double windSpeed5 = 9;//knots
Bearing vmgAngle5 = Bearing.fromDegrees(70);
double vmgSpeed5 = 15;
Bearing bearingUpperBound5 = Bearing.fromDegrees(70);
Bearing bearingLowerBound5 = Bearing.fromDegrees(55);
VMG calcVMG5 = polars.calculateVMG(windAngle5, windSpeed5, destAngle5, bearingLowerBound5, bearingUpperBound5);
Bearing calcVMGAngle5 = calcVMG5.getBearing();
double calcVMGSpeed5 = calcVMG5.getSpeed();
assertEquals(calcVMGAngle5.degrees(), vmgAngle5.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed5, vmgSpeed5, speedEpsilon);
assertTrue(calcVMGAngle5.degrees() >= bearingLowerBound5.degrees());
assertTrue(calcVMGAngle5.degrees() <= bearingUpperBound5.degrees());
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG6() {
//Test 6.
//This test has a bearing bound of [70, 55), which has a lower bound > upper bound, which is complementary to [55, 70).
Bearing windAngle6 = Bearing.fromDegrees(5);
Bearing destAngle6 = Bearing.fromDegrees(100);
double windSpeed6 = 11;//knots
Bearing vmgAngle6 = Bearing.fromDegrees(92.85);
double vmgSpeed6 = 20.086;
Bearing bearingUpperBound6 = Bearing.fromDegrees(55);
Bearing bearingLowerBound6 = Bearing.fromDegrees(70);
VMG calcVMG6 = polars.calculateVMG(windAngle6, windSpeed6, destAngle6, bearingLowerBound6, bearingUpperBound6);
Bearing calcVMGAngle6 = calcVMG6.getBearing();
double calcVMGSpeed6 = calcVMG6.getSpeed();
assertEquals(calcVMGAngle6.degrees(), vmgAngle6.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed6, vmgSpeed6, speedEpsilon);
if (bearingLowerBound6.degrees() > bearingUpperBound6.degrees()) {
assertTrue((calcVMGAngle6.degrees() >= bearingLowerBound6.degrees()) || (calcVMGAngle6.degrees() <= bearingUpperBound6.degrees()));
} else {
assertTrue(calcVMGAngle6.degrees() >= bearingLowerBound6.degrees());
assertTrue(calcVMGAngle6.degrees() <= bearingUpperBound6.degrees());
}
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG7() {
//Test 7.
//This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340).
Bearing windAngle7 = Bearing.fromDegrees(340);
Bearing destAngle7 = Bearing.fromDegrees(30);
double windSpeed7 = 7;//knots
Bearing vmgAngle7 = Bearing.fromDegrees(5);
double vmgSpeed7 = 11;
Bearing bearingUpperBound7 = Bearing.fromDegrees(5);
Bearing bearingLowerBound7 = Bearing.fromDegrees(340);
VMG calcVMG7 = polars.calculateVMG(windAngle7, windSpeed7, destAngle7, bearingLowerBound7, bearingUpperBound7);
Bearing calcVMGAngle7 = calcVMG7.getBearing();
double calcVMGSpeed7 = calcVMG7.getSpeed();
assertEquals(calcVMGAngle7.degrees(), vmgAngle7.degrees(), angleEpsilon);
assertEquals(calcVMGSpeed7, vmgSpeed7, speedEpsilon);
if (bearingLowerBound7.degrees() > bearingUpperBound7.degrees()) {
assertTrue((calcVMGAngle7.degrees() >= bearingLowerBound7.degrees()) || (calcVMGAngle7.degrees() <= bearingUpperBound7.degrees()));
} else {
assertTrue(calcVMGAngle7.degrees() >= bearingLowerBound7.degrees());
assertTrue(calcVMGAngle7.degrees() <= bearingUpperBound7.degrees());
}
}
/**
* Tests if we can calculate VMG for a variety of values.
*/
@Test
public void testVMG8() {
//Test 8.
//This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). Due to the wind, dest angles, and bearing bounds, it cannot actually find a VMG > 0 (valid VMGs will actually be in the angle interval [10, 190]), so it will return the VMG(angle=0, speed=0).
Bearing windAngle8 = Bearing.fromDegrees(5);
Bearing destAngle8 = Bearing.fromDegrees(100);
double windSpeed8 = 7;//knots
Bearing vmgAngle8 = Bearing.fromDegrees(0);
double vmgSpeed8 = 0;
Bearing bearingUpperBound8 = Bearing.fromDegrees(5);
Bearing bearingLowerBound8 = Bearing.fromDegrees(340);
VMG calcVMG8 = polars.calculateVMG(windAngle8, windSpeed8, destAngle8, bearingLowerBound8, bearingUpperBound8);
Bearing calcVMGAngle8 = calcVMG8.getBearing();
double calcVMGSpeed8 = calcVMG8.getSpeed();
assertEquals(calcVMGAngle8.degrees(), vmgAngle8.degrees(), 0);
assertEquals(calcVMGSpeed8, vmgSpeed8, 0);
}
}

@ -29,9 +29,9 @@ import static org.mockito.Mockito.*;
public class RaceTest{
public static final Marker ORIGIN = new Marker(new GPSCoordinate(0, 0));
public static final Marker THREE_NM_FROM_ORIGIN = new Marker(new GPSCoordinate(0.050246769, 0));
public static final Marker FIFTEEN_NM_FROM_ORIGIN = new Marker(new GPSCoordinate(0.251233845, 0));
public static final CompoundMark ORIGIN = new CompoundMark(new Mark(1, "test origin 1", new GPSCoordinate(0, 0)));
public static final CompoundMark THREE_NM_FROM_ORIGIN = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)));
public static final CompoundMark FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)));
public static ArrayList<Leg> TEST_LEGS = new ArrayList<>();
public static final int START_LEG_DISTANCE = 3;
public static final int MIDDLE_LEG_DISTANCE = 12;
@ -52,7 +52,7 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.initialiseBoats();
@ -71,7 +71,7 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
testRace.countdownTimer.handle(1);
@ -89,15 +89,15 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
assertEquals(testRace.boatsFinished, 1);
assertEquals(testRace.getNumberOfActiveBoats(), 0);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
@ -111,15 +111,15 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
assertEquals(testBoat.getVelocity(), 0, 1e-8);
assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
@ -133,10 +133,10 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
@ -155,15 +155,15 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE);
testRace.checkPosition(testBoat, 1);
assertEquals(testRace.boatsFinished, 0);
assertEquals(testRace.getNumberOfActiveBoats(), 1);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
@ -178,10 +178,10 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml"));
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
testRace.checkPosition(testBoat, 0);
@ -200,7 +200,7 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
@ -219,10 +219,9 @@ public class RaceTest{
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.setDnfChance(0);
assertFalse(testRace.doNotFinish());
@ -237,11 +236,11 @@ public class RaceTest{
public void boatsAreSetToDNF() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.setDnfChance(100);
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
testRace.checkPosition(testBoat, 1);
@ -259,13 +258,13 @@ public class RaceTest{
public void updatePositionIgnoresFinishedBoats() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
testRace.updatePosition(testBoat, 1);
testRace.updatePosition(testBoat, 1, 1);
assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
@ -279,22 +278,39 @@ public class RaceTest{
public void updatePositionChangesBoatPosition() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.startingBoats.get(0);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1);
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
testRace.updatePosition(testBoat, 100);
testRace.updatePosition(testBoat, 100, 100);
assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void windDirectionCorrectValues(){
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.setChangeWind(1);
// testRace.setWindDir(65535);
// testRace.changeWindDir();
// assertEquals(100, testRace.getWind());
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
}
}

@ -0,0 +1,8 @@
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20
20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24
25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30
30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32
1 Tws Twa0 Bsp0 Twa1 Bsp1 UpTwa UpBsp Twa2 Bsp2 Twa3 Bsp3 Twa4 Bsp4 Twa5 Bsp5 Twa6 Bsp6 DnTwa DnBsp Twa7 Bsp7
2 4 0 0 30 4 45 8 60 9 75 10 90 10 115 10 145 10 155 10 175 4
3 8 0 0 30 7 43 10 60 11 75 11 90 11 115 12 145 12 153 12 175 10
4 12 0 0 30 11 43 14.4 60 16 75 20 90 23 115 24 145 23 153 21.6 175 14
5 16 0 0 30 12 42 19.2 60 25 75 27 90 31 115 32 145 30 153 28.8 175 20
6 20 0 0 30 13 41 24 60 29 75 37 90 39 115 40 145 38 153 36 175 24
7 25 0 0 30 15 40 30 60 38 75 44 90 49 115 50 145 49 151 47 175 30
8 30 0 0 30 15 42 30 60 37 75 42 90 48 115 49 145 48 150 46 175 32

@ -5,6 +5,7 @@ import seng302.Networking.Messages.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static seng302.Networking.Utils.ByteConverter.*;
@ -32,7 +33,7 @@ public class RaceVisionByteEncoder {
*/
public static byte[] raceStatus(RaceStatus raceStatus){
ArrayList<BoatStatus> boatStatuses = raceStatus.getBoatStatuses();
List<BoatStatus> boatStatuses = raceStatus.getBoatStatuses();
ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size());
//Version Number 1 bytes

@ -251,7 +251,7 @@ public class BoatLocation extends AC35Data {
* @param angle Angle to convert. double.
* @return short representation of heading.
*/
public static short convertTrueWindAngleShortToDouble(double angle) {
public static short convertTrueWindAngleDoubleToShort(double angle) {
short angleShort = (short) ((angle / 180.0) * 32768.0);

@ -31,10 +31,10 @@ public class BoatStatus {
this.sourceID = sourceID;
this.boatStatus = boatStatusEnum.getValue();
this.legNumber = ByteConverter.intToBytes(legNum)[0];
numPenaltiesAwarded = 0;
numPenaltiesServed = 0;
estTimeAtFinish = 0;
estTimeAtNextMark = 0;
this.numPenaltiesAwarded = 0;
this.numPenaltiesServed = 0;
this.estTimeAtFinish = 0;
this.estTimeAtNextMark = 0;
}

@ -0,0 +1,108 @@
package seng302.Networking.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various statuses a race can have. See AC35 streaming spec, 4.2.
*/
public enum RaceStatusEnum {
NOT_ACTIVE(0),
/**
* Between 3:00 and 1:00 minutes before start.
*/
WARNING(1),
/**
* Less than 1:00 minutes before start.
*/
PREPARATORY(2),
STARTED(3),
/**
* Obsolete.
*/
FINISHED(4),
/**
* Obsolete.
*/
RETIRED(5),
ABANDONED(6),
POSTPONED(7),
TERMINATED(8),
RACE_START_TIME_NOT_SET(9),
/**
* More than 3:00 minutes until start.
*/
PRESTART(10),
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_A_STATUS(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a RaceStatusEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private RaceStatusEnum(int value) {
this.value = (byte) value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
/**
* Stores a mapping between Byte values and RaceStatusEnum values.
*/
private static final Map<Byte, RaceStatusEnum> byteToStatusMap = new HashMap<>();
/**
* Static initialization block. Initializes the byteToStatusMap.
*/
static {
for (RaceStatusEnum type : RaceStatusEnum.values()) {
RaceStatusEnum.byteToStatusMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param raceStatusByte Byte value to convert to a RaceStatusEnum value.
* @return The RaceStatusEnum value which corresponds to the given byte value.
*/
public static RaceStatusEnum fromByte(byte raceStatusByte) {
//Gets the corresponding MessageType from the map.
RaceStatusEnum type = RaceStatusEnum.byteToStatusMap.get(raceStatusByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_STATUS RaceStatusEnum.
return RaceStatusEnum.NOT_A_STATUS;
} else {
//Otherwise, return the RaceStatusEnum.
return type;
}
}
}

@ -0,0 +1,87 @@
package seng302.Networking.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types of races. See AC35 streaming spec, 4.2.
*/
public enum RaceTypeEnum {
/**
* A race between two boats.
*/
MATCH_RACE(1),
/**
* A race between a fleet of boats.
*/
FLEET_RACE(2),
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_A_STATUS(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a RaceTypeEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private RaceTypeEnum(int value) {
this.value = (byte) value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
/**
* Stores a mapping between Byte values and RaceStatusEnum values.
*/
private static final Map<Byte, RaceTypeEnum> byteToStatusMap = new HashMap<>();
/**
* Static initialization block. Initializes the byteToStatusMap.
*/
static {
for (RaceTypeEnum type : RaceTypeEnum.values()) {
RaceTypeEnum.byteToStatusMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param raceTypeEnum Byte value to convert to a RaceTypeEnum value.
* @return The RaceTypeEnum value which corresponds to the given byte value.
*/
public static RaceTypeEnum fromByte(byte raceTypeEnum) {
//Gets the corresponding MessageType from the map.
RaceTypeEnum type = RaceTypeEnum.byteToStatusMap.get(raceTypeEnum);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_STATUS RaceTypeEnum.
return RaceTypeEnum.NOT_A_STATUS;
} else {
//Otherwise, return the RaceTypeEnum.
return type;
}
}
}

@ -3,6 +3,7 @@ package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import java.util.ArrayList;
import java.util.List;
/**
* Created by fwy13 on 25/04/17.
@ -16,10 +17,10 @@ public class RaceStatus extends AC35Data {
private int windDirection;
private int windSpeed;
private int raceType;
private ArrayList<BoatStatus> boatStatuses;
private List<BoatStatus> boatStatuses;
private static final double windDirectionScalar = 360.0 / 32768.0; // 0x8000 / 360
public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, ArrayList<BoatStatus> boatStatuses){
public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List<BoatStatus> boatStatuses){
super(MessageType.RACESTATUS);
this.currentTime = currentTime;
this.raceID = raceID;
@ -74,7 +75,7 @@ public class RaceStatus extends AC35Data {
return raceType;
}
public ArrayList<BoatStatus> getBoatStatuses()
public List<BoatStatus> getBoatStatuses()
{
return boatStatuses;
}

@ -20,6 +20,15 @@ public class XMLMessage extends AC35Data {
public static int XMLTypeRace = 6;
public static int XMLTypeBoat = 7;
/**
* Constructor for an XML Message
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
* @param timeStamp Time received
* @param xmlMsgSubType Type of XML message
* @param sequenceNumber Order that it has arrived in
* @param xmlMsgLength Length of the xml message
* @param xmlMessage XML message
*/
public XMLMessage(int ackNumber, long timeStamp, int xmlMsgSubType, int sequenceNumber, int xmlMsgLength, InputStream xmlMessage){
super(MessageType.XMLMESSAGE);
this.ackNumber = ackNumber;
@ -30,10 +39,18 @@ public class XMLMessage extends AC35Data {
this.xmlMessage = xmlMessage;
}
/**
* Get the XML Message
* @return the XML message as an input stream
*/
public InputStream getXmlMessage() {
return xmlMessage;
}
/**
* Get the type of message
* @return Gets the type of message the XML message is
*/
public int getXmlMsgSubType() {
return xmlMsgSubType;
}

@ -17,12 +17,12 @@
<window id="1">
<content type="file-editors">
<state>
<leaf>
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="RaceXMLReader.java" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="588">
<caret line="101" column="7" lean-forward="true" selection-start-line="101" selection-start-column="7" selection-end-line="101" selection-end-column="7" />
<caret line="101" column="7" lean-forward="false" selection-start-line="101" selection-start-column="7" selection-end-line="101" selection-end-column="7" />
<folding />
</state>
</provider>
@ -31,8 +31,8 @@
<file leaf-file-name="RaceXMLTest.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/RaceXMLTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-167">
<caret line="40" column="58" lean-forward="true" selection-start-line="40" selection-start-column="58" selection-end-line="40" selection-end-column="58" />
<state relative-caret-position="595">
<caret line="40" column="58" lean-forward="false" selection-start-line="40" selection-start-column="58" selection-end-line="40" selection-end-column="58" />
<folding />
</state>
</provider>
@ -48,14 +48,18 @@
<favorites_list name="visualiser" />
</component>
<component name="FileEditorManager">
<leaf>
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="StreamedRaceTest.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Mock/StreamedRaceTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="188">
<caret line="21" column="0" lean-forward="false" selection-start-line="21" selection-start-column="0" selection-end-line="21" selection-end-column="0" />
<state relative-caret-position="153">
<caret line="20" column="0" lean-forward="false" selection-start-line="20" selection-start-column="0" selection-end-line="20" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<marker date="1494744508934" expanded="true" signature="2652:2658" ph="{...}" />
<marker date="1494744508934" expanded="true" signature="2664:2770" ph="/**...*/" />
<marker date="1494744508934" expanded="true" signature="2775:2792" ph="@{...}" />
<marker date="1494744508934" expanded="true" signature="2839:3439" ph="{...}" />
</folding>
</state>
</provider>
@ -65,19 +69,18 @@
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourseXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="191">
<caret line="75" column="41" lean-forward="false" selection-start-line="75" selection-start-column="41" selection-end-line="75" selection-end-column="41" />
<caret line="90" column="0" lean-forward="false" selection-start-line="90" selection-start-column="0" selection-end-line="90" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<element signature="e#4826#4827#0" expanded="true" />
<element signature="e#4865#4866#0" expanded="true" />
<element signature="e#4903#4904#0" expanded="true" />
<element signature="e#4939#4940#0" expanded="true" />
<element signature="e#4969#4970#0" expanded="true" />
<element signature="e#4998#4999#0" expanded="true" />
<element signature="e#5033#5034#0" expanded="true" />
<element signature="e#5064#5065#0" expanded="true" />
<element signature="e#5099#5100#0" expanded="true" />
<element signature="e#5130#5131#0" expanded="true" />
<marker date="1494744508803" expanded="true" signature="8461:8771" ph="{...}" />
<marker date="1494744508803" expanded="true" signature="8808:10139" ph="{...}" />
<marker date="1494744508803" expanded="false" signature="10185:10196" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10212:10218" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10260:10271" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10289:10295" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10341:10352" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10374:10380" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10412:10423" ph=" { " />
</folding>
</state>
</provider>
@ -86,7 +89,7 @@
<file leaf-file-name="BoatInRace.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/BoatInRace.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<state relative-caret-position="136">
<caret line="17" column="13" lean-forward="false" selection-start-line="17" selection-start-column="13" selection-end-line="17" selection-end-column="13" />
<folding />
</state>
@ -96,7 +99,7 @@
<file leaf-file-name="MarkerTest.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/MarkerTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<state relative-caret-position="119">
<caret line="10" column="13" lean-forward="false" selection-start-line="10" selection-start-column="13" selection-end-line="10" selection-end-column="13" />
<folding />
</state>
@ -106,7 +109,7 @@
<file leaf-file-name="Marker.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/Marker.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<state relative-caret-position="119">
<caret line="10" column="21" lean-forward="false" selection-start-line="10" selection-start-column="21" selection-end-line="10" selection-end-column="21" />
<folding />
</state>
@ -116,8 +119,8 @@
<file leaf-file-name="StreamedCourse.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourse.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="205">
<caret line="16" column="53" lean-forward="false" selection-start-line="16" selection-start-column="53" selection-end-line="16" selection-end-column="53" />
<state relative-caret-position="187">
<caret line="16" column="0" lean-forward="false" selection-start-line="16" selection-start-column="0" selection-end-line="16" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
</folding>
@ -128,7 +131,7 @@
<file leaf-file-name="ResizableRaceCanvas.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/ResizableRaceCanvas.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-3834">
<state relative-caret-position="204">
<caret line="22" column="18" lean-forward="false" selection-start-line="22" selection-start-column="18" selection-end-line="22" selection-end-column="18" />
<folding />
</state>
@ -138,8 +141,8 @@
<file leaf-file-name="RaceDataSource.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceDataSource.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="15" column="19" lean-forward="true" selection-start-line="15" selection-start-column="19" selection-end-line="15" selection-end-column="19" />
<state relative-caret-position="153">
<caret line="15" column="19" lean-forward="false" selection-start-line="15" selection-start-column="19" selection-end-line="15" selection-end-column="19" />
<folding />
</state>
</provider>
@ -148,7 +151,7 @@
<file leaf-file-name="RaceController.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Controllers/RaceController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="188">
<state relative-caret-position="1972">
<caret line="135" column="41" lean-forward="false" selection-start-line="135" selection-start-column="41" selection-end-line="135" selection-end-column="41" />
<folding />
</state>
@ -158,7 +161,7 @@
<file leaf-file-name="RaceClock.java" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/RaceClock.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="431">
<state relative-caret-position="408">
<caret line="35" column="29" lean-forward="false" selection-start-line="35" selection-start-column="29" selection-end-line="35" selection-end-column="29" />
<folding />
</state>
@ -211,8 +214,10 @@
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="ProjectFrameBounds">
<option name="width" value="1680" />
<option name="height" value="1023" />
<option name="x" value="-8" />
<option name="y" value="-8" />
<option name="width" value="1936" />
<option name="height" value="1092" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
@ -538,6 +543,18 @@
<recent name="$PROJECT_DIR$/src/test/resources" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager" selected="JUnit.StreamedRaceTest">
<configuration default="false" name="RegattaXMLTest" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea">
@ -676,136 +693,6 @@
<envs />
<method />
</configuration>
<configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
<option name="arquillianRunConfiguration">
<value>
<option name="containerStateName" value="" />
</value>
</option>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" />
<option name="MAIN_CLASS_NAME" />
<option name="METHOD_NAME" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
<configuration default="true" type="ArquillianTestNG" factoryName="">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
<option name="arquillianRunConfiguration">
<value>
<option name="containerStateName" value="" />
</value>
</option>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="SUITE_NAME" />
<option name="PACKAGE_NAME" />
<option name="MAIN_CLASS_NAME" />
<option name="METHOD_NAME" />
<option name="GROUP_NAME" />
<option name="TEST_OBJECT" value="CLASS" />
<option name="VM_PARAMETERS" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" />
<option name="OUTPUT_DIRECTORY" />
<option name="ANNOTATION_TYPE" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<option name="USE_DEFAULT_REPORTERS" value="false" />
<option name="PROPERTIES_FILE" />
<envs />
<properties />
<listeners />
<method />
</configuration>
<configuration default="true" type="Cold Fusion runner description" factoryName="Cold Fusion" custom_browser="" web_path="">
<method />
</configuration>
<configuration default="true" type="CucumberJavaRunConfigurationType" factoryName="Cucumber java">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="myFilePath" />
<option name="GLUE" />
<option name="myNameFilter" />
<option name="myGeneratedName" />
<option name="MAIN_CLASS_NAME" />
<option name="VM_PARAMETERS" />
<option name="PROGRAM_PARAMETERS" />
<option name="WORKING_DIRECTORY" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="ENABLE_SWING_INSPECTOR" value="false" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="" />
<envs />
<method />
</configuration>
<configuration default="true" type="FlashRunConfigurationType" factoryName="Flash App">
<option name="BCName" value="" />
<option name="IOSSimulatorSdkPath" value="" />
<option name="adlOptions" value="" />
<option name="airProgramParameters" value="" />
<option name="appDescriptorForEmulator" value="Android" />
<option name="debugTransport" value="USB" />
<option name="debuggerSdkRaw" value="BC SDK" />
<option name="emulator" value="NexusOne" />
<option name="emulatorAdlOptions" value="" />
<option name="fastPackaging" value="true" />
<option name="fullScreenHeight" value="0" />
<option name="fullScreenWidth" value="0" />
<option name="launchUrl" value="false" />
<option name="launcherParameters">
<LauncherParameters>
<option name="browser" value="a7bb68e0-33c0-4d6f-a81a-aac1fdb870c8" />
<option name="launcherType" value="OSDefault" />
<option name="newPlayerInstance" value="false" />
<option name="playerPath" value="/usr/bin/flashplayerdebugger" />
</LauncherParameters>
</option>
<option name="mobileRunTarget" value="Emulator" />
<option name="moduleName" value="" />
<option name="overriddenMainClass" value="" />
<option name="overriddenOutputFileName" value="" />
<option name="overrideMainClass" value="false" />
<option name="runTrusted" value="true" />
<option name="screenDpi" value="0" />
<option name="screenHeight" value="0" />
<option name="screenWidth" value="0" />
<option name="url" value="http://" />
<option name="usbDebugPort" value="7936" />
<method />
</configuration>
<configuration default="true" type="FlexUnitRunConfigurationType" factoryName="FlexUnit" appDescriptorForEmulator="Android" class_name="" emulatorAdlOptions="" method_name="" package_name="" scope="Class">
<option name="BCName" value="" />
<option name="launcherParameters">
<LauncherParameters>
<option name="browser" value="a7bb68e0-33c0-4d6f-a81a-aac1fdb870c8" />
<option name="launcherType" value="OSDefault" />
<option name="newPlayerInstance" value="false" />
<option name="playerPath" value="/usr/bin/flashplayerdebugger" />
</LauncherParameters>
</option>
<option name="moduleName" value="" />
<option name="trusted" value="true" />
<method />
</configuration>
<configuration default="true" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
@ -822,14 +709,6 @@
</ExternalSystemSettings>
<method />
</configuration>
<configuration default="true" type="GrailsRunConfigurationType" factoryName="Grails">
<setting name="vmparams" value="" />
<setting name="cmdLine" value="run-app" />
<setting name="passParentEnv" value="true" />
<setting name="launchBrowser" value="true" />
<setting name="launchBrowserUrl" value="" />
<method />
</configuration>
<configuration default="true" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
@ -872,15 +751,6 @@
<envs />
<method />
</configuration>
<configuration default="true" type="JavaScriptTestRunnerProtractor" factoryName="Protractor">
<config-file value="" />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
<method />
</configuration>
<configuration default="true" type="JetRunConfigurationType" factoryName="Kotlin">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" />
@ -914,12 +784,6 @@
<option name="PORT" value="5005" />
<method />
</configuration>
<configuration default="true" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
<envs />
<method />
</configuration>
<configuration default="true" type="TestNG" factoryName="TestNG">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="" />
@ -948,22 +812,6 @@
<listeners />
<method />
</configuration>
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
<method />
</configuration>
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
<command value="run-script" />
<scripts />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="osgi.bnd.run" factoryName="Run Launcher">
<method />
</configuration>
<configuration default="true" type="osgi.bnd.run" factoryName="Test Launcher (JUnit)">
<method />
</configuration>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="JUnit.RegattaXMLTest" />
<item index="1" class="java.lang.String" itemvalue="JUnit.StreamedRaceTest" />
@ -1025,13 +873,12 @@
<option name="totallyTimeSpent" value="15127000" />
</component>
<component name="ToolWindowManager">
<frame x="0" y="0" width="1680" height="1023" extended-state="0" />
<frame x="-8" y="-8" width="1936" height="1092" extended-state="0" />
<editor active="true" />
<layout>
<window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" />
<window_info id="Nl-Palette" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32937366" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Palette&#9;" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Image Layers" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Capture Analysis" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
@ -1043,9 +890,7 @@
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Capture Tool" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.24970202" sideWeight="0.5" order="6" side_tool="false" content_ui="combo" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.32937366" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.24947916" sideWeight="0.5" order="6" side_tool="false" content_ui="combo" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" />
<window_info id="UI Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
@ -1053,10 +898,13 @@
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="9" side_tool="false" content_ui="combo" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="9" side_tool="false" content_ui="combo" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32937366" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.32937366" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -1074,19 +922,110 @@
<option name="FILTER_TARGETS" value="false" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/BoatShape.java">
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Mock/StreamedRaceTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="90">
<caret line="5" column="24" lean-forward="true" selection-start-line="5" selection-start-column="24" selection-end-line="5" selection-end-column="24" />
<state relative-caret-position="170">
<caret line="21" column="0" lean-forward="false" selection-start-line="21" selection-start-column="0" selection-end-line="21" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<marker date="1494744508934" expanded="true" signature="2652:2658" ph="{...}" />
<marker date="1494744508934" expanded="true" signature="2664:2770" ph="/**...*/" />
<marker date="1494744508934" expanded="true" signature="2775:2792" ph="@{...}" />
<marker date="1494744508934" expanded="true" signature="2839:3439" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/BoatInRace.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="136">
<caret line="17" column="13" lean-forward="false" selection-start-line="17" selection-start-column="13" selection-end-line="17" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/MarkerTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="119">
<caret line="10" column="13" lean-forward="false" selection-start-line="10" selection-start-column="13" selection-end-line="10" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/Marker.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="119">
<caret line="10" column="21" lean-forward="false" selection-start-line="10" selection-start-column="21" selection-end-line="10" selection-end-column="21" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourse.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="187">
<caret line="16" column="0" lean-forward="true" selection-start-line="16" selection-start-column="0" selection-end-line="16" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/ResizableRaceCanvas.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="204">
<caret line="22" column="18" lean-forward="false" selection-start-line="22" selection-start-column="18" selection-end-line="22" selection-end-column="18" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceDataSource.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="15" column="19" lean-forward="true" selection-start-line="15" selection-start-column="19" selection-end-line="15" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Controllers/RaceController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1972">
<caret line="135" column="41" lean-forward="false" selection-start-line="135" selection-start-column="41" selection-end-line="135" selection-end-column="41" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/RaceClock.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="408">
<caret line="35" column="29" lean-forward="false" selection-start-line="35" selection-start-column="29" selection-end-line="35" selection-end-column="29" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourseXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="952">
<caret line="75" column="0" lean-forward="true" selection-start-line="75" selection-start-column="0" selection-end-line="75" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<marker date="1494744508803" expanded="true" signature="8461:8771" ph="{...}" />
<marker date="1494744508803" expanded="true" signature="8808:10139" ph="{...}" />
<marker date="1494744508803" expanded="false" signature="10185:10196" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10212:10218" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10260:10271" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10289:10295" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10341:10352" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10374:10380" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10412:10423" ph=" { " />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/BoatShape.java" />
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/GraphCoordinate.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="108">
<caret line="6" column="13" lean-forward="false" selection-start-line="6" selection-start-column="13" selection-end-line="6" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
@ -1094,7 +1033,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
@ -1102,7 +1040,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<caret line="8" column="13" lean-forward="false" selection-start-line="8" selection-start-column="13" selection-end-line="8" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
@ -1110,23 +1047,14 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="162">
<caret line="9" column="39" lean-forward="false" selection-start-line="9" selection-start-column="39" selection-end-line="9" selection-end-column="39" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/CompoundMark.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="8" column="36" lean-forward="false" selection-start-line="8" selection-start-column="36" selection-end-line="8" selection-end-column="36" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/CompoundMark.java" />
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/Race.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-3683">
<caret line="27" column="36" lean-forward="true" selection-start-line="27" selection-start-column="36" selection-end-line="27" selection-end-column="36" />
<folding />
</state>
</provider>
</entry>
@ -1134,7 +1062,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="91" column="0" lean-forward="true" selection-start-line="91" selection-start-column="0" selection-end-line="91" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
@ -1142,7 +1069,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="270">
<caret line="15" column="0" lean-forward="false" selection-start-line="15" selection-start-column="0" selection-end-line="15" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
@ -1150,15 +1076,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="18">
<caret line="6" column="13" lean-forward="false" selection-start-line="6" selection-start-column="13" selection-end-line="6" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/RaceXMLTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-167">
<caret line="40" column="58" lean-forward="true" selection-start-line="40" selection-start-column="58" selection-end-line="40" selection-end-column="58" />
<folding />
</state>
</provider>
</entry>
@ -1166,7 +1083,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-144">
<caret line="28" column="34" lean-forward="true" selection-start-line="28" selection-start-column="34" selection-end-line="28" selection-end-column="34" />
<folding />
</state>
</provider>
</entry>
@ -1174,7 +1090,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="90">
<caret line="5" column="27" lean-forward="false" selection-start-line="5" selection-start-column="27" selection-end-line="5" selection-end-column="27" />
<folding />
</state>
</provider>
</entry>
@ -1182,7 +1097,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-1451">
<caret line="16" column="13" lean-forward="false" selection-start-line="16" selection-start-column="13" selection-end-line="16" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
@ -1190,10 +1104,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="187">
<caret line="18" column="88" lean-forward="false" selection-start-line="18" selection-start-column="88" selection-end-line="18" selection-end-column="88" />
<folding>
<element signature="e#644#645#0" expanded="true" />
<element signature="e#712#713#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
@ -1201,7 +1111,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="376">
<caret line="32" column="0" lean-forward="false" selection-start-line="32" selection-start-column="0" selection-end-line="32" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
@ -1209,7 +1118,6 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="8" column="46" lean-forward="true" selection-start-line="8" selection-start-column="46" selection-end-line="8" selection-end-column="46" />
<folding />
</state>
</provider>
</entry>
@ -1217,25 +1125,26 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="414">
<caret line="23" column="0" lean-forward="true" selection-start-line="23" selection-start-column="0" selection-end-line="23" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Mock/StreamedRaceTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="188">
<caret line="21" column="0" lean-forward="false" selection-start-line="21" selection-start-column="0" selection-end-line="21" selection-end-column="0" />
<state relative-caret-position="153">
<caret line="20" column="0" lean-forward="false" selection-start-line="20" selection-start-column="0" selection-end-line="20" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<marker date="1494744508934" expanded="true" signature="2652:2658" ph="{...}" />
<marker date="1494744508934" expanded="true" signature="2664:2770" ph="/**...*/" />
<marker date="1494744508934" expanded="true" signature="2775:2792" ph="@{...}" />
<marker date="1494744508934" expanded="true" signature="2839:3439" ph="{...}" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/MarkerTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<state relative-caret-position="119">
<caret line="10" column="13" lean-forward="false" selection-start-line="10" selection-start-column="13" selection-end-line="10" selection-end-column="13" />
<folding />
</state>
@ -1245,14 +1154,13 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="288">
<caret line="66" column="70" lean-forward="true" selection-start-line="66" selection-start-column="70" selection-end-line="66" selection-end-column="70" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourse.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="205">
<caret line="16" column="53" lean-forward="false" selection-start-line="16" selection-start-column="53" selection-end-line="16" selection-end-column="53" />
<state relative-caret-position="187">
<caret line="16" column="0" lean-forward="false" selection-start-line="16" selection-start-column="0" selection-end-line="16" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
</folding>
@ -1263,14 +1171,13 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-299">
<caret line="5" column="13" lean-forward="false" selection-start-line="5" selection-start-column="13" selection-end-line="5" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceDataSource.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="15" column="19" lean-forward="true" selection-start-line="15" selection-start-column="19" selection-end-line="15" selection-end-column="19" />
<state relative-caret-position="153">
<caret line="15" column="19" lean-forward="false" selection-start-line="15" selection-start-column="19" selection-end-line="15" selection-end-column="19" />
<folding />
</state>
</provider>
@ -1279,13 +1186,12 @@
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="241">
<caret line="43" column="22" lean-forward="false" selection-start-line="43" selection-start-column="22" selection-end-line="43" selection-end-column="22" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/ResizableRaceCanvas.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-3834">
<state relative-caret-position="204">
<caret line="22" column="18" lean-forward="false" selection-start-line="22" selection-start-column="18" selection-end-line="22" selection-end-column="18" />
<folding />
</state>
@ -1293,7 +1199,7 @@
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Controllers/RaceController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="188">
<state relative-caret-position="1972">
<caret line="135" column="41" lean-forward="false" selection-start-line="135" selection-start-column="41" selection-end-line="135" selection-end-column="41" />
<folding />
</state>
@ -1301,31 +1207,22 @@
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/RaceClock.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="431">
<state relative-caret-position="408">
<caret line="35" column="29" lean-forward="false" selection-start-line="35" selection-start-column="29" selection-end-line="35" selection-end-column="29" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="588">
<caret line="101" column="7" lean-forward="true" selection-start-line="101" selection-start-column="7" selection-end-line="101" selection-end-column="7" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/Leg.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<caret line="9" column="13" lean-forward="false" selection-start-line="9" selection-start-column="13" selection-end-line="9" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/Marker.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="126">
<state relative-caret-position="119">
<caret line="10" column="21" lean-forward="false" selection-start-line="10" selection-start-column="21" selection-end-line="10" selection-end-column="21" />
<folding />
</state>
@ -1333,28 +1230,43 @@
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Model/BoatInRace.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<state relative-caret-position="136">
<caret line="17" column="13" lean-forward="false" selection-start-line="17" selection-start-column="13" selection-end-line="17" selection-end-column="13" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/test/java/seng302/Model/RaceXMLTest.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="595">
<caret line="40" column="58" lean-forward="false" selection-start-line="40" selection-start-column="58" selection-end-line="40" selection-end-column="58" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/RaceXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="588">
<caret line="101" column="7" lean-forward="false" selection-start-line="101" selection-start-column="7" selection-end-line="101" selection-end-column="7" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/seng302/Mock/StreamedCourseXMLReader.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="191">
<caret line="75" column="41" lean-forward="false" selection-start-line="75" selection-start-column="41" selection-end-line="75" selection-end-column="41" />
<caret line="90" column="0" lean-forward="false" selection-start-line="90" selection-start-column="0" selection-end-line="90" selection-end-column="0" />
<folding>
<element signature="imports" expanded="true" />
<element signature="e#4826#4827#0" expanded="true" />
<element signature="e#4865#4866#0" expanded="true" />
<element signature="e#4903#4904#0" expanded="true" />
<element signature="e#4939#4940#0" expanded="true" />
<element signature="e#4969#4970#0" expanded="true" />
<element signature="e#4998#4999#0" expanded="true" />
<element signature="e#5033#5034#0" expanded="true" />
<element signature="e#5064#5065#0" expanded="true" />
<element signature="e#5099#5100#0" expanded="true" />
<element signature="e#5130#5131#0" expanded="true" />
<marker date="1494744508803" expanded="true" signature="8461:8771" ph="{...}" />
<marker date="1494744508803" expanded="true" signature="8808:10139" ph="{...}" />
<marker date="1494744508803" expanded="false" signature="10185:10196" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10212:10218" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10260:10271" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10289:10295" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10341:10352" ph=" { " />
<marker date="1494744508803" expanded="false" signature="10374:10380" ph=" }" />
<marker date="1494744508803" expanded="false" signature="10412:10423" ph=" { " />
</folding>
</state>
</provider>

@ -163,4 +163,4 @@
</plugin>
</plugins>
</reporting>
</project>
</project>

@ -0,0 +1,17 @@
package seng302.Controllers;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by Joseph on 22/05/2017.
*/
public class ArrowController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
}
}

@ -16,7 +16,7 @@ import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by cbt24 on 3/05/17.
* Controls the connection that the VIsualiser can connect to.
*/
public class ConnectionController extends Controller {
@FXML

@ -14,7 +14,7 @@ import java.util.ResourceBundle;
/**
* Created by zwu18 on 6/05/17.
* Finish Screen for when the race finishs.
*/
public class FinishController extends Controller {
@ -33,10 +33,10 @@ public class FinishController extends Controller {
@FXML
Label raceWinnerLabel;
/**
* Sets up the finish table
* @param boats Boats to display
*/
private void setFinishTable(ObservableList<Boat> boats){
boatInfoTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
@ -52,6 +52,10 @@ public class FinishController extends Controller {
public void initialize(URL location, ResourceBundle resources){
}
/**
* Display the table
* @param boats boats to display on the table.
*/
public void enterFinish(ObservableList<Boat> boats){
finishWrapper.setVisible(true);
setFinishTable(boats);

@ -12,7 +12,7 @@ import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by fwy13 on 15/03/2017.
* Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage.
*/
public class MainController extends Controller {
@FXML private StartController startController;

@ -1,17 +1,23 @@
package seng302.Controllers;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import seng302.Mock.StreamedRace;
import seng302.Model.*;
import seng302.VisualiserInput;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.*;
/**
* Created by fwy13 on 15/03/2017.
@ -21,26 +27,60 @@ public class RaceController extends Controller {
//user saved data for annotation display
private ArrayList<Boolean> presetAnno;
private Map<String, Boolean> importantAnno;
private Map<String, Boolean> annoShownBeforeHide;
private int buttonChecked;//button currently checked allows the checkboxes to know whether or not to put it's state in history (if not hidden then store)
private int prevBtnChecked;//button to keep track of previous pressed button incase we want to check a checkbox straight from hidden we do not wish for all previous to come on.
private static String nameCheckAnno = "name";
private static String abbrevCheckAnno = "abbrev";
private static String speedCheckAnno = "speed";
private static String pathCheckAnno = "path";
private static String timeCheckAnno = "time";
private static int noBtn = 0;
private static int hideBtn = 1;
private static int showBtn = 2;
private static int partialBtn = 3;
private static int importantBtn = 4;
private ArrayList<Boat> startBoats;
private Integer sparkLineNumber = 0;
private ResizableRaceCanvas raceMap;
private ResizableRaceMap raceBoundaries;
private ToggleGroup annotationGroup;
private ArrayList<String> colours;
private Map<Integer, String> boatColours = new HashMap<>();
private int legNum;
private RaceClock raceClock;
@FXML Pane arrow;
@FXML SplitPane race;
@FXML StackPane arrowPane;
@FXML CheckBox showFPS;
@FXML CheckBox showBoatPath;
@FXML CheckBox showAnnotations;
@FXML Label timer;
@FXML Label FPS;
@FXML Label timeZone;
@FXML CheckBox showName;
@FXML CheckBox showAbbrev;
@FXML CheckBox showSpeed;
@FXML CheckBox showTime;
@FXML Label timer;
@FXML Label FPS;
@FXML Label timeZone;
@FXML Button saveAnno;
@FXML Button showSetAnno;
@FXML TableView<Boat> boatInfoTable;
@FXML TableColumn<Boat, String> boatPlacingColumn;
@FXML TableColumn<Boat, String> boatTeamColumn;
@FXML TableColumn<Boat, String> boatMarkColumn;
@FXML TableColumn<Boat, String> boatSpeedColumn;
@FXML RadioButton hideAnnoRBTN;
@FXML RadioButton showAnnoRBTN;
@FXML RadioButton partialAnnoRBTN;
@FXML RadioButton importantAnnoRBTN;
@FXML LineChart<Number, Number> sparklineChart;
@FXML NumberAxis xAxis;
@FXML NumberAxis yAxis;
/**
* Updates the ResizableRaceCanvas (raceMap) with most recent data
@ -76,6 +116,7 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
//listener for fps
startBoats = new ArrayList<>();
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
@ -83,6 +124,70 @@ public class RaceController extends Controller {
FPS.setVisible(false);
}
});
//adds all radios buttons for annotations to a group
annotationGroup = new ToggleGroup();
hideAnnoRBTN.setToggleGroup(annotationGroup);
showAnnoRBTN.setToggleGroup(annotationGroup);
partialAnnoRBTN.setToggleGroup(annotationGroup);
importantAnnoRBTN.setToggleGroup(annotationGroup);
}
/**
* Creates and sets initial display for Sparkline for race positions.
* A data series for each boat in the race is added.
* Position numbers are displayed.
*
* @param boats boats to display on the sparkline
*/
public void createSparkLine(ObservableList<Boat> boats){
// NOTE: Y axis is in negatives to display correct positions
makeColours();
startBoats.addAll(boats);
mapBoatColours();
// all boats start in 'last' place
for (int i=0; i<startBoats.size(); i++){
XYChart.Series<Number, Number> series = new XYChart.Series();
series.getData().add(new XYChart.Data(0, -startBoats.size()));
series.getData().add(new XYChart.Data(0, -startBoats.size()));
sparklineChart.getData().add(series);
sparklineChart.getData().get(i).getNode().setStyle("-fx-stroke: " +
""+boatColours.get(startBoats.get(i).getSourceID())+";");
}
sparklineChart.setCreateSymbols(false);
// set x axis details
xAxis.setAutoRanging(false);
xAxis.setTickMarkVisible(false);
xAxis.setTickLabelsVisible(false);
xAxis.setMinorTickVisible(false);
xAxis.setUpperBound((startBoats.size()+1)*legNum);
xAxis.setTickUnit((startBoats.size()+1)*legNum);
// set y axis details
yAxis.setLowerBound(-(startBoats.size()+1));
yAxis.setUpperBound(0);
yAxis.setAutoRanging(false);
yAxis.setLabel("Position in Race");
yAxis.setTickUnit(1);
yAxis.setTickMarkVisible(false);
yAxis.setMinorTickVisible(false);
// hide minus number from displaying on axis
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
@Override
public String toString(Number value) {
if ((Double)value == 0.0
|| (Double)value < -startBoats.size()){
return "";
}
else {
return String.format("%7.0f", -value.doubleValue());
}
}
});
}
/**
@ -93,8 +198,11 @@ public class RaceController extends Controller {
* @param raceClock The RaceClock to use for the race's countdown/elapsed duration + timezone.
*/
public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) {
StreamedRace newRace = new StreamedRace(visualiserInput, this);
//newRace.initialiseBoats();
legNum = visualiserInput.getCourse().getLegs().size()-1;
makeArrow();
raceMap = new ResizableRaceCanvas(visualiserInput.getCourse());
raceMap.setMouseTransparent(true);
@ -103,6 +211,7 @@ public class RaceController extends Controller {
//raceMap.setBoats(newRace.getStartingBoats());
raceMap.draw();
raceMap.setVisible(true);
raceMap.setArrow(arrow.getChildren().get(0));
canvasBase.getChildren().add(0, raceMap);
@ -112,34 +221,40 @@ public class RaceController extends Controller {
raceBoundaries.heightProperty().bind(canvasBase.heightProperty());
raceBoundaries.draw();
raceBoundaries.setVisible(true);
canvasBase.getChildren().add(0, raceBoundaries);
race.setVisible(true);
//Initialize save annotation array, fps listener, and annotation listeners
timeZone.setText(raceClock.getTimeZone());
timer.textProperty().bind(raceClock.durationProperty());
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
this.raceClock = raceClock;
raceMap.setRaceClock(raceClock);
StreamedRace newRace = new StreamedRace(visualiserInput, this);
initializeFPS();
initializeAnnotations();
new Thread((newRace)).start();
}
/**
* Finish Race View
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<Boat> boats){
race.setVisible(false);
parent.enterFinish(boats);
}
/**
* Set the value for the race clock label
*
* @param time time that the label will be updated to
*/
public void setTimer(String time) {
//timer.setText(time);
}
/**
* Set the value for the fps label
*
@ -163,35 +278,194 @@ public class RaceController extends Controller {
});
}
/**
* Updates the sparkline to display current boat positions.
* New points are plotted to represent each boat when required.
*
* @param boatsInRace used for current boat positions.
*/
public void updateSparkline(ObservableList<Boat> boatsInRace){
int placingVal = boatsInRace.size();
sparkLineNumber++;
for (int i = boatsInRace.size() - 1; i >= 0; i--){
for (int j = startBoats.size() - 1; j >= 0; j--){
if (boatsInRace.get(i)==startBoats.get(j)){
// when a boat is on its first leg
if (boatsInRace.get(i).getCurrentLeg().getLegNumber()==0){
// adjust boats latest point on X axis
sparklineChart.getData().get(j).getData().get(1)
.setXValue(sparkLineNumber);
}
// when a boat first enters its second leg
else if (boatsInRace.get(i).getCurrentLeg().getLegNumber
()==1 && sparklineChart.getData().get(j).getData
().size()==2){
// adjust boats position from start mark
sparklineChart.getData().get(j).getData().get(1)
.setYValue(-placingVal);
sparklineChart.getData().get(j).getData().get(1)
.setXValue(sparkLineNumber);
sparklineChart.getData().get(j).getData().add(new XYChart.Data<>
(sparkLineNumber, -placingVal));
}
// plot new point for boats current position
else {
sparklineChart.getData().get(j).getData().add
(new XYChart.Data<>(sparkLineNumber, -placingVal));
}
placingVal-=1;
}
}
}
// xAxis.setUpperBound(sparkLineNumber);
// xAxis.setTickUnit(sparkLineNumber);
}
private void makeColours() {
colours = new ArrayList<>(Arrays.asList(
colourToHex(Color.BLUEVIOLET),
colourToHex(Color.BLACK),
colourToHex(Color.RED),
colourToHex(Color.ORANGE),
colourToHex(Color.DARKOLIVEGREEN),
colourToHex(Color.LIMEGREEN),
colourToHex(Color.PURPLE),
colourToHex(Color.DARKGRAY),
colourToHex(Color.YELLOW)
));
}
private void mapBoatColours() {
int currentColour = 0;
for (Boat boat : startBoats) {
if (!boatColours.containsKey(boat.getSourceID())) {
boatColours.put(boat.getSourceID(), colours.get(currentColour));
}
currentColour = (currentColour + 1) % colours.size();
}
}
private void storeCurrentAnnotationState(){
annoShownBeforeHide.put(nameCheckAnno, showName.isSelected());
annoShownBeforeHide.put(abbrevCheckAnno, showAbbrev.isSelected());
annoShownBeforeHide.put(pathCheckAnno, showBoatPath.isSelected());
annoShownBeforeHide.put(speedCheckAnno, showSpeed.isSelected());
annoShownBeforeHide.put(timeCheckAnno, showTime.isSelected());
}
/**
* Set up boat annotations
*/
private void initializeAnnotations() {
presetAnno = new ArrayList<>();
//listener for annotation
showAnnotations.selectedProperty().addListener((ov, old_val, new_val) -> {
raceMap.toggleAnnotations();
raceMap.update();
});
importantAnno = new HashMap<>();
importantAnno.put(nameCheckAnno, false);
importantAnno.put(abbrevCheckAnno, false);
importantAnno.put(pathCheckAnno, false);
importantAnno.put(speedCheckAnno, false);
importantAnno.put(timeCheckAnno, true);
annoShownBeforeHide = new HashMap<>();
annoShownBeforeHide.put(nameCheckAnno, true);
annoShownBeforeHide.put(abbrevCheckAnno, true);
annoShownBeforeHide.put(pathCheckAnno, true);
annoShownBeforeHide.put(speedCheckAnno, true);
annoShownBeforeHide.put(timeCheckAnno, true);
//listener for show name in annotation
showName.selectedProperty().addListener((ov, old_val, new_val) -> {
raceMap.toggleAnnoName();
if (old_val != new_val) {
raceMap.toggleAnnoName();
}
if (buttonChecked != hideBtn) {
//if we are checking the box straight out of hide instead of using the radio buttons
if (prevBtnChecked == hideBtn && buttonChecked != showBtn){
storeCurrentAnnotationState();
} else {
annoShownBeforeHide.put(nameCheckAnno, showName.isSelected());
}
if (buttonChecked == noBtn) {
annotationGroup.selectToggle(showAnnoRBTN);
}
}
raceMap.update();
prevBtnChecked = noBtn;
});
//listener for show abbreviation for annotation
showAbbrev.selectedProperty().addListener((ov, old_val, new_val) -> {
raceMap.toggleAnnoAbbrev();
if (old_val != new_val) {
raceMap.toggleAnnoAbbrev();
}
if (buttonChecked != hideBtn) {
if (prevBtnChecked == hideBtn && buttonChecked != showBtn){
storeCurrentAnnotationState();
} else {
annoShownBeforeHide.put(abbrevCheckAnno, showAbbrev.isSelected());
}
if (buttonChecked == noBtn) {
annotationGroup.selectToggle(showAnnoRBTN);
}
}
raceMap.update();
prevBtnChecked = noBtn;
});
//listener for show boat path for annotation
showBoatPath.selectedProperty().addListener((ov, old_val, new_val) -> {
raceMap.toggleBoatPath();
if (old_val != new_val) {
raceMap.toggleBoatPath();
}
if (buttonChecked != hideBtn) {
if (prevBtnChecked == hideBtn && buttonChecked != showBtn){
storeCurrentAnnotationState();
} else {
annoShownBeforeHide.put(pathCheckAnno, showBoatPath.isSelected());
}
if (buttonChecked == noBtn) {
annotationGroup.selectToggle(showAnnoRBTN);
}
}
raceMap.update();
prevBtnChecked = noBtn;
});
//listener to show speed for annotation
showSpeed.selectedProperty().addListener((ov, old_val, new_val) -> {
raceMap.toggleAnnoSpeed();
if (old_val != new_val) {
raceMap.toggleAnnoSpeed();
}
if (buttonChecked != hideBtn) {
if (prevBtnChecked == hideBtn && buttonChecked != showBtn){
storeCurrentAnnotationState();
} else {
annoShownBeforeHide.put(speedCheckAnno, showSpeed.isSelected());
}
if (buttonChecked == noBtn) {
annotationGroup.selectToggle(showAnnoRBTN);
}
}
raceMap.update();
prevBtnChecked = noBtn;
});
showTime.selectedProperty().addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoTime();
}
if (buttonChecked != hideBtn) {
if (prevBtnChecked == hideBtn && buttonChecked != showBtn){
storeCurrentAnnotationState();
} else {
annoShownBeforeHide.put(timeCheckAnno, showTime.isSelected());
}
if (buttonChecked == noBtn) {
annotationGroup.selectToggle(showAnnoRBTN);
}
}
prevBtnChecked = noBtn;
raceMap.update();
});
//listener to save currently selected annotation
@ -201,16 +475,77 @@ public class RaceController extends Controller {
presetAnno.add(showAbbrev.isSelected());
presetAnno.add(showSpeed.isSelected());
presetAnno.add(showBoatPath.isSelected());
presetAnno.add(showTime.isSelected());
});
//listener for hiding
hideAnnoRBTN.selectedProperty().addListener((ov, old_val, new_val) ->{
buttonChecked = hideBtn;
//raceMap.hideAnnotations();
showName.setSelected(false);
showAbbrev.setSelected(false);
showBoatPath.setSelected(false);
showSpeed.setSelected(false);
showTime.setSelected(false);
annotationGroup.selectToggle(hideAnnoRBTN);
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = hideBtn;
});
//listener to show saved annotation
showSetAnno.setOnAction(event -> {
//listener for showing all annotations
showAnnoRBTN.selectedProperty().addListener((ov, old_val, new_val) ->{
buttonChecked = showBtn;
showName.setSelected(annoShownBeforeHide.get(nameCheckAnno));
showAbbrev.setSelected(annoShownBeforeHide.get(abbrevCheckAnno));
showBoatPath.setSelected(annoShownBeforeHide.get(pathCheckAnno));
showSpeed.setSelected(annoShownBeforeHide.get(speedCheckAnno));
showTime.setSelected(annoShownBeforeHide.get(timeCheckAnno));
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = showBtn;
});
//listener for showing all important
partialAnnoRBTN.selectedProperty().addListener((ov, old_val, new_val) ->{
buttonChecked = partialBtn;
showName.setSelected(false);
showAbbrev.setSelected(true);
showSpeed.setSelected(true);
showBoatPath.setSelected(false);
showTime.setSelected(false);
annotationGroup.selectToggle(partialAnnoRBTN);
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = partialBtn;
});
//listener for showing all important
importantAnnoRBTN.selectedProperty().addListener((ov, old_val, new_val) ->{
buttonChecked = importantBtn;
if (presetAnno.size() > 0) {
showName.setSelected(presetAnno.get(0));
showAbbrev.setSelected(presetAnno.get(1));
showSpeed.setSelected(presetAnno.get(2));
showBoatPath.setSelected(presetAnno.get(3));
showTime.setSelected(presetAnno.get(4));
annotationGroup.selectToggle(importantAnnoRBTN);
raceMap.update();
}
buttonChecked = noBtn;
prevBtnChecked = importantBtn;
});
annotationGroup.selectToggle(showAnnoRBTN);
}
private String colourToHex(Color color) {
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ) );
}
private void makeArrow() {
arrowPane.getChildren().add(arrow);
}
public RaceClock getRaceClock() {
return raceClock;
}
}

@ -26,7 +26,7 @@ import java.util.Observer;
import java.util.ResourceBundle;
/**
* Created by esa46 on 6/04/17.
* Controller to for waiting for the race to start
*/
public class StartController extends Controller implements Observer {
@ -54,11 +54,14 @@ public class StartController extends Controller implements Observer {
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
private boolean hasRaceStarted = false;
//Tracks whether or not a clock has been created and setup, which occurs after receiving enough information.
private boolean hasCreatedClock = false;
/**
* Begins the race with a scale factor of 1
*/
private void startRaceNoScaling() {
while(visualiserInput.getRaceStatus() == null);//TODO probably remove this.
//while(visualiserInput.getRaceStatus() == null);//TODO probably remove this.
countdownTimer();
}
@ -73,6 +76,9 @@ public class StartController extends Controller implements Observer {
return startWrapper;
}
/**
* Initiliases the tables that are to be shown on the pane
*/
private void initialiseTables() {
List<Boat> boats = raceData.getBoats();
ObservableList<Boat> observableBoats = FXCollections.observableArrayList(boats);
@ -104,22 +110,43 @@ public class StartController extends Controller implements Observer {
}.start();
}
/**
* Sets the clock that displays the time of at the current race venue.
*/
private void setRaceClock() {
raceClock = new RaceClock(raceData.getZonedDateTime());
timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
//TEMP REMOVE
//timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
raceClock.run();
}
/**
* Sets the time that the race is going to start.
*/
private void setStartingTime() {
String dateFormat = "'Starting time:' HH:mm dd/MM/YYYY";
Platform.runLater(()-> {
long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime();
raceClock.setStartingTime(raceClock.getLocalTime(utcTime));
raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime()));
timer.textProperty().bind(raceClock.durationProperty());
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
});
}
/**
* set the current time, may be used to update the time on the clock.
*/
private void setCurrentTime() {
Platform.runLater(()->
raceClock.setUTCTime(visualiserInput.getRaceStatus().getCurrentTime())
@ -138,12 +165,15 @@ public class StartController extends Controller implements Observer {
}
if (streamedCourse.hasReadCourse()) {
Platform.runLater(() -> {
setRaceClock();
if(visualiserInput.getRaceStatus() == null){
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
}// TODO - replace with observer on VisualiserInput
setStartingTime();
setCurrentTime();
if (!this.hasCreatedClock) {
this.hasCreatedClock = true;
setRaceClock();
if (visualiserInput.getRaceStatus() == null) {
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
}// TODO - replace with observer on VisualiserInput
setStartingTime();
setCurrentTime();
}
});
}

@ -15,7 +15,7 @@ import java.util.List;
import java.util.Map;
/**
* Created by Joseph on 24/04/2017.
* XML Read that reads the Boats that are goign to participate in the Race
*/
public class BoatXMLReader extends XMLReader {
private final Map<Integer, StreamedBoat> streamedBoatMap = new HashMap<>();
@ -97,6 +97,7 @@ public class BoatXMLReader extends XMLReader {
/**
* Reads the information about one boat
* Ignored values: ShapeID, StoweName, HullNum, Skipper, Type
* @param boat The node to read boat data from.
*/
private void readSingleBoat(Node boat) {
StreamedBoat streamedBoat;
@ -127,14 +128,21 @@ public class BoatXMLReader extends XMLReader {
}
}
/**
* Reads the positional information about a boat
* Ignored values: FlagPosition, MastTop, Z value of GPSposition
* @param sourceID The source ID of the boat.
* @param GPSPosition The relative GPS position of the boat.
*/
private void readBoatPositionInformation(int sourceID, Node GPSPosition) {
// TODO Get relative point before implementing. (GPSposition is based off a relative point).
}
/**
* Sets the participants
* @param participants boats participating the race mapped by their source ID's
*/
public void setParticipants(Map<Integer, StreamedBoat> participants) {
this.participants = participants;
}

@ -52,6 +52,13 @@ public class RegattaXMLReader extends XMLReader {
}
}
/**
* Alternate Constructor that takes in an inputstream instead
* @param xmlString Input stream of the XML
* @throws IOException Error with input
* @throws SAXException Error with XML Format
* @throws ParserConfigurationException Error with XMl contents
*/
public RegattaXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException {
super(xmlString);
read();
@ -66,6 +73,10 @@ public class RegattaXMLReader extends XMLReader {
makeRegatta(attributes);
}
/**
* Extracts the information from the attributes
* @param attributes attributes to extract information form.
*/
private void makeRegatta(Element attributes) {
this.regattaID = Integer.parseInt(getTextValueOfNode(attributes, "RegattaID"));
this.regattaName = getTextValueOfNode(attributes, "RegattaName");

@ -3,7 +3,7 @@ package seng302.Mock;
import seng302.Model.Boat;
/**
* Created by Joseph on 24/04/2017.
* Boats that are been received.
*/
public class StreamedBoat extends Boat {
private final int sourceID;
@ -13,6 +13,12 @@ public class StreamedBoat extends Boat {
this.velocity = 0;
}
/**
* COnstructor
* @param sourceID ID of the BOat
* @param name Name of the Boat
* @param abbrev Boat's name shortened.
*/
public StreamedBoat(int sourceID, String name, String abbrev) {
super(name, abbrev);
this.sourceID = sourceID;
@ -20,9 +26,7 @@ public class StreamedBoat extends Boat {
}
public StreamedBoat(int sourceID) {
super("None", "None");
this.sourceID = sourceID;
this.init();
this(sourceID, "None", "None");
}
public int getSourceID() {

@ -11,7 +11,7 @@ import java.util.List;
import java.util.Observable;
/**
* Created by jjg64 on 21/04/17.
* COurse that the is being received.
*/
public class StreamedCourse extends Observable implements RaceDataSource {
private StreamedCourseXMLReader streamedCourseXMLReader = null;
@ -21,6 +21,10 @@ public class StreamedCourse extends Observable implements RaceDataSource {
public StreamedCourse() {}
/**
* Read and set the new XML that has been received.
* @param boatXMLReader new XMl of the boats.
*/
public void setBoatXMLReader(BoatXMLReader boatXMLReader) {
this.boatXMLReader = boatXMLReader;
if (streamedCourseXMLReader != null && boatXMLReader != null) {
@ -36,6 +40,10 @@ public class StreamedCourse extends Observable implements RaceDataSource {
return streamedCourseXMLReader;
}
/**
* Read and sets the new Course that has been received
* @param streamedCourseXMLReader COurse XML that has been received
*/
public void setStreamedCourseXMLReader(StreamedCourseXMLReader streamedCourseXMLReader) {
this.streamedCourseXMLReader = streamedCourseXMLReader;
if (streamedCourseXMLReader != null && boatXMLReader != null) {
@ -44,6 +52,10 @@ public class StreamedCourse extends Observable implements RaceDataSource {
}
}
/**
* Reads and sets the new Regatta that has been received
* @param regattaXMLReader Regatta XMl that has been received.
*/
public void setRegattaXMLReader(RegattaXMLReader regattaXMLReader) {
this.regattaXMLReader = regattaXMLReader;
setChanged();

@ -18,7 +18,7 @@ import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* Created by jjg64 on 21/04/17.
* XML Read for the Course that is being received
*/
public class StreamedCourseXMLReader extends XMLReader {
private static final double COORDINATEPADDING = 0.000;
@ -95,6 +95,9 @@ public class StreamedCourseXMLReader extends XMLReader {
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
}
/**
* Reads the participants of the race.
*/
private void readParticipants() {
Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0);
nParticipants.getChildNodes().getLength();
@ -122,6 +125,7 @@ public class StreamedCourseXMLReader extends XMLReader {
/**
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
* @throws StreamedCourseXMLException if a CompoundMark element contains an unhandled number of compoundMarks.
* @see seng302.Model.Marker
*/
private void readCompoundMarks() throws StreamedCourseXMLException {
@ -158,12 +162,22 @@ public class StreamedCourseXMLReader extends XMLReader {
return marker;
}
/**
* Extracts the GPS Coordinates from a XMl Element
* @param mark Element to Extract from
* @return the GPS coordinate that it reflects
*/
private GPSCoordinate getCoordinate(Element mark) {
double lat = Double.parseDouble(mark.getAttribute("TargetLat"));
double lon = Double.parseDouble(mark.getAttribute("TargetLng"));
return new GPSCoordinate(lat,lon);
}
/**
* Extracts the SourceID from a XML ELement
* @param mark Element to Extract from
* @return the Source ID of the Extracted Element.
*/
private int getSourceId(Element mark) {
String sourceId = mark.getAttribute("SourceID");
if (sourceId.isEmpty()){
@ -209,6 +223,9 @@ public class StreamedCourseXMLReader extends XMLReader {
}
}
/**
* Reads the course boundary limitations of the course recieved.
*/
private void readCourseLimit() {
Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) {

@ -18,7 +18,7 @@ import seng302.VisualiserInput;
import java.util.List;
/**
* Created by jjg64 on 21/04/17.
* The Class used to view the race streamed.
*/
public class StreamedRace implements Runnable {
private final VisualiserInput visualiserInput;
@ -55,7 +55,8 @@ public class StreamedRace implements Runnable {
if (boat != null) {
Leg startLeg = new Leg(name, 0);
startLeg.setEndMarker(endCompoundMark);
boat.setCurrentLeg(startLeg);
boat.setCurrentLeg(startLeg, controller.getRaceClock());
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
}
}
}
@ -76,6 +77,7 @@ public class StreamedRace implements Runnable {
* @param timeElapsed Time that has elapse since the start of the the race.
*/
private void checkPosition(Boat boat, long timeElapsed) {
boolean legChanged = false;
StreamedCourse raceData = visualiserInput.getCourse();
BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID());
if (boatStatusMessage != null) {
@ -83,8 +85,12 @@ public class StreamedRace implements Runnable {
int legNumber = boatStatusMessage.getLegNumber();
if (legNumber >= 1 && legNumber < legs.size()) {
boat.setCurrentLeg(legs.get(legNumber));
if (boat.getCurrentLeg() != legs.get(legNumber)){
boat.setCurrentLeg(legs.get(legNumber), controller.getRaceClock());
legChanged = true;
}
}
if (boatStatusEnum == BoatStatusEnum.RACING) {
@ -95,11 +101,13 @@ public class StreamedRace implements Runnable {
boatsFinished++;
boat.setTimeFinished(timeElapsed);
boat.setFinished(true);
//System.out.println("Boat finished");
}
}
//Update the boat display table in the GUI to reflect the leg change
updatePositions();
if (legChanged) {
//Update the boat display table in the GUI to reflect the leg change
updatePositions();
controller.updateSparkline(startingBoats);
}
}
/**
@ -153,6 +161,7 @@ public class StreamedRace implements Runnable {
*/
public void run() {
setControllerListeners();
Platform.runLater(() -> controller.createSparkLine(startingBoats));
initialiseBoats();
startRaceStream();
}
@ -187,26 +196,21 @@ public class StreamedRace implements Runnable {
@Override
public void handle(long arg0) {
if (boatsFinished < startingBoats.size()) {
boatsFinished = 0;
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
for (Boat boat : startingBoats) {
if (boat != null && !boat.isFinished()) {
updatePosition(boat, Math.round(1000 / lastFPS) > 20 ? 15 : Math.round(1000 / lastFPS));
checkPosition(boat, totalTimeElapsed);
}
if (boat.isFinished()){
boatsFinished++;
}
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
for (Boat boat : startingBoats) {
if (boat != null && !boat.isFinished()) {
updatePosition(boat, Math.round(1000 / lastFPS) > 20 ? 15 : Math.round(1000 / lastFPS));
checkPosition(boat, totalTimeElapsed);
}
for (Marker mark: boatMarkers){
if (mark != null){
updateMarker(mark);
}
}
for (Marker mark: boatMarkers){
if (mark != null){
updateMarker(mark);
}
//System.out.println(boatsFinished + ":" + startingBoats.size());
} else {
}
//System.out.println(boatsFinished + ":" + startingBoats.size());
if (visualiserInput.getRaceStatus().isFinished()){
controller.finishRace(startingBoats);
stop();
}

@ -6,6 +6,7 @@ import org.geotools.referencing.GeodeticCalculator;
import seng302.GPSCoordinate;
import java.awt.geom.Point2D;
import java.time.ZonedDateTime;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -31,6 +32,8 @@ public class Boat {
private final Queue<TrackPoint> track = new ConcurrentLinkedQueue<>();
private long nextValidTime = 0;
private ZonedDateTime timeSinceLastMark;
/**
* Boat initializer which keeps all of the information of the boat.
*
@ -66,7 +69,7 @@ public class Boat {
*/
public GPSCoordinate getWake() {
double reverseHeading = getHeading() - 180;
double wakeScale = 3;
double wakeScale = 5;
double distance = wakeScale * getVelocity();
GeodeticCalculator calc = new GeodeticCalculator();
@ -181,9 +184,10 @@ public class Boat {
return currentLeg;
}
public void setCurrentLeg(Leg currentLeg) {
public void setCurrentLeg(Leg currentLeg, RaceClock raceClock) {
this.currentLeg = currentLeg;
this.currentLegName.setValue(currentLeg.getName());
this.setTimeSinceLastMark(raceClock.getTime());
}
public boolean isFinished() {
@ -236,4 +240,12 @@ public class Boat {
public void setDnf(boolean dnf) {
this.dnf = dnf;
}
public ZonedDateTime getTimeSinceLastMark() {
return timeSinceLastMark;
}
public void setTimeSinceLastMark(ZonedDateTime timeSinceLastMark) {
this.timeSinceLastMark = timeSinceLastMark;
}
}

@ -59,6 +59,8 @@ public class RaceClock implements Runnable {
this.time = time;
this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
this.lastTime = System.currentTimeMillis();
if(startingTime != null) {
long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
if(seconds < 0)
@ -66,6 +68,7 @@ public class RaceClock implements Runnable {
else
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
}
}
/**

@ -1,6 +1,8 @@
package seng302.Model;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.collections.ObservableList;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
@ -10,9 +12,11 @@ import seng302.Mock.StreamedCourse;
import seng302.RaceDataSource;
import seng302.RaceMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
/**
* This creates a JavaFX Canvas that is fills it's parent.
@ -23,14 +27,18 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private RaceMap map;
private List<Boat> boats;
private List<Marker> boatMarkers;
private boolean raceAnno = true;
private boolean annoName = true;
private boolean annoAbbrev = true;
private boolean annoSpeed = true;
private boolean annoPath = true;
private boolean annoTimeSinceLastMark = true;
private List<Color> colours;
private final List<Marker> markers;
private final RaceDataSource raceData;
private Map<Integer, Color> boatColours = new HashMap<>();
private Node arrow;
private RaceClock raceClock;
public ResizableRaceCanvas(RaceDataSource raceData) {
super();
@ -54,6 +62,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
public void setBoats(List<Boat> boats) {
this.boats = boats;
mapBoatColours();
}
@ -140,6 +149,13 @@ public class ResizableRaceCanvas extends ResizableCanvas {
gc.restore();
}
private void displayFancyArrow(GraphCoordinate coordinate, double angle) {
angle = angle % 360;
if (arrow != null && arrow.getRotate() != angle) {
arrow.setRotate(angle);
}
}
/**
* Rotates things on the canvas Note: this must be called in between gc.save() and gc.restore() else they will rotate everything
*
@ -159,8 +175,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* @param abbrev abbreviation of the boat name
* @param speed speed of the boat
* @param coordinate coordinate the text appears
* @param timeSinceLastMark time since the last mark was passed
*/
private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate) {
private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, ZonedDateTime timeSinceLastMark) {
String text = "";
//Check name toggle value
if (annoName){
@ -172,7 +189,12 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
//Check speed toggle value
if (annoSpeed){
text += String.format("%.2fkn", speed);
text += String.format("%.2fkn ", speed);
}
//Check time since last mark toggle value
if(annoTimeSinceLastMark){
Duration timeSince = Duration.between(timeSinceLastMark, raceClock.getTime());
text += String.format("%d", timeSince.getSeconds());
}
//String text = String.format("%s, %2$.2fkn", name, speed);
long xCoord = coordinate.getX() + 20;
@ -233,18 +255,12 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//display wind direction arrow - specify origin point and angle - angle now set to random angle
if (raceData instanceof StreamedCourse) {
displayArrow(new GraphCoordinate((int) getWidth() - 40, 40), ((StreamedCourse) raceData).getWindDirection());
displayFancyArrow(new GraphCoordinate((int) getWidth() - 40, 40), ((StreamedCourse) raceData).getWindDirection());
} else {
displayArrow(new GraphCoordinate((int) getWidth() - 40, 40), 150);
displayFancyArrow(new GraphCoordinate((int) getWidth() - 40, 40), 150);
}
}
/**
* Toggle the raceAnno value
*/
public void toggleAnnotations() {
raceAnno = !raceAnno;
}
/**
* Toggle name display in annotation
@ -253,10 +269,18 @@ public class ResizableRaceCanvas extends ResizableCanvas {
annoName = !annoName;
}
/**
* Toggle boat path display in annotation
*/
public void toggleBoatPath() {
annoPath = !annoPath;
}
/**
* Toggle boat time display in annotation
*/
public void toggleAnnoTime() { annoTimeSinceLastMark = !annoTimeSinceLastMark;}
/**
* Toggle abbreviation display in annotation
*/
@ -275,27 +299,29 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* Draws boats while race in progress, when leg heading is set.
*/
private void updateBoats() {
int currentColour = 0;
if (boats != null) {
if (boatColours.size() < boats.size()) mapBoatColours();
for (Boat boat : boats) {
boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF");
boolean isStart = boat.isStarted();
int sourceID = boat.getSourceID();
if (!finished && isStart) {
displayBoat(boat, boat.getHeading(), colours.get(currentColour));
displayBoat(boat, boat.getHeading(), boatColours.get(sourceID));
GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition());
GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake());
displayLine(wakeFrom, wakeTo, colours.get(currentColour));
displayLine(wakeFrom, wakeTo, boatColours.get(sourceID));
} else if (!isStart) {
displayBoat(boat, boat.getHeading(), colours.get(currentColour));
displayBoat(boat, boat.getHeading(), boatColours.get(sourceID));
} else {
displayBoat(boat, 0, colours.get(currentColour));
displayBoat(boat, 0, boatColours.get(sourceID));
}
if (raceAnno)
displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));
if (Duration.between(boat.getTimeSinceLastMark(), raceClock.getTime()).getSeconds() < 0) {
boat.setTimeSinceLastMark(raceClock.getTime());
}
displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getTimeSinceLastMark());
//TODO this needs to be fixed.
drawTrack(boat, colours.get(currentColour));
currentColour = (currentColour + 1) % colours.size();
drawTrack(boat, boatColours.get(sourceID));
}
}
}
@ -303,10 +329,11 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/**
* Draws all track points for a given boat. Colour is set by boat, opacity by track point.
* @param boat whose track is displayed
* @param colour The color to use for the track.
* @see seng302.Model.TrackPoint
*/
private void drawTrack(Boat boat, Color colour) {
if (annoPath && raceAnno) {
if (annoPath) {
for (TrackPoint point : boat.getTrack()) {
GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate());
gc.setFill(new Color(colour.getRed(), colour.getGreen(), colour.getBlue(), point.getAlpha()));
@ -315,6 +342,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
}
}
/**
* makes colours
*/
private void makeColours() {
colours = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
@ -329,4 +359,22 @@ public class ResizableRaceCanvas extends ResizableCanvas {
));
}
}
public void setArrow(Node arrow) {
this.arrow = arrow;
}
public void setRaceClock(RaceClock raceClock) {
this.raceClock = raceClock;
}
private void mapBoatColours() {
int currentColour = 0;
for (Boat boat : boats) {
if (!boatColours.containsKey(boat.getSourceID())) {
boatColours.put(boat.getSourceID(), colours.get(currentColour));
}
currentColour = (currentColour + 1) % colours.size();
}
}
}

@ -17,6 +17,10 @@ public class ResizableRaceMap extends ResizableCanvas {
private double[] xpoints = {};
private double[] ypoints = {};
/**
* Constructor
* @param raceData Race which it is taking its information to be displayed from
*/
public ResizableRaceMap(RaceDataSource raceData){
super();
raceBoundaries = raceData.getBoundary();
@ -29,9 +33,14 @@ public class ResizableRaceMap extends ResizableCanvas {
//draw();
}
/**
* Sets the map race that it is auppost to be viewing.
* @param map
*/
private void setMap(RaceMap map) {
this.map = map;
}
/**
* Draw boundary of the race.
*/
@ -46,6 +55,9 @@ public class ResizableRaceMap extends ResizableCanvas {
gc.fillPolygon(xpoints, ypoints, xpoints.length);
}
/**
* Sets the coordinately of the race boundaries
*/
private void setRaceBoundCoordinates() {
xpoints = new double[this.raceBoundaries.size()];
ypoints = new double[this.raceBoundaries.size()];
@ -56,6 +68,9 @@ public class ResizableRaceMap extends ResizableCanvas {
}
}
/**
* Draw update for the canvas
*/
public void draw(){
double width = getWidth();

@ -127,6 +127,7 @@ public class VisualiserInput implements Runnable {
/**
* Sets the wind direction for the current course.
* @param direction The new wind direction for the course.
*/
protected void setCourseWindDirection(double direction) {
this.course.setWindDirection(direction);

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<StackPane fx:id="arrow" prefHeight="125.0" prefWidth="125.0">
<children>
<ImageView fitHeight="75.0" fitWidth="75.0">
<image>
<Image url="@../images/arrow.png" />
</image>
</ImageView>
</children>
</StackPane>
<Circle fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
<Label layoutX="55.0" layoutY="1.0" text="N">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Label layoutX="42.0" layoutY="99.0" text="Wind">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
</children>
</Pane>

@ -1,51 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<SplitPane xmlns:fx="http://javafx.com/fxml/1" fx:id="race" dividerPositions="0.7" visible="false" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns="http://javafx.com/javafx/8" fx:controller="seng302.Controllers.RaceController">
<SplitPane fx:id="race" dividerPositions="0.7" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.RaceController">
<items>
<GridPane fx:id="canvasBase">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<fx:include fx:id="arrow" source="arrow.fxml" />
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<children>
<Accordion>
<panes>
<TitledPane animated="false" text="Annotation Control">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="240.0" prefWidth="200.0">
<AnchorPane minHeight="0.0" minWidth="0.0">
<children>
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true"
text="Show Boat Paths" AnchorPane.leftAnchor="0.0"
AnchorPane.topAnchor="104.0"/>
<CheckBox fx:id="showAnnotations" layoutX="-2.0" layoutY="14.0"
mnemonicParsing="false" selected="true"
text="Show Annotations" AnchorPane.leftAnchor="0.0"
AnchorPane.topAnchor="0.0"/>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false"
selected="true" text="Show Boat Name"
AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="26.0"/>
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false"
selected="true" text="Show Boat Abbreviation"
AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="52.0"/>
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false"
selected="true" text="Show Boat Speed"
AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="78.0"/>
<Button fx:id="saveAnno" layoutX="11.0" layoutY="106.0" maxWidth="154.0"
mnemonicParsing="false" prefWidth="154.0" text="Save Annotation"
AnchorPane.topAnchor="130.0"/>
<Button fx:id="showSetAnno" layoutX="11.0" layoutY="139.0"
mnemonicParsing="false" text="Show Set Annotation"
AnchorPane.topAnchor="160.0"/>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false" selected="true" text="Show Boat Name" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false" selected="true" text="Show Boat Abbreviation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" />
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false" selected="true" text="Show Boat Speed" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" />
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true" text="Show Boat Paths" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="75.0" />
<CheckBox fx:id="showTime" mnemonicParsing="false" selected="true" text="Show Boat Leg Time" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="100.0" />
<Separator prefWidth="200.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="130.0" />
<RadioButton fx:id="hideAnnoRBTN" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="155.0" />
<RadioButton fx:id="showAnnoRBTN" mnemonicParsing="false" text="Visible" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="180.0" />
<RadioButton fx:id="partialAnnoRBTN" mnemonicParsing="false" text="Partial" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="205.0" />
<RadioButton fx:id="importantAnnoRBTN" mnemonicParsing="false" text="Important" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="230.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="106.0" maxWidth="154.0" mnemonicParsing="false" prefWidth="154.0" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="255.0" />
</children>
</AnchorPane>
</content>
@ -54,9 +48,7 @@
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0"
mnemonicParsing="false" selected="true" text="Show FPS"
AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0"/>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
@ -65,42 +57,49 @@
</Accordion>
</children>
</Pane>
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" maxHeight="20.0" text="0:0"
AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" GridPane.halignment="RIGHT"
GridPane.valignment="BOTTOM">
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" maxHeight="20.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0"/>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
GridPane.halignment="LEFT" GridPane.valignment="BOTTOM">
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0"/>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="20.0"/>
<Insets bottom="20.0" />
</GridPane.margin>
<font>
<Font name="System Bold" size="15.0"/>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false" />
</children>
</GridPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0"
GridPane.columnIndex="1">
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="600.0" prefWidth="264.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0"
AnchorPane.topAnchor="0.0">
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="265.0" prefWidth="242.0" AnchorPane.bottomAnchor="164.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place"/>
<TableColumn fx:id="boatTeamColumn" prefWidth="100.0" text="Team"/>
<TableColumn fx:id="boatMarkColumn" prefWidth="130.0" text="Mark"/>
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed"/>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
<TableColumn fx:id="boatTeamColumn" prefWidth="100.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="130.0" text="Mark" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
</columns>
</TableView>
<AnchorPane layoutY="265.0" prefHeight="167.0" prefWidth="178.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<LineChart fx:id="sparklineChart" layoutX="-211.0" layoutY="-186.0" mouseTransparent="true" prefHeight="167.0" prefWidth="178.0" titleSide="LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
</children>
</AnchorPane>
</items>

Loading…
Cancel
Save