Removed the old mock/network/visualiser files.

main
fjc40 9 years ago
parent 3a85fad211
commit 2a82298571

@ -1,75 +0,0 @@
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.TransformerException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class App extends Application {
/**
* Entry point for running the programme
*
* @param args for starting the programme
*/
public static void main(String[] args) {
launch(args);
}
@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, boatPolars);
raceEvent.start();
} 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));
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
return XMLReader.getContents(doc);
}
}

@ -1,49 +0,0 @@
package seng302;
/**
* Constants that are used throughout the program
* Created by Erika on 19-Mar-17.
*/
public class Constants {
/**
* 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;
}

@ -1,14 +0,0 @@
package seng302.DataInput;
import seng302.Model.Boat;
import seng302.Model.Mark;
import java.util.Map;
/**
* Boats Data
*/
public interface BoatDataSource {
Map<Integer, Boat> getBoats();
Map<Integer, Mark> getMarkerBoats();
}

@ -1,149 +0,0 @@
package seng302.DataInput;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
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;
import java.util.HashMap;
import java.util.Map;
/**
* 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<>();
/**
* Polars table to assign to each boat.
*/
Polars boatPolars;
/**
* Constructor for Boat XML
*
* @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, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
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++) {
Node boat = nBoats.getChildNodes().item(i);
if (boat.getNodeName().equals("Boat")) {
readBoatNode(boat);
}
}
}
/**
* Ignored data
*/
private void readShapes() {
}
/**
* Ignored data
*/
private void readSettings() {
}
/**
* 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");
}
/**
* Reads the information about one boat
* Ignored values: ShapeID, StoweName, HullNum, Skipper, Type
*/
private void readBoatNode(Node boatNode) {
int sourceID = Integer.parseInt(boatNode.getAttributes().getNamedItem("SourceID").getTextContent());
String name = boatNode.getAttributes().getNamedItem("BoatName").getTextContent();
if (isYachtNode(boatNode)) readYacht(boatNode, sourceID, name);
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, this.boatPolars));
} else {
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());
double y = Double.parseDouble(nCoord.getAttributes().getNamedItem("Y").getTextContent());
Mark mark = new Mark(sourceID, name, new GPSCoordinate(y,x));
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;
}
}

@ -1,105 +0,0 @@
package seng302.DataInput;
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,32 +0,0 @@
package seng302.DataInput;
import seng302.Model.Boat;
import seng302.Model.CompoundMark;
import seng302.Model.GPSCoordinate;
import seng302.Model.Leg;
import java.time.ZonedDateTime;
import java.util.List;
/**
* Data Class for a Race
*/
public interface RaceDataSource {
List<Boat> getBoats();
List<Leg> getLegs();
List<GPSCoordinate> getBoundary();
List<CompoundMark> getCompoundMarks();
int getRaceId();
String getRaceType();
ZonedDateTime getZonedDateTime();
GPSCoordinate getMapTopLeft();
GPSCoordinate getMapBottomRight();
}

@ -1,288 +0,0 @@
package seng302.DataInput;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.Exceptions.StreamedCourseXMLException;
import seng302.Model.*;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.text.ParseException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* 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;
private GPSCoordinate mapTopLeft, mapBottomRight;
private final List<GPSCoordinate> boundary = new ArrayList<>();
private final Map<Integer,Element> compoundMarkMap = new HashMap<>();
private final Map<Integer, Boat> participants = new HashMap<>();
private final List<Leg> legs = new ArrayList<>();
private final List<CompoundMark> compoundMarks = new ArrayList<>();
private ZonedDateTime creationTimeDate;
private ZonedDateTime raceStartTime;
private int raceID;
private String raceType;
private boolean postpone;
private Map<Integer, Boat> boats;
private Map<Integer, Mark> marks;
/**
* 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
* @throws ParseException error
* @throws StreamedCourseXMLException error
*/
public RaceXMLReader(String filePath, BoatDataSource boatData) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
this(filePath, boatData, true);
}
/**
* 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
* @throws ParserConfigurationException error
* @throws ParseException error
* @throws StreamedCourseXMLException error
*/
public RaceXMLReader(String filePath, BoatDataSource boatData, boolean read) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
super(filePath);
this.boats = boatData.getBoats();
this.marks = boatData.getMarkerBoats();
if (read) {
read();
}
}
/**
* reads
* @throws StreamedCourseXMLException error
*/
private void read() throws StreamedCourseXMLException {
readRace();
readParticipants();
readCourse();
}
/**
* reads a race
*/
private void readRace() {
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
raceType = getTextValueOfNode(settings, "RaceType");
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
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();
for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) {
int sourceID;
Node yacht = nParticipants.getChildNodes().item(i);
if (yacht.getNodeName().equals("Yacht")) {
if (exists(yacht, "SourceID")) {
sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
participants.put(sourceID, boats.get(sourceID));
}
}
}
}
/**
* reads a course
* @throws StreamedCourseXMLException error
*/
private void readCourse() throws StreamedCourseXMLException {
readCompoundMarks();
readCompoundMarkSequence();
readCourseLimit();
}
/**
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
* @see CompoundMark
*/
private void readCompoundMarks() throws StreamedCourseXMLException {
Element nCourse = (Element) doc.getElementsByTagName("Course").item(0);
for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) {
Node compoundMark = nCourse.getChildNodes().item(i);
if(compoundMark.getNodeName().equals("CompoundMark")) {
int compoundMarkID = getCompoundMarkID((Element) compoundMark);
compoundMarkMap.put(compoundMarkID, (Element)compoundMark);
compoundMarks.add(getCompoundMark(compoundMarkID));
}
}
}
/**
* Generates a CompoundMark from the CompoundMark element with given ID.
* @param compoundMarkID index of required CompoundMark element
* @return generated CompoundMark
* @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks
* @see CompoundMark
*/
private CompoundMark getCompoundMark(int compoundMarkID) throws StreamedCourseXMLException {
Element compoundMark = compoundMarkMap.get(compoundMarkID);
NodeList nMarks = compoundMark.getElementsByTagName("Mark");
CompoundMark marker;
switch(nMarks.getLength()) {
case 1: marker = new CompoundMark(getMark((Element)nMarks.item(0)));
break;
case 2: marker = new CompoundMark(getMark((Element)nMarks.item(0)), getMark((Element)nMarks.item(1))); break;
default: throw new StreamedCourseXMLException();
}
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);
}
/**
* Reads "compoundMarkID" attribute of CompoundMark or Corner element
* @param element with "compoundMarkID" attribute
* @return value of "compoundMarkID" attribute
*/
private int getCompoundMarkID(Element element) {
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
}
/**
* Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID
* @param compoundMarkID unique ID for CompoundMark element
* @return value of "name" attribute
*/
private String getCompoundMarkName(int compoundMarkID) {
return compoundMarkMap.get(compoundMarkID).getAttribute("Name");
}
/**
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
* @throws StreamedCourseXMLException if compoundMarks cannot be resolved from CompoundMark
*/
private void readCompoundMarkSequence() throws StreamedCourseXMLException {
Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner");
Element markXML = (Element)nCorners.item(0);
CompoundMark lastCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
String legName = getCompoundMarkName(getCompoundMarkID(markXML));
for(int i = 1; i < nCorners.getLength(); i++) {
markXML = (Element)nCorners.item(i);
CompoundMark currentCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
legs.add(new Leg(legName, lastCompoundMark, currentCompoundMark, i-1));
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++) {
Node limit = nCourseLimit.getChildNodes().item(i);
if (limit.getNodeName().equals("Limit")) {
double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent());
double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent());
boundary.add(new GPSCoordinate(lat, lon));
}
}
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
}
public List<GPSCoordinate> getBoundary() {
return boundary;
}
public GPSCoordinate getMapTopLeft() {
return mapTopLeft;
}
public GPSCoordinate getMapBottomRight() {
return mapBottomRight;
}
public List<Leg> getLegs() {
return legs;
}
public List<CompoundMark> getCompoundMarks() { return compoundMarks; }
public Double getPadding() {
return COORDINATEPADDING;
}
public ZonedDateTime getCreationTimeDate() {
return creationTimeDate;
}
public ZonedDateTime getZonedDateTime() {
return raceStartTime;
}
public int getRaceId() {
return raceID;
}
public String getRaceType() {
return raceType;
}
public boolean isPostpone() {
return postpone;
}
public List<Boat> getBoats() {
return new ArrayList<>(participants.values());
}
}

@ -1,112 +0,0 @@
package seng302.DataInput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
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 java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
/**
* Base Reader for XML Files
*/
public abstract class XMLReader {
protected Document doc;
/**
* Read in XML file
* @param filePath filepath for XML file
* @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 {
InputSource fXmlFile;
if (filePath.contains("<")) {
fXmlFile = new InputSource();
fXmlFile.setCharacterStream(new StringReader(filePath));
} else {
fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath));
}
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
}
/**
* Alternate constructor
* @param xmlFile File to be read
* @param isWholeFile boolean value whether entire file is being passed
*/
public XMLReader(String xmlFile, Boolean isWholeFile) {
}
/**
* Return Document data of the read-in XML
* @return XML document
*/
public Document getDocument() {
return doc;
}
/**
* Get content of a tag in an element
* @param n Element to read tags from
* @param tagName Name of the tag
* @return Content of the tag
*/
public String getTextValueOfNode(Element n, String tagName) {
return n.getElementsByTagName(tagName).item(0).getTextContent();
}
/**
* Get attributes for an element
* @param n Element to read attributes from
* @param attr Attributes of element
* @return Attributes of element
*/
public String getAttribute(Element n, String attr) {
return n.getAttribute(attr);
}
protected boolean exists(Node node, String attribute) {
return node.getAttributes().getNamedItem(attribute) != null;
}
/**
* Get the contents of the XML FILe.
* @param document holds all xml information
* @return String representation of document
* @throws TransformerException when document is malformed, and cannot be turned into a string
*/
public static String getContents(Document document) throws TransformerException {
DOMSource source = new DOMSource(document);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
StringWriter stringWriter = new StringWriter();
StreamResult result = new StreamResult(stringWriter);
transformer.transform(source, result);
return stringWriter.toString();
}
}

@ -1,14 +0,0 @@
package seng302.Exceptions;
/**
* An exception thrown when we cannot generate Boats.xml and send an XML message.
*/
public class InvalidBoatDataException extends RuntimeException {
public InvalidBoatDataException() {
}
public InvalidBoatDataException(String message) {
super(message);
}
}

@ -1,24 +0,0 @@
package seng302.Exceptions;
/**
* 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);
}
}

@ -1,13 +0,0 @@
package seng302.Exceptions;
/**
* Exception thrown when we cannot generate Race.xml data, and send an XML message.
*/
public class InvalidRaceDataException extends RuntimeException {
public InvalidRaceDataException() {
}
public InvalidRaceDataException(String message) {
super(message);
}
}

@ -1,7 +0,0 @@
package seng302.Exceptions;
/**
* Created by cbt24 on 25/04/17.
*/
public class StreamedCourseXMLException extends Throwable {
}

@ -1,251 +0,0 @@
package seng302;
import seng302.Networking.BinaryMessageEncoder;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Messages.RaceStatus;
import seng302.Networking.Messages.XMLMessage;
import java.io.*;
import java.net.*;
import java.util.concurrent.ArrayBlockingQueue;
/**
* TCP server to send race information to connected clients.
*/
public class MockOutput implements Runnable
{
///Timestamp of the last sent heartbeat message.
private long lastHeartbeatTime;
///Period for the heartbeat - that is, how often we send it.
private double heartbeatPeriod = 5.0;
///Port to expose server on.
private int serverPort = 4942;
///Socket used to listen for clients on.
private ServerSocket serverSocket;
///Socket used to communicate with a client.
private Socket mockSocket;
///Output stream which wraps around mockSocket outstream.
private DataOutputStream outToVisualiser;
///A queue that contains items that are waiting to be sent.
private ArrayBlockingQueue<byte[]> messagesToSendQueue = new ArrayBlockingQueue<>(99999999);
///Sequence numbers used in messages.
private short messageNumber = 1;
private short xmlSequenceNumber = 1;
private int heartbeatSequenceNum = 1;
private int boatLocationSequenceNumber = 1;
private int raceStatusSequenceNumber = 1;
///Strings containing XML data as strings.
private String raceXml;
private String regattaXml;
private String boatsXml;
private boolean stop = false; //whether or not hte thread keeps running
/**
* Ctor.
* @throws IOException if server socket cannot be opened.
*/
public MockOutput() throws IOException {
lastHeartbeatTime = System.currentTimeMillis();
serverSocket = new ServerSocket(serverPort);
}
/**
* Calculates the time since last heartbeat message, in seconds.
* @return Time since last heartbeat message, in seconds.
*/
private double timeSinceHeartbeat() {
long now = System.currentTimeMillis();
return (now - lastHeartbeatTime) / 1000.0;
}
//returns the heartbeat message
/**
* Increment the heartbeat value
* @return message for heartbeat data
*/
private byte[] heartbeat(){
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum);
heartbeatSequenceNum++;
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage);
messageNumber++;
return binaryMessageEncoder.getFullMessage();
}
/**
* Used to give the mockOutput an xml string to be made into a message and sent
* @param xmlString the xml string to send
* @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat)
*/
public synchronized void parseXMLString(String xmlString, int messageType){
XMLMessageEncoder encoder = new XMLMessageEncoder(messageNumber, System.currentTimeMillis(), messageType, xmlSequenceNumber,(short) xmlString.length(), xmlString);
//iterates the sequence numbers
xmlSequenceNumber++;
byte[] encodedXML = encoder.encode();
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.XMLMESSAGE, System.currentTimeMillis(), messageNumber, (short)encodedXML.length, encodedXML);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Used to give the mocOutput information about boat location to be made into a message and sent
* @param sourceID id of the boat
* @param lat latitude of boat
* @param lon longitude of boat
* @param heading heading of boat
* @param speed speed of boat
* @param time historical time of race
*/
public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){
BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time);
//iterates the sequence number
boatLocationSequenceNumber++;
//encodeds the messages
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
//encodeds the full message with header
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length,
encodedBoatLoc);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Parse the race status data and add it to the buffer to be sent
* @param raceStatus race status to parses
*/
public synchronized void parseRaceStatus(RaceStatus raceStatus){
//iterates the sequence number
raceStatusSequenceNumber++;
//encodeds the messages
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
//encodeds the full message with header
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.RACESTATUS, System.currentTimeMillis(), messageNumber, (short)encodedRaceStatus.length,
encodedRaceStatus);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Add a message to the buffer to be sent
* @param messagesToSendBuffer message to add to the buffer
*/
private synchronized void addMessageToBufferToSend(byte[] messagesToSendBuffer) {
this.messagesToSendQueue.add(messagesToSendBuffer);
}
/**
* Sending loop of the Server
*/
public void run() {
try {
while (!stop){
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
mockSocket = serverSocket.accept();
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
if (boatsXml == null || regattaXml == null || raceXml == null){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
parseXMLString(raceXml, XMLMessage.XMLTypeRace);
parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta);
parseXMLString(boatsXml, XMLMessage.XMLTypeBoat);
while(true) {
try {
//Sends a heartbeat every so often.
if (timeSinceHeartbeat() >= heartbeatPeriod) {
outToVisualiser.write(heartbeat());
lastHeartbeatTime = System.currentTimeMillis();
}
//Checks the buffer to see if there is anything to send.
while (messagesToSendQueue.size() > 0) {
//Grabs message from head of queue.
byte[] binaryMessage = messagesToSendQueue.remove();
//sends the message to the visualiser
outToVisualiser.write(binaryMessage);
}
}catch(SocketException e){
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
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;
}
public static void main(String argv[]) throws Exception
{
MockOutput client = new MockOutput();
client.run();
}
}

@ -1,130 +0,0 @@
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;
}
/**
* 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;
}
}

@ -1,68 +0,0 @@
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());
}
}

@ -1,66 +0,0 @@
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,454 +0,0 @@
package seng302.Model;
import seng302.Constants;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* 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;
private long estimatedTime = 0;
/**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
*
* @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, Polars polars) {
this.country = country;
this.name = name;
this.sourceID = sourceID;
this.polars = polars;
this.bearing = Bearing.fromDegrees(0d);
this.status = BoatStatusEnum.UNDEFINED;
}
/**
* Calculate the bearing of the boat to its next marker.
* @return The bearing to the next marker.
*/
public Bearing calculateBearingToNextMarker() {
//Get the start and end points.
GPSCoordinate currentPosition = this.getCurrentPosition();
GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();
//Calculate bearing.
Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition);
return bearing;
}
/**
* 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 calculateDistanceToNextMarker() {
//Get start and end markers.
GPSCoordinate startPosition = this.getCurrentPosition();
//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;
}
/**
* Returns the current speed of the boat, in knots.
* @return The current speed of the boat, in knots.
*/
public double getCurrentSpeed() {
return currentSpeed;
}
/**
* 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;
}
/**
* 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;
}
/**
* Returns the current bearing of the boat.
* @return The current bearing of the boat.
*/
public Bearing getBearing() {
return bearing;
}
/**
* Sets the current bearing of the boat.
* @param bearing The new bearing of the boat.
*/
public void setBearing(Bearing bearing) {
this.bearing = bearing;
}
/**
* Returns the polars table for this boat.
* @return The polars table for this boat.
*/
public Polars getPolars() {
return polars;
}
/**
* 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;
}
public long getEstimatedTime() {
return estimatedTime;
}
public void setEstimatedTime(long estimatedTime) {
this.estimatedTime = estimatedTime;
}
}

@ -1,110 +0,0 @@
package seng302.Model;
/**
* Represents a compound mark - that is, either one or two individual marks which form a single compound mark.
*/
public class CompoundMark {
/**
* The first mark in the compound mark.
*/
private Mark mark1;
/**
* 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) {
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) {
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
}
/**
* 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;
}
/**
* 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();
}
/**
* 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 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;
}
}

@ -1,82 +0,0 @@
package seng302.Model;
import org.xml.sax.SAXException;
import seng302.DataInput.*;
import seng302.Exceptions.StreamedCourseXMLException;
import seng302.MockOutput;
import seng302.Networking.Messages.Enums.MessageType;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.text.ParseException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
/**
* 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, Polars boatPolars) {
this.raceXML = getRaceXMLAtCurrentTime(raceXML);
this.boatXML = boatXML;
this.regattaXML = regattaXML;
this.boatPolars = boatPolars;
try {
mockOutput = new MockOutput();
new Thread(mockOutput).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Sends the initial race data and then begins race simulation
*/
public void start() {
try {
sendXMLs();
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) {
e.printStackTrace();
}
}
/**
* Sends out each xml string, via the mock output
*/
private void sendXMLs() {
mockOutput.setRegattaXml(regattaXML);
mockOutput.parseXMLString(regattaXML, MessageType.XMLMESSAGE.getValue());
mockOutput.setRaceXml(raceXML);
mockOutput.parseXMLString(raceXML, MessageType.XMLMESSAGE.getValue());
mockOutput.setBoatsXml(boatXML);
mockOutput.parseXMLString(boatXML, MessageType.XMLMESSAGE.getValue());
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 3 minutes
* @param raceXML
* @return String containing edited xml
*/
private String getRaceXMLAtCurrentTime(String raceXML) {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
return raceXML.replace("CREATION_TIME", dateFormat.format(creationTime))
.replace("START_TIME", dateFormat.format(creationTime.plusMinutes(3)));
}
}

@ -1,517 +0,0 @@
package seng302.Model;
import javafx.util.Pair;
import org.geotools.referencing.GeodeticCalculator;
import seng302.Constants;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* 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;
/**
* 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) {
this.latitude = latitude;
this.longitude = longitude;
}
/**
* Gets the Latitude that the Coordinate is at.
*
* @return Returns the latitude of the Coordinate.
*/
public double getLatitude() {
return latitude;
}
/**
* Gets the Longitude that the Coordinate is at.
*
* @return Returns the longitude of the Coordinate.
*/
public double getLongitude() {
return longitude;
}
/**
* To String method of the Coordinate in the form Latitude: $f, Longitude: $f.
*
* @return A String representation of the GPSCoordinate Class.
*/
public String toString() {
return String.format("Latitude: %f, Longitude: %f", latitude, longitude);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GPSCoordinate that = (GPSCoordinate) o;
if (Math.abs(this.latitude - that.latitude) > 1e-7) return false;
return (Math.abs(this.longitude - that.longitude) < 1e-7);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(latitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(longitude);
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 boundary
* @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 boundary.
* @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;
}
/**
* Takes a list of GPS coordinates describing a course boundary, and "shrinks" it inwards by 50m.
* @param boundary The boundary of course.
* @return A copy of the course boundary list, shrunk inwards by 50m.
*/
public static List<GPSCoordinate> getShrinkBoundary(List<GPSCoordinate> boundary) {
double shrinkDistance = 50d;
List<GPSCoordinate> shrunkBoundary = new ArrayList<>(boundary.size());
//This is a list of edges that have been shrunk/shifted inwards.
List<Pair<GPSCoordinate, GPSCoordinate>> shrunkEdges = new ArrayList<>();
//We need to invert some of our operations depending if the boundary is clockwise or anti-clockwise.
boolean isClockwise = GPSCoordinate.isClockwisePolygon(boundary);
double clockwiseScaleFactor = 0;
if (isClockwise) {
clockwiseScaleFactor = 1;
} else {
clockwiseScaleFactor = -1;
}
/*
Starting at a vertex, face anti-clockwise along an adjacent edge.
Replace the edge with a new, parallel edge placed at distance d to the "left" of the old one.
Repeat for all edges.
Find the intersections of the new edges to get the new vertices.
Detect if you've become a crossed polynomial and decide what to do about it. Probably add a new vertex at the crossing-point and get rid of some old ones. I'm not sure whether there's a better way to detect this than just to compare every pair of non-adjacent edges to see if their intersection lies between both pairs of vertices.
*/
//For the first (size-1) adjacent pairs.
for (int i = 0; i < (boundary.size() - 1); i++) {
//Get the points.
GPSCoordinate firstPoint = boundary.get(i);
GPSCoordinate secondPoint = boundary.get(i + 1);
//Get the bearing between two adjacent points.
Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint);
//Calculate angle perpendicular to bearing.
Bearing perpendicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor));
//Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge.
GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing));
GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing));
//Add edge to list.
shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated));
}
//For the final adjacent pair, between the last and first point.
//Get the points.
GPSCoordinate firstPoint = boundary.get(boundary.size() - 1);
GPSCoordinate secondPoint = boundary.get(0);
//Get the bearing between two adjacent points.
Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint);
//Calculate angle perpendicular to bearing.
Bearing perpendicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor));
//Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge.
GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing));
GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing));
//Add edge to list.
shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated));
//We now have a list of edges that have been shifted inwards.
//We need to find the intersections between adjacent vertices in our edge list. E.g., intersection between edge1-right, and edge2-left.
//For the first (size-1) adjacent pairs.
for (int i = 0; i < (shrunkEdges.size() - 1); i++) {
//Get the pair of adjacent edges.
Pair<GPSCoordinate, GPSCoordinate> edge1 = shrunkEdges.get(i);
Pair<GPSCoordinate, GPSCoordinate> edge2 = shrunkEdges.get(i + 1);
//Get the x and y coordinates of first edge.
double x1 = edge1.getKey().getLongitude();
double x2 = edge1.getValue().getLongitude();
double y1 = edge1.getKey().getLatitude();
double y2 = edge1.getValue().getLatitude();
//Get the x and y coordinates of second edge.
double x3 = edge2.getKey().getLongitude();
double x4 = edge2.getValue().getLongitude();
double y3 = edge2.getKey().getLatitude();
double y4 = edge2.getValue().getLatitude();
//Find the equations for both edges.
// y = a*x + b
//First equation.
double a1 = (y2 - y1) / (x2 - x1);
double b1 = y1 - a1 * x1;
//Second equation.
double a2 = (y4 - y3) / (x4 - x3);
double b2 = y3 - a2 * x3;
//Find intersecting x coordinate.
// a1 * x + b1 = a2 * x + b2
double x0 = -(b1 - b2) / (a1 - a2);
//Find intersecting y coordinate.
double y0 = x0 * a1 + b1;
//Add this to shrunk boundary list.
GPSCoordinate coordinate = new GPSCoordinate(y0, x0);
shrunkBoundary.add(coordinate);
}
//For the final adjacent pair, between the last and first point.
//Get the pair of adjacent edges.
Pair<GPSCoordinate, GPSCoordinate> edge1 = shrunkEdges.get(shrunkEdges.size() - 1);
Pair<GPSCoordinate, GPSCoordinate> edge2 = shrunkEdges.get(0);
//Get the x and y coordinates of first edge.
double x1 = edge1.getKey().getLongitude();
double x2 = edge1.getValue().getLongitude();
double y1 = edge1.getKey().getLatitude();
double y2 = edge1.getValue().getLatitude();
//Get the x and y coordinates of second edge.
double x3 = edge2.getKey().getLongitude();
double x4 = edge2.getValue().getLongitude();
double y3 = edge2.getKey().getLatitude();
double y4 = edge2.getValue().getLatitude();
//Find the equations for both edges.
// y = a*x + b
//First equation.
double a1 = (y2 - y1) / (x2 - x1);
double b1 = y1 - a1 * x1;
//Second equation.
double a2 = (y4 - y3) / (x4 - x3);
double b2 = y3 - a2 * x3;
//Find intersecting x coordinate.
// a1 * x + b1 = a2 * x + b2
double x0 = -(b1 - b2) / (a1 - a2);
//Find intersecting y coordinate.
double y0 = x0 * a1 + b1;
//Add this to shrunk boundary list.
GPSCoordinate coordinate = new GPSCoordinate(y0, x0);
shrunkBoundary.add(coordinate);
return shrunkBoundary;
}
/**
* Determines if a list of coordinates describes a boundary polygon in clockwise or anti-clockwise order.
* @param boundary The list of coordinates.
* @return True if clockwise, false if anti-clockwise.
*/
public static boolean isClockwisePolygon(List<GPSCoordinate> boundary) {
/* From https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
sum all pairs (x2 x1)(y2 + y1)
point[0] = (5,0) edge[0]: (6-5)(4+0) = 4
point[1] = (6,4) edge[1]: (4-6)(5+4) = -18
point[2] = (4,5) edge[2]: (1-4)(5+5) = -30
point[3] = (1,5) edge[3]: (1-1)(0+5) = 0
point[4] = (1,0) edge[4]: (5-1)(0+0) = 0
---
-44 counter-clockwise
*/
double sum = 0;
//For the first (size-1) adjacent pairs.
for (int i = 0; i < (boundary.size() - 1); i++) {
//Get the points.
GPSCoordinate firstPoint = boundary.get(i);
GPSCoordinate secondPoint = boundary.get(i + 1);
double xDelta = secondPoint.getLongitude() - firstPoint.getLongitude();
double ySum = secondPoint.getLatitude() + firstPoint.getLatitude();
double product = xDelta * ySum;
sum += product;
}
//For the final adjacent pair, between the last and first point.
//Get the points.
GPSCoordinate firstPoint = boundary.get(boundary.size() - 1);
GPSCoordinate secondPoint = boundary.get(0);
double xDelta = secondPoint.getLongitude() - firstPoint.getLongitude();
double ySum = secondPoint.getLatitude() + firstPoint.getLatitude();
double product = xDelta * ySum;
sum += product;
//sum > 0 is clockwise, sum < 0 is anticlockwise.
return sum > 0;
}
}

@ -1,128 +0,0 @@
package seng302.Model;
/**
* Leg of the race, this is what each part of the race is divided into, from mark to mark.
*/
public class Leg {
/**
* 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;
/**
* Constructs a leg from a name, start marker, end marker, and leg number.
*
* @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, CompoundMark start, CompoundMark end, int number) {
this.name = name;
this.startCompoundMark = start;
this.endCompoundMark = end;
this.legNumber = number;
this.calculateLegDistance();
}
/**
* 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's position within the race.
*/
public Leg(String name, int number) {
this.name = name;
this.legNumber = number;
}
/**
* Returns the name of the Leg.
* @return The name of the Leg.
*/
public String getName() {
return name;
}
/**
* Get the distance in nautical miles.
* @return The total distance of the leg.
*/
public double getDistanceNauticalMiles() {
return distanceNauticalMiles;
}
/**
* 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;
}
/**
* Returns the starting marker of the leg.
* @return The starting marker of the leg.
*/
public CompoundMark getStartCompoundMark() {
return startCompoundMark;
}
/**
* Returns the ending marker of the leg.
* @return The ending marker of the leg.
*/
public CompoundMark getEndCompoundMark() {
return endCompoundMark;
}
/**
* Calculates the distance of the leg, in nautical miles.
*/
public void calculateLegDistance() {
//Gets the start and end coordinates.
GPSCoordinate startMarker = this.startCompoundMark.getAverageGPSCoordinate();
GPSCoordinate endMarker = this.endCompoundMark.getAverageGPSCoordinate();
//Calculates the distance between markers.
double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startMarker, endMarker);
this.distanceNauticalMiles = distanceNauticalMiles;
}
}

@ -1,64 +0,0 @@
package seng302.Model;
/**
* 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,428 +0,0 @@
package seng302.Model;
import javafx.util.Pair;
import java.util.*;
/**
* 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 = Polars.isFlippedInterval(bearingLowerBound, bearingUpperBound);
//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 += 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 (!Polars.isBearingInsideInterval(trueBoatBearing, bearingLowerBound, bearingUpperBound)) {
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);
}
}
/**
* Determines whether an interval is "flipped". This means that the lower bound is greater than the upper bound (e.g., [290, 43] degrees).
* @param lowerBound The lower bound.
* @param upperBound The upper bound.
* @return True if the interval is flipped, false otherwise.
*/
public static boolean isFlippedInterval(Bearing lowerBound, Bearing upperBound) {
//If the lower bound is greater than the upper bound, we have a "flipped" interval.
boolean flippedInterval = false;
if (lowerBound.degrees() > upperBound.degrees()) {
flippedInterval = true;
}
return flippedInterval;
}
/**
* Determines if a bearing is inside an interval.
* @param bearing The bearing to check.
* @param lowerBound The lower bound of the interval.
* @param upperBound The upper bound of the interval.
* @return True if the bearing is inside the interval, false otherwise.
*/
public static boolean isBearingInsideInterval(Bearing bearing, Bearing lowerBound, Bearing upperBound) {
//Check if it's a flipped interval.
boolean flippedInterval = Polars.isFlippedInterval(lowerBound, upperBound);
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 ((bearing.degrees() >= lowerBound.degrees()) || (bearing.degrees() <= upperBound.degrees())) {
return true;
} else {
return false;
}
} else {
//Bearing must be inside [lower, upper].
if ((bearing.degrees() >= lowerBound.degrees()) && (bearing.degrees() <= upperBound.degrees())) {
return true;
} else {
return false;
}
}
}
/**
* 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;
}
}

@ -1,929 +0,0 @@
package seng302.Model;
import javafx.animation.AnimationTimer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.Constants;
import seng302.DataInput.RaceDataSource;
import seng302.MockOutput;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import seng302.Networking.Messages.Enums.RaceStatusEnum;
import seng302.Networking.Messages.Enums.RaceTypeEnum;
import seng302.Networking.Messages.RaceStatus;
import seng302.Networking.Utils.AC35UnitConverter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import static java.lang.Math.cos;
/**
* Represents a yacht race.
* Has a course, boats, boundaries, etc...
* Is responsible for simulating the race, and sending messages to a MockOutput instance.
*/
public class Race implements Runnable {
/**
* An observable list of boats in the race.
*/
private ObservableList<Boat> boats;
/**
* An observable list of compound marks in the race.
*/
private ObservableList<CompoundMark> compoundMarks;
/**
* A list of legs in the race.
*/
private List<Leg> legs;
/**
* A list of coordinates describing the boundary of the course.
*/
private List<GPSCoordinate> boundary;
/**
* A copy of the boundary list, except "shrunk" inwards by 50m.
*/
private List<GPSCoordinate> shrinkBoundary;
/**
* The elapsed time, in milliseconds, of the race.
*/
private long totalTimeElapsed;
/**
* The starting timestamp, in milliseconds, of the race.
*/
private long startTime;
/**
* The scale factor of the race.
* Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/
private int scaleFactor = 5;
/**
* The race ID of the course.
*/
private int raceId;
/**
* The current status of the race.
*/
private RaceStatusEnum raceStatusEnum;
/**
* The type of race this is.
*/
private RaceTypeEnum raceType;
/**
* The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint.
* 0 = 0%, 100 = 100%.
*/
private int dnfChance = 0;
/**
* The mockOutput to send messages to.
*/
private MockOutput mockOutput;
/**
* Wind direction bearing.
*/
private Bearing windDirection;
/**
* Wind speed (knots).
* Convert this to millimeters per second before passing to RaceStatus.
*/
private double windSpeed;
private double windDirDegrees;
private double windDir;
private int changeWind = 4;
private static final int windUpperBound = 235;
private static final int windLowerBound = 215;
/**
* Constructs a race object with a given RaceDataSource and sends events to the given mockOutput.
* @param raceData Data source for race related data (boats, legs, etc...).
* @param mockOutput The mockOutput to send events to.
*/
public Race(RaceDataSource raceData, MockOutput mockOutput) {
this.mockOutput = mockOutput;
this.boats = FXCollections.observableArrayList(raceData.getBoats());
this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks());
this.boundary = raceData.getBoundary();
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
this.legs = raceData.getLegs();
this.legs.add(new Leg("Finish", this.legs.size()));
this.raceId = raceData.getRaceId();
//The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another.
this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor);
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
this.raceType = RaceTypeEnum.FLEET_RACE;
this.windSpeed = 12;
this.windDirection = Bearing.fromDegrees(180);
}
/**
* Runnable for the thread.
*/
public void run() {
initialiseBoats();
initialiseWindDir();
countdownTimer.start();
}
/**
* Parse the compound marker boats through mock output.
*/
private void parseMarks() {
for (CompoundMark compoundMark : this.compoundMarks) {
//Get the individual marks from the compound mark.
Mark mark1 = compoundMark.getMark1();
Mark mark2 = compoundMark.getMark2();
//If they aren't null, parse them (some compound marks only have one mark).
if (mark1 != null) {
this.parseIndividualMark(mark1);
}
if (mark2 != null) {
this.parseIndividualMark(mark2);
}
}
}
/**
* Parses an individual marker boat, and sends it to mockOutput.
* @param mark The marker boat to parse.
*/
private void parseIndividualMark(Mark mark) {
this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime);
}
/**
* Parse the boats in the race, and send it to mockOutput.
*/
private void parseBoatLocations() {
//Parse each boat.
for (Boat boat : this.boats) {
this.parseIndividualBoatLocation(boat);
}
}
/**
* Parses an individual boat, and sends it to mockOutput.
* @param boat The boat to parse.
*/
private void parseIndividualBoatLocation(Boat boat) {
this.mockOutput.parseBoatLocation(
boat.getSourceID(),
boat.getCurrentPosition().getLatitude(),
boat.getCurrentPosition().getLongitude(),
boat.getBearing().degrees(),
boat.getCurrentSpeed(),
startTime + totalTimeElapsed
);
}
/**
* Updates the race status enumeration based on the current time, in milliseconds.
* @param currentTime The current time, in milliseconds.
*/
private void updateRaceStatusEnum(long currentTime) {
//The amount of milliseconds until the race starts.
long timeToStart = this.startTime - currentTime;
//Scale the time to start based on the scale factor.
long timeToStartScaled = timeToStart / this.scaleFactor;
if (timeToStartScaled > Constants.RacePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
} else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) {
//Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
} else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) {
//Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
} else {
//Otherwise, the race has started!
this.setRaceStatusEnum(RaceStatusEnum.STARTED);
}
}
/**
* Parses the race status, and sends it to mockOutput.
*/
private void parseRaceStatus() {
//A race status message contains a list of boat statuses.
List<BoatStatus> boatStatuses = new ArrayList<>();
//Add each boat status to the status list.
for (Boat boat : boats) {
BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime());
boatStatuses.add(boatStatus);
}
//TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue.
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees());
int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond);
//Create race status object, and send it.
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses);
mockOutput.parseRaceStatus(raceStatus);
}
/**
* Sets the status of all boats in the race to RACING.
*/
private void setBoatsStatusToRacing() {
for (Boat boat : this.boats) {
boat.setStatus(BoatStatusEnum.RACING);
}
}
/**
* Countdown timer until race starts.
*/
protected AnimationTimer countdownTimer = new AnimationTimer() {
long currentTime = System.currentTimeMillis();
@Override
public void handle(long arg0) {
//Update the race status based on the current time.
updateRaceStatusEnum(this.currentTime);
//Parse the boat locations.
parseBoatLocations();
//Parse the marks.
parseMarks();
// Change wind direction
changeWindDir();
//Parse the race status.
parseRaceStatus();
if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
System.setProperty("javafx.animation.fullspeed", "true");
setBoatsStatusToRacing();
raceTimer.start();
this.stop();
}
//Update the animations timer's time.
currentTime = System.currentTimeMillis();
}
};
/**
* Timer that runs for the duration of the race, until all boats finish.
*/
private AnimationTimer raceTimer = new AnimationTimer() {
/**
* Start time of loop, in milliseconds.
*/
long timeRaceStarted = System.currentTimeMillis();
/**
* The time of the previous frame, in milliseconds.
*/
long lastFrameTime = timeRaceStarted;
@Override
public void handle(long arg0) {
//Get the current time.
long currentTime = System.currentTimeMillis();
//Update the total elapsed time.
totalTimeElapsed = currentTime - this.timeRaceStarted;
//As long as there is at least one boat racing, we still simulate the race.
if (getNumberOfActiveBoats() != 0) {
//Get the time period of this frame.
long framePeriod = currentTime - lastFrameTime;
//We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues.
framePeriod = 20;
//For each boat, we update its position, and generate a BoatLocationMessage.
for (Boat boat : boats) {
//If it is still racing, update its position.
if (boat.getStatus() == BoatStatusEnum.RACING) {
updatePosition(boat, framePeriod, totalTimeElapsed);
}
}
} else {
//Otherwise, the race is over!
raceFinished.start();
setRaceStatusEnum(RaceStatusEnum.FINISHED);
this.stop();
}
if (getNumberOfActiveBoats() != 0) {
// Change wind direction
changeWindDir();
//Parse the boat locations.
parseBoatLocations();
//Parse the marks.
parseMarks();
//Parse the race status.
parseRaceStatus();
//Update the last frame time.
this.lastFrameTime = currentTime;
}
}
};
/**
* Broadcast that the race has finished.
*/
protected AnimationTimer raceFinished = new AnimationTimer(){
int iters = 0;
@Override
public void handle(long now) {
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>());
mockOutput.parseRaceStatus(raceStatus);
if (iters > 500){
mockOutput.stop();
stop();
}
iters++;
}
};
/**
* Initialise the boats in the race.
* This sets their starting positions and current legs.
*/
public void initialiseBoats() {
//Gets the starting positions of the boats.
List<GPSCoordinate> startingPositions = getSpreadStartingPositions();
//Get iterators for our boat and position lists.
Iterator<Boat> boatIt = this.boats.iterator();
Iterator<GPSCoordinate> startPositionIt = startingPositions.iterator();
//Iterate over the pair of lists.
while (boatIt.hasNext() && startPositionIt.hasNext()) {
//Get the next boat and position.
Boat boat = boatIt.next();
GPSCoordinate startPosition = startPositionIt.next();
//The boat starts on the first leg of the race.
boat.setCurrentLeg(this.legs.get(0));
//Boats start with 0 knots speed.
boat.setCurrentSpeed(0d);
//Place the boat at its starting position.
boat.setCurrentPosition(startPosition);
//Boats start facing their next marker.
boat.setBearing(boat.calculateBearingToNextMarker());
//Sets the boats status to prestart - it changes to racing when the race starts.
boat.setStatus(BoatStatusEnum.PRESTART);
//We set a large time since tack change so that it calculates a new VMG when the simulation starts.
boat.setTimeSinceTackChange(999999);
}
}
/**
* Creates a list of starting positions for the different boats, so they do not appear cramped at the start line.
*
* @return A list of starting positions.
*/
public List<GPSCoordinate> getSpreadStartingPositions() {
//The first compound marker of the race - the starting gate.
CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark();
//The position of the two markers from the compound marker.
GPSCoordinate mark1Position = compoundMark.getMark1Position();
GPSCoordinate mark2Position = compoundMark.getMark2Position();
//Calculates the azimuth between the two points.
Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position);
//Calculates the distance between the two points.
double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position);
//The number of boats in the race.
int numberOfBoats = this.boats.size();
//Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks.
double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1);
//List to store coordinates in.
List<GPSCoordinate> positions = new ArrayList<>();
//We start spacing boats out from mark 1.
GPSCoordinate position = mark1Position;
//For each boat, displace position, and store it.
for (int i = 0; i < numberOfBoats; i++) {
position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth);
positions.add(position);
}
return positions;
}
/**
* Calculates a boat's VMG.
* @param boat The boat to calculate VMG for.
* @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course.
* @return VMG for the specified boat.
*/
private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {
//Get the lower and upper acceptable bounds.
Bearing lowerAcceptableBound = bearingBounds[0];
Bearing upperAcceptableBound = bearingBounds[1];
//Find the VMG inside these bounds.
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
return bestVMG;
}
/**
* Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG.
* @param currentVMG The current VMG of the boat.
* @param potentialVMG The new VMG to test.
* @param bearingToDestination The bearing between the boat and its destination.
* @return True if the new VMG is improves velocity, false otherwise.
*/
private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) {
//Calculates the angle between the boat and its destination.
Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees());
//Calculates the angle between the new VMG and the boat's destination.
Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees());
//Calculate the boat's current velocity.
double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed();
//Calculate the potential velocity with the new VMG.
double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed();
//Return whether or not the new VMG gives better velocity.
return vmgVelocity > currentVelocity;
}
/**
* Determines whether or not a given VMG improves the velocity of a boat.
* @param boat The boat to test.
* @param vmg The new VMG to test.
* @return True if the new VMG is improves velocity, false otherwise.
*/
private boolean improvesVelocity(Boat boat, VMG vmg) {
//Get the boats "current" VMG.
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
//Check if the new VMG is better than the boat's current VMG.
return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker());
}
/**
* Calculates the distance a boat has travelled and updates its current position according to this value.
*
* @param boat The boat to be updated.
* @param updatePeriodMilliseconds The time, in milliseconds, since the last update.
* @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race.
*/
protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
//Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg());
if (!finish) {
//Calculates the distance travelled, in meters, in the current timeslice.
double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);
//Scale it.
distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
//Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);
//Only get a new VMG if the boat will go outside the course, or X seconds have elapsed.
boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition());
long tackPeriod = 15000;
if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) {
//Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course.
Bearing[] bearingBounds = this.calculateBearingBounds(boat);
//Calculate the new VMG.
VMG newVMG = this.calculateVMG(boat, bearingBounds);
//If the new vmg improves velocity, use it.
if (improvesVelocity(boat, newVMG)) {
boat.setVMG(newVMG);
} else {
//We also need to use the new VMG if our current bearing will take us out of the course.
if (!willStayInsideCourse) {
boat.setVMG(newVMG);
}
}
}
this.updateEstimatedTime(boat);
//Check the boats position (update leg and stuff).
this.checkPosition(boat, totalTimeElapsed);
}
}
/**
* Calculates the upper and lower bounds that the boat may have in order to not go outside of the course.
* @param boat The boat to check.
* @return An array of bearings. The first is the lower bound, the second is the upper bound.
*/
private Bearing[] calculateBearingBounds(Boat boat) {
Bearing[] bearings = new Bearing[2];
Bearing lowerBearing = Bearing.fromDegrees(0.001);
Bearing upperBearing = Bearing.fromDegrees(359.999);
double lastAngle = -1;
boolean lastAngleWasGood = false;
//Check all bearings between [0, 360).
for (double angle = 0; angle < 360; angle += 1) {
//Create bearing from angle.
Bearing bearing = Bearing.fromDegrees(angle);
//Check that if it is acceptable.
boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition());
if (lastAngle != -1) {
if (lastAngleWasGood && !bearingIsGood) {
//We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing.
upperBearing = Bearing.fromDegrees(lastAngle);
}
if (!lastAngleWasGood && bearingIsGood) {
//We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing.
lowerBearing = Bearing.fromDegrees(angle);
}
}
lastAngle = angle;
lastAngleWasGood = bearingIsGood;
}
//TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001)
bearings[0] = lowerBearing;
bearings[1] = upperBearing;
return bearings;
}
/**
* Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries.
* @param bearing The bearing to check.
* @param position The position to start from.
* @return True if the bearing would keep the boat in the course, false if it would take it out of the course.
*/
private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) {
//Get azimuth from bearing.
Azimuth azimuth = Azimuth.fromBearing(bearing);
//Tests to see if a point in front of the boat is out of bounds.
double epsilonMeters = 50d;
GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth);
//If it isn't inside the boundary, calculate new bearing.
if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) {
return true;
} else {
return false;
}
}
/**
* Checks if a boat has finished any legs, or has pulled out of race (DNF).
* @param boat The boat to check.
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
*/
protected void checkPosition(Boat boat, long timeElapsed) {
//The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10.
if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
//Boat has reached its target marker, and has moved on to a new leg.
//Calculate how much the boat overshot the marker by.
double overshootMeters = boat.calculateDistanceToNextMarker();
//Move boat on to next leg.
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg);
//Add overshoot distance into the distance travelled for the next leg.
boat.setDistanceTravelledInLeg(overshootMeters);
//Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark.
boat.setTimeSinceTackChange(999999);
//Check if the boat has finished or stopped racing.
if (this.isLastLeg(boat.getCurrentLeg())) {
//Boat has finished.
boat.setTimeFinished(timeElapsed);
boat.setCurrentSpeed(0);
boat.setStatus(BoatStatusEnum.FINISHED);
} else if (doNotFinish()) {
//Boat has pulled out of race.
boat.setTimeFinished(timeElapsed);
boat.setCurrentLeg(new Leg("DNF", -1));
boat.setCurrentSpeed(0);
boat.setStatus(BoatStatusEnum.DNF);
}
}
}
/**
* Determines whether or not a specific leg is the last leg in the race.
* @param leg The leg to check.
* @return Returns true if it is the last, false otherwse.
*/
private boolean isLastLeg(Leg leg) {
//Get the last leg.
Leg lastLeg = this.legs.get(this.legs.size() - 1);
//Check its ID.
int lastLegID = lastLeg.getLegNumber();
//Get the specified leg's ID.
int legID = leg.getLegNumber();
//Check if they are the same.
return legID == lastLegID;
}
/**
* Sets the chance each boat has of failing at a gate or marker
*
* @param chance percentage chance a boat has of failing per checkpoint.
*/
protected void setDnfChance(int chance) {
if (chance >= 0 && chance <= 100) {
dnfChance = chance;
}
}
/**
* Decides if a boat should received a DNF status.
* @return True means it should DNF, false means it shouldn't.
*/
protected boolean doNotFinish() {
Random rand = new Random();
return rand.nextInt(100) < dnfChance;
}
/**
* Returns the current race status.
* @return The current race status.
*/
public RaceStatusEnum getRaceStatusEnum() {
return raceStatusEnum;
}
/**
* Sets the current race status.
* @param raceStatusEnum The new status of the race.
*/
private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
this.raceStatusEnum = raceStatusEnum;
}
/**
* Returns the type of race this is.
* @return The type of race this is.
*/
public RaceTypeEnum getRaceType() {
return raceType;
}
/**
* Returns the number of boats that are still active in the race.
* They become inactive by either finishing or withdrawing.
* @return The number of boats still active in the race.
*/
protected int getNumberOfActiveBoats() {
int numberofActiveBoats = 0;
for (Boat boat : this.boats) {
//If the boat is currently racing, count it.
if (boat.getStatus() == BoatStatusEnum.RACING) {
numberofActiveBoats++;
}
}
return numberofActiveBoats;
}
/**
* Returns an observable list of boats in the race.
* @return List of boats in the race.
*/
public ObservableList<Boat> getBoats() {
return boats;
}
protected void initialiseWindDir(){
windDirDegrees = 225;
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
/*windDir = new Random().nextInt(65535+1);
windDir = BoatLocation.convertHeadingIntToDouble(255);*/
this.windDirection = new Bearing((int)windDir);
}
protected void changeWindDir(){
int r = new Random().nextInt(changeWind)+1;
if(r==1){
windDirDegrees = (0.5 + windDirDegrees) % 360;
} else if (r==2){
windDirDegrees = ((windDirDegrees - 0.5) + 360) % 360;///keep the degrees positive when below 0
}
if (windDirDegrees > windUpperBound){
windDirDegrees = windUpperBound;
}
if (windDirDegrees < windLowerBound){
windDirDegrees = windLowerBound;
}
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
this.windDirection = new Bearing(windDirDegrees);
}
protected void setChangeWind(int changeVal){
if (changeVal>=0){
changeWind = changeVal;
}
}
protected int getWind(){
return (int)windDir;
}
/**
* Updates the boat's estimated time to next mark if positive
* @param boat to estimate time given its velocity
*/
private void updateEstimatedTime(Boat boat) {
double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond;
if (velocityToMark > 0) {
long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark);
boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow);
}
}
}

@ -1,46 +0,0 @@
package seng302.Model;
/**
* 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;
}
}

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<BoatConfig>
<Boats>
<!--Mark Boats-->
<Boat Type="Mark" BoatName="PRO" SourceID="101" >
<GPSposition X= "-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="PIN" SourceID="102" >
<GPSposition X= "-64.855242" Y="32.293771" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="Marker1" SourceID="103" >
<GPSposition X= "-64.843983" Y="32.293039" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="WGL" SourceID="104" >
<GPSposition X= "-64.850045" Y="32.28468" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="WGR" SourceID="105" >
<GPSposition X= "-64.847591" Y="32.280164" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="LGL" SourceID="106" >
<GPSposition X= "-64.835249" Y="32.309693" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="LGR" SourceID="107" >
<GPSposition X= "-64.831785" Y="32.308046" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="FL" SourceID="108" >
<GPSposition X= "-64.839291" Y="32.317379" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="FR" SourceID="109" >
<GPSposition X= "-64.83626" Y="32.317257" Z="0"/>
</Boat>
<!--Participants-->
<Boat BoatName="Team ORACLE USA" HullNum="RG01" ShapeID="0" ShortName="USA" SourceID="121" StoweName="USA" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Land Rover BAR" HullNum="RG01" ShapeID="0" ShortName="GBR" SourceID="122" StoweName="GBR" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="SoftBank Team Japan" HullNum="RG01" ShapeID="0" ShortName="JPN" SourceID="123" StoweName="JPN" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Groupama Team France" HullNum="RG01" ShapeID="0" ShortName="FRA" SourceID="124" StoweName="FRA" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Artemis Racing" HullNum="RG01" ShapeID="0" ShortName="SWE" SourceID="125" StoweName="SWE" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Emirates Team New Zealand" HullNum="RG01" ShapeID="0" ShortName="NZL" SourceID="126" StoweName="NZL" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
</Boats>
</BoatConfig>

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="START_TIME"/>
<Participants>
<Yacht SourceID="121"/>
<Yacht SourceID="122"/>
<Yacht SourceID="123"/>
<Yacht SourceID="124"/>
<Yacht SourceID="125"/>
<Yacht SourceID="126"/>
</Participants>
<CompoundMarkSequence>
<Corner CompoundMarkID="1" SeqID="1"/>
<Corner CompoundMarkID="2" SeqID="2"/>
<Corner CompoundMarkID="4" SeqID="3"/>
<Corner CompoundMarkID="3" SeqID="4"/>
<Corner CompoundMarkID="4" SeqID="5"/>
<Corner CompoundMarkID="5" SeqID="6"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Windward Gate">
<Mark Name="WGL" SeqId="1" TargetLat="32.28468" TargetLng="-64.850045" SourceID="104"/>
<Mark Name="WGR" SeqId="2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Leeward Gate">
<Mark Name="LGL" SeqId="1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
<Mark Name="LGR" SeqId="2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
<Mark Name="FR" SeqId="2" TargetLat="32.317257" TargetLng="-64.83626" SourceID="109"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="32.313922" Lon="-64.837168" SeqID="1"/>
<Limit Lat="32.317379" Lon="-64.839291" SeqID="2"/>
<Limit Lat="32.317911" Lon="-64.836996" SeqID="3"/>
<Limit Lat="32.317257" Lon="-64.83626" SeqID="4"/>
<Limit Lat="32.304273" Lon="-64.822834" SeqID="5"/>
<Limit Lat="32.279097" Lon="-64.841545" SeqID="6"/>
<Limit Lat="32.279604" Lon="-64.849871" SeqID="7"/>
<Limit Lat="32.289545" Lon="-64.854162" SeqID="8"/>
<Limit Lat="32.290198" Lon="-64.858711" SeqID="9"/>
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>
</CourseLimit>
</Race>

@ -1,10 +0,0 @@
<RegattaConfig>
<RegattaID>3</RegattaID>
<RegattaName>New Zealand Test</RegattaName>
<CourseName>North Head</CourseName>
<CentralLatitude>-36.82791529</CentralLatitude>
<CentralLongitude>174.81218919</CentralLongitude>
<CentralAltitude>0.00</CentralAltitude>
<UtcOffset>12</UtcOffset>
<MagneticVariation>14.1</MagneticVariation>
</RegattaConfig>

@ -1,8 +0,0 @@
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

@ -1,38 +0,0 @@
package seng302;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class App extends Application {
/**
* Entry point for running the programme
*
* @param args for starting the programme
*/
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) throws Exception {
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
Platform.exit();
System.exit(0);
}
});
FXMLLoader loader = new FXMLLoader(getClass().getResource("/scenes/main.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 1200, 800);
stage.setScene(scene);
stage.setTitle("RaceVision - Team 7");
stage.show();
}
}

@ -1,97 +0,0 @@
package seng302.Controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import seng302.RaceConnection;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Controls the connection that the VIsualiser can connect to.
*/
public class ConnectionController extends Controller {
@FXML
private AnchorPane connectionWrapper;
@FXML
private TableView connectionTable;
@FXML
private TableColumn<RaceConnection, String> hostnameColumn;
@FXML
private TableColumn<RaceConnection, String> statusColumn;
@FXML
private Button connectButton;
@FXML
private TextField urlField;
@FXML
private TextField portField;
private ObservableList<RaceConnection> connections;
@Override
public void initialize(URL location, ResourceBundle resources) {
// TODO - replace with config file
connections = FXCollections.observableArrayList();
connections.add(new RaceConnection("livedata.americascup.com", 4941));
connections.add(new RaceConnection("localhost", 4942));
connectionTable.setItems(connections);
hostnameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
connectionTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> {
if (curr != null && ((RaceConnection)curr).check()) connectButton.setDisable(false);
else connectButton.setDisable(true);
});
connectButton.setDisable(true);
}
/**
* Sets current status of all connections.
*/
public void checkConnections() {
for(RaceConnection connection: connections) {
connection.check();
}
}
public AnchorPane startWrapper(){
return connectionWrapper;
}
/**
* Connects to host currently selected in table. Button enabled only if host is ready.
*/
public void connectSocket() {
try{
RaceConnection connection = (RaceConnection)connectionTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
connectionWrapper.setVisible(false);
parent.enterLobby(socket);
} catch (IOException e) { /* Never reached */ }
}
/**
* adds a new connection
*/
public void addConnection(){
String hostName = urlField.getText();
String portString = portField.getText();
try{
int port = Integer.parseInt(portString);
connections.add(new RaceConnection(hostName, port));
}catch(NumberFormatException e){
System.err.println("Port number entered is not a number");
}
}
}

@ -1,32 +0,0 @@
package seng302.Controllers;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Controller parent for app controllers.
* Created by fwy13 on 15/03/2017.
*/
public abstract class Controller implements Initializable {
protected MainController parent;
/**
* Sets the parent of the application
*
* @param parent controller
*/
public void setParent(MainController parent) {
this.parent = parent;
}
/**
* Initialisation class that is run on start up.
*
* @param location resources location
* @param resources resources bundle
*/
@Override
public abstract void initialize(URL location, ResourceBundle resources);
}

@ -1,64 +0,0 @@
package seng302.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import seng302.Model.Boat;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Finish Screen for when the race finishs.
*/
public class FinishController extends Controller {
@FXML
AnchorPane finishWrapper;
@FXML
TableView<Boat> boatInfoTable;
@FXML
TableColumn<Boat, String> boatRankColumn;
@FXML
TableColumn<Boat, String> boatNameColumn;
@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());
boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty());
raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
@Override
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);
}
}

@ -1,60 +0,0 @@
package seng302.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
import seng302.Model.Boat;
import seng302.Model.RaceClock;
import seng302.VisualiserInput;
import java.net.Socket;
import java.net.URL;
import java.util.ResourceBundle;
/**
* 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;
@FXML private RaceController raceController;
@FXML private ConnectionController connectionController;
@FXML private FinishController finishController;
public void beginRace(VisualiserInput visualiserInput, RaceClock raceClock) {
raceController.startRace(visualiserInput, raceClock);
}
public void enterLobby(Socket socket) {
startController.enterLobby(socket);
}
public void enterFinish(ObservableList<Boat> boats) { finishController.enterFinish(boats); }
/**
* Main Controller for the applications will house the menu and the displayed pane.
*
* @param location of resources
* @param resources bundle
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
startController.setParent(this);
raceController.setParent(this);
connectionController.setParent(this);
finishController.setParent(this);
AnchorPane.setTopAnchor(startController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(startController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(connectionController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(connectionController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(connectionController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(connectionController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setBottomAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setLeftAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setRightAnchor(finishController.finishWrapper, 0.0);
}
}

@ -1,200 +0,0 @@
package seng302.Controllers;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import seng302.Mock.StreamedRace;
import seng302.Model.*;
import seng302.VisualiserInput;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by fwy13 on 15/03/2017.
*/
public class RaceController extends Controller {
private ResizableRaceCanvas raceMap;
private ResizableRaceMap raceBoundaries;
private RaceClock raceClock;
private Sparkline sparkline;
private int legNum;
@FXML GridPane canvasBase;
@FXML Pane arrow;
@FXML SplitPane race;
@FXML StackPane arrowPane;
@FXML Label timer;
@FXML Label FPS;
@FXML Label timeZone;
@FXML CheckBox showFPS;
@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 LineChart<Number, Number> sparklineChart;
@FXML AnchorPane annotationPane;
/**
* Updates the ResizableRaceCanvas (raceMap) with most recent data
*
* @param boats boats that are to be displayed in the race
* @param boatMarkers Markers for boats
* @see ResizableRaceCanvas
*/
public void updateMap(ObservableList<Boat> boats, ObservableList<Marker> boatMarkers) {
raceMap.setBoats(boats);
raceMap.setBoatMarkers(boatMarkers);
raceMap.update();
raceBoundaries.draw();
//stop if the visualiser is no longer running
}
/**
* Updates the array listened by the TableView (boatInfoTable) that displays the boat information.
*
* @param race Race to listen to.
*/
public void setInfoTable(StreamedRace race) {
boatInfoTable.setItems(race.getStartingBoats());
boatTeamColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
boatSpeedColumn.setCellValueFactory(cellData -> cellData.getValue().getVelocityProp());
boatMarkColumn.setCellValueFactory(cellData -> cellData.getValue().getCurrentLegName());
boatPlacingColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty());
}
@Override
public void initialize(URL location, ResourceBundle resources) {
//listener for fps
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
/**
* Creates and sets initial display for Sparkline for race positions.
* @param boats boats to display on the sparkline
*/
public void createSparkLine(ObservableList<Boat> boats){
sparkline = new Sparkline(boats, legNum, sparklineChart);
}
/**
* Updates the sparkline to display current boat positions.
* @param boatsInRace used for current boat positions.
*/
public void updateSparkline(ObservableList<Boat> boatsInRace){
sparkline.updateSparkline(boatsInRace);
}
/**
* Initializes and runs the race, based on the user's chosen scale factor
* Currently uses an example racecourse
*
* @param visualiserInput input from network
* @param raceClock The RaceClock to use for the race's countdown/elapsed duration + timezone.
*/
public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) {
legNum = visualiserInput.getCourse().getLegs().size()-1;
makeArrow();
raceMap = new ResizableRaceCanvas(visualiserInput.getCourse());
raceMap.setMouseTransparent(true);
raceMap.widthProperty().bind(canvasBase.widthProperty());
raceMap.heightProperty().bind(canvasBase.heightProperty());
raceMap.draw();
raceMap.setVisible(true);
raceMap.setArrow(arrow.getChildren().get(0));
canvasBase.getChildren().add(0, raceMap);
raceBoundaries = new ResizableRaceMap(visualiserInput.getCourse());
raceBoundaries.setMouseTransparent(true);
raceBoundaries.widthProperty().bind(canvasBase.widthProperty());
raceBoundaries.heightProperty().bind(canvasBase.heightProperty());
raceBoundaries.draw();
raceBoundaries.setVisible(true);
canvasBase.getChildren().add(0, raceBoundaries);
race.setVisible(true);
timeZone.setText(raceClock.getTimeZone());
//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();
// set up annotation displays
new Annotations(annotationPane, raceMap);
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 fps label
*
* @param fps fps that the label will be updated to
*/
public void setFrames(String fps) {
FPS.setText((fps));
}
/**
* Set up FPS display at bottom of screen
*/
private void initializeFPS() {
showFPS.setVisible(true);
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
private void makeArrow() {
arrowPane.getChildren().add(arrow);
}
public RaceClock getRaceClock() {
return raceClock;
}
}

@ -1,212 +0,0 @@
package seng302.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import seng302.Mock.StreamedCourse;
import seng302.Model.Boat;
import seng302.Model.RaceClock;
import seng302.VisualiserInput;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;
/**
* Controller to for waiting for the race to start
*/
public class StartController extends Controller implements Observer {
@FXML private GridPane start;
@FXML private AnchorPane startWrapper;
@FXML private Label raceTitleLabel;
@FXML private Label raceStartLabel;
@FXML private TableView<Boat> boatNameTable;
@FXML private TableColumn<Boat, String> boatNameColumn;
@FXML private TableColumn<Boat, String> boatCodeColumn;
@FXML private Label timeZoneTime;
@FXML private Label timer;
@FXML private Label raceStatusLabel;
//@FXML Button fifteenMinButton;
private RaceClock raceClock;
private StreamedCourse raceData;
private int raceStat;
private VisualiserInput visualiserInput;
///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.
countdownTimer();
}
@Override
public void initialize(URL location, ResourceBundle resources){
raceData = new StreamedCourse();
raceData.addObserver(this);
}
public AnchorPane startWrapper(){
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);
boatNameTable.setItems(observableBoats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
boatCodeColumn.setCellValueFactory(new PropertyValueFactory<>("abbrev"));
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
raceStat = visualiserInput.getRaceStatus().getRaceStatus();
raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus());
if (raceStat==2 || raceStat == 3) {
stop();
startWrapper.setVisible(false);
start.setVisible(false);
parent.beginRace(visualiserInput, raceClock);
}
}
}.start();
}
/**
* Sets the clock that displays the time of at the current race venue.
*/
private void setRaceClock() {
raceClock = new RaceClock(raceData.getZonedDateTime());
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()));
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())
);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof StreamedCourse) {
StreamedCourse streamedCourse = (StreamedCourse) o;
if(streamedCourse.hasReadBoats()) {
initialiseTables();
}
if(streamedCourse.hasReadRegatta()) {
Platform.runLater(() -> raceTitleLabel.setText(streamedCourse.getRegattaName()));
}
if (streamedCourse.hasReadCourse()) {
Platform.runLater(() -> {
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();
}
});
}
//TODO this is a somewhat temporary fix for when not all of the race data (boats, course, regatta) is received in time.
//Previously, startRaceNoScaling was called in the enterLobby function after the visualiserInput was started, but when connecting to the official data source it sometimes didn't send all of the race data, causing startRaceNoScaling to start, even though we didn't have enough information to start it.
if (streamedCourse.hasReadBoats() && streamedCourse.hasReadCourse() && streamedCourse.hasReadRegatta()) {
Platform.runLater(() -> {
if (!this.hasRaceStarted) {
if(visualiserInput.getRaceStatus() == null) {
}
else {
this.hasRaceStarted = true;
startRaceNoScaling();
}
}
});
}
}
}
/**
* Show starting information for a race given a socket.
* @param socket network source of information
*/
public void enterLobby(Socket socket) {
startWrapper.setVisible(true);
try {
visualiserInput = new VisualiserInput(socket, raceData);
new Thread(visualiserInput).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -1,74 +0,0 @@
package seng302;
/**
* GPS Coordinate for the world map.
* It is converted to a {@link seng302.GraphCoordinate GraphCoordinate} via
* the {@link seng302.RaceMap RaceMap} to display objects in their relative
* positions on the {@link seng302.Model.ResizableRaceMap ResizableRaceMap}.
*/
public class GPSCoordinate {
private final double latitude;
private final double longitude;
/**
* Constructor Method
*
* @param latitude latitude the coordinate is located at.
* @param longitude Longitude that the coordinate is located at.
*/
public GPSCoordinate(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
/**
* Gets the Latitude that the Coordinate is at.
*
* @return Returns the latitude of the Coordinate.
*/
public double getLatitude() {
return latitude;
}
/**
* Gets the Longitude that the Coordinate is at.
*
* @return Returns the longitude of the Coordinate.
*/
public double getLongitude() {
return longitude;
}
/**
* To String method of the Coordinate in the form Latitude: $f, Longitude: $f.
*
* @return A String representation of the GPSCoordinate Class.
*/
public String toString() {
return String.format("Latitude: %f, Longitude: %f", latitude, longitude);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GPSCoordinate that = (GPSCoordinate) o;
if (Math.abs(this.latitude - latitude) > 1e-6) return false;
return (Math.abs(this.longitude - longitude) < 1e-6);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(latitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(longitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}

@ -1,42 +0,0 @@
package seng302;
/**
* It is a coordinate representing a location on the
* {@link seng302.Model.ResizableRaceMap ResizableRaceMap}.
* It has been converted from a {@link seng302.GPSCoordinate GPSCoordinate}
* to display objects in their relative positions.
*/
public class GraphCoordinate {
private final int x;
private final int y;
/**
* Constructor method.
*
* @param x X coordinate.
* @param y Y coordinate.
*/
public GraphCoordinate(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Returns the X coordinate.
*
* @return x axis Coordinate.
*/
public int getX() {
return x;
}
/**
* Returns the Y coordinate.
*
* @return y axis Coordinate.
*/
public int getY() {
return y;
}
}

@ -1,157 +0,0 @@
package seng302.Mock;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import seng302.Model.Boat;
import seng302.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* XML Reader that reads in a file and initializes
* {@link seng302.Mock.StreamedBoat StreamedBoat}s that will be participating
* in a race.
*/
public class BoatXMLReader extends XMLReader {
private final Map<Integer, StreamedBoat> streamedBoatMap = new HashMap<>();
private Map<Integer, StreamedBoat> participants = new HashMap<>();
/**
* Constructor for Boat XML Reader
* @param filePath path of the file
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
this(filePath, true);
}
/**
* Constructor for Boat XML Reader
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
if (read) {
read();
}
}
/**
* Constructor for Boat XML Reader
* @param xmlString sting to read
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException {
super(xmlString);
read();
}
public void read() {
readSettings();
readShapes();
readBoats();
}
/**
* Reads boats settings.
* INFORMATION FROM HERE IS IGNORED FOR NOW
*/
private void readSettings() {
}
/**
* Reads different kinds of boat.
* INFORMATION FROM HERE IS IGNORED FOR NOW
*/
private void readShapes() {
}
/**
* Reads the boats in the race
*/
private void readBoats() {
Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0);
for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) {
Node boat = nBoats.getChildNodes().item(i);
if (boat.getNodeName().equals("Boat") && boat.getAttributes().getNamedItem("Type").getTextContent().equals("Yacht")) {
readSingleBoat(boat);
}
}
}
/**
* 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;
String country = null;
int sourceID = Integer.parseInt(boat.getAttributes().getNamedItem("SourceID").getTextContent());
String boatName = boat.getAttributes().getNamedItem("BoatName").getTextContent();
String shortName = boat.getAttributes().getNamedItem("ShortName").getTextContent();
if (exists(boat, "Country")) country = boat.getAttributes().getNamedItem("Country").getTextContent();
// Ignore all non participating boats
if (participants.containsKey(sourceID)) {
if (!streamedBoatMap.containsKey(sourceID)) {
if (country != null) {
streamedBoat = new StreamedBoat(sourceID, boatName, country);
} else {
streamedBoat = new StreamedBoat(sourceID, boatName, shortName);
}
streamedBoatMap.put(sourceID, streamedBoat);
// Override boat with new boat
participants.put(sourceID, streamedBoat);
}
for (int i = 0; i < boat.getChildNodes().getLength(); i++) {
Node GPSposition = boat.getChildNodes().item(i);
if (GPSposition.getNodeName().equals("GPSposition"))
readBoatPositionInformation(sourceID, GPSposition);
}
}
}
/**
* 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;
}
public List<Boat> getBoats() {
return new ArrayList<>(streamedBoatMap.values());
}
}

@ -1,166 +0,0 @@
package seng302.Mock;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.GPSCoordinate;
import seng302.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by jjg64 on 19/04/17.
*/
public class RegattaXMLReader extends XMLReader {
private int regattaID;
private String regattaName;
private int raceID = 0;
private String courseName;
private double centralLatitude;
private double centralLongitude;
private double centralAltitude;
private float utcOffset;
private float magneticVariation;
/**
* Constructor for Regatta XML
*
* @param filePath path of the file
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public RegattaXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
this(filePath, true);
}
/**
* Constructor for Regatta XML
*
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
private RegattaXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
if (read) {
read();
}
}
/**
* 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();
}
/**
* Read the XML
*/
private void read() {
NodeList attributeConfig = doc.getElementsByTagName("RegattaConfig");
Element attributes = (Element) attributeConfig.item(0);
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");
this.courseName = getTextValueOfNode(attributes, "CourseName");
this.centralLatitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLatitude"));
this.centralLongitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLongitude"));
this.centralAltitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralAltitude"));
this.utcOffset = Float.parseFloat(getTextValueOfNode(attributes, "UtcOffset"));
this.magneticVariation = Float.parseFloat(getTextValueOfNode(attributes, "MagneticVariation"));
}
public int getRegattaID() {
return regattaID;
}
public void setRegattaID(int ID) {
this.regattaID = ID;
}
public String getRegattaName() {
return regattaName;
}
public void setRegattaName(String regattaName) {
this.regattaName = regattaName;
}
public int getRaceID() {
return raceID;
}
public void setRaceID(int raceID) {
this.raceID = raceID;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public double getCentralLatitude() {
return centralLatitude;
}
public void setCentralLatitude(double centralLatitude) {
this.centralLatitude = centralLatitude;
}
public double getCentralLongitude() {
return centralLongitude;
}
public void setCentralLongitude(double centralLongitude) {
this.centralLongitude = centralLongitude;
}
public double getCentralAltitude() {
return centralAltitude;
}
public void setCentralAltitude(double centralAltitude) {
this.centralAltitude = centralAltitude;
}
public float getUtcOffset() {
return utcOffset;
}
public void setUtcOffset(float utcOffset) {
this.utcOffset = utcOffset;
}
public float getMagneticVariation() {
return magneticVariation;
}
public void setMagneticVariation(float magneticVariation) {
this.magneticVariation = magneticVariation;
}
public GPSCoordinate getGPSCoordinate() {
return new GPSCoordinate(centralLatitude, centralLongitude);
}
}

@ -1,36 +0,0 @@
package seng302.Mock;
import seng302.Model.Boat;
/**
* Boats that are been received.
*/
public class StreamedBoat extends Boat {
private final int sourceID;
private boolean dnf = false;
private void init() {
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;
this.init();
}
public StreamedBoat(int sourceID) {
this(sourceID, "None", "None");
}
public int getSourceID() {
return sourceID;
}
}

@ -1,106 +0,0 @@
package seng302.Mock;
import seng302.GPSCoordinate;
import seng302.Model.Boat;
import seng302.Model.Leg;
import seng302.Model.Marker;
import seng302.RaceDataSource;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Observable;
/**
* COurse that the is being received.
*/
public class StreamedCourse extends Observable implements RaceDataSource {
private StreamedCourseXMLReader streamedCourseXMLReader = null;
private BoatXMLReader boatXMLReader = null;
private RegattaXMLReader regattaXMLReader = null;
private double windDirection = 0;
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) {
this.boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
boatXMLReader.read();
}
setChanged();
notifyObservers();
}
public StreamedCourseXMLReader getStreamedCourseXMLReader() {
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) {
boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
boatXMLReader.read();
}
}
/**
* 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();
notifyObservers();
}
public void setWindDirection(double windDirection) {
this.windDirection = windDirection;
}
public double getWindDirection() {
return windDirection;
}
public boolean hasReadRegatta() { return regattaXMLReader != null; }
public boolean hasReadBoats() { return boatXMLReader != null; }
public boolean hasReadCourse() { return streamedCourseXMLReader != null; }
public String getRegattaName() { return regattaXMLReader.getRegattaName(); }
public List<Boat> getBoats() {
return boatXMLReader.getBoats();
}
public List<Leg> getLegs() {
return streamedCourseXMLReader.getLegs();
}
public List<Marker> getMarkers() { return streamedCourseXMLReader.getMarkers(); }
public List<GPSCoordinate> getBoundary() {
return streamedCourseXMLReader.getBoundary();
}
public ZonedDateTime getZonedDateTime() {
return streamedCourseXMLReader.getRaceStartTime();
}
public GPSCoordinate getMapTopLeft() {
return streamedCourseXMLReader.getMapTopLeft();
}
public GPSCoordinate getMapBottomRight() {
return streamedCourseXMLReader.getMapBottomRight();
}
}

@ -1,7 +0,0 @@
package seng302.Mock;
/**
* Created by cbt24 on 25/04/17.
*/
public class StreamedCourseXMLException extends Throwable {
}

@ -1,291 +0,0 @@
package seng302.Mock;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.GPSCoordinate;
import seng302.Model.Leg;
import seng302.Model.Marker;
import seng302.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* XML Read for the Course that is being received
*/
public class StreamedCourseXMLReader extends XMLReader {
private static final double COORDINATEPADDING = 0.000;
private GPSCoordinate mapTopLeft, mapBottomRight;
private final List<GPSCoordinate> boundary = new ArrayList<>();
private final Map<Integer,Element> compoundMarks = new HashMap<>();
private final Map<Integer, StreamedBoat> participants = new HashMap<>();
private final List<Leg> legs = new ArrayList<>();
private final List<Marker> markers = new ArrayList<>();
private ZonedDateTime creationTimeDate;
private ZonedDateTime raceStartTime;
private int raceID;
private String raceType;
private boolean postpone;
/**
* Constructor for Streamed Race XML
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
* @throws StreamedCourseXMLException error
*/
public StreamedCourseXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException {
super(filePath);
if (read) {
read();
}
}
/**
* Constructor for Streamed Race XML
* @param xmlString string to read
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
* @throws StreamedCourseXMLException error
*/
public StreamedCourseXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException {
super(xmlString);
read();
}
/**
* reads
* @throws StreamedCourseXMLException error
*/
private void read() throws StreamedCourseXMLException {
readRace();
readParticipants();
readCourse();
}
/**
* reads a race
*/
private void readRace() {
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
raceType = getTextValueOfNode(settings, "RaceType");
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
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();
for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) {
int sourceID;
Node yacht = nParticipants.getChildNodes().item(i);
if (yacht.getNodeName().equals("Yacht")) {
if (exists(yacht, "SourceID")) {
sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
participants.put(sourceID, new StreamedBoat(sourceID));
}
}
}
}
/**
* reads a course
* @throws StreamedCourseXMLException error
*/
private void readCourse() throws StreamedCourseXMLException {
readCompoundMarks();
readCompoundMarkSequence();
readCourseLimit();
}
/**
* 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 {
Element nCourse = (Element) doc.getElementsByTagName("Course").item(0);
for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) {
Node compoundMark = nCourse.getChildNodes().item(i);
if(compoundMark.getNodeName().equals("CompoundMark")) {
int compoundMarkID = getCompoundMarkID((Element) compoundMark);
compoundMarks.put(compoundMarkID, (Element)compoundMark);
markers.add(getMarker(compoundMarkID));
}
}
}
/**
* Generates a Marker from the CompoundMark element with given ID.
* @param compoundMarkID index of required CompoundMark element
* @return generated Marker
* @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks
* @see seng302.Model.Marker
*/
private Marker getMarker(int compoundMarkID) throws StreamedCourseXMLException {
Element compoundMark = compoundMarks.get(compoundMarkID);
NodeList nMarks = compoundMark.getElementsByTagName("Mark");
Marker marker;
switch(nMarks.getLength()) {
case 1: marker = new Marker(getCoordinate((Element)nMarks.item(0)),getSourceId((Element)nMarks.item(0))); break;
case 2: marker = new Marker(getCoordinate((Element)nMarks.item(0)), getCoordinate((Element)nMarks.item(1)),
getSourceId((Element)nMarks.item(0)), getSourceId((Element)nMarks.item(1))); break;
default: throw new StreamedCourseXMLException();
}
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()){
return 0;
}
return Integer.parseInt(sourceId);
}
/**
* Reads "compoundMarkID" attribute of CompoundMark or Corner element
* @param element with "compoundMarkID" attribute
* @return value of "compoundMarkID" attribute
*/
private int getCompoundMarkID(Element element) {
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
}
/**
* Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID
* @param compoundMarkID unique ID for CompoundMark element
* @return value of "name" attribute
*/
private String getCompoundMarkName(int compoundMarkID) {
return compoundMarks.get(compoundMarkID).getAttribute("Name");
}
/**
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
* @throws StreamedCourseXMLException if markers cannot be resolved from CompoundMark
*/
private void readCompoundMarkSequence() throws StreamedCourseXMLException {
Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner");
Element markXML = (Element)nCorners.item(0);
Marker lastMarker = getMarker(getCompoundMarkID(markXML));
String legName = getCompoundMarkName(getCompoundMarkID(markXML));
for(int i = 1; i < nCorners.getLength(); i++) {
markXML = (Element)nCorners.item(i);
Marker currentMarker = getMarker(getCompoundMarkID(markXML));
legs.add(new Leg(legName, lastMarker, currentMarker, i-1));
lastMarker = currentMarker;
legName = getCompoundMarkName(getCompoundMarkID(markXML));
}
}
/**
* 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++) {
Node limit = nCourseLimit.getChildNodes().item(i);
if (limit.getNodeName().equals("Limit")) {
double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent());
double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent());
boundary.add(new GPSCoordinate(lat, lon));
}
}
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
}
public List<GPSCoordinate> getBoundary() {
return boundary;
}
public GPSCoordinate getMapTopLeft() {
return mapTopLeft;
}
public GPSCoordinate getMapBottomRight() {
return mapBottomRight;
}
public List<Leg> getLegs() {
return legs;
}
public List<Marker> getMarkers() { return markers; }
public Double getPadding() {
return COORDINATEPADDING;
}
public ZonedDateTime getRaceStartTime() {
return raceStartTime;
}
public int getRaceID() {
return raceID;
}
public String getRaceType() {
return raceType;
}
public boolean isPostpone() {
return postpone;
}
public Map<Integer, StreamedBoat> getParticipants() {
return participants;
}
}

@ -1,285 +0,0 @@
package seng302.Mock;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.Controllers.FinishController;
import seng302.Controllers.RaceController;
import seng302.GPSCoordinate;
import seng302.Model.Boat;
import seng302.Model.Leg;
import seng302.Model.Marker;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import seng302.VisualiserInput;
import java.util.List;
/**
* The Class used to view the race streamed.
*/
public class StreamedRace implements Runnable {
private final VisualiserInput visualiserInput;
private final ObservableList<Boat> startingBoats;
private final ObservableList<Marker> boatMarkers;
private final List<Leg> legs;
private RaceController controller;
protected FinishController finishController;
private int boatsFinished = 0;
private long totalTimeElapsed;
private int lastFPS = 20;
public StreamedRace(VisualiserInput visualiserInput, RaceController controller) {
StreamedCourse course = visualiserInput.getCourse();
this.startingBoats = FXCollections.observableArrayList(course.getBoats());
this.boatMarkers = FXCollections.observableArrayList(course.getMarkers());
this.legs = course.getLegs();
this.legs.add(new Leg("Finish", this.legs.size()));
this.controller = controller;
if (startingBoats != null && startingBoats.size() > 0) {
initialiseBoats();
}
this.visualiserInput = visualiserInput;
}
private void initialiseBoats() {
Leg officialStart = legs.get(0);
String name = officialStart.getName();
Marker endCompoundMark = officialStart.getEndMarker();
for (Boat boat : startingBoats) {
if (boat != null) {
Leg startLeg = new Leg(name, 0);
startLeg.setEndMarker(endCompoundMark);
boat.setCurrentLeg(startLeg, controller.getRaceClock());
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
}
}
}
/**
* Checks if the boat cannot finish the race
* @return True if boat cannot finish the race
*/
protected boolean doNotFinish() {
// DNF is no longer random and is now determined by a dnf packet
return false;
}
/**
* Checks the position of the boat.
*
* @param boat Boat that the position is to be updated for.
* @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) {
BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatusMessage.getBoatStatus());
int legNumber = boatStatusMessage.getLegNumber();
if (legNumber >= 1 && legNumber < legs.size()) {
if (boat.getCurrentLeg() != legs.get(legNumber)){
boat.setCurrentLeg(legs.get(legNumber), controller.getRaceClock());
legChanged = true;
}
}
if (boatStatusEnum == BoatStatusEnum.RACING) {
boat.addTrackPoint(boat.getCurrentPosition());
} else if (boatStatusEnum == BoatStatusEnum.DNF) {
boat.setDnf(true);
} else if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == raceData.getLegs().size()) {
boatsFinished++;
boat.setTimeFinished(timeElapsed);
boat.setFinished(true);
}
}
if (legChanged) {
//Update the boat display table in the GUI to reflect the leg change
updatePositions();
controller.updateSparkline(startingBoats);
}
}
/**
* Updates the boat's gps coordinates
*
* @param boat to be updated
*/
private void updatePosition(Boat boat) {
int sourceID = boat.getSourceID();
BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID);
BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID);
if(boatLocation != null) {
double lat = boatLocation.getLatitudeDouble();
double lon = boatLocation.getLongitudeDouble();
boat.setCurrentPosition(new GPSCoordinate(lat, lon));
boat.setHeading(boatLocation.getHeadingDegrees());
boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime()));
double MMPS_TO_KN = 0.001944;
boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN);
}
}
/**
* Updates the boat's gps coordinates
*
* @param mark to be updated
*/
private void updateMarker(Marker mark) {
int sourceID = mark.getSourceId1();
BoatLocation boatLocation1 = visualiserInput.getBoatLocationMessage(sourceID);
if(boatLocation1 != null) {
double lat = boatLocation1.getLatitudeDouble();
double lon = boatLocation1.getLongitudeDouble();
mark.setCurrentPosition1(new GPSCoordinate(lat, lon));
}
int sourceID2 = mark.getSourceId2();
BoatLocation boatLocation2 = visualiserInput.getBoatLocationMessage(sourceID2);
if(boatLocation2 != null) {
double lat = boatLocation2.getLatitudeDouble();
double lon = boatLocation2.getLongitudeDouble();
mark.setCurrentPosition2(new GPSCoordinate(lat, lon));
}
}
public void setController(RaceController controller) {
this.controller = controller;
}
/**
* Runnable for the thread.
*/
public void run() {
setControllerListeners();
Platform.runLater(() -> controller.createSparkLine(startingBoats));
initialiseBoats();
startRaceStream();
}
/**
* Update the calculated fps to the fps label
*
* @param fps The new calculated fps value
*/
private void updateFPS(int fps) {
Platform.runLater(() -> controller.setFrames("FPS: " + fps));
}
/**
* Starts the Race Simulation, playing the race start to finish with the timescale.
* This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
*/
private void startRaceStream() {
System.setProperty("javafx.animation.fullspeed", "true");
new AnimationTimer() {
final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
int fps = 0; //init fps value
long timeCurrent = System.currentTimeMillis(); //current time
@Override
public void handle(long arg0) {
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
//Check if the race has actually started.
if (visualiserInput.getRaceStatus().isStarted()) {
//Set all boats to started.
for (Boat boat : startingBoats) {
boat.setStarted(true);
}
}
for (Boat boat : startingBoats) {
if (boat != null && !boat.isFinished()) {
updatePosition(boat);
checkPosition(boat, totalTimeElapsed);
}
}
for (Marker mark: boatMarkers){
if (mark != null){
updateMarker(mark);
}
}
if (visualiserInput.getRaceStatus().isFinished()) {
controller.finishRace(startingBoats);
stop();
}
controller.updateMap(startingBoats, boatMarkers);
fps++;
if ((System.currentTimeMillis() - timeCurrent) > 1000) {
updateFPS(fps);
lastFPS = fps;
fps = 0;
timeCurrent = System.currentTimeMillis();
}
}
}.start();
}
/**
* Update position of boats in race, no position if on starting leg or DNF.
*/
private void updatePositions() {
FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber());
for(Boat boat: startingBoats) {
if(boat != null) {
boat.setPosition(Integer.toString(startingBoats.indexOf(boat) + 1));
if (boat.isDnf() || !boat.isStarted() || boat.getCurrentLeg().getLegNumber() < 0)
boat.setPosition("-");
}
}
}
/**
* Update call for the controller.
*/
private void setControllerListeners() {
if (controller != null) controller.setInfoTable(this);
}
/**
* Returns the boats that have started the race.
*
* @return ObservableList of Boat class that participated in the race.
* @see ObservableList
* @see Boat
*/
public ObservableList<Boat> getStartingBoats() {
return startingBoats;
}
/**
* Takes an estimated time an event will occur, and converts it to the
* number of seconds before the event will occur.
*
* @param estTimeMillis estimated time in milliseconds
* @return int difference between time the race started and the estimated time
*/
private int convertEstTime(long estTimeMillis, long currentTime) {
long estElapsedMillis = estTimeMillis - currentTime;
int estElapsedSecs = Math.round(estElapsedMillis/1000);
return estElapsedSecs;
}
}

@ -1,284 +0,0 @@
package seng302.Model;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.layout.AnchorPane;
import java.util.HashMap;
import java.util.Map;
/**
* Class that processes user selected annotation visibility options to
* display the requested information on the
* {@link seng302.Model.ResizableRaceMap ResizbleRaceMap}. These are displayed
* via the {@link seng302.Controllers.RaceController RaceController}. <br>
* Annotation options for a {@link seng302.Model.Boat Boat} include: its name,
* abbreviation, speed, the time since it passed the last
* {@link seng302.Model.Marker Marker}, estimated time to the next marker,
* and a path it has travelled made up of
* {@link seng302.Model.TrackPoint TrackPoint}s.
*/
public class Annotations {
private ResizableRaceCanvas raceMap;
// checkable objects in the anchor pane
private Map<String, CheckBox> checkBoxes = new HashMap<>();
private Map<String, Toggle> annoToggles = new HashMap<>();
// maps of selected and saved annotations
private Map<String, Boolean> importantAnno = new HashMap<>();
private Map<String, Boolean> annoShownBeforeHide = new HashMap<>();
// string values match the fx:id value of check boxes
private static String nameCheckAnno = "showName";
private static String abbrevCheckAnno = "showAbbrev";
private static String speedCheckAnno = "showSpeed";
private static String pathCheckAnno = "showBoatPath";
private static String timeCheckAnno = "showTime";
private static String estTimeCheckAnno = "showEstTime";
// string values match the fx:id value of radio buttons
private static String noBtn = "noBtn";
private static String hideBtn = "hideAnnoRBtn";
private static String showBtn = "showAnnoRBtn";
private static String partialBtn = "partialAnnoRBtn";
private static String importantBtn = "importantAnnoRBtn";
private Boolean selectShow = false;
private String buttonChecked;
private String prevBtnChecked;
@FXML Button saveAnnoBtn;
/**
* Constructor to set up and display initial annotations
* @param annotationPane javaFX pane containing annotation options
* @param raceMap the canvas to update annotation displays
*/
public Annotations(AnchorPane annotationPane, ResizableRaceCanvas raceMap){
this.raceMap = raceMap;
for (Node child : annotationPane.getChildren()) {
// collect all check boxes into a map
if (child.getClass()==CheckBox.class){
checkBoxes.put(child.getId(), (CheckBox)child);
}
// collect annotation toggle radio buttons into a map
else if (child.getClass()== RadioButton.class){
//annotationGroup.getToggles().add((RadioButton)child);
annoToggles.put(child.getId(), (RadioButton)child);
}
else if (child.getClass() == Button.class){
saveAnnoBtn = (Button)child;
}
}
initializeAnnotations();
}
/**
* Set up initial boat annotations and shows all data.
* Defines partial annotations.
* Creates listeners for when the user selects a different annotation
* visibility.
*/
public void initializeAnnotations() {
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet())
{ annoShownBeforeHide.put(checkBox.getKey(), true); }
addCheckBoxListeners();
addSaveAnnoListener();
addAnnoToggleListeners();
annoToggles.get(showBtn).setSelected(true);
}
/**
* Creates listeners for each checkbox so the annotation display is
* updated when a user selects a different level of annotation visibility.
*/
private void addCheckBoxListeners(){
//listener for show name in annotation
checkBoxes.get(nameCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoName();
storeCurrentAnnotationState(nameCheckAnno, new_val);
raceMap.update();
}
});
//listener for show abbreviation for annotation
checkBoxes.get(abbrevCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoAbbrev();
storeCurrentAnnotationState(abbrevCheckAnno, new_val);
raceMap.update();
}
});
//listener for show boat path for annotation
checkBoxes.get(pathCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleBoatPath();
storeCurrentAnnotationState(pathCheckAnno, new_val);
raceMap.update();
}
});
//listener to show speed for annotation
checkBoxes.get(speedCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoSpeed();
storeCurrentAnnotationState(speedCheckAnno, new_val);
raceMap.update();
}
});
//listener to show time for annotation
checkBoxes.get(timeCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoTime();
storeCurrentAnnotationState(timeCheckAnno, new_val);
raceMap.update();
}
});
//listener to show estimated time for annotation
checkBoxes.get(estTimeCheckAnno).selectedProperty()
.addListener((ov, old_val, new_val) -> {
if (old_val != new_val) {
raceMap.toggleAnnoEstTime();
storeCurrentAnnotationState(estTimeCheckAnno, new_val);
raceMap.update();
}
});
}
/**
* Creates a listener so the system knows when to save a users currently
* selected annotation options as important for future use.
*/
private void addSaveAnnoListener(){
//listener to save currently selected annotations as important
saveAnnoBtn.setOnAction(event -> {
importantAnno.clear();
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
importantAnno.put(checkBox.getKey(),
checkBox.getValue().isSelected());
}
});
}
/**
* Creates listeners for each visibility option so that the annotation
* display is updated when a user selects a different level of annotation
* visibility.
*/
private void addAnnoToggleListeners(){
//listener for hiding all annotations
RadioButton hideAnnoRBtn = (RadioButton)annoToggles.get(hideBtn);
hideAnnoRBtn.setOnAction((e)->{
buttonChecked = hideBtn;
selectShow = false;
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
checkBox.getValue().setSelected(false);
}
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = hideBtn;
selectShow = true;
});
//listener for showing previously visible annotations
RadioButton showAnnoRBTN = (RadioButton)annoToggles.get(showBtn);
showAnnoRBTN.setOnAction((e)->{
if (selectShow) {
buttonChecked = showBtn;
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
checkBox.getValue().setSelected(
annoShownBeforeHide.get(checkBox.getKey()));
}
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = showBtn;
}
selectShow = true;
});
//listener for showing a predetermined subset of annotations
RadioButton partialAnnoRBTN = (RadioButton)annoToggles.get(partialBtn);
partialAnnoRBTN.setOnAction((e)->{
selectShow = false;
buttonChecked = partialBtn;
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
// the checkbox defaults for partial annotations
if (checkBox.getKey().equals(abbrevCheckAnno)
|| checkBox.getKey().equals(speedCheckAnno)){
checkBox.getValue().setSelected(true);
}
else { checkBox.getValue().setSelected(false); }
}
raceMap.update();
buttonChecked = noBtn;
prevBtnChecked = partialBtn;
selectShow = true;
});
//listener for showing all saved important annotations
RadioButton importantAnnoRBTN = (RadioButton)annoToggles.get(importantBtn);
importantAnnoRBTN.setOnAction((e) ->{
selectShow = false;
buttonChecked = importantBtn;
if (importantAnno.size()>0){
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
checkBox.getValue().setSelected
(importantAnno.get(checkBox.getKey()));
}
}
buttonChecked = noBtn;
prevBtnChecked = importantBtn;
selectShow = true;
});
}
/**
* Updates the current state of an annotation so that when a user
* deselects the hidden visibility option, the previous annotations will
* be displayed again.
* @param dictionaryAnnotationKey annotation checkbox
* @param selected boolean value representing the state of the checkbox
*/
private void storeCurrentAnnotationState(String dictionaryAnnotationKey, boolean selected){
if (buttonChecked != hideBtn) {
//if we are checking the box straight out of hide instead of using the radio buttons
annoShownBeforeHide.put(dictionaryAnnotationKey, selected);
if (prevBtnChecked == hideBtn && buttonChecked == noBtn){
storeCurrentAnnotationDictionary();
}
if (buttonChecked == noBtn) {
selectShow = false;
annoToggles.get(showBtn).setSelected(true);
}
}
}
/**
* Stores all current annotation states so that when a user
* deselects the hidden visibility option, the previous annotations will
* be displayed again.
*/
private void storeCurrentAnnotationDictionary(){
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
annoShownBeforeHide.put(checkBox.getKey(),
checkBox.getValue().isSelected());
}
}
}

@ -1,272 +0,0 @@
package seng302.Model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
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;
/**
* This class is used to represent and store information about a boat which may
* travel around in a race. It is displayed on the
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas} via the
* {@link seng302.Controllers.RaceController RaceController}.
*/
public class Boat {
private final StringProperty name;
protected double velocity;
private final StringProperty velocityProp;
private final String abbrev;
private GPSCoordinate currentPosition;
protected double heading;
private Leg currentLeg;
private final StringProperty currentLegName;
private boolean finished = false;
private long timeFinished;
private final StringProperty position;
private boolean started = false;
private boolean dnf = false;
private int sourceID;
private int estTime;
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.
*
* @param name Name of the Boat.
* @param velocity Speed in m/s that the boat travels at.
* @param abbrev nam abbreviation
*/
public Boat(String name, double velocity, String abbrev) {
this.velocity = velocity;
this.velocityProp = new SimpleStringProperty(String.valueOf(Math.round(velocity)));
this.abbrev = abbrev;
this.name = new SimpleStringProperty(name);
currentLegName = new SimpleStringProperty("");
position = new SimpleStringProperty("-");
}
/**
* Boat initializer which keeps all of the information of the boat.
*
* @param name Name of the Boat.
* @param abbrev nam abbreviation
*/
protected Boat(String name, String abbrev) {
this(name, 0, abbrev);
}
/**
* Returns the position of the end of the boat's wake, which is 180 degrees
* from the boat's heading, and whose length is proportional to the boat's
* speed.
*
* @return GPSCoordinate of wake endpoint.
*/
public GPSCoordinate getWake() {
double reverseHeading = getHeading() - 180;
double wakeScale = 5;
double distance = wakeScale * getVelocity();
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(
new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude())
);
calc.setDirection(reverseHeading, distance);
Point2D endpoint = calc.getDestinationGeographicPoint();
return new GPSCoordinate(endpoint.getY(), endpoint.getX());
}
/**
* Adds a new point to boat's track.
* @param coordinate of point on track
* @see seng302.Model.TrackPoint
*/
public void addTrackPoint(GPSCoordinate coordinate) {
Boolean added = System.currentTimeMillis() >= nextValidTime;
long currentTime = System.currentTimeMillis();
if (added && this.started) {
float trackPointTimeInterval = 5000;
nextValidTime = currentTime + (long) trackPointTimeInterval;
int TRACK_POINT_LIMIT = 10;
track.add(new TrackPoint(coordinate, currentTime, TRACK_POINT_LIMIT * (long) trackPointTimeInterval));
}
}
/**
* Returns the boat's sampled track between start of race and current time.
* @return queue of track points
* @see seng302.Model.TrackPoint
*/
public Queue<TrackPoint> getTrack() {
return track;
}
/**
* @return Name of the boat
*/
public StringProperty getName() {
return name;
}
/**
* Sets the boat name
*
* @param name of boat
*/
public void setName(String name) {
this.name.setValue(name);
}
/**
* @return Speed of the boat.
*/
public double getVelocity() {
return velocity;
}
/**
* Sets the speed of the boat in knots.
*
* @param velocity speed in knots
*/
public void setVelocity(double velocity) {
this.velocity = velocity;
this.velocityProp.setValue(String.valueOf(Math.round(velocity)));
}
/**
* Print method prints the name of the boat
*
* @return Name of the boat.
*/
public String toString() {
return getName().getValue();
}
/**
* @return Velocity String Property of the boat
*/
public StringProperty getVelocityProp() {
return velocityProp;
}
/**
* @return Abbreviation of the boat
*/
public String getAbbrev() {
return abbrev;
}
public GPSCoordinate getCurrentPosition() {
return currentPosition;
}
public void setCurrentPosition(GPSCoordinate currentPosition) {
this.currentPosition = currentPosition;
}
public double getHeading() {
return heading;
}
public void setHeading(double heading) {
this.heading = heading;
}
public Leg getCurrentLeg() {
return currentLeg;
}
public void setCurrentLeg(Leg currentLeg, RaceClock raceClock) {
this.currentLeg = currentLeg;
this.currentLegName.setValue(currentLeg.getName());
this.setTimeSinceLastMark(raceClock.getTime());
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public long getTimeFinished() {
return timeFinished;
}
public void setTimeFinished(long timeFinished) {
this.timeFinished = timeFinished;
}
public StringProperty positionProperty() {
return position;
}
public void setPosition(String position) {
this.position.set(position);
}
public boolean isStarted() {
return started;
}
public void setStarted(boolean started) {
this.started = started;
}
/**
* @return Name of boat's current leg
*/
public StringProperty getCurrentLegName() {
return currentLegName;
}
public int getSourceID() {
return this.sourceID;
}
public boolean isDnf() {
return dnf;
}
public void setDnf(boolean dnf) {
this.dnf = dnf;
}
public ZonedDateTime getTimeSinceLastMark() {
return timeSinceLastMark;
}
public void setTimeSinceLastMark(ZonedDateTime timeSinceLastMark) {
this.timeSinceLastMark = timeSinceLastMark;
}
public void setEstTime(int estTime) { this.estTime = estTime; }
public String getFormattedEstTime() {
if (estTime < 0) {
return " -";
}
if (estTime <= 60) {
return " " + estTime + "s";
} else {
int seconds = estTime % 60;
int minutes = (estTime - seconds) / 60;
return String.format(" %dm %ds", minutes, seconds);
}
}
}

@ -1,311 +0,0 @@
//package seng302.Model;
//
//import javafx.beans.property.SimpleStringProperty;
//import javafx.beans.property.StringProperty;
//import javafx.scene.paint.Color;
//import org.geotools.referencing.GeodeticCalculator;
//import seng302.GPSCoordinate;
//
//import java.awt.geom.Point2D;
//import java.util.Queue;
//import java.util.concurrent.ConcurrentLinkedQueue;
//
///**
// * Boat in the Race extends {@link seng302.Model.Boat Boat}.
// * The extended properties are related to the boats current race position.
// * @See seng302.Model.Boat
// */
//public class BoatInRace extends Boat {
//
// private Leg currentLeg;
// private double scaledVelocity;
// private double distanceTravelledInLeg;
// private GPSCoordinate currentPosition;
// private long timeFinished;
// private Color colour;
// private boolean finished = false;
// private final StringProperty currentLegName;
// private boolean started = false;
// private final StringProperty position;
// private double heading;
// private static final double WAKE_SCALE = 10;
//
// private final Queue<TrackPoint> track = new ConcurrentLinkedQueue<>();
// private long nextValidTime = 0;
//
// private static final float BASE_TRACK_POINT_TIME_INTERVAL = 5000;
// private static float trackPointTimeInterval = 5000; // every 1 seconds
//
// /**
// * Constructor method.
// *
// * @param name Name of the boat.
// * @param velocity Speed that the boat travels.
// * @param colour Colour the boat will be displayed as on the map
// * @param abbrev of boat
// */
// public BoatInRace(String name, double velocity, Color colour, String abbrev) {
// super(name, velocity, abbrev);
// setColour(colour);
// currentLegName = new SimpleStringProperty("");
// position = new SimpleStringProperty("-");
// }
//
// /**
// * 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).
// */
// public double calculateAzimuth() {
//
// GeodeticCalculator calc = new GeodeticCalculator();
// GPSCoordinate start = currentLeg.getStartMarker().getAverageGPSCoordinate();
// GPSCoordinate end = currentLeg.getEndMarker().getAverageGPSCoordinate();
//
// calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude());
// calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude());
//
// return calc.getAzimuth();
// }
//
// /**
// * Converts an azimuth to a bearing
// *
// * @param azimuth azimuth value to be converted
// * @return the bearings in degrees (0 to 360).
// */
// private static double calculateHeading(double azimuth) {
// if (azimuth >= 0) {
// return azimuth;
// } else {
// return azimuth + 360;
// }
// }
//
// public double getHeading() {
// return heading;
// }
//
// public void setHeading(double heading) {
// this.heading = heading;
// }
//
// /**
// * Calculates the bearing of the travel via map coordinates of the raceMarkers
// *
// * @return the direction that the boat is heading towards in degrees (0 to 360).
// */
// public double calculateHeading() {
// double azimuth = calculateAzimuth();
// return calculateHeading(azimuth);
// }
//
// /**
// * Returns the position of the end of the boat's wake, which is 180 degrees
// * from the boat's heading, and whose length is proportional to the boat's
// * speed.
// *
// * @return GPSCoordinate of wake endpoint.
// */
// public GPSCoordinate getWake() {
// double reverseHeading = getHeading() - 180;
// double distance = WAKE_SCALE * getVelocity();
//
// GeodeticCalculator calc = new GeodeticCalculator();
// calc.setStartingGeographicPoint(
// new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude())
// );
// calc.setDirection(reverseHeading, distance);
// Point2D endpoint = calc.getDestinationGeographicPoint();
// return new GPSCoordinate(endpoint.getY(), endpoint.getX());
// }
//
// /**
// * @return Scaled velocity of the boat
// */
// public double getScaledVelocity() {
// return scaledVelocity;
// }
//
// /**
// * Sets the boat's scaled velocity
// *
// * @param velocity of boat
// */
// public void setScaledVelocity(double velocity) {
// this.scaledVelocity = velocity;
// }
//
// /**
// * @return Returns the current position of the boat in a GPSCoordinate Class.
// * @see GPSCoordinate
// */
// public GPSCoordinate getCurrentPosition() {
// return currentPosition;
// }
//
// /**
// * Sets the current position on the GPS that the boat.
// *
// * @param position GPSCoordinate of the position that the boat is currently on.
// * @see GPSCoordinate
// */
// public void setCurrentPosition(GPSCoordinate position) {
// this.currentPosition = position;
// }
//
// /**
// * @return Returns the time that the boat finished the race.
// */
// public long getTimeFinished() {
// return timeFinished;
// }
//
// /**
// * Sets the time that the boat finished the race.
// *
// * @param timeFinished Time the boat finished the race.
// */
// public void setTimeFinished(long timeFinished) {
// this.timeFinished = timeFinished;
// }
//
// /**
// * @return Returns the colour of the boat.
// */
// public Color getColour() {
// return colour;
// }
//
// /**
// * Sets the colour that boat will be shown as when drawn on the ResizableRaceCanvas.
// *
// * @param colour Colour that the boat is to be set to.
// * @see ResizableRaceCanvas
// */
// private void setColour(Color colour) {
// this.colour = colour;
// }
//
// /**
// * Gets the current leg that the boat is on.
// *
// * @return returns the leg the boat is on in a Leg class
// * @see Leg
// */
// public Leg getCurrentLeg() {
// return currentLeg;
// }
//
// /**
// * Sets the boat's current leg.
// *
// * @param currentLeg Leg class that the boat is currently on.
// * @see Leg
// */
// public void setCurrentLeg(Leg currentLeg) {
// this.currentLeg = currentLeg;
// this.currentLegName.setValue(currentLeg.getName());
// }
//
// /**
// * @return Name of boat's current leg
// */
// public StringProperty getCurrentLegName() {
// return currentLegName;
// }
//
// /**
// * Gets the distance travelled by the boat in the leg.
// *
// * @return Returns the value in nautical miles (1.852km) that the boat has traversed.
// */
// public double getDistanceTravelledInLeg() {
// return distanceTravelledInLeg;
// }
//
// /**
// * Sets the distance travelled by the boat in the leg in nautical miles (1.852km)
// *
// * @param distanceTravelledInLeg Distance travelled by the boat in nautical miles.
// */
// public void setDistanceTravelledInLeg(double distanceTravelledInLeg) {
// this.distanceTravelledInLeg = distanceTravelledInLeg;
// }
//
// /**
// * @return true if boat has finished, false if not
// */
// public boolean isFinished() {
// return this.finished;
// }
//
// /**
// * Sets whether boat is finished or not
// *
// * @param bool is finished value
// */
// public void setFinished(boolean bool) {
// this.finished = bool;
// }
//
// public boolean isStarted() {
// return started;
// }
//
// public void setStarted(boolean started) {
// this.started = started;
// }
//
// public String getPosition() {
// return position.get();
// }
//
// public StringProperty positionProperty() {
// return position;
// }
//
// public void setPosition(String position) {
// this.position.set(position);
// }
//
// /**
// * Adds a new point to boat's track.
// * @param coordinate of point on track
// * @see seng302.Model.TrackPoint
// */
// public void addTrackPoint(GPSCoordinate coordinate) {
// Boolean added = System.currentTimeMillis() >= nextValidTime;
// long currentTime = System.currentTimeMillis();
// if (added && this.started) {
// nextValidTime = currentTime + (long) trackPointTimeInterval;
// int TRACK_POINT_LIMIT = 10;
// track.add(new TrackPoint(coordinate, currentTime, TRACK_POINT_LIMIT * (long) trackPointTimeInterval));
// }
// }
//
// /**
// * Returns the boat's sampled track between start of race and current time.
// * @return queue of track points
// * @see seng302.Model.TrackPoint
// */
// public Queue<TrackPoint> getTrack() {
// return track;
// }
//
// /**
// * Get base track point time interval
// * @return base track point time interval
// */
// public static float getBaseTrackPointTimeInterval() {
// return BASE_TRACK_POINT_TIME_INTERVAL;
// }
//
// /**
// * Set track point time interval
// * @param value track point time interval value
// */
// public static void setTrackPointTimeInterval(float value) {
// trackPointTimeInterval = value;
// }
//}

@ -1,208 +0,0 @@
//package seng302.Model;
//
//import org.geotools.referencing.GeodeticCalculator;
//import seng302.Constants;
//import seng302.Controllers.RaceController;
//import seng302.GPSCoordinate;
//import seng302.RaceDataSource;
//
//import java.awt.geom.Point2D;
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.List;
//import java.util.Random;
//
///**
// * Created by cbt24 on 6/03/17.
// *
// * @deprecated
// */
//public class ConstantVelocityRace extends Race {
//
// private int dnfChance = 0; //%percentage chance a boat fails at each checkpoint
//
// /**
// * Initializer for a constant velocity race without standard data source
// *
// * @param startingBoats in race
// * @param legs in race
// * @param controller for graphics
// * @param scaleFactor of timer
// */
// public ConstantVelocityRace(List<BoatInRace> startingBoats, List<Leg> legs, RaceController controller, int scaleFactor) {
// super(startingBoats, legs, controller, scaleFactor);
// }
//
// /**
// * Initializer for legacy tests
// *
// * @param startingBoats in race
// * @param legs in race
// * @param controller for graphics
// * @param scaleFactor of timer
// *
// * @deprecated Please use {@link #ConstantVelocityRace(List, List, RaceController, int) } for future tests.
// */
// public ConstantVelocityRace(BoatInRace[] startingBoats, List<Leg> legs, RaceController controller, int scaleFactor) {
// super(Arrays.asList(startingBoats), legs, controller, scaleFactor);
// }
//
// /**
// * Initializer for constant velocity race with standard data source
// * @param raceData for race
// * @param controller for graphics
// * @param scaleFactor of timer
// */
// public ConstantVelocityRace(RaceDataSource raceData, RaceController controller, int scaleFactor) {
// super(raceData, controller, scaleFactor);
// }
//
// public void initialiseBoats() {
// Leg officialStart = legs.get(0);
// String name = officialStart.getName();
// CompoundMark endMarker = officialStart.getEndCompoundMark();
//
// BoatInRace.setTrackPointTimeInterval(BoatInRace.getBaseTrackPointTimeInterval() / scaleFactor);
//
// ArrayList<CompoundMark> startMarkers = getSpreadStartingPositions();
// for (int i = 0; i < startingBoats.size(); i++) {
// BoatInRace boat = startingBoats.get(i);
// if (boat != null) {
// boat.setScaledVelocity(boat.getVelocity() * scaleFactor);
// Leg startLeg = new Leg(name, 0);
// boat.setCurrentPosition(startMarkers.get(i).getAverageGPSCoordinate());
// startLeg.setStartCompoundMark(startMarkers.get(i));
// startLeg.setEndCompoundMark(endMarker);
// startLeg.calculateDistance();
// boat.setCurrentLeg(startLeg);
// boat.setHeading(boat.calculateHeading());
// }
// }
// }
//
// /**
// * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line
// *
// * @return list of starting positions
// */
// public ArrayList<CompoundMark> getSpreadStartingPositions() {
//
// int nBoats = startingBoats.size();
// CompoundMark marker = legs.get(0).getStartCompoundMark();
//
// GeodeticCalculator initialCalc = new GeodeticCalculator();
// initialCalc.setStartingGeographicPoint(marker.getMark1().getLongitude(), marker.getMark1().getLatitude());
// initialCalc.setDestinationGeographicPoint(marker.getMark2().getLongitude(), marker.getMark2().getLatitude());
//
// double azimuth = initialCalc.getAzimuth();
// double distanceBetweenMarkers = initialCalc.getOrthodromicDistance();
// double distanceBetweenBoats = distanceBetweenMarkers / (nBoats + 1);
//
// GeodeticCalculator positionCalc = new GeodeticCalculator();
// positionCalc.setStartingGeographicPoint(marker.getMark1().getLongitude(), marker.getMark1().getLatitude());
// ArrayList<CompoundMark> positions = new ArrayList<>();
//
// for (int i = 0; i < nBoats; i++) {
// positionCalc.setDirection(azimuth, distanceBetweenBoats);
// Point2D position = positionCalc.getDestinationGeographicPoint();
// positions.add(new CompoundMark(new GPSCoordinate(position.getY(), position.getX())));
//
// positionCalc = new GeodeticCalculator();
// positionCalc.setStartingGeographicPoint(position);
// }
// return positions;
// }
//
// /**
// * Sets the chance each boat has of failing at a gate or marker
// * @param chance percentage chance a boat has of failing per checkpoint.
// */
// protected void setDnfChance(int chance) {
// if (chance >= 0 && chance <= 100) {
// dnfChance = chance;
// }
// }
//
// protected boolean doNotFinish() {
// Random rand = new Random();
// return rand.nextInt(100) < dnfChance;
// }
//
// /**
// * Calculates the distance a boat has travelled and updates its current position according to this value.
// *
// * @param boat to be updated
// * @param millisecondsElapsed since last update
// */
// protected void updatePosition(BoatInRace boat, int millisecondsElapsed) {
//
// //distanceTravelled = velocity (nm p hr) * time taken to update loop
// double distanceTravelled = (boat.getScaledVelocity() * millisecondsElapsed) / 3600000;
//
// double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg();
//
// boolean finish = boat.getCurrentLeg().getName().equals("Finish");
// if (!finish) {
// boat.setHeading(boat.calculateHeading());
// //update boat's distance travelled
// boat.setDistanceTravelledInLeg(totalDistanceTravelled);
// //Calculate boat's new position by adding the distance travelled onto the start point of the leg
// boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartCompoundMark().getAverageGPSCoordinate(),
// totalDistanceTravelled, boat.calculateAzimuth()));
// }
// }
//
// protected void checkPosition(BoatInRace boat, long timeElapsed) {
// if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) {
// //boat has passed onto new leg
// if (boat.getCurrentLeg().getName().equals("Finish")) {
// //boat has finished
// boatsFinished++;
// boat.setFinished(true);
// boat.setTimeFinished(timeElapsed);
// } else if (doNotFinish()) {
// boatsFinished++;
// boat.setFinished(true);
// boat.setCurrentLeg(new Leg("DNF", -1));
// boat.setVelocity(0);
// boat.setScaledVelocity(0);
// } else {
// //Calculate how much the boat overshot the marker by
// boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance());
// //Move boat on to next leg
// Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1);
//
// boat.setCurrentLeg(nextLeg);
// //Add overshoot distance into the distance travelled for the next leg
// boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg());
// }
// //Update the boat display table in the GUI to reflect the leg change
// updatePositions();
// }
// }
//
// /**
// * Calculates the boats next GPS position based on its distance travelled and heading
// *
// * @param oldCoordinates GPS coordinates of the boat's starting position
// * @param distanceTravelled distance in nautical miles
// * @param azimuth boat's current direction. Value between -180 and 180
// * @return The boat's new coordinate
// */
// public static GPSCoordinate calculatePosition(GPSCoordinate oldCoordinates, double distanceTravelled, double azimuth) {
//
// //Find new coordinate using current heading and distance
//
// GeodeticCalculator geodeticCalculator = new GeodeticCalculator();
// //Load start point into calculator
// Point2D startPoint = new Point2D.Double(oldCoordinates.getLongitude(), oldCoordinates.getLatitude());
// geodeticCalculator.setStartingGeographicPoint(startPoint);
// //load direction and distance tranvelled into calculator
// geodeticCalculator.setDirection(azimuth, distanceTravelled * Constants.NMToMetersConversion);
// //get new point
// Point2D endPoint = geodeticCalculator.getDestinationGeographicPoint();
//
// return new GPSCoordinate(endPoint.getY(), endPoint.getX());
// }
//
//}

@ -1,106 +0,0 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import seng302.GPSCoordinate;
/**
* This class represents a leg, which is a portion of a race between two
* {@link seng302.Model.Marker Marker}s. A leg is used to identify a
* {@link seng302.Model.Boat Boat}'s progress through a race.
*/
public class Leg {
private final String name; //nautical miles
private double distance;
private Marker startMarker;
private Marker endMarker;
private final int legNumber;
// 1 nautical mile = 1852 meters
public static final int NM_TO_METERS = 1852;
/**
* Leg Initializer
*
* @param name Name of the Leg
* @param start marker
* @param end marker
* @param number Leg's position in race
*/
public Leg(String name, Marker start, Marker end, int number) {
this.name = name;
this.startMarker = start;
this.endMarker = end;
this.legNumber = number;
calculateDistance();
}
/**
* Construction Method
*
* @param name Name of the Leg
* @param number leg number
*/
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
*/
public String getName() {
return name;
}
/**
* Get the distance in nautical miles
*
* @return Returns the total distance of the leg.
*/
public double getDistance() {
return distance;
}
/**
* Returns the leg number that the leg exists in the Race
*
* @return Returns the Leg
*/
public int getLegNumber() {
return legNumber;
}
public Marker getStartMarker() {
return startMarker;
}
public void setStartMarker(Marker startMarker) {
this.startMarker = startMarker;
}
public Marker getEndMarker() {
return endMarker;
}
public void setEndMarker(Marker endMarker) {
this.endMarker = endMarker;
}
/**
* Calculates the distance that the legs are in nautical miles (1.852 km).
*/
private void calculateDistance() {
GeodeticCalculator calc = new GeodeticCalculator();
//Load start and end of leg
GPSCoordinate startMarker = this.startMarker.getAverageGPSCoordinate();
GPSCoordinate endMarker = this.endMarker.getAverageGPSCoordinate();
calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude());
calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude());
this.distance = calc.getOrthodromicDistance() / NM_TO_METERS;
}
}

@ -1,105 +0,0 @@
package seng302.Model;
import org.geotools.referencing.GeodeticCalculator;
import seng302.GPSCoordinate;
import java.awt.geom.Point2D;
/**
* A marker on a race course that a boat can pass, to be displayed on the
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}. This is
* displayed via the {@link seng302.Controllers.RaceController RaceController}.
* <br>Markers are also used to calculate a {@link seng302.Model.Leg Leg}
* distance.
*/
public class Marker {
private final GPSCoordinate averageGPSCoordinate;
private GPSCoordinate mark1;
private GPSCoordinate mark2;
private final int sourceId1;
private final int sourceId2;
public Marker(GPSCoordinate mark1, int sourceId) {
this(mark1, mark1, sourceId, sourceId);
}
public Marker(GPSCoordinate mark1, GPSCoordinate mark2, int sourceId1, int sourceId2) {
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
this.sourceId1 = sourceId1;
this.sourceId2 = sourceId2;
}
/**
*
* @param mark1 Mark coordinates.
*/
public Marker(GPSCoordinate mark1) {
this(mark1, mark1, 0,0);
}
/**
*
* @param mark1 Mark one coordinate
* @param mark2 Mark two coordinate
*/
public Marker(GPSCoordinate mark1, GPSCoordinate mark2) {
this(mark1, mark2, 0,0);
}
public GPSCoordinate getMark1() {
return mark1;
}
public GPSCoordinate getMark2() {
return mark2;
}
/**
* Returns true if mark consists of two points, e.g. it is a gate.
* @return boolean
*/
public boolean isCompoundMark() {
return mark1 != mark2;
}
public GPSCoordinate getAverageGPSCoordinate() {
return averageGPSCoordinate;
}
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());
}
public void setCurrentPosition1(GPSCoordinate gpsCoordinate){
mark1 = gpsCoordinate;
}
public void setCurrentPosition2(GPSCoordinate gpsCoordinate){
mark2 = gpsCoordinate;
}
public int getSourceId1() {
return sourceId1;
}
public int getSourceId2() {
return sourceId2;
}
}

@ -1,133 +0,0 @@
package seng302.Model;
import com.github.bfsmith.geotimezone.TimeZoneLookup;
import com.github.bfsmith.geotimezone.TimeZoneResult;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import seng302.GPSCoordinate;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas} via the
* {@link seng302.Controllers.RaceController RaceController} and the
* {@link seng302.Controllers.StartController StartController}.
*/
public class RaceClock implements Runnable {
private long lastTime;
private final ZoneId zoneId;
private ZonedDateTime time;
private ZonedDateTime startingTime;
private final StringProperty timeString;
private StringProperty duration;
public RaceClock(ZonedDateTime zonedDateTime) {
this.zoneId = zonedDateTime.getZone();
this.timeString = new SimpleStringProperty();
this.duration = new SimpleStringProperty();
this.time = zonedDateTime;
setTime(time);
}
public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
ZoneId zone = ZoneId.of(timeZoneResult.getResult());
return LocalDateTime.now(zone).atZone(zone);
}
public void run() {
new AnimationTimer() {
@Override
public void handle(long now) {
updateTime();
}
}.start();
}
/**
* Sets time to arbitrary zoned time.
*
* @param time arbitrary time with timezone.
*/
private void setTime(ZonedDateTime time) {
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)
duration.set(String.format("Starting in: %02d:%02d:%02d", -seconds/3600, -(seconds%3600)/60, -seconds%60));
else
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
}
}
/**
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
* @param time UTC time
*/
public void setUTCTime(long time) {
Date utcTime = new Date(time);
setTime(utcTime.toInstant().atZone(this.zoneId));
}
public ZonedDateTime getStartingTime() {
return startingTime;
}
public void setStartingTime(ZonedDateTime startingTime) {
this.startingTime = startingTime;
}
/**
* Get ZonedDateTime corresponding to local time zone and given UTC time.
* @param time time in mills
* @return local date time
*/
public ZonedDateTime getLocalTime(long time) {
Date utcTime = new Date(time);
return utcTime.toInstant().atZone(this.zoneId);
}
/**
* Updates time by duration elapsed since last update.
*/
private void updateTime() {
this.time = this.time.plus(Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS));
this.lastTime = System.currentTimeMillis();
setTime(time);
}
public String getDuration() {
return duration.get();
}
public StringProperty durationProperty() {
return duration;
}
public String getTimeZone() {
return zoneId.toString();
}
public ZonedDateTime getTime() {
return time;
}
public StringProperty timeStringProperty() {
return timeString;
}
}

@ -1,55 +0,0 @@
package seng302.Model;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
/**
* The abstract class for the resizable race canvases.
*/
public abstract class ResizableCanvas extends Canvas {
protected final GraphicsContext gc;
public ResizableCanvas(){
this.gc = this.getGraphicsContext2D();
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
abstract void draw();
/**
* Set the Canvas to resizable.
*
* @return That the Canvas is resizable.
*/
@Override
public boolean isResizable() {
return true;
}
/**
* Returns the preferred width of the Canvas
*
* @param width of canvas
* @return Returns the width of the Canvas
*/
@Override
public double prefWidth(double width) {
return getWidth();
}
/**
* Returns the preferred height of the Canvas
*
* @param height of canvas
* @return Returns the height of the Canvas
*/
@Override
public double prefHeight(double height) {
return getHeight();
}
}

@ -1,393 +0,0 @@
package seng302.Model;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.transform.Rotate;
import seng302.GraphCoordinate;
import seng302.Mock.StreamedCourse;
import seng302.RaceDataSource;
import seng302.RaceMap;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.*;
/**
* This JavaFX Canvas is used to update and display details for a
* {@link seng302.RaceMap RaceMap} via the
* {@link seng302.Controllers.RaceController RaceController}.<br>
* It fills it's parent and cannot be downsized. <br>
* Details displayed include:
* {@link seng302.Model.Boat Boats} (and their
* {@link seng302.Model.TrackPoint TrackPoint}s),
* {@link seng302.Model.Marker Markers}, a
* {@link seng302.Model.RaceClock RaceClock}, a wind direction arrow and
* various user selected {@link seng302.Model.Annotations Annotations}.
*/
public class ResizableRaceCanvas extends ResizableCanvas {
private RaceMap map;
private List<Boat> boats;
private List<Marker> boatMarkers;
private boolean annoName = true;
private boolean annoAbbrev = true;
private boolean annoSpeed = true;
private boolean annoPath = true;
private boolean annoEstTime = 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();
double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude();
double lat2 = raceData.getMapBottomRight().getLatitude();
double long2 = raceData.getMapBottomRight().getLongitude();
setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight()));
this.markers = raceData.getMarkers();
makeColours();
this.raceData = raceData;
}
/**
* Sets the boats that are to be displayed in this race.
*
* @param boats in race
*/
public void setBoats(List<Boat> boats) {
this.boats = boats;
mapBoatColours();
}
/**
* Sets the boat markers that are to be displayed in this race.
*
* @param boatMarkers in race
*/
public void setBoatMarkers(ObservableList<Marker> boatMarkers) {
this.boatMarkers = boatMarkers;
}
/**
* Sets the RaceMap that the RaceCanvas is to be displaying for.
*
* @param map race map
*/
private void setMap(RaceMap map) {
this.map = map;
}
private void displayBoat(Boat boat, double angle, Color colour) {
if (boat.getCurrentPosition() != null) {
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
double[] x = {pos.getX() - 6, pos.getX(), pos.getX() + 6};
double[] y = {pos.getY() + 12, pos.getY() - 12, pos.getY() + 12};
gc.setFill(colour);
gc.save();
rotate(angle, pos.getX(), pos.getY());
gc.fillPolygon(x, y, 3);
gc.restore();
}
}
/**
* Displays a line on the map with rectangles on the starting and ending point of the line.
*
* @param graphCoordinateA Starting Point of the line in GraphCoordinate.
* @param graphCoordinateB End Point of the line in GraphCoordinate.
* @param paint Colour the line is to coloured.
* @see GraphCoordinate
* @see Color
* @see Paint
*/
private void displayLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) {
gc.setStroke(paint);
gc.setFill(paint);
gc.fillOval(graphCoordinateA.getX() - 3, graphCoordinateA.getY() - 3, 6, 6);
gc.fillOval(graphCoordinateB.getX() - 3, graphCoordinateB.getY() - 3, 6, 6);
gc.strokeLine(graphCoordinateA.getX(), graphCoordinateA.getY(), graphCoordinateB.getX(), graphCoordinateB.getY());
}
/**
* Display a point on the Canvas
*
* @param graphCoordinate Coordinate that the point is to be displayed at.
* @param paint Colour that the boat is to be coloured.
* @see GraphCoordinate
* @see Paint
* @see Color
*/
private void displayPoint(GraphCoordinate graphCoordinate, Paint paint) {
gc.setFill(paint);
gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 10, 10);
}
/**
* Displays an arrow representing wind direction on the Canvas
*
* @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up).
* @see GraphCoordinate
*/
private void displayWindArrow(double angle) {
angle = angle % 360;
// show direction wind is coming from
if (angle<180){angle = angle + 180;}
else {angle = angle - 180;}
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
*
* @param angle Bearing angle to rotate at in degrees
* @param px Pivot point x of rotation.
* @param py Pivot point y of rotation.
*/
private void rotate(double angle, double px, double py) {
Rotate r = new Rotate(angle, px, py);
gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
}
/**
* Display given name and speed of boat at a graph coordinate
*
* @param name name of the boat
* @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, String estTime, ZonedDateTime timeSinceLastMark) {
String text = "";
//Check name toggle value
if (annoName){
text += String.format("%s ", name);
}
//Check abbreviation toggle value
if (annoAbbrev){
text += String.format("%s ", abbrev);
}
//Check speed toggle value
if (annoSpeed){
text += String.format("%.2fkn ", speed);
}
if (annoEstTime) {
text += estTime;
}
//Check time since last mark toggle value
if(annoTimeSinceLastMark){
Duration timeSince = Duration.between(timeSinceLastMark, raceClock.getTime());
text += String.format(" %ds ", timeSince.getSeconds());
}
//String text = String.format("%s, %2$.2fkn", name, speed);
long xCoord = coordinate.getX() + 20;
long yCoord = coordinate.getY();
if (xCoord + (text.length() * 7) >= getWidth()) {
xCoord -= text.length() * 7;
}
if (yCoord - (text.length() * 2) <= 0) {
yCoord += 30;
}
gc.fillText(text, xCoord, yCoord);
}
/**
* Draws race map with up to date data.
*/
public void update() {
this.draw();
this.updateBoats();
}
/**
* Draw race markers
*/
private void drawMarkers() {
for(Marker marker: markers) {
GraphCoordinate mark1 = this.map.convertGPS(marker.getMark1());
// removed drawing of lines between the marks as only
// the start and finish line should have a line drawn
if(marker.isCompoundMark()) {
GraphCoordinate mark2 = this.map.convertGPS(marker.getMark2());
displayPoint(mark1, Color.LIMEGREEN);
displayPoint(mark2, Color.LIMEGREEN);
} else {
displayPoint(mark1, Color.GREEN);
}
}
}
/**
* Draws the Race Map
*/
public void draw() {
double width = getWidth();
double height = getHeight();
gc.clearRect(0, 0, width, height);
if (map == null) {
return;//TODO this should return a exception in the future
}
this.map.setHeight((int) height);
this.map.setWidth((int) width);
gc.setLineWidth(2);
updateBoats();
drawMarkers();
//display wind direction arrow - specify origin point and angle - angle now set to random angle
if (raceData instanceof StreamedCourse) {
displayWindArrow(((StreamedCourse) raceData).getWindDirection());
} else {
displayWindArrow(150);
}
}
/**
* Toggle name display in annotation
*/
public void toggleAnnoName() {
annoName = !annoName;
}
/**
* Toggle boat path display in annotation
*/
public void toggleBoatPath() {
annoPath = !annoPath;
}
public void toggleAnnoEstTime() {
annoEstTime = !annoEstTime;
}
/**
* Toggle boat time display in annotation
*/
public void toggleAnnoTime() { annoTimeSinceLastMark = !annoTimeSinceLastMark;}
/**
* Toggle abbreviation display in annotation
*/
public void toggleAnnoAbbrev() {
annoAbbrev = !annoAbbrev;
}
/**
* Toggle speed display in annotation
*/
public void toggleAnnoSpeed() {
annoSpeed = !annoSpeed;
}
/**
* Draws boats while race in progress, when leg heading is set.
*/
private void updateBoats() {
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(), boatColours.get(sourceID));
GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition());
GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake());
displayLine(wakeFrom, wakeTo, boatColours.get(sourceID));
} else if (!isStart) {
displayBoat(boat, boat.getHeading(), boatColours.get(sourceID));
} else {
displayBoat(boat, 0, boatColours.get(sourceID));
}
if (Duration.between(boat.getTimeSinceLastMark(), raceClock.getTime()).getSeconds() < 0) {
boat.setTimeSinceLastMark(raceClock.getTime());
}
//If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts.
if (boat.isStarted() == false) {
boat.setTimeSinceLastMark(raceClock.getTime());
}
displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getFormattedEstTime(), boat.getTimeSinceLastMark());
//TODO this needs to be fixed.
drawTrack(boat, boatColours.get(sourceID));
}
}
}
/**
* 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) {
for (TrackPoint point : boat.getTrack()) {
GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate());
gc.setFill(new Color(colour.getRed(), colour.getGreen(), colour.getBlue(), point.getAlpha()));
gc.fillOval(scaledCoordinate.getX(), scaledCoordinate.getY(), 5, 5);
}
}
}
/**
* makes colours
*/
private void makeColours() {
colours = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
Color.BLACK,
Color.RED,
Color.ORANGE,
Color.DARKOLIVEGREEN,
Color.LIMEGREEN,
Color.PURPLE,
Color.DARKGRAY,
Color.YELLOW
));
}
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();
}
}
}

@ -1,94 +0,0 @@
package seng302.Model;
import javafx.scene.paint.Color;
import seng302.GPSCoordinate;
import seng302.GraphCoordinate;
import seng302.RaceDataSource;
import seng302.RaceMap;
import java.util.List;
/**
* This JavaFX Canvas is used to generate the size of a
* {@link seng302.RaceMap RaceMap} using co-ordinates from a
* {@link seng302.RaceDataSource RaceDataSource}. This is done via the
* {@link seng302.Controllers.RaceController RaceController}.
*/
public class ResizableRaceMap extends ResizableCanvas {
private RaceMap map;
private final List<GPSCoordinate> raceBoundaries;
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();
double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude();
double lat2 = raceData.getMapBottomRight().getLatitude();
double long2 = raceData.getMapBottomRight().getLongitude();
setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight()));
//draw();
}
/**
* Sets the map race that it is supposed to be viewing.
* @param map the map to be set
*/
private void setMap(RaceMap map) {
this.map = map;
}
/**
* Draw boundary of the race.
*/
private void drawBoundaries() {
if (this.raceBoundaries == null) {
return;
}
gc.setFill(Color.AQUA);
setRaceBoundCoordinates();
gc.setLineWidth(1);
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()];
for (int i = 0; i < raceBoundaries.size(); i++) {
GraphCoordinate coord = map.convertGPS(raceBoundaries.get(i));
xpoints[i] = coord.getX();
ypoints[i] = coord.getY();
}
}
/**
* Draw update for the canvas
*/
public void draw(){
double width = getWidth();
double height = getHeight();
gc.clearRect(0, 0, width, height);
if (map == null) {
return;//TODO this should return a exception in the future
}
this.map.setHeight((int) height);
this.map.setWidth((int) width);
gc.setLineWidth(2);
drawBoundaries();
}
}

@ -1,179 +0,0 @@
package seng302.Model;
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.paint.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Class to process and modify a sparkline display. This display keeps visual
* track of {@link seng302.Model.Boat Boats}s in a race and their current
* placing position as they complete each {@link seng302.Model.Leg Leg} by
* passing a course {@link seng302.Model.Marker Marker}. <br>
* This sparkline is displayed using the
* {@link seng302.Controllers.RaceController RaceController}.
*/
public class Sparkline {
private ArrayList<String> colours;
private ArrayList<Boat> startBoats = new ArrayList<>();
private Map<Integer, String> boatColours = new HashMap<>();
private Integer legNum;
private Integer sparkLineNumber = 0;
@FXML LineChart<Number, Number> sparklineChart;
@FXML NumberAxis xAxis;
@FXML NumberAxis yAxis;
/**
* Constructor to set up initial sparkline (LineChart) object
* @param boats boats to display on the sparkline
* @param legNum total number of legs in the race
* @param sparklineChart javaFX LineChart for the sparkline
*/
public Sparkline(ObservableList<Boat> boats, Integer legNum,
LineChart<Number,Number> sparklineChart) {
this.sparklineChart = sparklineChart;
this.legNum = legNum;
this.yAxis = (NumberAxis)sparklineChart.getYAxis();
this.xAxis = (NumberAxis)sparklineChart.getXAxis();
startBoats.addAll(boats);
makeColours();
mapBoatColours();
createSparkline();
}
/**
* 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.
*/
public void createSparkline(){
// NOTE: Y axis is in negatives to display correct positions
// 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());
}
}
});
}
/**
* Updates the sparkline to display current boat positions.
* New points are plotted to represent each boat when required.
* @param boatsInRace current position of the boats in race
*/
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;
}
}
}
}
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 String colourToHex(Color color) {
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ) );
}
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();
}
}
}

@ -1,60 +0,0 @@
package seng302.Model;
import seng302.GPSCoordinate;
/**
* A TrackPoint is a point plotted to display the track a
* {@link seng302.Model.Boat Boat} has travelled in a race. <br>
* TrackPoints are displayed on a
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}, via the
* {@link seng302.Controllers.RaceController RaceController}. <br>
* Track points can be made visible or hidden via the RaceController's
* {@link seng302.Model.Annotations Annotations}.
*/
public class TrackPoint {
private final GPSCoordinate coordinate;
private final long timeAdded;
private final long expiry;
private final double minAlpha;
/**
* Creates a new track point with fixed GPS coordinates and time, to reach minimum opacity on expiry.
*
* @param coordinate position of point on physical race map
* @param timeAdded system clock at time of addition
* @param expiry time to minimum opacity after added
*/
public TrackPoint(GPSCoordinate coordinate, long timeAdded, long expiry) {
this.coordinate = coordinate;
this.timeAdded = timeAdded;
this.expiry = expiry;
this.minAlpha = 0.1;
}
/**
* Gets the position of the point on physical race map.
*
* @return GPS coordinate of point
*/
public GPSCoordinate getCoordinate() {
return coordinate;
}
/**
* Gets opacity of point scaled by age in proportion to expiry, between 1 and minimum opacity inclusive.
*
* @return greater of minimum opacity and scaled opacity
*/
public double getAlpha() {
return Double.max(minAlpha, 1.0 - (double) (System.currentTimeMillis() - timeAdded) / expiry);
}
/**
* Gets time point was added to track.
*
* @return system clock at time of addition
*/
public long getTimeAdded() {
return timeAdded;
}
}

@ -1,57 +0,0 @@
package seng302;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* Created by cbt24 on 3/05/17.
*/
public class RaceConnection {
private final StringProperty hostname;
private final int port;
private final StringProperty status;
public RaceConnection(String hostname, int port) {
this.hostname = new SimpleStringProperty(hostname);
this.port = port;
this.status = new SimpleStringProperty("");
check();
}
/**
* Tries to create a socket to hostname and port, indicates status after test.
* @return true if socket can connect
*/
@SuppressWarnings("unused")
public boolean check() {
InetSocketAddress i = new InetSocketAddress(hostname.get(), port);
try (Socket s = new Socket()){
s.connect(i, 5000);
status.set("Ready");
return true;
} catch (IOException e) {}
status.set("Offline");
return false;
}
public String getHostname() {
return hostname.get();
}
public StringProperty hostnameProperty() {
return hostname;
}
public int getPort() {
return port;
}
public StringProperty statusProperty() {
return status;
}
}

@ -1,26 +0,0 @@
package seng302;
import seng302.Model.Boat;
import seng302.Model.Leg;
import seng302.Model.Marker;
import java.time.ZonedDateTime;
import java.util.List;
/**
* An object that holds relevant data for a race. <br>
* Information includes: {@link seng302.Model.Boat Boat}s,
* {@link seng302.Model.Leg Leg}s, {@link seng302.Model.Marker Marker}s and
* the {@link seng302.GPSCoordinate GPSCoordinate}s to create a
* {@link seng302.Model.ResizableRaceMap ResizableRaceMap}.
*/
public interface RaceDataSource {
List<Boat> getBoats();
List<Leg> getLegs();
List<Marker> getMarkers();
List<GPSCoordinate> getBoundary();
ZonedDateTime getZonedDateTime();
GPSCoordinate getMapTopLeft();
GPSCoordinate getMapBottomRight();
}

@ -1,76 +0,0 @@
package seng302;
/**
* The base size of the map to be used for the
* {@link seng302.Model.ResizableRaceMap ResizableRaceMap} and
* {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}. It is used
* to convert {@link seng302.GPSCoordinate GPSCoordinate}s to relative
* {@link seng302.GraphCoordinate GraphCoordinate}s.
*/
public class RaceMap {
private final double x1;
private final double x2;
private final double y1;
private final double y2;
private int width, height;
/**
* Constructor Method.
*
* @param x1 Longitude of the top left point.
* @param y1 Latitude of the top left point.
* @param x2 Longitude of the top right point.
* @param y2 Latitude of the top right point.
* @param width width that the Canvas the race is to be drawn on is.
* @param height height that the Canvas the race is to be drawn on is.
*/
public RaceMap(double y1, double x1, double y2, double x2, int height, int width) {
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
this.width = width;
this.height = height;
}
/**
* Converts GPS coordinates to coordinates for container
*
* @param lat GPS latitude
* @param lon GPS longitude
* @return GraphCoordinate (pair of doubles)
* @see GraphCoordinate
*/
private GraphCoordinate convertGPS(double lat, double lon) {
int difference = Math.abs(width - height);
int size = width;
if (width > height) {
size = height;
return new GraphCoordinate((int) ((size * (lon - x1) / (x2 - x1)) + difference / 2), (int) (size - (size * (lat - y1) / (y2 - y1))));
} else {
return new GraphCoordinate((int) (size * (lon - x1) / (x2 - x1)), (int) ((size - (size * (lat - y1) / (y2 - y1))) + difference / 2));
}
//return new GraphCoordinate((int) (width * (lon - x1) / (x2 - x1)), (int) (height - (height * (lat - y1) / (y2 - y1))));
}
/**
* Converts the GPS Coordinate to GraphCoordinates
*
* @param coordinate GPSCoordinate representation of Latitude and Longitude.
* @return GraphCoordinate that the GPS is coordinates are to be displayed on the map.
* @see GraphCoordinate
* @see GPSCoordinate
*/
public GraphCoordinate convertGPS(GPSCoordinate coordinate) {
return convertGPS(coordinate.getLatitude(), coordinate.getLongitude());
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
}

@ -1,434 +0,0 @@
package seng302;
import javafx.application.Platform;
import org.xml.sax.SAXException;
import seng302.Mock.*;
import seng302.Networking.BinaryMessageDecoder;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.Messages.*;
import javax.xml.parsers.ParserConfigurationException;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static seng302.Networking.Utils.ByteConverter.bytesToShort;
/**
* TCP client which receives packets/messages from a race data source
* (e.g., mock source, official source), and exposes them to any observers.
* @see seng302.Mock.StreamedCourse
*/
public class VisualiserInput implements Runnable {
///Timestamp of the last heartbeat.
private long lastHeartbeatTime = -1;
///Sequence number of the last heartbeat.
private long lastHeartbeatSequenceNum = -1;
///The socket that we have connected to.
private Socket connectionSocket;
///Object to store parsed course data. //TODO comment?
private StreamedCourse course;
///The last RaceStatus message received.
private RaceStatus raceStatus;
///A map of the last BoatStatus message received, for each boat.
private final Map<Integer, BoatStatus> boatStatusMap = new HashMap<>();
///A map of the last BoatLocation message received, for each boat.
private final Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
///The last AverageWind message received.
private AverageWind averageWind;
///The last CourseWinds message received.
private CourseWinds courseWinds;
///A map of the last MarkRounding message received, for each boat.
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
///InputStream (from the socket).
private DataInputStream inStream;
/**
* Ctor.
* @param socket Socket from which we will receive race data.
* @param course TODO comment?
* @throws IOException If there is something wrong with the socket's input stream.
*/
public VisualiserInput(Socket socket, StreamedCourse course) throws IOException {
this.connectionSocket = socket;
//We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled.
this.inStream = new DataInputStream(connectionSocket.getInputStream());
this.course = course;
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Provides StreamedCourse container for fixed course data.
* @return Course for current VisualiserInput instance.
* @see seng302.Mock.StreamedCourse
*/
public StreamedCourse getCourse() {
return course;
}
/**
* Returns the last boat location message associated with the given boat source ID.
* @param sourceID Unique global identifier for the boat.
* @return The most recent location message.
*/
public BoatLocation getBoatLocationMessage(int sourceID) {
return boatLocationMap.get(sourceID);
}
public BoatStatus getBoatStatusMessage(int sourceID) {
return boatStatusMap.get(sourceID);
}
/**
* Calculates the time since last heartbeat, in milliseconds.
* @return Time since last heartbeat, in milliseconds..
*/
private double timeSinceHeartbeat() {
long now = System.currentTimeMillis();
return (now - lastHeartbeatTime);
}
/**
* Returns the boat locations map. Maps from Integer (Boat ID) to BoatLocation.
* @return Map of boat locations.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Gets the status of the race.
* @return The status of the race.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Returns the boat statuses map. Maps from Integer (Boat ID) to BoatStatus.
* @return Map of boat statuses.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the average wind of the race.
* @return Average wind in the race.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Returns winds in the course.
* @return Winds that are in the course.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Returns the mark roundings map. Maps from Integer (Boat ID) to MarkRounding.
* @return Map of mark roundings.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Sets the wind direction for the current course.
* @param direction The new wind direction for the course.
*/
private void setCourseWindDirection(double direction) {
this.course.setWindDirection(direction);
}
/**
* Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead.
* @return Encoded binary message bytes.
* @throws IOException Thrown when an error occurs while reading from the socket.
*/
private byte[] getNextMessageBytes() throws IOException {
inStream.mark(0);
short CRCLength = 4;
short headerLength = 15;
//Read the header of the next message.
byte[] headerBytes = new byte[headerLength];
inStream.readFully(headerBytes);
//Read the message body length.
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
//Read the message body.
byte[] messageBodyBytes = new byte[messageBodyLength];
inStream.readFully(messageBodyBytes);
//Read the message CRC.
byte[] messageCRCBytes = new byte[CRCLength];
inStream.readFully(messageCRCBytes);
//Put the head + body + crc into one large array.
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
messageBytes.put(headerBytes);
messageBytes.put(messageBodyBytes);
messageBytes.put(messageCRCBytes);
return messageBytes.array();
}
/**
* Reads and returns the next message object from the socket.
* @return The message object. Use instanceof for concrete type.
* @throws IOException Thrown when an error occurs while reading from the socket.
* @throws InvalidMessageException Thrown when the message is invalid in some way.
*/
private AC35Data getNextMessage() throws IOException, InvalidMessageException
{
//Get the next message from the socket as a block of bytes.
byte[] messageBytes = this.getNextMessageBytes();
//Decode the binary message into an appropriate message object.
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
return decoder.decode();
}
/**
* Main loop which reads messages from the socket, and exposes them.
*/
public void run(){
boolean receiverLoop = true;
//receiver loop that gets the input
while (receiverLoop) {
//If no heartbeat has been received in more the heartbeat period
//then the connection will need to be restarted.
//System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE
long heartBeatPeriod = 10 * 1000;
if (timeSinceHeartbeat() > heartBeatPeriod) {
System.out.println("Connection has stopped, trying to reconnect.");
//Attempt to reconnect the socket.
try {//This attempt doesn't really work. Under what circumstances would
this.connectionSocket = new Socket(this.connectionSocket.getInetAddress(), this.connectionSocket.getPort());
//this.connectionSocket.connect(this.connectionSocket.getRemoteSocketAddress());
//Reset the heartbeat timer.
this.lastHeartbeatTime = System.currentTimeMillis();
}
catch (IOException e) {
System.err.println("Unable to reconnect.");
//Wait 500ms. Ugly hack, should refactor.
long waitPeriod = 500;
long waitTimeStart = System.currentTimeMillis() + waitPeriod;
while (System.currentTimeMillis() < waitTimeStart){
//Nothing. Busyloop.
}
//Swallow the exception.
continue;
}
}
//Reads the next message.
AC35Data message;
try {
message = this.getNextMessage();
}
catch (InvalidMessageException | IOException e) {
//Prints exception to stderr, and iterate loop (that is, read the next message).
System.err.println("Unable to read message: " + e.getMessage());
try {
inStream.reset();
} catch (IOException e1) {
e1.printStackTrace();
}
//Continue to the next loop iteration/message.
continue;
}/*
//Add it to message queue.
this.messagesReceivedQueue.add(message);*/
//Checks which message is being received and does what is needed for that message.
//Heartbeat.
if (message instanceof Heartbeat) {
Heartbeat heartbeat = (Heartbeat) message;
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
}
//RaceStatus.
else if (message instanceof RaceStatus) {
RaceStatus raceStatus = (RaceStatus) message;
//System.out.println("Race Status Message");
this.raceStatus = raceStatus;
for (BoatStatus boatStatus: this.raceStatus.getBoatStatuses()) {
this.boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
}
setCourseWindDirection(raceStatus.getScaledWindDirection());
}
//DisplayTextMessage.
/*else if (message instanceof DisplayTextMessage) {
//System.out.println("Display Text Message");
//No decoder for this.
}*/
//XMLMessage.
else if (message instanceof XMLMessage) {
XMLMessage xmlMessage = (XMLMessage) message;
//System.out.println("XML Message!");
Platform.runLater(()-> {
if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRegatta) {
//System.out.println("Setting Regatta");
try {
course.setRegattaXMLReader(new RegattaXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a RegattaXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating RegattaXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRace) {
//System.out.println("Setting Course");
try {
course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a StreamedCourseXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException | StreamedCourseXMLException e) {
System.err.println("Error creating StreamedCourseXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
} else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeBoat) {
//System.out.println("Setting Boats");
try {
course.setBoatXMLReader(new BoatXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a BoatXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating BoatXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
}
}
});
}
//RaceStartStatus.
else if (message instanceof RaceStartStatus) {
//System.out.println("Race Start Status Message");
}
//YachtEventCode.
/*else if (message instanceof YachtEventCode) {
YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("Yacht Event Code!");
//No decoder for this.
}*/
//YachtActionCode.
/*else if (message instanceof YachtActionCode) {
YachtActionCode yachtActionCode = (YachtActionCode) message;
//System.out.println("Yacht Action Code!");
//No decoder for this.
}*/
//ChatterText.
/*else if (message instanceof ChatterText) {
ChatterText chatterText = (ChatterText) message;
//System.out.println("Chatter Text Message!");
//No decoder for this.
}*/
//BoatLocation.
else if (message instanceof BoatLocation) {
BoatLocation boatLocation = (BoatLocation) message;
//System.out.println("Boat Location!");
if (this.boatLocationMap.containsKey(boatLocation.getSourceID())) {
//If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (boatLocation.getTime() > this.boatLocationMap.get(boatLocation.getSourceID()).getTime()){
//If it is, replace the old message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}
//MarkRounding.
else if (message instanceof MarkRounding) {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Mark Rounding Message!");
if (this.markRoundingMap.containsKey(markRounding.getSourceID())) {
//If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (markRounding.getTime() > this.markRoundingMap.get(markRounding.getSourceID()).getTime()){
//If it is, replace the old message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
}
//CourseWinds.
else if (message instanceof CourseWinds) {
//System.out.println("Course Wind Message!");
this.courseWinds = (CourseWinds) message;
}
//AverageWind.
else if (message instanceof AverageWind) {
//System.out.println("Average Wind Message!");
this.averageWind = (AverageWind) message;
}
//Unrecognised message.
else {
System.out.println("Broken Message!");
}
}
}
}

@ -1,52 +0,0 @@
package seng302;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
/**
* The abstract class for reading in XML race data.
*/
public abstract class XMLReader {
protected Document doc;
protected XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException {
InputStream fXmlFile = getClass().getClassLoader().getResourceAsStream(filePath);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
}
protected XMLReader(InputStream xmlInput) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(xmlInput);
}
public Document getDocument() {
return doc;
}
protected String getTextValueOfNode(Element n, String tagName) {
return n.getElementsByTagName(tagName).item(0).getTextContent();
}
protected boolean exists(Node node, String attribute) {
return node.getAttributes().getNamedItem(attribute) != null;
}
public String getAttribute(Element n, String attr) {
return n.getAttribute(attr);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@ -1,251 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2012-05-17T07:49:40+0200</Modified>
<Version>12</Version>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.659"/>
<Vtx Seq="2" Y="18.359" X="-2.659"/>
<Vtx Seq="3" Y="18.359" X="2.659"/>
<Vtx Seq="4" Y="0" X="2.659"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="1">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.278"/>
<Vtx Seq="2" Y="8.876" X="-1.278"/>
<Vtx Seq="3" Y="8.876" X="1.278"/>
<Vtx Seq="4" Y="0" X="1.278"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="2">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.1"/>
<Vtx Seq="2" Y="8.3" X="-1.1"/>
<Vtx Seq="3" Y="8.3" X="1.1"/>
<Vtx Seq="4" Y="0" X="1.1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="3">
<Vertices>
<Vtx Seq="1" Y="0" X="-0.75"/>
<Vtx Seq="2" Y="3" X="-0.75"/>
<Vtx Seq="3" Y="3" X="0.75"/>
<Vtx Seq="4" Y="0" X="0.75"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="4">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="5"/>
</BoatShapes>
<Boats>
<Boat Type="RC" SourceID="121" ShapeID="0" HullNum="RG01" StoweName="PRO" ShortName="PRO"
BoatName="Regardless">
<GPSposition Z="6.840" Y="7.800" X="0.000"/>
<FlagPosition Z="0.000" Y="7.800" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="122" ShapeID="1" HullNum="LC05" StoweName="CON" ShortName="Constellation"
BoatName="Constellation">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="123" ShapeID="1" HullNum="LC04" StoweName="MIS" ShortName="Mischief"
BoatName="Mischief">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="124" ShapeID="1" HullNum="LC03" ShortName="Atalanta" BoatName="Atalanta">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat SourceID="125" ShapeID="1" StoweName="VOL" HullNum="LC01" ShortName="Volunteer"
BoatName="Volunteer">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="126" ShapeID="1" HullNum="LC13" StoweName="MS2" ShortName="Defender"
BoatName="Defender">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="128" ShapeID="1" HullNum="LC01" ShortName="Shamrock" BoatName="Shamrock">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="101" ShapeID="4" HullNum="AC4501" StoweName="KOR" ShortName="TEAM KOREA"
BoatName="TEAM KOREA" Country="KOR">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
</Boats>
</BoatConfig>

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>11080703</RaceID>
<RaceType>Match</RaceType>
<CreationTimeDate>2011-08-06T13:25:00-0000</CreationTimeDate>
<RaceStartTime Time="2011-08-06T13:30:00-0700" Postpone="false"/>
<Participants>
<Yacht SourceID="101" Entry="Port"/>
<Yacht SourceID="108" Entry="Stbd"/>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<Mark SeqID="1" Name="PRO" TargetLat="-36.83" TargetLng="174.83" SourceID="101"/>
<Mark SeqID="2" Name="PIN" TargetLat="-36.84" TargetLng="174.81" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="M1">
<Mark Name="M1" TargetLat="-36.63566590" TargetLng="174.88543944" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="M2">
<Mark Name="M2" TargetLat="-36.83" TargetLng="174.80" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Gate">
<Mark SeqID="1" Name="G1" TargetLat="-36.63566590" TargetLng="174.97205159" SourceID="104"/>
<Mark SeqID="2" Name="G2" TargetLat="-36.64566590" TargetLng="174.98205159" SourceID="105"/>
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Stbd" ZoneSize="6"/>
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="6"/>
<Corner SeqID="5" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="-36.8325" Lon="174.8325"/>
<Limit SeqID="2" Lat="-36.82883" Lon="174.81983"/>
<Limit SeqID="3" Lat="-36.82067" Lon="174.81983"/>
<Limit SeqID="4" Lat="-36.811" Lon="174.8265"/>
<Limit SeqID="5" Lat="-36.81033" Lon="174.83833"/>
<Limit SeqID="6" Lat="-36.81533" Lon="174.8525"/>
<Limit SeqID="7" Lat="-36.81533" Lon="174.86733"/>
<Limit SeqID="8" Lat="-36.81633" Lon="174.88217"/>
<Limit SeqID="9" Lat="-36.83383" Lon="174.87117"/>
<Limit SeqID="10" Lat="-36.83417" Lon="174.84767"/>
</CourseLimit>
</Race>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RegattaConfig>
<RegattaID>3</RegattaID>
<RegattaName>New Zealand Test</RegattaName>
<CourseName>North Head</CourseName>
<CentralLatitude>-36.82791529</CentralLatitude>
<CentralLongitude>174.81218919</CentralLongitude>
<CentralAltitude>0.00</CentralAltitude>
<UtcOffset>12</UtcOffset>
<MagneticVariation>14.1</MagneticVariation>
</RegattaConfig>

@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2017-04-19T15:49:40+1200</Modified>
<Version>1</Version>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.659"/>
<Vtx Seq="2" Y="18.359" X="-2.659"/>
<Vtx Seq="3" Y="18.359" X="2.659"/>
<Vtx Seq="4" Y="0" X="2.659"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="1">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.278"/>
<Vtx Seq="2" Y="8.876" X="-1.278"/>
<Vtx Seq="3" Y="8.876" X="1.278"/>
<Vtx Seq="4" Y="0" X="1.278"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="2">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.1"/>
<Vtx Seq="2" Y="8.3" X="-1.1"/>
<Vtx Seq="3" Y="8.3" X="1.1"/>
<Vtx Seq="4" Y="0" X="1.1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="3">
<Vertices>
<Vtx Seq="1" Y="0" X="-0.75"/>
<Vtx Seq="2" Y="3" X="-0.75"/>
<Vtx Seq="3" Y="3" X="0.75"/>
<Vtx Seq="4" Y="0" X="0.75"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="4">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="5"/>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="101" ShapeID="4" HullNum="AC4501" ShortName="USA"
BoatName="ORACLE TEAM USA" Country="USA">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="102" ShapeID="4" HullNum="AC4502" ShortName="GBR"
BoatName="Land Rover BAR" Country="United Kingdom">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="103" ShapeID="4" HullNum="AC4503" ShortName="JPN"
BoatName="SoftBank Team Japan" Country="Japan">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="104" ShapeID="4" HullNum="AC4504" ShortName="FRA"
BoatName="Groupama Team France" Country="France">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="105" ShapeID="4" HullNum="AC4505" ShortName="SWE"
BoatName="Artemis Racing" Country="Sweden">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="106" ShapeID="4" HullNum="AC4506" ShortName="NZL"
BoatName="Emirates Team New Zealand" Country="New Zealand">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
</Boats>
</BoatConfig>

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>17041901</RaceID>
<RaceType>Fleet</RaceType>
<CreationTimeDate>2017-04-19T15:30:00+1200</CreationTimeDate>
<RaceStartTime Time="2019-06-01T13:30:00-0400" Postpone="false"/>
<Participants>
<Yacht SourceID="001" Entry="Port"/>
<Yacht SourceID="002" Entry="Port"/>
<Yacht SourceID="003" Entry="Port"/>
<Yacht SourceID="004" Entry="Port"/>
<Yacht SourceID="005" Entry="Port"/>
<Yacht SourceID="006" Entry="Port"/>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<Mark SeqID="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101"/>
<Mark SeqID="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="M1">
<Mark Name="M1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="WindwardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.284680" TargetLng="-64.850045" SourceID="104"/>
<Mark SeqID="2" Name="G2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="LeewardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
<Mark SeqID="2" Name="G2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="FinishLine">
<Mark SeqID="1" Name="PRO" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
<Mark SeqID="2" Name="PIN" TargetLat="32.317257" TargetLng="-64.836260" SourceID="109"/>
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="4" CompoundMarkID="4" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="32.313922" Lon="-64.837168"/>
<Limit SeqID="2" Lat="32.317379" Lon="-64.839291"/>
<Limit SeqID="3" Lat="32.317911" Lon="-64.836996"/>
<Limit SeqID="4" Lat="32.317257" Lon="-64.836260"/>
<Limit SeqID="5" Lat="32.304273" Lon="-64.822834"/>
<Limit SeqID="6" Lat="32.279097" Lon="-64.841545"/>
<Limit SeqID="7" Lat="32.279604" Lon="-64.849871"/>
<Limit SeqID="8" Lat="32.289545" Lon="-64.854162"/>
<Limit SeqID="9" Lat="32.290198" Lon="-64.858711"/>
<Limit SeqID="10" Lat="32.297164" Lon="-64.856394"/>
<Limit SeqID="11" Lat="32.296148" Lon="-64.849184"/>
</CourseLimit>
</Race>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RegattaConfig>
<RegattaID>1</RegattaID>
<RegattaName>Seng302 Mock Test</RegattaName>
<CourseName>Bermuda AC35</CourseName>
<CentralLatitude>-32.296577</CentralLatitude>
<CentralLongitude>64.854304</CentralLongitude>
<CentralAltitude>0.00</CentralAltitude>
<UtcOffset>-4</UtcOffset>
<MagneticVariation>-14.78</MagneticVariation>
</RegattaConfig>

@ -1,269 +0,0 @@
<race>
<raceId>5326</raceId>
<boats>
<boat>
<name>ORACLE TEAM USA</name>
<speed>20</speed>
<abbr>USA</abbr>
<colour>BLUEVIOLET</colour>
</boat>
<boat>
<name>Land Rover BAR</name>
<speed>30</speed>
<abbr>GBR</abbr>
<colour>BLACK</colour>
</boat>
<boat>
<name>SoftBank Team Japan</name>
<speed>25</speed>
<abbr>JPN</abbr>
<colour>RED</colour>
</boat>
<boat>
<name>Groupama Team France</name>
<speed>20</speed>
<abbr>FRA</abbr>
<colour>ORANGE</colour>
</boat>
<boat>
<name>Artemis Racing</name>
<speed>29</speed>
<abbr>SWE</abbr>
<colour>DARKOLIVEGREEN</colour>
</boat>
<boat>
<name>Emirates Team New Zealand</name>
<speed>62</speed>
<abbr>NZL</abbr>
<colour>LIMEGREEN</colour>
</boat>
</boats>
<legs>
<leg>
<name>Start to Mark 1</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</coordinate>
<coordinate>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Mark 1 to Leeward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Leeward Gate to Windward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Windward Gate to Leeward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Leeward Gate to Finish</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
</legs>
<course>
<boundaries>
<coordinate>
<latitude>32.313922</latitude>
<longitude>-64.837168</longitude>
</coordinate>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317911</latitude>
<longitude>-64.836996</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
<coordinate>
<latitude>32.304273</latitude>
<longitude>-64.822834</longitude>
</coordinate>
<coordinate>
<latitude>32.279097</latitude>
<longitude>-64.841545</longitude>
</coordinate>
<coordinate>
<latitude>32.279604</latitude>
<longitude>-64.849871</longitude>
</coordinate>
<coordinate>
<latitude>32.289545</latitude>
<longitude>-64.854162</longitude>
</coordinate>
<coordinate>
<latitude>32.290198</latitude>
<longitude>-64.858711</longitude>
</coordinate>
<coordinate>
<latitude>32.297164</latitude>
<longitude>-64.856394</longitude>
</coordinate>
<coordinate>
<latitude>32.296148</latitude>
<longitude>-64.849184</longitude>
</coordinate>
</boundaries>
<compoundMark>
<name>Start Line</name>
<coordinate>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</coordinate>
<coordinate>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Mark</name>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Windward Gate</name>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Leeward Gate</name>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Finish Line</name>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
</compoundMark>
</course>
</race>

@ -1,34 +0,0 @@
<?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,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.ConnectionController">
<children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="182.0" minHeight="10.0" prefHeight="182.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="434.0" minHeight="10.0" prefHeight="434.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="174.0" minHeight="10.0" prefHeight="174.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="50.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="connectionTable" prefHeight="200.0" prefWidth="1080.0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<columns>
<TableColumn fx:id="hostnameColumn" prefWidth="453.99998474121094" text="Host" />
<TableColumn fx:id="statusColumn" prefWidth="205.0" text="Status" />
</columns>
<GridPane.margin>
<Insets left="50.0" right="50.0" />
</GridPane.margin>
</TableView>
<Button mnemonicParsing="false" onAction="#checkConnections" text="Refresh" GridPane.halignment="RIGHT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets right="20.0" />
</GridPane.margin>
</Button>
<Button fx:id="connectButton" mnemonicParsing="false" onAction="#connectSocket" text="Connect" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label text="Welcome to RaceVision" GridPane.columnSpan="2" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<GridPane GridPane.columnSpan="2" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="urlField" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="portField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button mnemonicParsing="false" onAction="#addConnection" text="Add New Connection" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Label text="Host Name:" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
<Label text="Port:" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="finishWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.FinishController">
<children>
<GridPane fx:id="start" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="372.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="394.0" minWidth="10.0" prefWidth="250.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="416.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="241.0" minHeight="10.0" prefHeight="116.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="383.0" minHeight="10.0" prefHeight="48.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="261.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="53.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="82.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="boatInfoTable" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<placeholder>
<Label text="Initial lineup..." />
</placeholder>
<columns>
<TableColumn fx:id="boatRankColumn" prefWidth="120.0" style="-fx-font-size: 16;" text="Ranking" />
<TableColumn fx:id="boatNameColumn" prefWidth="367.0" style="-fx-font-size: 16;" text="Team" />
</columns>
</TableView>
<Label fx:id="raceFinishLabel" alignment="CENTER" text="Race Finished" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<Label fx:id="raceWinnerLabel" alignment="CENTER" maxWidth="1.7976931348623157E308" text="Winner" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="main" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.MainController">
<children>
<fx:include fx:id="race" source="race.fxml" />
<fx:include fx:id="start" source="start.fxml" />
<fx:include fx:id="connection" source="connect.fxml" />
<fx:include fx:id="finish" source="finish.fxml" />
</children>
</AnchorPane>

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<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.0.111" 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>
<rowConstraints>
<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" prefHeight="395.0" prefWidth="222.0" text="Annotation Control">
<content>
<AnchorPane fx:id="annotationPane" minHeight="0.0" minWidth="0.0">
<children>
<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" />
<CheckBox fx:id="showEstTime" mnemonicParsing="false" selected="true" text="Show Est. Time to Next Mark" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<Separator layoutX="19.6" layoutY="175.6" prefHeight="0.0" prefWidth="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="150.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="150.0" />
<RadioButton fx:id="hideAnnoRBtn" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="175.0">
<toggleGroup>
<ToggleGroup fx:id="annoToggleGroup" />
</toggleGroup></RadioButton>
<RadioButton fx:id="showAnnoRBtn" mnemonicParsing="false" text="Visible" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="200.0" />
<RadioButton fx:id="partialAnnoRBtn" mnemonicParsing="false" text="Partial" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="225.0" />
<RadioButton fx:id="importantAnnoRBtn" mnemonicParsing="false" text="Important" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="250.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="126.0" mnemonicParsing="false" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="275.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane animated="false" text="FPS Control">
<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" />
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</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">
<font>
<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">
<font>
<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" />
</GridPane.margin>
<font>
<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">
<children>
<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" />
</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>
</SplitPane>

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="startWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.StartController">
<children>
<GridPane fx:id="start" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="372.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="394.0" minWidth="10.0" prefWidth="250.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="416.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="241.0" minHeight="10.0" prefHeight="116.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="383.0" minHeight="10.0" prefHeight="48.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="261.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="53.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="82.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="boatNameTable" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="2">
<placeholder>
<Label text="Initial lineup..." />
</placeholder>
<columns>
<TableColumn fx:id="boatNameColumn" prefWidth="360.0" style="-fx-font-size: 16;" text="Team Name" />
<TableColumn fx:id="boatCodeColumn" prefWidth="133.0" style="-fx-font-size: 16;" text="Code" />
</columns>
</TableView>
<Label fx:id="timeZoneTime" contentDisplay="CENTER" text="Local time..." GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
<Label fx:id="timer" text=" " GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="5" />
<Label fx:id="raceStartLabel" text="Starting time..." GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
<Label fx:id="raceTitleLabel" text="Welcome to RaceVision" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<Label fx:id="raceStatusLabel" alignment="CENTER" text="Race Status:" textAlignment="CENTER" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
</children>
</GridPane>
</children>
</AnchorPane>
Loading…
Cancel
Save