merged in the changes from the sprint5_refactor branch

# Conflicts:
#	visualiser/src/main/java/seng302/Model/ResizableRaceMap.java
#story[1087]
main
hba56 9 years ago
commit 956ad1f59b

@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>

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

@ -13,69 +13,16 @@
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>com.github.bfsmith</groupId>
<artifactId>geotimezone</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>seng302</groupId>
<artifactId>network</artifactId>
<artifactId>racevisionGame</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>opengeo</id>
<name>OpenGeo Maven Repository</name>
<url>http://repo.opengeo.org</url>
</repository>
</repositories>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
@ -97,7 +44,7 @@
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>seng302.App</Main-Class>
<Main-Class>mock.app.App</Main-Class>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
</manifestEntries>

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

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

@ -1,315 +0,0 @@
package seng302.Model;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.xml.sax.SAXException;
import seng302.DataInput.BoatDataSource;
import seng302.DataInput.BoatXMLReader;
import seng302.DataInput.RaceDataSource;
import seng302.DataInput.RaceXMLReader;
import seng302.Exceptions.StreamedCourseXMLException;
import seng302.MockOutput;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Created by esa46 on 15/03/17.
*/
public class RaceTest{
public static final CompoundMark ORIGIN = new CompoundMark(new Mark(1, "test origin 1", new GPSCoordinate(0, 0)));
public static final CompoundMark THREE_NM_FROM_ORIGIN = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)));
public static final CompoundMark FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)));
public static ArrayList<Leg> TEST_LEGS = new ArrayList<>();
public static final int START_LEG_DISTANCE = 3;
public static final int MIDDLE_LEG_DISTANCE = 12;
public static final Leg START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0);
public static final Leg MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1);
public static final Leg FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2);
@Before
public void setUp() {
TEST_LEGS.add(START_LEG);
TEST_LEGS.add(MIDDLE_LEG);
TEST_LEGS.add(FINISH_LEG);
}
@Ignore
@Test
public void countdownTimerSendsBoatLocations() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.initialiseBoats();
testRace.countdownTimer.handle(1);
verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void countdownTimerSendsRaceStatusMessages() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
testRace.countdownTimer.handle(1);
verify(mockOutput, atLeast(1)).parseRaceStatus(any());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void checkPositionFinishedUpdatesNumberFinishedBoats() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
assertEquals(testRace.getNumberOfActiveBoats(), 0);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void checkPositionSetFinishedBoatVelocityTo0() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void checkPositionSetsFinishTime() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setDistanceTravelledInLeg(1);
testRace.checkPosition(testBoat, 1);
assertEquals(testBoat.getTimeFinished(), 1, 1e-8);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE);
testRace.checkPosition(testBoat, 1);
assertEquals(testRace.getNumberOfActiveBoats(), 1);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void distanceTravelledBeforeUpdatingLegIsRetained() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
Race testRace = new Race(dataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
testRace.checkPosition(testBoat, 0);
assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7);
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void doNotFinishAnswersYesIf100PercentChance() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.setDnfChance(100);
assertTrue(testRace.doNotFinish());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void doNotFinishAnswersNoIf0PercentChance() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.setDnfChance(0);
assertFalse(testRace.doNotFinish());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void boatsAreSetToDNF() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.setDnfChance(100);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
testRace.checkPosition(testBoat, 1);
assertEquals(testBoat.getCurrentLeg().getName(), "DNF");
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void updatePositionIgnoresFinishedBoats() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(FINISH_LEG);
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
testRace.updatePosition(testBoat, 1, 1);
assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void updatePositionChangesBoatPosition() {
try {
MockOutput mockOutput = Mockito.mock(MockOutput.class);
BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
Race testRace = new Race(raceDataSource, mockOutput);
testRace.initialiseBoats();
Boat testBoat = testRace.getBoats().get(0);
testBoat.setCurrentLeg(START_LEG);
testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1);
testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
testRace.updatePosition(testBoat, 100, 100);
assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate());
} catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
e.printStackTrace();
fail();
}
}
@Ignore
@Test
public void windDirectionCorrectValues(){
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.setChangeWind(1);
// testRace.setWindDir(65535);
// testRace.changeWindDir();
// assertEquals(100, testRace.getWind());
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
}
}

@ -14,12 +14,6 @@
<dependencies>
<dependency>
<groupId>seng302</groupId>
<artifactId>sharedModel</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>

@ -1,60 +0,0 @@
package seng302.Networking.MessageEncoders;
import java.nio.ByteBuffer;
import static seng302.Networking.Utils.ByteConverter.intToBytes;
import static seng302.Networking.Utils.ByteConverter.longToBytes;
import static seng302.Networking.Utils.ByteConverter.shortToBytes;
/**
* Encodes a XML file into a message of AC35 format
*/
public class XMLMessageEncoder {
private byte[] messageVersionNumber;
private short ackNumber;
private long timeStamp;
private byte[] xmlMsgSubType;
private short sequenceNumber;
private short xmlMsgLength;
private String xmlMessage;
public XMLMessageEncoder(short ackNumber, long timeStamp, int xmlMsgSubType, short sequenceNumber, short xmlMsgLength, String xmlMessage) {
this.messageVersionNumber = intToBytes(1, 1);
this.ackNumber = ackNumber;
this.timeStamp = timeStamp;
this.xmlMsgSubType = intToBytes(xmlMsgSubType, 1);
this.sequenceNumber = sequenceNumber;
this.xmlMsgLength = xmlMsgLength;
this.xmlMessage = xmlMessage;
}
public byte[] encode() {
byte[] messageBytes = xmlMessage.getBytes();
if (messageBytes.length > this.xmlMsgLength) {
//System.err.println("Xml message is to big");
return null;
}
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
//ackNumber converted to bytes
byte[] ackNumberBytes = shortToBytes(ackNumber, 2);
//sequenceNumber converted to bytes
byte[] sequenceNumberBytes = shortToBytes(sequenceNumber, 2);
//xmlMsgLength converted to bytes
byte[] xmlMsgLengthBytes = shortToBytes(xmlMsgLength, 2);
tempOutputByteBuffer.put(messageVersionNumber);
tempOutputByteBuffer.put(ackNumberBytes);
tempOutputByteBuffer.put(longToBytes(timeStamp, 6));
tempOutputByteBuffer.put(xmlMsgSubType);
tempOutputByteBuffer.put(sequenceNumberBytes);
tempOutputByteBuffer.put(xmlMsgLengthBytes);
tempOutputByteBuffer.put(messageBytes);
return tempOutputByteBuffer.array();
}
}

@ -1,57 +0,0 @@
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import java.io.InputStream;
/**
* Created by fwy13 on 25/04/17.
*/
public class XMLMessage extends AC35Data {
private int ackNumber;
private long timeStamp;
private int xmlMsgSubType;
private int sequenceNumber;
private int xmlMsgLength;
private InputStream xmlMessage;
public static int XMLTypeRegatta = 5;
public static int XMLTypeRace = 6;
public static int XMLTypeBoat = 7;
/**
* Constructor for an XML Message
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
* @param timeStamp Time received
* @param xmlMsgSubType Type of XML message
* @param sequenceNumber Order that it has arrived in
* @param xmlMsgLength Length of the xml message
* @param xmlMessage XML message
*/
public XMLMessage(int ackNumber, long timeStamp, int xmlMsgSubType, int sequenceNumber, int xmlMsgLength, InputStream xmlMessage){
super(MessageType.XMLMESSAGE);
this.ackNumber = ackNumber;
this.timeStamp = timeStamp;
this.xmlMsgSubType = xmlMsgSubType;
this.sequenceNumber = sequenceNumber;
this.xmlMsgLength = xmlMsgLength;
this.xmlMessage = xmlMessage;
}
/**
* Get the XML Message
* @return the XML message as an input stream
*/
public InputStream getXmlMessage() {
return xmlMessage;
}
/**
* Get the type of message
* @return Gets the type of message the XML message is
*/
public int getXmlMsgSubType() {
return xmlMsgSubType;
}
}

@ -1,46 +0,0 @@
package seng302.Networking.MessageDecoders;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Created by hba56 on 20/04/17.
*/
public class XMLMessageDecoderTest {
@Test
public void getByteArrayTest(){
try{
StringBuilder xmlString;
BufferedReader br = new BufferedReader(new InputStreamReader(
this.getClass().getResourceAsStream(("../../../raceXML/Regatta.xml"))));
String line;
xmlString = new StringBuilder();
while((line=br.readLine())!= null){
xmlString.append(line.trim());
}
long time = System.currentTimeMillis();
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, time, (byte)7, (short)1, (short)xmlString.length(), xmlString.toString());
byte[] encodedXML = testEncoder.encode();
XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML);
decoderXML.decode();
Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber());
Assert.assertEquals((short)1, decoderXML.getAckNumber());
Assert.assertEquals(time, decoderXML.getTimeStamp());
Assert.assertEquals((byte)7, decoderXML.getXmlMsgSubType());
Assert.assertEquals((short)1, decoderXML.getSequenceNumber());
Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength());
}catch (IOException e){
e.printStackTrace();
}
}
}

@ -1,55 +0,0 @@
package seng302.Networking;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import java.io.*;
/**
* Created by hba56 on 19/04/17.
*/
public class XMLMessageEncoderTest {
@Test
public void getByteArrayTest(){
StringBuilder xmlString;
try{
BufferedReader br = new BufferedReader(new InputStreamReader(
this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml"))));
String line;
xmlString = new StringBuilder();
while((line=br.readLine())!= null){
xmlString.append(line.trim());
}
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)xmlString.length(), xmlString.toString());
byte[] encodedXML = testEncoder.encode();
//1 + 2 + 6 + 1 + 2 + 2 + 374
Assert.assertEquals(388, encodedXML.length);
}catch (IOException e){
e.printStackTrace();
}
}
@Test
public void getByteArrayNullTest(){
StringBuilder xmlString;
try{
BufferedReader br = new BufferedReader(new InputStreamReader(
this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml"))));
String line;
xmlString = new StringBuilder();
while((line=br.readLine())!= null){
xmlString.append(line.trim());
}
XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)1, xmlString.toString());
byte[] encodedXML = testEncoder.encode();
Assert.assertEquals(null, encodedXML);
}catch (IOException e){
e.printStackTrace();
}
}
}

@ -8,10 +8,10 @@
<name>team-7</name>
<modules>
<module>racevisionGame</module>
<module>mock</module>
<module>visualiser</module>
<module>network</module>
<module>sharedModel</module>
</modules>
<url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url>

@ -0,0 +1,218 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>seng302</groupId>
<artifactId>team-7</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<name>racevisionGame</name>
<artifactId>racevisionGame</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jetbrains/annotations -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>15.0</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>9.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
</repositories>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<profiles>
<profile>
<id>mock</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>mock.app.App</Main-Class>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>visualiser</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>visualiser.app.App</Main-Class>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.6</version>
<configuration>
<linkXRef>true</linkXRef>
<targetJdk>${maven.compiler.target}</targetJdk>
<rulesets>
<ruleset>/rulesets/java/basic.xml</ruleset>
<ruleset>/rulesets/java/imports.xml</ruleset>
<ruleset>/rulesets/java/codesize.xml</ruleset>
<ruleset>/rulesets/java/design.xml</ruleset>
<ruleset>/rulesets/java/empty.xml</ruleset>
<ruleset>/rulesets/java/junit.xml</ruleset>
<ruleset>/rulesets/java/unusedcode.xml</ruleset>
</rulesets>
<includeXmlInSite>true</includeXmlInSite>
<sourceEncoding>utf-8</sourceEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.19.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8.1</version>
</plugin>
</plugins>
</reporting>
</project>

@ -0,0 +1,54 @@
package mock.app;
import javafx.application.Application;
import javafx.stage.Stage;
import mock.dataInput.PolarParser;
import mock.model.Polars;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import shared.dataInput.XMLReader;
import shared.enums.XMLFileType;
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("mock/polars/acc_polars.csv");
String regattaXML = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8);
String raceXML = XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8);
String boatXML = XMLReader.readXMLFileToString("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8);
Event raceEvent = new Event(raceXML, regattaXML, boatXML, XMLFileType.Contents, boatPolars);
raceEvent.start();
} catch (Exception e) {
//Catch all exceptions, print, and exit.
e.printStackTrace();
System.exit(1);
}
}
}

@ -0,0 +1,124 @@
package mock.app;
import mock.model.MockRace;
import mock.model.Polars;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import shared.model.Constants;
import java.io.IOException;
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 {
private String raceXML;
private String regattaXML;
private String boatXML;
private XMLFileType xmlFileType;
private Polars boatPolars;
private MockOutput mockOutput;
private LatestMessages latestMessages;
/**
* Constructs an event, using various XML files.
* @param raceXML The race.xml file.
* @param regattaXML The regatta.xml file.
* @param boatXML The boat.xml file.
* @param type How to read the file - e.g., load as resource.
* @param boatPolars polars that the boat uses
*/
public Event(String raceXML, String regattaXML, String boatXML, XMLFileType type, Polars boatPolars) {
this.raceXML = getRaceXMLAtCurrentTime(raceXML);
this.boatXML = boatXML;
this.regattaXML = regattaXML;
this.xmlFileType = type;
this.boatPolars = boatPolars;
this.latestMessages = new LatestMessages();
try {
this.mockOutput = new MockOutput(this.latestMessages);
new Thread(mockOutput).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Sends the initial race data and then begins race simulation.
* @throws InvalidRaceDataException Thrown if the race xml file cannot be parsed.
* @throws XMLReaderException Thrown if any of the xml files cannot be parsed.
* @throws InvalidBoatDataException Thrown if the boat xml file cannot be parsed.
* @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed.
*/
public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException {
sendXMLs();
//Parse the XML files into data sources.
RaceDataSource raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType);
BoatDataSource boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType);
RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType);
//Create and start race.
MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale);
new Thread(newRace).start();
}
/**
* Sends out each xml string, via the mock output
*/
private void sendXMLs() {
mockOutput.setRegattaXml(regattaXML);
mockOutput.setRaceXml(raceXML);
mockOutput.setBoatsXml(boatXML);
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
* @return String containing edited xml
*/
private String getRaceXMLAtCurrentTime(String raceXML) {
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
raceXML = raceXML.replace("CREATION_TIME", dateFormat.format(creationTime));
raceXML = raceXML.replace("START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
return raceXML;
}
}

@ -0,0 +1,431 @@
package mock.app;
import network.BinaryMessageEncoder;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.XMLMessageType;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
/**
* 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;
/**
* An object containing the set of latest messages to send.
* Every server frame, MockOutput reads messages from this, and send them.
*/
private LatestMessages latestMessages;
/**
* Ack numbers used in messages.
*/
private int ackNumber = 1;
/**
* Sequence number for race xml messages.
*/
private short raceXMLSequenceNumber = 1;
/**
* Sequence number for boat xml messages.
*/
private short boatXMLSequenceNumber = 1;
/**
* Sequence number for regatta xml messages.
*/
private short regattaXMLSequenceNumber = 1;
/**
* Sequence number for heartbeat messages.
*/
private int heartbeatSequenceNum = 1;
private boolean stop = false; //whether or not hte thread keeps running
/**
* Ctor.
* @param latestMessages The collection of messages to send to connected clients.
* @throws IOException if server socket cannot be opened.
*/
public MockOutput(LatestMessages latestMessages) throws IOException {
this.lastHeartbeatTime = System.currentTimeMillis();
this.serverSocket = new ServerSocket(serverPort);
this.latestMessages = latestMessages;
}
/**
* Increments the ackNumber value, and returns it.
* @return Incremented ackNumber.
*/
private int getNextAckNumber(){
this.ackNumber++;
return this.ackNumber;
}
/**
* 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;
}
/**
* Generates the next heartbeat message and returns it. Increments the heartbeat sequence number.
* @return The next heartbeat message.
*/
private Heartbeat createHeartbeatMessage() {
//Create the heartbeat message.
Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum);
heartbeatSequenceNum++;
return heartbeat;
}
/**
* Serializes a heartbeat message into a packet to be sent, and returns the byte array.
* @param heartbeat The heartbeat message to serialize.
* @return Byte array containing the next heartbeat message.
*/
private byte[] parseHeartbeat(Heartbeat heartbeat) {
//Serializes the heartbeat message.
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat);
//Places the serialized message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.HEARTBEAT,
System.currentTimeMillis(),
getNextAckNumber(),
(short) heartbeatMessage.length,
heartbeatMessage );
return binaryMessageEncoder.getFullMessage();
}
/**
* Creates an XMLMessage of a specified subtype using the xml contents string.
* @param xmlString The contents of the xml file.
* @param messageType The subtype of xml message (race, regatta, boat).
* @return The created XMLMessage object.
*/
private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) {
//Get the correct sequence number to use, and increment it.
short sequenceNumber = 0;
if (messageType == XMLMessageType.RACE) {
sequenceNumber = this.raceXMLSequenceNumber;
this.raceXMLSequenceNumber++;
} else if (messageType == XMLMessageType.BOAT) {
sequenceNumber = this.boatXMLSequenceNumber;
this.boatXMLSequenceNumber++;
} else if (messageType == XMLMessageType.REGATTA) {
sequenceNumber = this.regattaXMLSequenceNumber;
this.regattaXMLSequenceNumber++;
}
//Create the message.
XMLMessage message = new XMLMessage(
XMLMessage.currentVersionNumber,
getNextAckNumber(),
System.currentTimeMillis(),
messageType,
sequenceNumber,
xmlString );
return message;
}
/**
* Encodes/serialises a XMLMessage message, and returns it.
* @param xmlMessage The XMLMessage message to serialise.
* @return The XMLMessage message in a serialised form.
*/
private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) {
//Serialize the xml message.
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage);
//Place the message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.XMLMESSAGE,
System.currentTimeMillis(),
xmlMessage.getAckNumber(), //We use the ack number from the xml message.
(short) encodedXML.length,
encodedXML );
return binaryMessageEncoder.getFullMessage();
}
/**
* Encodes/serialises a BoatLocation message, and returns it.
* @param boatLocation The BoatLocation message to serialise.
* @return The BoatLocation message in a serialised form.
*/
private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){
//Encodes the message.
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
//Encodes the full message with header.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.BOATLOCATION,
System.currentTimeMillis(),
getNextAckNumber(),
(short) encodedBoatLoc.length,
encodedBoatLoc );
return binaryMessageEncoder.getFullMessage();
}
/**
* Encodes/serialises a RaceStatus message, and returns it.
* @param raceStatus The RaceStatus message to serialise.
* @return The RaceStatus message in a serialised form.
*/
private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){
//Encodes the messages.
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
//Encodes the full message with header.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
MessageType.RACESTATUS,
System.currentTimeMillis(),
getNextAckNumber(),
(short) encodedRaceStatus.length,
encodedRaceStatus );
return binaryMessageEncoder.getFullMessage();
}
/**
* Sending loop of the Server
*/
public void run() {
try {
while (!stop){
//Wait for a client to connect.
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
mockSocket = serverSocket.accept();
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
//Wait until all of the xml files have been set.
if (!this.latestMessages.hasAllXMLMessages()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
long previousFrameTime = System.currentTimeMillis();
boolean sentXMLs = false;
while(true) {
try {
long currentFrameTime = System.currentTimeMillis();
//This is the time elapsed, in milliseconds, since the last server "frame".
long framePeriod = currentFrameTime - previousFrameTime;
//We only attempt to send packets every X milliseconds.
long minimumFramePeriod = 16;
if (framePeriod >= minimumFramePeriod) {
//Sends a heartbeat every so often.
if (timeSinceHeartbeat() >= heartbeatPeriod) {
outToVisualiser.write(parseHeartbeat(createHeartbeatMessage()));
lastHeartbeatTime = System.currentTimeMillis();
}
//Send XML messages.
if (!sentXMLs) {
//Serialise them.
byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage());
byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage());
byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage());
//Send them.
outToVisualiser.write(raceXMLBlob);
outToVisualiser.write(regattaXMLBlob);
outToVisualiser.write(boatsXMLBlob);
sentXMLs = true;
}
//Sends the RaceStatus message.
if (this.latestMessages.getRaceStatus() != null) {
byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus());
this.outToVisualiser.write(raceStatusBlob);
}
//Send all of the BoatLocation messages.
for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) {
//Get the message.
BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID);
if (boatLocation != null) {
//Encode.
byte[] boatLocationBlob = this.parseBoatLocation(boatLocation);
//Write it.
this.outToVisualiser.write(boatLocationBlob);
}
}
previousFrameTime = currentFrameTime;
} else {
//Wait until the frame period will be large enough.
long timeToWait = minimumFramePeriod - framePeriod;
try {
Thread.sleep(timeToWait);
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
e.printStackTrace();
//Re-set the interrupt flag.
Thread.currentThread().interrupt();
return;
}
}
} 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) {
//Create the message.
XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE);
//Place it in LatestMessages.
this.latestMessages.setRaceXMLMessage(message);
}
/**
* Sets the Regatta XMl to send.
* @param regattaXml XML to send to Client.
*/
public void setRegattaXml(String regattaXml) {
//Create the message.
XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA);
//Place it in LatestMessages.
this.latestMessages.setRegattaXMLMessage(message);
}
/**
* Sets the Boats XML to send.
* @param boatsXml XMl to send to the Client.
*/
public void setBoatsXml(String boatsXml) {
//Create the message.
XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT);
//Place it in LatestMessages.
this.latestMessages.setBoatXMLMessage(message);
}
public static void main(String argv[]) throws Exception
{
MockOutput client = new MockOutput(new LatestMessages());
client.run();
}
}

@ -1,8 +1,10 @@
package seng302.DataInput;
package mock.dataInput;
import seng302.Exceptions.InvalidPolarFileException;
import seng302.Model.Bearing;
import seng302.Model.Polars;
import mock.exceptions.InvalidPolarFileException;
import mock.model.Polars;
import shared.model.Bearing;
import java.io.*;
import java.util.ArrayList;

@ -1,4 +1,4 @@
package seng302.Exceptions;
package mock.exceptions;
/**
* An exception thrown when we cannot parse a polar data file.

@ -0,0 +1,206 @@
package mock.model;
import shared.model.*;
/**
* Represents a Boat on the mock side of a race.
* This adds mock specific functionality to a boat.
*/
public class MockBoat extends Boat {
/**
* 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.
*/
private long timeSinceTackChange = 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 MockBoat(int sourceID, String name, String country, Polars polars) {
super(sourceID, name, country);
this.polars = polars;
}
/**
* Constructs a mock boat object from a given boat and polars table.
*
* @param boat The boat to convert into a MockBoat.
* @param polars The polars table to use for this boat.
*/
public MockBoat(Boat boat, Polars polars) {
super(boat.getSourceID(), boat.getName(), boat.getCountry());
this.polars = polars;
}
/**
* 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 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;
}
/**
* Moves the boat meters forward in the direction that it is facing
* @param meters The number of meters to move forward.
* @param milliseconds The number of milliseconds to advance the boat's timers by.
*/
public void moveForwards(double meters, long milliseconds) {
//Update the boat's time since last tack.
this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds);
//Update the time into the current leg.
this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds);
//Update the distance into the current leg.
this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters);
//Updates the current position of the boat.
GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing()));
this.setCurrentPosition(newPosition);
}
/**
* Sets the boats speed and bearing to those in the given VMG.
* @param newVMG The new VMG to use for the boat - contains speed and bearing.
*/
public void setVMG(VMG newVMG) {
this.setBearing(newVMG.getBearing());
this.setCurrentSpeed(newVMG.getSpeed());
this.setTimeSinceTackChange(0);
}
/**
* Calculates the number of nautical miles the boat will travel in a given time slice.
* E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles.
* @param timeSlice The timeslice to use.
* @return The distance travelled, in nautical miles, over the given timeslice.
*/
public double calculateNauticalMilesTravelled(long timeSlice) {
//The proportion of one hour the current timeslice is.
//This will be a low fractional number, so we need to go from long -> double.
double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds;
//Calculates the distance travelled, in nautical miles, in the current timeslice.
//distanceTravelledNM = speed (nm p hr) * time taken to update loop
double distanceTravelledNM = this.getCurrentSpeed() * hourProportion;
return distanceTravelledNM;
}
/**
* Calculates the number of meters the boat will travel in a given time slice.
* E.g., in 53 milliseconds a boat may travel 0.02 meters.
* @param timeSlice The timeslice to use.
* @return The distance travelled, in meters, over the given timeslice.
*/
public double calculateMetersTravelled(long timeSlice) {
//Calculate the distance travelled, in nautical miles.
double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice);
//Convert to meters.
double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion;
return distanceTravelledMeters;
}
}

@ -1,22 +1,22 @@
package seng302.Model;
package mock.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 network.Messages.BoatLocation;
import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.LatestMessages;
import network.Messages.RaceStatus;
import network.Utils.AC35UnitConverter;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RegattaDataSource;
import shared.model.*;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.*;
import static java.lang.Math.cos;
@ -26,64 +26,27 @@ import static java.lang.Math.cos;
* Has a course, boats, boundaries, etc...
* Is responsible for simulating the race, and sending messages to a MockOutput instance.
*/
public class Race implements Runnable {
public class MockRace extends Race {
/**
* An observable list of boats in the race.
*/
private ObservableList<Boat> boats;
private List<MockBoat> 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.
* See {@link Constants#RaceTimeScale}.
*/
private int raceId;
private int scaleFactor;
/**
* 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.
@ -92,56 +55,50 @@ public class Race implements Runnable {
private int dnfChance = 0;
/**
* The mockOutput to send messages to.
* Used to generate random numbers when changing the wind direction.
*/
private MockOutput mockOutput;
private int changeWind = 4;
/**
* The bearing the wind direction starts at.
*/
private static final Bearing windBaselineBearing = Bearing.fromDegrees(225);
/**
* Wind direction bearing.
* The lower bearing angle that the wind may have.
*/
private Bearing windDirection;
private static final Bearing windLowerBound = Bearing.fromDegrees(215);
/**
* Wind speed (knots).
* Convert this to millimeters per second before passing to RaceStatus.
* The upper bearing angle that the wind may have.
*/
private double windSpeed;
private static final Bearing windUpperBound = Bearing.fromDegrees(235);
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.
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
* @param boatDataSource Data source for boat related data (yachts and marker boats).
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
* @param latestMessages The LatestMessages to send events to.
* @param polars The polars table to be used for boat simulation.
* @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}.
*/
public Race(RaceDataSource raceData, MockOutput mockOutput) {
public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) {
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);
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
this.scaleFactor = timeScale;
this.legs = raceData.getLegs();
this.legs.add(new Leg("Finish", this.legs.size()));
this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars);
this.raceId = raceData.getRaceId();
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
//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);
@ -149,15 +106,45 @@ public class Race implements Runnable {
}
/**
* Generates a list of MockBoats given a list of Boats, and a list of participating boats.
* @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat.
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
* @param polars The polars table to be used for boat simulation.
* @return A list of MockBoats that are participating in the race.
*/
private List<MockBoat> generateMockBoats(Map<Integer, Boat> boats, List<Integer> sourceIDs, Polars polars) {
List<MockBoat> mockBoats = new ArrayList<>(sourceIDs.size());
//For each sourceID participating...
for (int sourceID : sourceIDs) {
//Get the boat associated with the sourceID.
Boat boat = boats.get(sourceID);
//Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars);
mockBoats.add(mockBoat);
}
return mockBoats;
}
/**
* Runnable for the thread.
*/
public void run() {
initialiseBoats();
initialiseWindDir();
countdownTimer.start();
initialiseWindDirection();
this.countdownTimer.start();
}
/**
* Parse the compound marker boats through mock output.
*/
@ -186,7 +173,20 @@ public class Race implements Runnable {
*/
private void parseIndividualMark(Mark mark) {
this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime);
//Create message.
BoatLocation boatLocation = new BoatLocation(
mark.getSourceID(),
mark.getPosition().getLatitude(),
mark.getPosition().getLongitude(),
this.boatLocationSequenceNumber,
0, 0,
this.raceClock.getCurrentTimeMilli());
//Iterates the sequence number.
this.boatLocationSequenceNumber++;
this.latestMessages.setBoatLocation(boatLocation);
}
@ -196,7 +196,7 @@ public class Race implements Runnable {
private void parseBoatLocations() {
//Parse each boat.
for (Boat boat : this.boats) {
for (MockBoat boat : this.boats) {
this.parseIndividualBoatLocation(boat);
@ -208,42 +208,52 @@ public class Race implements Runnable {
* Parses an individual boat, and sends it to mockOutput.
* @param boat The boat to parse.
*/
private void parseIndividualBoatLocation(Boat boat) {
private void parseIndividualBoatLocation(MockBoat boat) {
this.mockOutput.parseBoatLocation(
BoatLocation boatLocation = new BoatLocation(
boat.getSourceID(),
boat.getCurrentPosition().getLatitude(),
boat.getCurrentPosition().getLongitude(),
this.boatLocationSequenceNumber,
boat.getBearing().degrees(),
boat.getCurrentSpeed(),
startTime + totalTimeElapsed
);
this.raceClock.getCurrentTimeMilli());
//Iterates the sequence number.
this.boatLocationSequenceNumber++;
this.latestMessages.setBoatLocation(boatLocation);
}
/**
* Updates the race status enumeration based on the current time, in milliseconds.
* @param currentTime The current time, in milliseconds.
* Updates the race time to a specified value, in milliseconds since the unix epoch.
* @param currentTime Milliseconds since unix epoch.
*/
private void updateRaceStatusEnum(long currentTime) {
private void updateRaceTime(long currentTime) {
this.raceClock.setUTCTime(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;
/**
* Updates the race status enumeration based on the current time.
*/
private void updateRaceStatusEnum() {
//The millisecond duration of the race. Negative means it hasn't started, so we flip sign.
long timeToStart = - this.raceClock.getDurationMilli();
if (timeToStartScaled > Constants.RacePreStartTime) {
if (timeToStart > Constants.RacePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
} else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) {
} else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) {
//Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
} else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) {
} else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) {
//Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
@ -265,23 +275,35 @@ public class Race implements Runnable {
List<BoatStatus> boatStatuses = new ArrayList<>();
//Add each boat status to the status list.
for (Boat boat : boats) {
for (MockBoat boat : this.boats) {
BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime());
BoatStatus boatStatus = new BoatStatus(
boat.getSourceID(),
boat.getStatus(),
boat.getCurrentLeg().getLegNumber(),
boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() );
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);
int windSpeedInt = (int) (this.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);
RaceStatus raceStatus = new RaceStatus(
System.currentTimeMillis(),
this.raceId,
this.getRaceStatusEnum().getValue(),
this.raceClock.getStartingTimeMilli(),
windDirectionInt,
windSpeedInt,
this.getRaceType().getValue(),
boatStatuses);
mockOutput.parseRaceStatus(raceStatus);
this.latestMessages.setRaceStatus(raceStatus);
}
@ -292,12 +314,24 @@ public class Race implements Runnable {
*/
private void setBoatsStatusToRacing() {
for (Boat boat : this.boats) {
for (MockBoat boat : this.boats) {
boat.setStatus(BoatStatusEnum.RACING);
}
}
/**
* Sets the estimated time at next mark for each boat to a specified time. This is used during the countdown timer to provide this value to boat before the race starts.
* @param time The time to provide to each boat.
*/
private void setBoatsTimeNextMark(ZonedDateTime time) {
for (MockBoat boat : this.boats) {
boat.setEstimatedTimeAtNextMark(time);
}
}
/**
* Countdown timer until race starts.
*/
@ -309,8 +343,14 @@ public class Race implements Runnable {
@Override
public void handle(long arg0) {
//Update race time.
updateRaceTime(currentTime);
//Update the race status based on the current time.
updateRaceStatusEnum(this.currentTime);
updateRaceStatusEnum();
//Provide boat's with an estimated time at next mark until the race starts.
setBoatsTimeNextMark(raceClock.getCurrentTime());
//Parse the boat locations.
parseBoatLocations();
@ -319,14 +359,13 @@ public class Race implements Runnable {
parseMarks();
// Change wind direction
changeWindDir();
changeWindDirection();
//Parse the race status.
parseRaceStatus();
if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
System.setProperty("javafx.animation.fullspeed", "true");
setBoatsStatusToRacing();
raceTimer.start();
this.stop();
@ -348,6 +387,11 @@ public class Race implements Runnable {
*/
long timeRaceStarted = System.currentTimeMillis();
/**
* Current time during a loop iteration.
*/
long currentTime = System.currentTimeMillis();
/**
* The time of the previous frame, in milliseconds.
*/
@ -357,27 +401,25 @@ public class Race implements Runnable {
public void handle(long arg0) {
//Get the current time.
long currentTime = System.currentTimeMillis();
currentTime = System.currentTimeMillis();
//Update race time.
updateRaceTime(currentTime);
//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) {
for (MockBoat boat : boats) {
//If it is still racing, update its position.
if (boat.getStatus() == BoatStatusEnum.RACING) {
updatePosition(boat, framePeriod, totalTimeElapsed);
updatePosition(boat, framePeriod, raceClock.getDurationMilli());
}
@ -392,7 +434,7 @@ public class Race implements Runnable {
if (getNumberOfActiveBoats() != 0) {
// Change wind direction
changeWindDir();
changeWindDirection();
//Parse the boat locations.
parseBoatLocations();
@ -417,34 +459,36 @@ public class Race implements Runnable {
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();
parseRaceStatus();
if (iters > 500) {
stop();
}
iters++;
}
};
/**
* Initialise the boats in the race.
* This sets their starting positions and current legs.
*/
public void initialiseBoats() {
@Override
protected 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<MockBoat> 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();
MockBoat boat = boatIt.next();
GPSCoordinate startPosition = startPositionIt.next();
@ -524,7 +568,7 @@ public class Race implements Runnable {
* @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) {
private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) {
//Get the lower and upper acceptable bounds.
Bearing lowerAcceptableBound = bearingBounds[0];
@ -535,7 +579,6 @@ public class Race implements Runnable {
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
return bestVMG;
}
@ -574,7 +617,7 @@ public class Race implements Runnable {
* @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) {
private boolean improvesVelocity(MockBoat boat, VMG vmg) {
//Get the boats "current" VMG.
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
@ -592,7 +635,7 @@ public class Race implements Runnable {
* @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) {
protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
//Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg());
@ -640,7 +683,7 @@ public class Race implements Runnable {
//Check the boats position (update leg and stuff).
this.checkPosition(boat, totalTimeElapsed);
this.checkPosition(boat, totalElapsedMilliseconds);
}
@ -651,7 +694,7 @@ public class Race implements Runnable {
* @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) {
private Bearing[] calculateBearingBounds(MockBoat boat) {
Bearing[] bearings = new Bearing[2];
@ -734,7 +777,7 @@ public class Race implements Runnable {
* @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) {
protected void checkPosition(MockBoat 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.
@ -766,6 +809,7 @@ public class Race implements Runnable {
boat.setTimeFinished(timeElapsed);
boat.setCurrentSpeed(0);
boat.setStatus(BoatStatusEnum.FINISHED);
} else if (doNotFinish()) {
//Boat has pulled out of race.
boat.setTimeFinished(timeElapsed);
@ -780,26 +824,6 @@ public class Race implements Runnable {
}
/**
* 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;
}
/**
@ -823,31 +847,6 @@ public class Race implements Runnable {
}
/**
* 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.
@ -856,74 +855,86 @@ public class Race implements Runnable {
*/
protected int getNumberOfActiveBoats() {
int numberofActiveBoats = 0;
int numberOfActiveBoats = 0;
for (Boat boat : this.boats) {
for (MockBoat boat : this.boats) {
//If the boat is currently racing, count it.
if (boat.getStatus() == BoatStatusEnum.RACING) {
numberofActiveBoats++;
numberOfActiveBoats++;
}
}
return numberofActiveBoats;
return numberOfActiveBoats;
}
/**
* Returns an observable list of boats in the race.
* Returns a list of boats in the race.
* @return List of boats in the race.
*/
public ObservableList<Boat> getBoats() {
public List<MockBoat> 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);
/**
* Initialises the wind bearing with the value of the windBaselineBearing.
*/
protected void initialiseWindDirection() {
//Set the starting bearing.
this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees());
}
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;
/**
* Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound].
*/
protected void changeWindDirection() {
//Randomly add or remove 0.5 degrees.
int r = new Random().nextInt(changeWind) + 1;
if (r == 1) {
//Add 0.5 degrees to the wind bearing.
this.windDirection.setDegrees(this.windDirection.degrees() + 0.5);
} else if (r == 2) {
//Minus 0.5 degrees from the wind bearing.
this.windDirection.setDegrees(this.windDirection.degrees() - 0.5);
}
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
this.windDirection = new Bearing(windDirDegrees);
}
//Ensure that the wind is in the correct bounds.
if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) {
this.windDirection.setBearing(MockRace.windUpperBound);
} else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) {
this.windDirection.setBearing(MockRace.windLowerBound);
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) {
private void updateEstimatedTime(MockBoat 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);
//Calculate milliseconds until boat reaches mark.
long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
//Calculate time at which it will reach mark.
ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS);
boat.setEstimatedTimeAtNextMark(timeAtMark);
}
}
}

@ -1,8 +1,12 @@
package seng302.Model;
package mock.model;
import javafx.util.Pair;
import shared.model.Bearing;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Encapsulates an entire polar table. Has a function to calculate VMG.

@ -1,4 +1,6 @@
package seng302.Model;
package mock.model;
import shared.model.Bearing;
/**
* This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component.

@ -1,10 +1,11 @@
package seng302.Networking;
package network;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.MessageDecoders.*;
import seng302.Networking.Messages.*;
import seng302.Networking.Utils.*;
import seng302.Networking.Messages.Enums.MessageType;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.*;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import static network.Utils.ByteConverter.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -72,20 +73,20 @@ public class BinaryMessageDecoder {
this.headerMessageType = this.messageHeader[2];
//Get the header timestamp.
this.headerTimeStamp = ByteConverter.bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9));
this.headerTimeStamp = bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9));
//Get the source ID for the message.
this.headerSourceID = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13));
this.headerSourceID = bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13));
//Get the length of the message body.
this.messageBodyLength = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15));
this.messageBodyLength = bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15));
//Get the messageBody.
this.messageBody = Arrays.copyOfRange(this.fullMessage, this.headerLength, this.headerLength + this.messageBodyLength);
//Get the CRC value.
this.messageCRCValue = ByteConverter.bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length));
this.messageCRCValue = bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length));
//Combine the header and body into a single array.
ByteBuffer headerBodyByteBuffer = ByteBuffer.allocate(messageHeader.length + messageBody.length);
@ -134,7 +135,7 @@ public class BinaryMessageDecoder {
//System.out.println("Decoding HeartBeat Message!");
//TODO maybe use HeartbeatDecoder.decode(message).
//TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);.
return new Heartbeat(ByteConverter.bytesToLong(messageBody));
return new Heartbeat(bytesToLong(messageBody));
case RACESTATUS:
//System.out.println("Race Status Message");
@ -150,7 +151,7 @@ public class BinaryMessageDecoder {
//System.out.println("XML Message!");
XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody);
xmdecoder.decode();
return new XMLMessage(xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMsgLength(), xmdecoder.getXmlMessageInputStream());
return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents());
case RACESTARTSTATUS:
//System.out.println("Race Start Status Message");

@ -1,14 +1,13 @@
package seng302.Networking;
package network;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
import static network.Utils.ByteConverter.*;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import static seng302.Networking.Utils.ByteConverter.intToBytes;
import static seng302.Networking.Utils.ByteConverter.longToBytes;
import static seng302.Networking.Utils.ByteConverter.shortToBytes;
/**
* This class can be used to encode/convert a byte array message body, plus header data into a byte array containing the entire message, ready to send.

@ -1,4 +1,4 @@
package seng302.Networking.Exceptions;
package network.Exceptions;
/**
* Exception which is thrown when a message is read, but it is invalid in some way (CRC is wrong, sync bytes, etc...).

@ -1,7 +1,8 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import seng302.Networking.Messages.AverageWind;
import seng302.Networking.Utils.ByteConverter;
import network.Messages.AverageWind;
import network.Utils.ByteConverter;
import java.util.Arrays;

@ -1,10 +1,14 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import seng302.Networking.Messages.BoatLocation;
import network.Messages.BoatLocation;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.*;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
import static network.Utils.ByteConverter.bytesToShort;
/**
* Created by hba56 on 21/04/17.

@ -1,11 +1,14 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import seng302.Networking.Messages.CourseWind;
import network.Messages.CourseWind;
import java.util.ArrayList;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.*;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
/**
* Created by hba56 on 23/04/17.

@ -1,7 +1,8 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import seng302.Networking.Utils.ByteConverter;
import seng302.Networking.Messages.MarkRounding;
import network.Messages.MarkRounding;
import network.Utils.ByteConverter;
import java.util.Arrays;

@ -1,10 +1,10 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.*;
import static network.Utils.ByteConverter.*;
/**
* Created by hba56 on 21/04/17.

@ -1,11 +1,15 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import seng302.Networking.Messages.BoatStatus;
import network.Messages.BoatStatus;
import java.util.ArrayList;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.*;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
import static network.Utils.ByteConverter.bytesToShort;
/**
* Created by hba56 on 21/04/17.
@ -24,7 +28,7 @@ public class RaceStatusDecoder {
private long time;
private int race;
private int raceState;
private byte raceState;
private long startTime;
private int raceWindDir;
private short raceWindSpeed;
@ -47,7 +51,7 @@ public class RaceStatusDecoder {
time = bytesToLong(timeBytes);
race = bytesToInt(raceID);
raceState = bytesToInt(raceStatus);
raceState = raceStatus;
startTime = bytesToLong(expectedStart);
raceWindDir = bytesToInt(raceWind);
raceWindSpeed = bytesToShort(windSpeed);
@ -87,7 +91,7 @@ public class RaceStatusDecoder {
return race;
}
public int getRaceState() {
public byte getRaceState() {
return raceState;
}

@ -1,12 +1,15 @@
package seng302.Networking.MessageDecoders;
package network.MessageDecoders;
import network.Messages.Enums.XMLMessageType;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.bytesToLong;
import static seng302.Networking.Utils.ByteConverter.bytesToShort;
import static network.Utils.ByteConverter.bytesToLong;
import static network.Utils.ByteConverter.bytesToShort;
/**
* Created by hba56 on 20/04/17.
@ -41,7 +44,7 @@ public class XMLMessageDecoder {
this.sequenceNumber = bytesToShort(sequenceNumberBytes);
this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes);
this.xmlMessage = new String(xmlMessagebytes);
this.xmlMessage = new String(xmlMessagebytes).trim();
}
public byte getMessageVersionNumber() {
@ -56,8 +59,8 @@ public class XMLMessageDecoder {
return timeStamp;
}
public byte getXmlMsgSubType() {
return xmlMsgSubType;
public XMLMessageType getXmlMsgSubType() {
return XMLMessageType.fromByte(xmlMsgSubType);
}
public short getSequenceNumber() {
@ -68,14 +71,14 @@ public class XMLMessageDecoder {
return xmlMsgLength;
}
/**
* this will be used latter for the vis
* @return xml string as inputsource
* Returns the contents of the XML message (e.g., the contents of a race.xml file).
* @return The contents of the XML message.
*/
public InputStream getXmlMessageInputStream() {
InputStream is = new ByteArrayInputStream(xmlMessage.trim().getBytes(StandardCharsets.UTF_8));
// InputSource is = new InputSource(new StringReader(xmlMessage.trim()));
return is;
public String getXmlMessageContents() {
return xmlMessage;
}
}

@ -1,13 +1,16 @@
package seng302.Networking.MessageEncoders;
package network.MessageEncoders;
import seng302.Networking.Messages.*;
import network.Messages.*;
import static network.Utils.ByteConverter.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static seng302.Networking.Utils.ByteConverter.*;
/**
* Created by fwy13 on 19/04/17.
@ -16,13 +19,16 @@ public class RaceVisionByteEncoder {
/**
* Serializes a heartbeat message.
* @param seq Heartbeat value.
* @param heartbeat Heartbeat message.
* @return Serialized message.
*/
public static byte[] heartBeat(long seq){
public static byte[] heartBeat(Heartbeat heartbeat) {
ByteBuffer heartBeat = ByteBuffer.allocate(4);
heartBeat.put(longToBytes(seq, 4));
byte [] result = heartBeat.array();
heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4));
byte[] result = heartBeat.array();
return result;
}
@ -78,7 +84,7 @@ public class RaceVisionByteEncoder {
return raceStatusMessage.array();
}
public byte[] displayTextMessage(RaceMessage[] message){
public static byte[] displayTextMessage(RaceMessage[] message){
//ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32);
int messageVersionNumber = 0b1;//version number
short ackNum = 0;//no clue what this does just a placeholder for 2 bytes.
@ -119,7 +125,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){
public static byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){
int messageVersion = 0b1;
byte[] timestamp = longToBytes(time, 6);
byte[] ackNumber = intToBytes(ack, 2);
@ -138,7 +144,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID,
public static byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID,
int eventID){
int messageVersion = 0b10;
byte[] encodeTime = longToBytes(time, 6);
@ -159,7 +165,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] chatterText(int messageType, String message){
public static byte[] chatterText(int messageType, String message){
int messageVersion = 0b1;
byte[] type = intToBytes(messageType, 1);
byte[] text = message.getBytes();
@ -174,6 +180,44 @@ public class RaceVisionByteEncoder {
return result.array();
}
/**
* Serializes an xml message into a byte array.
* @param xmlMessage The message to serialize.
* @return byte array contaning serialized message.
*/
public static byte[] xmlMessage(XMLMessage xmlMessage) {
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
//ackNumber converted to bytes
byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2);
//Timestamp converted to bytes.
byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6);
//sequenceNumber converted to bytes
byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2);
//xmlMsgLength converted to bytes
byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2);
tempOutputByteBuffer.put(xmlMessage.getVersionNumber());
tempOutputByteBuffer.put(ackNumberBytes);
tempOutputByteBuffer.put(timestampBytes);
tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue());
tempOutputByteBuffer.put(sequenceNumberBytes);
tempOutputByteBuffer.put(xmlMsgLengthBytes);
tempOutputByteBuffer.put(messageBytes);
return tempOutputByteBuffer.array();
}
public static byte[] boatLocation(BoatLocation boatLocation){
int messageVersionNumber = 0b1;
byte[] time = longToBytes(boatLocation.getTime(), 6);
@ -224,7 +268,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
int messageVersionNumber = 0b1;
byte[] byteTime = longToBytes(time, 6);
byte[] byteAck = intToBytes(ackNumber, 2);
@ -248,7 +292,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] courseWind(byte windID, ArrayList<CourseWind> courseWinds){
public static byte[] courseWind(byte windID, ArrayList<CourseWind> courseWinds){
int messageVersionNumber = 0b1;
byte byteWindID = windID;
byte[] loopcount = intToBytes(courseWinds.size(), 1);
@ -269,7 +313,7 @@ public class RaceVisionByteEncoder {
return result.array();
}
public byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){
public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){
int messageVersionNumber = 0b1;
byte[] byteTime = longToBytes(time,6);
byte[] byteRawPeriod = intToBytes(rawPeriod, 2);

@ -1,14 +1,16 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* The base class for all message types.
*/
public abstract class AC35Data {
///Message type from the header.
/**
* Message type from the header.
*/
private MessageType type;

@ -1,6 +1,7 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.

@ -1,19 +1,19 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Utils.AC35UnitConverter;
import static seng302.Networking.Utils.AC35UnitConverter.convertGPS;
import static seng302.Networking.Utils.AC35UnitConverter.convertGPSToInt;
import network.Messages.Enums.MessageType;
import network.Utils.AC35UnitConverter;
import shared.model.Constants;
import static network.Utils.AC35UnitConverter.convertGPS;
import static network.Utils.AC35UnitConverter.convertGPSToInt;
/**
* Represents the information in a boat location message (AC streaming spec: 4.9).
*/
public class BoatLocation extends AC35Data {
//Knots x this = meters per second.
public static final double KnotsToMetersPerSecondConversionFactor =
0.514444;
//TODO move these to an enum.
public static final byte Unknown = 0;
public static final byte RacingYacht = 1;
public static final byte CommitteeBoat = 2;
@ -28,6 +28,7 @@ public class BoatLocation extends AC35Data {
public static final byte WeatherStation = 11;
public static final byte Helicopter = 12;
public static final byte DataProcessingApplication = 13;
///Version number of the message - is always 1.
private byte messageVersionNumber = 1;
///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int.
@ -263,11 +264,8 @@ public class BoatLocation extends AC35Data {
* @return Speed in millimeters per second, stored as an int (using only the two least significant bytes).
*/
public static int convertBoatSpeedDoubleToInt(double speed) {
//Calculate meters per second.
double metersPerSecond = speed * KnotsToMetersPerSecondConversionFactor;
//Calculate millimeters per second.
double millimetersPerSecond = metersPerSecond * 1000.0;
double millimetersPerSecond = speed * Constants.KnotsToMMPerSecond;
//Convert to an int.
int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond);
@ -282,11 +280,9 @@ public class BoatLocation extends AC35Data {
* @return Speed in knots, stored as a double.
*/
public static double convertBoatSpeedIntToDouble(int speed) {
//Calculate meters per second.
double metersPerSecond = speed / 1000.0;
//Calculate knots.
double knots = metersPerSecond / KnotsToMetersPerSecondConversionFactor;
double knots = speed / Constants.KnotsToMMPerSecond;
return knots;
}

@ -1,7 +1,8 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Utils.ByteConverter;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.BoatStatusEnum;
import network.Utils.ByteConverter;
/**
* Created by hba56 on 23/04/17.

@ -1,6 +1,7 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 21/04/17.

@ -1,8 +1,9 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import java.util.ArrayList;
import network.Messages.Enums.MessageType;
import java.util.List;
/**
* Created by fwy13 on 25/04/17.
@ -11,9 +12,9 @@ public class CourseWinds extends AC35Data {
private int msgVerNum;
private int selectedWindID;
private ArrayList<CourseWind> courseWinds;
private List<CourseWind> courseWinds;
public CourseWinds(int msgVerNum, int selectedWindID, ArrayList<CourseWind> courseWinds){
public CourseWinds(int msgVerNum, int selectedWindID, List<CourseWind> courseWinds){
super(MessageType.COURSEWIND);
this.msgVerNum = msgVerNum;
this.selectedWindID = selectedWindID;

@ -1,4 +1,4 @@
package seng302.Networking.Messages.Enums;
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;

@ -1,4 +1,4 @@
package seng302.Networking.Messages.Enums;
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;

@ -1,4 +1,4 @@
package seng302.Networking.Messages.Enums;
package network.Messages.Enums;
import java.util.HashMap;
@ -20,6 +20,10 @@ public enum RaceStatusEnum {
* Less than 1:00 minutes before start.
*/
PREPARATORY(2),
/**
* Race has started.
*/
STARTED(3),
/**

@ -1,4 +1,4 @@
package seng302.Networking.Messages.Enums;
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
@ -22,7 +22,7 @@ public enum RaceTypeEnum {
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_A_STATUS(-1);
NOT_A_RACE_TYPE(-1);
/**
@ -48,6 +48,29 @@ public enum RaceTypeEnum {
}
/**
* Attempts to convert a string into a RaceTypeEnum.
* Ignores case.
* Treats anything starting with "fleet" as {@link #FLEET_RACE}, and anything starting with "match" as {@link #MATCH_RACE}.
* @param value The string to convert.
* @return The RaceTypeEnum.
*/
public static RaceTypeEnum fromString(String value) {
//Convert to lower case.
value = value.toLowerCase();
if (value.startsWith("fleet")) {
return FLEET_RACE;
} else if (value.startsWith("match")) {
return MATCH_RACE;
} else {
return NOT_A_RACE_TYPE;
}
}
/**
* Stores a mapping between Byte values and RaceStatusEnum values.
*/
@ -74,8 +97,8 @@ public enum RaceTypeEnum {
RaceTypeEnum type = RaceTypeEnum.byteToStatusMap.get(raceTypeEnum);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_STATUS RaceTypeEnum.
return RaceTypeEnum.NOT_A_STATUS;
//If the byte value wasn't found, return the NOT_A_RACE_TYPE RaceTypeEnum.
return RaceTypeEnum.NOT_A_RACE_TYPE;
} else {
//Otherwise, return the RaceTypeEnum.
return type;

@ -0,0 +1,87 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types of XML messages that can be sent.
*/
public enum XMLMessageType {
/**
* A regatta.xml message.
*/
REGATTA(5),
/**
* A race.xml message.
*/
RACE(6),
/**
* A boats.xml message.
*/
BOAT(7),
/**
* Used for unrecognised byte values.
*/
NOT_A_MESSAGE_TYPE(0);
///Primitive value of the enum.
private byte value;
/**
* Ctor. Creates a XMLMessageType enum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private XMLMessageType(int value) {
this.value = (byte)value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
///Stores a mapping between Byte values and XMLMessageType values.
private static final Map<Byte, XMLMessageType> byteToTypeMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToTypeMap.
*/
static {
for (XMLMessageType type : XMLMessageType.values()) {
byteToTypeMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param messageTypeByte Byte value to convert to a XMLMessageType value.
* @return The XMLMessageType value which corresponds to the given byte value.
*/
public static XMLMessageType fromByte(byte messageTypeByte) {
//Gets the corresponding XMLMessageType from the map.
XMLMessageType type = byteToTypeMap.get(messageTypeByte);
if (type == null) {
//If the byte value wasn't found, return the NOTAMESSAGE XMLMessageType.
return XMLMessageType.NOT_A_MESSAGE_TYPE;
}
else {
//Otherwise, return the XMLMessageType.
return type;
}
}
}

@ -1,13 +1,16 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Represents a Heartbeat message.
*/
public class Heartbeat extends AC35Data {
///Sequence number of the heartbeat.
/**
* Sequence number of the heartbeat.
*/
private long sequenceNumber;
/**

@ -0,0 +1,303 @@
package network.Messages;
import network.Messages.Enums.XMLMessageType;
import shared.dataInput.RaceDataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
/**
* This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...).
* Currently, LatestMessage only notifies observers of change when a new XMLMessage is received.
*/
public class LatestMessages extends Observable {
/**
* The latest RaceStatus message.
*/
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<>();
/**
* A map of the last MarkRounding message received, for each boat.
*/
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
/**
* The last AverageWind message received.
*/
private AverageWind averageWind;
/**
* The last CourseWinds message received.
*/
private CourseWinds courseWinds;
/**
* The latest race data XML message.
*/
private XMLMessage raceXMLMessage;
/**
* The latest boat data XML message.
*/
private XMLMessage boatXMLMessage;
/**
* The latest regatta data XML message.
*/
private XMLMessage regattaXMLMessage;
/**
* Ctor.
*/
public LatestMessages() {
}
/**
* Gets the latest RaceStatus message received.
* @return The latest RaceStatus message received.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Sets the latest RaceStatus message received.
* @param raceStatus The new RaceStatus message to store.
*/
public void setRaceStatus(RaceStatus raceStatus) {
this.raceStatus = raceStatus;
}
/**
* Returns the latest BoatStatus message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest BoatStatus message for the specified boat.
*/
public BoatStatus getBoatStatus(int sourceID) {
return boatStatusMap.get(sourceID);
}
/**
* Inserts a BoatStatus message for a given boat.
* @param boatStatus The BoatStatus message to set.
*/
public void setBoatStatus(BoatStatus boatStatus) {
boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
}
/**
* Returns the latest BoatLocation message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest BoatLocation message for the specified boat.
*/
public BoatLocation getBoatLocation(int sourceID) {
return boatLocationMap.get(sourceID);
}
/**
* Inserts a BoatLocation message for a given boat.
* @param boatLocation The BoatLocation message to set.
*/
public void setBoatLocation(BoatLocation boatLocation) {
//TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one.
boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
/**
* Returns the latest MarkRounding message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest MarkRounding message for the specified boat.
*/
public MarkRounding getMarkRounding(int sourceID) {
return markRoundingMap.get(sourceID);
}
/**
* Inserts a MarkRounding message for a given boat.
* @param markRounding The MarkRounding message to set.
*/
public void setMarkRounding(MarkRounding markRounding) {
//TODO should compare the sequence number of the new markRounding with the existing boatLocation for this boat (if it exists), and use the newer one.
markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
/**
* Gets the latest AverageWind message received.
* @return The latest AverageWind message received.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Sets the latest AverageWind message received.
* @param averageWind The new AverageWind message to store.
*/
public void setAverageWind(AverageWind averageWind) {
this.averageWind = averageWind;
}
/**
* Gets the latest CourseWinds message received.
* @return The latest CourseWinds message received.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Sets the latest CourseWinds message received.
* @param courseWinds The new CourseWinds message to store.
*/
public void setCourseWinds(CourseWinds courseWinds) {
this.courseWinds = courseWinds;
}
/**
* Returns the map of boat sourceIDs to BoatLocation messages.
* @return Map between boat sourceID and BoatLocation.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Returns the map of boat sourceIDs to BoatStatus messages.
* @return Map between boat sourceID and BoatStatus.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the map of boat sourceIDs to MarkRounding messages.
* @return Map between boat sourceID and MarkRounding.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Returns the latest race xml message.
* @return The latest race xml message.
*/
public XMLMessage getRaceXMLMessage() {
return raceXMLMessage;
}
/**
* Sets the latest race xml message to a specified race XML message.
* @param raceXMLMessage The new race XML message to use.
*/
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
this.raceXMLMessage = raceXMLMessage;
this.setChanged();
this.notifyObservers();
}
/**
* Returns the latest boat xml message.
* @return The latest boat xml message.
*/
public XMLMessage getBoatXMLMessage() {
return boatXMLMessage;
}
/**
* Sets the latest boat xml message to a specified boat XML message.
* @param boatXMLMessage The new boat XML message to use.
*/
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
this.boatXMLMessage = boatXMLMessage;
this.setChanged();
this.notifyObservers();
}
/**
* Returns the latest regatta xml message.
* @return The latest regatta xml message.
*/
public XMLMessage getRegattaXMLMessage() {
return regattaXMLMessage;
}
/**
* Sets the latest regatta xml message to a specified regatta XML message.
* @param regattaXMLMessage The new regatta XML message to use.
*/
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
this.regattaXMLMessage = regattaXMLMessage;
this.setChanged();
this.notifyObservers();
}
/**
* Checks the type of xml message, and places it in this LatestMessages object.
* @param xmlMessage The new xml message to use.
*/
public void setXMLMessage(XMLMessage xmlMessage) {
if (xmlMessage.getXmlMsgSubType() == XMLMessageType.RACE) {
this.setRaceXMLMessage(xmlMessage);
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.REGATTA) {
this.setRegattaXMLMessage(xmlMessage);
} else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.BOAT) {
this.setBoatXMLMessage(xmlMessage);
}
}
/**
* Returns whether or not there is an xml message for each message type.
* @return True if race, boat, and regatta have an xml message, false otherwise.
*/
public boolean hasAllXMLMessages() {
if ((this.regattaXMLMessage == null) || (this.boatXMLMessage == null) || (this.raceXMLMessage == null)) {
return false;
} else {
return true;
}
}
}

@ -1,6 +1,7 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.

@ -1,6 +1,7 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 19/04/17.

@ -1,6 +1,7 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.

@ -1,7 +1,9 @@
package seng302.Networking.Messages;
package network.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Utils.AC35UnitConverter;
import network.Messages.Enums.MessageType;
import network.Utils.AC35UnitConverter;
import shared.model.Constants;
import java.util.List;
@ -12,14 +14,14 @@ public class RaceStatus extends AC35Data {
private long currentTime;
private int raceID;
private int raceStatus;
private byte raceStatus;
private long expectedStartTime;
private int windDirection;
private int windSpeed;
private int raceType;
private List<BoatStatus> boatStatuses;
public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List<BoatStatus> boatStatuses){
public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List<BoatStatus> boatStatuses){
super(MessageType.RACESTATUS);
this.currentTime = currentTime;
this.raceID = raceID;
@ -49,7 +51,7 @@ public class RaceStatus extends AC35Data {
*
* @return race status number
*/
public int getRaceStatus()
public byte getRaceStatus()
{
return raceStatus;
}
@ -64,6 +66,10 @@ public class RaceStatus extends AC35Data {
return windDirection;
}
/**
* Returns the wind speed for this race status, in millimeters per second.
* @return Wind speed in millimeters per second.
*/
public int getWindSpeed()
{
return windSpeed;
@ -124,6 +130,14 @@ public class RaceStatus extends AC35Data {
}
public double getScaledWindDirection() {
return (double) AC35UnitConverter.convertHeading(windDirection);
return AC35UnitConverter.convertHeading(windDirection);
}
/**
* Returns the wind speed for this race status, in knots.
* @return Wind speed in knots.
*/
public double getWindSpeedKnots() {
return (windSpeed / Constants.KnotsToMMPerSecond);
}
}

@ -0,0 +1,136 @@
package network.Messages;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.XMLMessageType;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* Created by fwy13 on 25/04/17.
*/
public class XMLMessage extends AC35Data {
/**
* The current version number for xml messages is 1.
*/
public static byte currentVersionNumber = 1;
/**
* The version number of the message.
*/
private byte versionNumber;
/**
* The ack number of the message.
*/
private int ackNumber;
/**
* The timestamp of the message.
* Milliseconds since unix epoch.
*/
private long timeStamp;
/**
* The subtype of the xml message (e.g., race xml message).
*/
private XMLMessageType xmlMsgSubType;
/**
* The sequence number of this specific xml subtype.
* Increments whenever the xml contents for a specific xml subtype changes.
*/
private int sequenceNumber;
/**
* The length of the xml message.
* Number of bytes.
*/
private int xmlMsgLength;
/**
* The contents of the xml message.
*/
private String xmlMessage;
/**
* Constructor for an XML Message
* @param versionNumber The version number of the xml message.
* @param ackNumber Number for acknowledgement inherited for the AC35Data Packet
* @param timeStamp Time received
* @param xmlMsgSubType Type of XML message
* @param sequenceNumber Order that it has arrived in
* @param xmlMessage XML message
*/
public XMLMessage(byte versionNumber, int ackNumber, long timeStamp, XMLMessageType xmlMsgSubType, int sequenceNumber, String xmlMessage) {
super(MessageType.XMLMESSAGE);
this.versionNumber = versionNumber;
this.ackNumber = ackNumber;
this.timeStamp = timeStamp;
this.xmlMsgSubType = xmlMsgSubType;
this.sequenceNumber = sequenceNumber;
this.xmlMsgLength = xmlMessage.getBytes(StandardCharsets.UTF_8).length;
this.xmlMessage = xmlMessage;
}
/**
* Get the XML Message.
* @return the XML message as string.
*/
public String getXmlMessage() {
return xmlMessage;
}
/**
* Get the type of message
* @return Gets the type of message the XML message is
*/
public XMLMessageType getXmlMsgSubType() {
return xmlMsgSubType;
}
/**
* Returns the version number of this xml message.
* @return The version number of this xml message.
*/
public byte getVersionNumber() {
return versionNumber;
}
/**
* Returns the ack number of this xml message.
* @return The ack number of this xml message.
*/
public int getAckNumber() {
return ackNumber;
}
/**
* Returns the timestamp of this xml message.
* @return The timestamp of this xml message.
*/
public long getTimeStamp() {
return timeStamp;
}
/**
* Returns the sequence number of this xml message. This is specific to each message subtype.
* @return The sequence number of this xml message.
*/
public int getSequenceNumber() {
return sequenceNumber;
}
/**
* Returns the length, in number of bytes, of the xml message.
* @return The length, in bytes, of the xml message.
*/
public int getXmlMsgLength() {
return xmlMsgLength;
}
}

@ -1,8 +1,9 @@
package seng302.Networking.PacketDump;
package network.PacketDump;
import seng302.Networking.BinaryMessageDecoder;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.Messages.AC35Data;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import java.io.IOException;
import java.net.URISyntaxException;

@ -1,4 +1,4 @@
package seng302.Networking.PacketDump;
package network.PacketDump;
/**
* Created by fwy13 on 25/04/17.

@ -1,4 +1,4 @@
package seng302.Networking.Utils;
package network.Utils;
/**
* Created by fwy13 on 28/04/17.

@ -1,4 +1,4 @@
package seng302.Networking.Utils;
package network.Utils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

@ -0,0 +1,25 @@
package shared.dataInput;
import shared.model.Boat;
import shared.model.Mark;
import java.util.Map;
/**
* Provides information about the boats and marker boats in a race.
*/
public interface BoatDataSource {
/**
* Returns a map between source ID and boat for all boats in the race.
* @return Map between source ID and boat.
*/
Map<Integer, Boat> getBoats();
/**
* Returns a map between source ID and mark for all marks in the race.
* @return Map between source ID and mark.
*/
Map<Integer, Mark> getMarkerBoats();
}

@ -1,47 +1,74 @@
package seng302.DataInput;
package shared.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 shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.XMLReaderException;
import shared.model.Boat;
import shared.model.GPSCoordinate;
import shared.model.Mark;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* Xml Reader class for Boat XML used for the race
* Xml Reader class for Boat XML used for the race.
*/
public class BoatXMLReader extends XMLReader implements BoatDataSource {
/**
* A map of source ID to boat for all boats in the race.
*/
private final Map<Integer, Boat> boatMap = new HashMap<>();
/**
* A map of source ID to mark for all marks in the race.
*/
private final Map<Integer, Mark> markerMap = new HashMap<>();
/**
* Polars table to assign to each boat.
* Constructor for Boat XML using a file.
*
* @param file The file to read.
* @param type How to read the file - e.g., load as resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
* @throws InvalidBoatDataException Thrown if the file cannot be parsed correctly.
*/
Polars boatPolars;
public BoatXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidBoatDataException {
super(file, type);
//Attempt to read boat xml file.
try {
read();
} catch (Exception e) {
throw new InvalidBoatDataException("An error occurred while reading the boat xml file", e);
}
}
/**
* Constructor for Boat XML
* Constructor for Boat XML, using an InputStream.
*
* @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
* @param fileStream Stream to read boat data from.
* @throws XMLReaderException Thrown if the file cannot be parsed.
* @throws InvalidBoatDataException Thrown if the stream cannot be parsed correctly.
*/
public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
this.boatPolars = boatPolars;
read();
public BoatXMLReader(InputStream fileStream) throws XMLReaderException, InvalidBoatDataException {
super(fileStream);
//Attempt to read boat xml stream.
try {
read();
} catch (Exception e) {
throw new InvalidBoatDataException("An error occurred while reading the boat xml stream", e);
}
}
/**
* Read the XML
*/
@ -109,9 +136,9 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
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));
boatMap.put(sourceID, new Boat(sourceID, name, country));
} else {
boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars));
boatMap.put(sourceID, new Boat(sourceID, name, shortName));
}
}
@ -139,8 +166,8 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
}
/**
* 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.
* 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() {

@ -0,0 +1,87 @@
package shared.dataInput;
import network.Messages.Enums.RaceTypeEnum;
import shared.model.Boat;
import shared.model.CompoundMark;
import shared.model.GPSCoordinate;
import shared.model.Leg;
import java.time.ZonedDateTime;
import java.util.List;
/**
* An object that holds relevant data for a race. <br>
* Information includes: {@link shared.model.Boat Boat}s,
* {@link shared.model.Leg Leg}s, {@link shared.model.CompoundMark CompoundMark}s and
* the {@link shared.model.GPSCoordinate GPSCoordinate}s.
*/
public interface RaceDataSource {
/**
* Returns the list of sourceIDs for boats competing in the race.
* @return SourceIDs for boats competing in the race.
*/
List<Integer> getParticipants();
/**
* Returns the list of legs in the race.
* @return The list of legs in the race.
*/
List<Leg> getLegs();
/**
* Returns a list of coordinates representing the boundary of the race.
* @return The boundary of the race.
*/
List<GPSCoordinate> getBoundary();
/**
* Returns a list of CompoundMarks in the race.
* @return The sequence of compounds marks in the race.
*/
List<CompoundMark> getCompoundMarks();
/**
* Returns the ID of the race.
* @return The ID of the race.
*/
int getRaceId();
/**
* Returns the type of race.
* @return The type of race.
*/
RaceTypeEnum getRaceType();
/**
* Returns the start time/date of the race.
* @return The race's start time.
*/
ZonedDateTime getStartDateTime();
/**
* Returns the creation time/date of the race xml file.
* @return The race xml file's creation time.
*/
ZonedDateTime getCreationDateTime();
/**
* Returns whether or not the race has been postponed.
* @return True if the race has been postponed, false otherwise.
*/
boolean getPostponed();
/**
* Returns the GPS coordinate of the top left of the race map area.
* @return Top left GPS coordinate.
*/
GPSCoordinate getMapTopLeft();
/**
* Returns the GPS coordinate of the bottom right of the race map area.
* @return Bottom right GPS coordinate.
*/
GPSCoordinate getMapBottomRight();
}

@ -0,0 +1,463 @@
package shared.dataInput;
import network.Messages.Enums.RaceTypeEnum;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.XMLReaderException;
import shared.model.*;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* Xml Reader class for Race XML used for the race.
*/
public class RaceXMLReader extends XMLReader implements RaceDataSource {
/**
* The GPS coordinate of the top left of the race boundary.
*/
private GPSCoordinate mapTopLeft;
/**
* The GPS coordinate of the bottom right of the race boundary.
*/
private GPSCoordinate mapBottomRight;
/**
* A list of GPS coordinates that make up the boundary of the race.
*/
private final List<GPSCoordinate> boundary = new ArrayList<>();
/**
* A map between compoundMarkID and a CompoundMark for all CompoundMarks in a race.
*/
private final Map<Integer, CompoundMark> compoundMarkMap = new HashMap<>();
/**
* A list of boat sourceIDs participating in the race.
*/
private final List<Integer> participants = new ArrayList<>();
/**
* A list of legs in the race.
*/
private final List<Leg> legs = new ArrayList<>();
/**
* The time that the race.xml file was created.
*/
private ZonedDateTime creationTimeDate;
/**
* The time that the race should start at, if it hasn't been postponed.
*/
private ZonedDateTime raceStartTime;
/**
* Whether or not the race has been postponed.
*/
private boolean postpone;
/**
* The ID number of the race.
*/
private int raceID;
/**
* The type of the race.
*/
private RaceTypeEnum raceType;
/**
* Constructor for Streamed Race XML
* @param file The file to read.
* @param type How to read the file - e.g., load as resource.
* @throws XMLReaderException Thrown if an XML reader cannot be constructed for the given file.
* @throws InvalidRaceDataException Thrown if the XML file is invalid in some way.
*/
public RaceXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidRaceDataException {
super(file, type);
//Attempt to read race xml file.
try {
read();
} catch (Exception e) {
throw new InvalidRaceDataException("An error occurred while reading the race xml file", e);
}
}
/**
* Reads the contents of the race xml file.
* @throws InvalidRaceDataException Thrown if we cannot parse the document properly.
*/
private void read() throws InvalidRaceDataException {
readRace();
readParticipants();
readCourse();
}
/**
* Reads race related data from the race xml file.
*/
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");
}
//Race ID.
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
//Race type.
String raceTypeString = getTextValueOfNode(settings, "RaceType");
raceType = RaceTypeEnum.fromString(raceTypeString);
//XML creation time.
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
//Race start time.
if (raceTimeTag.getNamedItem("Time") != null) {
raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
} else {
raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
}
//Postpone status.
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
}
/**
* Reads in the participants for this race.
*/
private void readParticipants() {
//Gets the "<Participants>..</Participants>" element.
Element participants = (Element) doc.getElementsByTagName("Participants").item(0);
//Gets the number of participants.
int numberOfParticipants = participants.getChildNodes().getLength();
//For each participant, read its sourceID.
for (int i = 0; i < numberOfParticipants; i++) {
//Get the participating yacht.
Node yacht = participants.getChildNodes().item(i);
if (yacht.getNodeName().equals("Yacht")) {
if (exists(yacht, "SourceID")) {
//If the node is a valid yacht with a sourceID, add it to participant list.
int sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
this.participants.add(sourceID);
}
}
}
}
/**
* Reads course data from the xml file.
* @throws InvalidRaceDataException Thrown if we cannot parse the document properly.
*/
private void readCourse() throws InvalidRaceDataException {
readCompoundMarks();
readCompoundMarkSequence();
readCourseLimits();
readMapTopLeft();
readMapBottomRight();
}
/**
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
* @throws InvalidRaceDataException thrown if we cannot create a compound mark from the document.
* @see CompoundMark
*/
private void readCompoundMarks() throws InvalidRaceDataException {
//Gets the "<Course>...</..>" element.
Element course = (Element) doc.getElementsByTagName("Course").item(0);
//Get the list of CompoundMark elements.
NodeList compoundMarkList = course.getElementsByTagName("CompoundMark");
//Number of compound marks in the course.
int numberOfCompoundMarks = compoundMarkList.getLength();
//For each CompoundMark element, create a CompoundMark object.
for(int i = 0; i < numberOfCompoundMarks; i++) {
//Get the CompoundMark element.
Element compoundMarkElement = (Element) compoundMarkList.item(i);
//Convert to CompoundMark object.
CompoundMark compoundMark = createCompoundMark(compoundMarkElement);
compoundMarkMap.put(compoundMark.getId(), compoundMark);
}
}
/**
* Generates a CompoundMark from a given CompondMark element.
* @param compoundMarkElement The CompoundMark element to turn into a CompoundMark object.
* @return The corresponding CompoundMark object.
* @throws InvalidRaceDataException If the element cannot be converted into a CompoundMark.
*/
private CompoundMark createCompoundMark(Element compoundMarkElement) throws InvalidRaceDataException {
//CompoundMark ID.
int compoundMarkID = getCompoundMarkID(compoundMarkElement);
//CompoundMark name.
String compoundMarkName = getCompoundMarkName(compoundMarkElement);
//Get the list of marks within the compound mark.
NodeList marks = compoundMarkElement.getElementsByTagName("Mark");
CompoundMark compoundMark;
switch(marks.getLength()) {
case 1: {
//Create the Mark sub-object.
Mark mark1 = createMark((Element) marks.item(0));
//Create compound mark.
compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1);
break;
} case 2: {
//Create the Mark sub-objects.
Mark mark1 = createMark((Element) marks.item(0));
Mark mark2 = createMark((Element) marks.item(1));
//Create compound mark.
compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1, mark2);
break;
} default: {
throw new InvalidRaceDataException("Cannot create CompoundMark from " + compoundMarkElement.toString());
}
}
return compoundMark;
}
/**
* Gets a mark from an Element.
* @param mark The {@link Element} describing the {@link Mark}.
* @return The {@link Mark}.
*/
private Mark createMark(Element mark) {
//Source ID.
int sourceID = Integer.parseInt(mark.getAttribute("SourceID"));
//Name.
String name = mark.getAttribute("Name");
//Latitude.
double latitude = Double.parseDouble(mark.getAttribute("TargetLat"));
//Longitude.
double longitude = Double.parseDouble(mark.getAttribute("TargetLng"));
//Create mark.
return new Mark(sourceID, name, new GPSCoordinate(latitude, longitude));
}
/**
* 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 a CompoundMark element.
* @param element The CompoundMark element with a "Name" attribute.
* @return value of "name" attribute.
*/
private String getCompoundMarkName(Element element) {
return element.getAttribute("Name");
}
/**
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
*/
private void readCompoundMarkSequence() {
//The "<CompoundMarkSequence>...</...>" element. This contains a sequence of Corner elements.
Element compoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
//Gets the list of Corner elements.
NodeList corners = compoundMarkSequence.getElementsByTagName("Corner");
//Gets the first corner.
Element cornerElement = (Element) corners.item(0);
//Gets the ID number of this corner element.
int cornerID = getCompoundMarkID(cornerElement);
//Gets the CompoundMark associated with this corner.
CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID);
//The name of the leg is the name of the first compoundMark in the leg.
String legName = lastCompoundMark.getName();
//For each following corner, create a leg between cornerN and cornerN+1.
for(int i = 1; i < corners.getLength(); i++) {
//Gets the next corner element.
cornerElement = (Element) corners.item(i);
//Gets the ID number of this corner element.
cornerID = getCompoundMarkID(cornerElement);
//Gets the CompoundMark associated with this corner.
CompoundMark currentCompoundMark = this.compoundMarkMap.get(cornerID);
//Create a leg from these two adjacent compound marks.
Leg leg = new Leg(legName, lastCompoundMark, currentCompoundMark, i - 1);
legs.add(leg);
//Prepare for next iteration.
lastCompoundMark = currentCompoundMark;
legName = lastCompoundMark.getName();
}
}
/**
* Reads the boundary limits of the course.
*/
private void readCourseLimits() {
//The "<CourseLimit>...</...>" element. This contains a sequence of Limit elements.
Element courseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
//Get the list of Limit elements.
NodeList limitList = courseLimit.getElementsByTagName("Limit");
//For each limit element...
for(int i = 0; i < limitList.getLength(); i++) {
//Get the Limit element.
Element limit = (Element) limitList.item(i);
//Convert to GPSCoordinate.
double latitude = Double.parseDouble(limit.getAttribute("Lat"));
double longitude = Double.parseDouble(limit.getAttribute("Lon"));
boundary.add(new GPSCoordinate(latitude, longitude));
}
}
/**
* Reads the gps coordinate of the top left of the map, using the course limits.
*/
private void readMapTopLeft(){
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude();
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude();
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
}
/**
* Reads the gps coordinate of the bottom right of the map, using the course limits.
*/
private void readMapBottomRight(){
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude();
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude();
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 new ArrayList<>(compoundMarkMap.values());
}
public ZonedDateTime getCreationDateTime() {
return creationTimeDate;
}
public ZonedDateTime getStartDateTime() {
return raceStartTime;
}
public int getRaceId() {
return raceID;
}
public RaceTypeEnum getRaceType() {
return raceType;
}
public boolean getPostponed() {
return postpone;
}
public List<Integer> getParticipants() {
return participants;
}
}

@ -0,0 +1,70 @@
package shared.dataInput;
/**
* Provides information about a race regatta.
*/
public interface RegattaDataSource {
/**
* Returns the ID of the regatta.
* @return The ID of the regatta.
*/
int getRegattaID();
/**
* Returns the name of the regatta.
* @return The name of the regatta.
*/
String getRegattaName();
/**
* Returns the ID of the race this regatta relates to.
* @return The ID of the race that this regatta relates to.
*/
int getRaceID();
/**
* Returns the name of the course.
* @return the name of the course
*/
String getCourseName();
/**
* Returns the latitude of the centre of the course.
* @return The latitude of the centre of the course.
*/
double getCentralLatitude();
/**
* Returns the longitude of the centre of the course.
* @return The longitude of the centre of the course.
*/
double getCentralLongitude();
/**
* Returns the altitude of the centre of the course.
* @return The altitude of the centre of the course.
*/
double getCentralAltitude();
/**
* Returns the UTC offset of the course's location.
* @return The UTC offset of the course.
*/
float getUtcOffset();
/**
* Returns the magnetic variation of the course's location.
* @return The magnetic variation of the course.
*/
float getMagneticVariation();
}

@ -1,69 +1,108 @@
package seng302.Mock;
package shared.dataInput;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.GPSCoordinate;
import seng302.XMLReader;
import shared.dataInput.XMLReader;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import shared.model.GPSCoordinate;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by jjg64 on 19/04/17.
* XML reader class for regatta xml file.
*/
public class RegattaXMLReader extends XMLReader {
public class RegattaXMLReader extends XMLReader implements RegattaDataSource {
/**
* The regatta ID.
*/
private int regattaID;
/**
* The regatta name.
*/
private String regattaName;
/**
* The race ID.
*/
private int raceID = 0;
/**
* The course name.
*/
private String courseName;
/**
* The central latitude of the course.
*/
private double centralLatitude;
/**
* The central longitude of the course.
*/
private double centralLongitude;
/**
* The central altitude of the course.
*/
private double centralAltitude;
/**
* The UTC offset of the course.
*/
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
* The magnetic variation of the course.
*/
public RegattaXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
this(filePath, true);
}
private float magneticVariation;
/**
* Constructor for Regatta XML
* Constructor for Regatta XML using a file.
*
* @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
* @param file The file.
* @param type How to read the file - e.g., load as resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
* @throws InvalidRegattaDataException Thrown if the file cannot be parsed correctly.
*/
private RegattaXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
if (read) {
public RegattaXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidRegattaDataException {
super(file, type);
//Attempt to read boat xml file.
try {
read();
} catch (Exception e) {
throw new InvalidRegattaDataException("An error occurred while reading the regatta xml file", e);
}
}
/**
* 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
* Constructor for Regatta XML using an InputStream.
* @param xmlString Input stream of the XML.
* @throws XMLReaderException Thrown if the input stream cannot be parsed.
* @throws InvalidRegattaDataException Thrown if the stream cannot be parsed correctly.
*/
public RegattaXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException {
public RegattaXMLReader(InputStream xmlString) throws XMLReaderException, InvalidRegattaDataException {
super(xmlString);
read();
//Attempt to read boat xml file.
try {
read();
} catch (Exception e) {
throw new InvalidRegattaDataException("An error occurred while reading the regatta xml stream", e);
}
}
/**
* Read the XML
*/
@ -78,14 +117,21 @@ public class RegattaXMLReader extends XMLReader {
* @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.raceID = Integer.parseInt(getTextValueOfNode(attributes, "RaceID"));
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() {
@ -160,6 +206,10 @@ public class RegattaXMLReader extends XMLReader {
this.magneticVariation = magneticVariation;
}
/**
* Returns the GPS coorindates of the centre of the regatta.
* @return The gps coordinate for the centre of the regatta.
*/
public GPSCoordinate getGPSCoordinate() {
return new GPSCoordinate(centralLatitude, centralLongitude);
}

@ -0,0 +1,189 @@
package shared.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 shared.enums.XMLFileType;
import shared.exceptions.XMLReaderException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Base Reader for XML Files
*/
public abstract class XMLReader {
protected Document doc;
/**
* Reads an XML file.
* @param file The file to read.
* @param type How to read the file - e.g., load as resource.
* @throws XMLReaderException Throw if the file cannot be parsed.
*/
public XMLReader(String file, XMLFileType type) throws XMLReaderException {
InputStream xmlInputStream = null;
//Create an input stream. Method depends on type parameter.
if (type == XMLFileType.Contents) {
//Wrap file contents in input stream.
xmlInputStream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8));
} else if (type == XMLFileType.ResourcePath) {
xmlInputStream = XMLReader.class.getClassLoader().getResourceAsStream(file);
} else if (type == XMLFileType.FilePath) {
try {
xmlInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new XMLReaderException("Could not open file " + file, e);
}
}
this.doc = parseInputStream(xmlInputStream);
}
/**
* Reads an XML file from an input stream.
* @param xmlInputStream The input stream to parse.
* @throws XMLReaderException Thrown if the input stream cannot be parsed.
*/
public XMLReader(InputStream xmlInputStream) throws XMLReaderException {
this.doc = parseInputStream(xmlInputStream);
}
/**
* Parses an input stream into a document.
* @param inputStream The xml input stream to parse.
* @return The parsed document.
* @throws XMLReaderException Thrown when a document builder cannot be constructed, or the stream cannot be parsed.
*/
private static Document parseInputStream(InputStream inputStream) throws XMLReaderException {
//Create document builder.
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new XMLReaderException("Could not create a DocumentBuilder.", e);
}
//Parse document.
Document document = null;
try {
document = dBuilder.parse(inputStream);
} catch (SAXException | IOException e) {
throw new XMLReaderException("Could not parse the xml input stream.", e);
}
document.getDocumentElement().normalize();
return document;
}
/**
* 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();
}
/**
* Reads the an XML file, as a resource, into a string.
* @param path path of the XML
* @param encoding encoding of the xml
* @return A string containing the contents of the specified file.
* @throws TransformerException Issue with the XML format
* @throws XMLReaderException Thrown if file cannot be read for some reason.
*/
public static String readXMLFileToString(String path, Charset encoding) throws TransformerException, XMLReaderException {
InputStream fileStream = XMLReader.class.getClassLoader().getResourceAsStream(path);
//Resource can't be found.
if (fileStream == null) {
throw new XMLReaderException("Could not open resource: " + path, new IOException());
}
Document doc = XMLReader.parseInputStream(fileStream);
doc.getDocumentElement().normalize();
return XMLReader.getContents(doc);
}
}

@ -0,0 +1,24 @@
package shared.enums;
/**
* Represents the various ways in which we want to open/read an xml file - e.g., a string may contain the file contents, or the file path.
*/
public enum XMLFileType {
/**
* This means that a provided string contains the contents of an XML file.
*/
Contents,
/**
* This means that a provided string contains the path to an XML file.
*/
FilePath,
/**
* This means that a provided string contains the path, to be loaded as a resource, of an XML file.
*/
ResourcePath;
}

@ -0,0 +1,15 @@
package shared.exceptions;
/**
* An exception thrown when we cannot generate Boats.xml and send an XML message, or we cannot parse a Boats.xml file.
*/
public class InvalidBoatDataException extends Exception {
public InvalidBoatDataException(String message) {
super(message);
}
public InvalidBoatDataException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,15 @@
package shared.exceptions;
/**
* Exception thrown when we cannot generate Race.xml data, and send an XML message, or we cannot parse a Race.xml file.
*/
public class InvalidRaceDataException extends Exception {
public InvalidRaceDataException(String message) {
super(message);
}
public InvalidRaceDataException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,15 @@
package shared.exceptions;
/**
* An exception thrown when we cannot generate Regatta.xml and send an XML message, or we cannot parse a Regatta.xml file.
*/
public class InvalidRegattaDataException extends Exception {
public InvalidRegattaDataException(String message) {
super(message);
}
public InvalidRegattaDataException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,15 @@
package shared.exceptions;
/**
* An exception thrown when an XMLReader cannot be constructed for some reason.
*/
public class XMLReaderException extends Exception {
public XMLReaderException(String message) {
super(message);
}
public XMLReaderException(String message, Throwable cause) {
super(message, cause);
}
}

@ -1,4 +1,4 @@
package seng302.Model;
package shared.model;
/**
* This represents an angle.
@ -84,6 +84,13 @@ public class Angle implements Comparable<Angle> {
}
@Override
public int hashCode() {
return Double.hashCode(this.degrees);
}
/**
* Returns an int describing the ordering between this angle object, and another.
* @param o Other angle to compare to.
@ -127,4 +134,23 @@ public class Angle implements Comparable<Angle> {
return angle;
}
/**
* Sets the degrees value of the angle.
* @param degrees New value of the angle.
*/
public void setDegrees(double degrees) {
this.degrees = degrees;
}
/**
* Sets the radians value of the angle.
* @param radians New value of the angle.
*/
public void setRadians(double radians) {
this.setDegrees(Math.toDegrees(radians));
}
}

@ -1,4 +1,4 @@
package seng302.Model;
package shared.model;
@ -65,4 +65,45 @@ public class Azimuth extends Angle{
return Azimuth.fromDegrees(bearing.degrees());
}
/**
* Constructs an Azimuth object from another Azimuth object.
* @param azimuth Azimuth object to read value from.
* @return Azimuth object.
*/
public static Azimuth fromAzimuth(Azimuth azimuth) {
return Azimuth.fromDegrees(azimuth.degrees());
}
/**
* Sets the degrees value of the azimuth.
* @param degrees New value of the azimuth.
*/
public void setDegrees(double degrees) {
//Put degree value in correct interval.
degrees = Azimuth.toAzimuthInterval(degrees);
//Update.
super.setDegrees(degrees);
}
/**
* Sets the radians value of the azimuth.
* @param radians New value of the azimuth.
*/
public void setRadians(double radians) {
this.setDegrees(Math.toDegrees(radians));
}
/**
* Sets the value of this azimuth from another azimuth.
* @param azimuth Azimuth to copy the value from.
*/
public void setAzimuth(Azimuth azimuth) {
this.setDegrees(azimuth.degrees());
}
}

@ -1,4 +1,4 @@
package seng302.Model;
package shared.model;
/**
* Represents a bearing. Also known as a heading.
@ -28,7 +28,7 @@ public class Bearing extends Angle {
*/
public static double toBearingInterval(double degrees) {
return Angle.toPeriodicInterval(degrees, -0d, 360d, 360d);
return Angle.toPeriodicInterval(degrees, 0d, 360d, 360d);
}
/**
@ -63,4 +63,45 @@ public class Bearing extends Angle {
return Bearing.fromDegrees(azimuth.degrees());
}
/**
* Constructs a Bearing object from another Bearing object.
* This can be used to copy a bearing.
* @param bearing Bearing object to read value from.
* @return Bearing object.
*/
public static Bearing fromBearing(Bearing bearing) {
return Bearing.fromDegrees(bearing.degrees());
}
/**
* Sets the degrees value of the bearing.
* @param degrees New value of the bearing.
*/
public void setDegrees(double degrees) {
//Put degree value in correct interval.
degrees = Bearing.toBearingInterval(degrees);
//Update.
super.setDegrees(degrees);
}
/**
* Sets the radians value of the bearing.
* @param radians New value of the bearing.
*/
public void setRadians(double radians) {
this.setDegrees(Math.toDegrees(radians));
}
/**
* Sets the value of this bearing from another bearing.
* @param bearing Bearing to copy the value from.
*/
public void setBearing(Bearing bearing) {
this.setDegrees(bearing.degrees());
}
}

@ -1,8 +1,11 @@
package seng302.Model;
package shared.model;
import seng302.Constants;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import javafx.beans.property.*;
import network.Messages.Enums.BoatStatusEnum;
import org.jetbrains.annotations.Nullable;
import java.time.ZonedDateTime;
/**
* Boat Model that is used to store information on the boats that are running in the race.
@ -11,13 +14,13 @@ public class Boat {
/**
* The name of the boat/team.
*/
private String name;
private StringProperty name = new SimpleStringProperty();
/**
* The current speed of the boat, in knots.
* TODO knots
*/
private double currentSpeed;
private DoubleProperty currentSpeed = new SimpleDoubleProperty(0);
/**
* The current bearing/heading of the boat.
@ -32,7 +35,7 @@ public class Boat {
/**
* The country or team abbreviation of the boat.
*/
private String country;
private StringProperty country = new SimpleStringProperty();
/**
* The source ID of the boat.
@ -43,7 +46,7 @@ public class Boat {
/**
* The leg of the race that the boat is currently on.
*/
private Leg currentLeg;
private Property<Leg> currentLeg = new SimpleObjectProperty<>();
/**
* The distance, in meters, that the boat has travelled in the current leg.
@ -51,6 +54,12 @@ public class Boat {
*/
private double distanceTravelledInLeg;
/**
* The boat's position within the race (e.g., 5th).
*/
private StringProperty positionInRace = new SimpleStringProperty();
/**
* The time, in milliseconds, that has elapsed during the current leg.
* TODO milliseconds
@ -69,19 +78,17 @@ public class 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;
* The time at which the boat is estimated to reach the next mark, in milliseconds since unix epoch.
*/
private ZonedDateTime estimatedTimeAtNextMark;
private long estimatedTime = 0;
/**
* The time at which the boat reached the previous mark.
*/
@Nullable
private ZonedDateTime timeAtLastMark;
/**
@ -90,13 +97,12 @@ public class Boat {
* @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;
public Boat(int sourceID, String name, String country) {
this.sourceID = sourceID;
this.polars = polars;
this.setName(name);
this.setCountry(country);
this.bearing = Bearing.fromDegrees(0d);
@ -106,54 +112,12 @@ public class Boat {
/**
* 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;
return name.getValue();
}
/**
@ -161,16 +125,23 @@ public class Boat {
* @param name Name of the boat/team.
*/
public void setName(String name) {
this.name = name;
this.name.setValue(name);
}
/**
* Returns the name property of the boat.
* @return The name of the boat, in a StringProperty.
*/
public StringProperty nameProperty() {
return name;
}
/**
* Returns the current speed of the boat, in knots.
* @return The current speed of the boat, in knots.
*/
public double getCurrentSpeed() {
return currentSpeed;
return currentSpeed.get();
}
/**
@ -178,7 +149,15 @@ public class Boat {
* @param currentSpeed The new speed of the boat, in knots.
*/
public void setCurrentSpeed(double currentSpeed) {
this.currentSpeed = currentSpeed;
this.currentSpeed.set(currentSpeed);
}
/**
* Returns the current speed property of the boat.
* @return The current speed of the boat, in a DoubleProperty.
*/
public DoubleProperty currentSpeedProperty() {
return currentSpeed;
}
@ -187,7 +166,7 @@ public class Boat {
* @return The country/team abbreviation of the boat.
*/
public String getCountry() {
return country;
return country.getValue();
}
/**
@ -195,9 +174,16 @@ public class Boat {
* @param country The new country/team abbreviation for the boat.
*/
public void setCountry(String country) {
this.country = country;
this.country.setValue(country);
}
/**
* Returns the country/abbreviation property of the boat.
* @return The country/abbreviation of the boat, in a StringProperty.
*/
public StringProperty countryProperty() {
return country;
}
/**
* Returns the source ID of the boat.
@ -220,6 +206,14 @@ public class Boat {
* @return The current leg of the race the boat is in.
*/
public Leg getCurrentLeg() {
return currentLeg.getValue();
}
/**
* Returns the current leg, wrapped in a property.
* @return Current leg, wrapped in a property.
*/
public Property<Leg> legProperty() {
return currentLeg;
}
@ -229,9 +223,12 @@ public class Boat {
* @param currentLeg The new leg of the race the boat is in.
*/
public void setCurrentLeg(Leg currentLeg) {
this.currentLeg = currentLeg;
this.currentLeg.setValue(currentLeg);
this.setTimeElapsedInCurrentLeg(0);
this.setDistanceTravelledInLeg(0);
}
@ -252,6 +249,31 @@ public class Boat {
}
/**
* Returns the position within the race the boat has (e.g., 5th).
* @return The boat's position in race.
*/
public StringProperty positionProperty() {
return positionInRace;
}
/**
* Sets the position within the race the boat has (e.g., 5th).
* @param position The boat's position in race.
*/
public void setPosition(String position) {
this.positionInRace.set(position);
}
/**
* Returns the position within the race the boat has (e.g., 5th).
* @return The boat's position in race.
*/
public String getPosition() {
return this.positionInRace.get();
}
/**
* Returns the current position of the boat.
* @return The current position of the boat.
@ -305,40 +327,6 @@ public class Boat {
}
/**
* 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.
@ -374,81 +362,37 @@ public class Boat {
/**
* 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.
* Returns the time at which the boat should reach the next mark.
* @return Time at which the boat should reach next mark.
*/
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);
public ZonedDateTime getEstimatedTimeAtNextMark() {
return estimatedTimeAtNextMark;
}
/**
* 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.
* Sets the time at which the boat should reach the next mark.
* @param estimatedTimeAtNextMark Time at which the boat should reach next mark.
*/
public void setVMG(VMG newVMG) {
this.setBearing(newVMG.getBearing());
this.setCurrentSpeed(newVMG.getSpeed());
this.setTimeSinceTackChange(0);
public void setEstimatedTimeAtNextMark(ZonedDateTime estimatedTimeAtNextMark) {
this.estimatedTimeAtNextMark = estimatedTimeAtNextMark;
}
/**
* 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.
* Returns the time at which the boat reached the previous mark.
* @return The time at which the boat reached the previous mark. May be null.
*/
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;
@Nullable
public ZonedDateTime getTimeAtLastMark() {
return timeAtLastMark;
}
/**
* 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.
* Sets the time at which the boat reached the previous mark to a specified time.
* @param timeAtLastMark Time at which boat passed previous mark.
*/
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 void setTimeAtLastMark(ZonedDateTime timeAtLastMark) {
this.timeAtLastMark = timeAtLastMark;
}
public long getEstimatedTime() {
return estimatedTime;
}
public void setEstimatedTime(long estimatedTime) {
this.estimatedTime = estimatedTime;
}
}

@ -1,4 +1,4 @@
package seng302.Model;
package shared.model;
/**
@ -6,6 +6,16 @@ package seng302.Model;
*/
public class CompoundMark {
/**
* The ID of the compound mark.
*/
private int id;
/**
* The name of the compound mark.
*/
private String name;
/**
* The first mark in the compound mark.
*/
@ -24,9 +34,13 @@ public class CompoundMark {
/**
* Constructs a compound mark from a single mark.
* @param id the id of the compound mark
* @param name name of the compound mark
* @param mark1 The individual mark that comprises this compound mark.
*/
public CompoundMark(Mark mark1) {
public CompoundMark(int id, String name, Mark mark1) {
this.id = id;
this.name = name;
this.mark1 = mark1;
this.averageGPSCoordinate = calculateAverage();
@ -35,10 +49,14 @@ public class CompoundMark {
/**
* Constructs a compound mark from a pair of marks.
* @param id the id of the compound mark
* @param name name of the compound mark
* @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) {
public CompoundMark(int id, String name, Mark mark1, Mark mark2) {
this.id = id;
this.name = name;
this.mark1 = mark1;
this.mark2 = mark2;
this.averageGPSCoordinate = calculateAverage();
@ -46,6 +64,22 @@ public class CompoundMark {
}
/**
* Returns the ID of this compound mark.
* @return The ID of this compound mark.
*/
public int getId() {
return id;
}
/**
* Returns the name of this compound mark
* @return The name of this compound mark.
*/
public String getName() {
return name;
}
/**
* Returns the first mark of the compound mark.
* @return The first mark of the compound mark.

@ -1,4 +1,4 @@
package seng302;
package shared.model;
/**
* Constants that are used throughout the program
@ -27,6 +27,14 @@ public class Constants {
public static final double KnotsToMMPerSecond = 514.444;
/**
* 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.
*/
public static final int RaceTimeScale = 10;
/**
* The race pre-start time, in milliseconds. 3 minutes.
*/
@ -41,9 +49,22 @@ public class Constants {
/**
* The number of milliseconds in one hour.
* <br>
* Multiply by this factor to convert milliseconds to hours.
* <br>
* Divide by this factor to convert hours to milliseconds.
*/
public static long OneHourMilliseconds = 1 * 60 * 60 * 1000;
/**
* The number of seconds in one hour.
* <br>
* Multiply by this factor to convert seconds to hours.
* <br>
* Divide by this factor to convert hours to seconds.
*/
public static long OneHourSeconds = 1 * 60 * 60;
}

@ -1,8 +1,7 @@
package seng302.Model;
package shared.model;
import javafx.util.Pair;
import org.geotools.referencing.GeodeticCalculator;
import seng302.Constants;
import java.awt.geom.Point2D;
import java.util.ArrayList;
@ -300,6 +299,7 @@ public class GPSCoordinate {
*/
public static List<GPSCoordinate> getShrinkBoundary(List<GPSCoordinate> boundary) {
//TODO shrinkDistance should be a parameter. Also the code should be refactored to be smaller/simpler.
double shrinkDistance = 50d;
List<GPSCoordinate> shrunkBoundary = new ArrayList<>(boundary.size());
@ -336,12 +336,12 @@ public class GPSCoordinate {
//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));
//Calculate angle perpindicular to bearing.
Bearing perpindicularBearing = 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));
GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing));
GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing));
//Add edge to list.
shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated));
@ -356,12 +356,12 @@ public class GPSCoordinate {
//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));
//Calculate angle perpindicular to bearing.
Bearing perpindicularBearing = 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));
GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing));
GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing));
//Add edge to list.
shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated));

@ -1,4 +1,4 @@
package seng302.Model;
package shared.model;
/**
* Represents an individual mark.
@ -60,5 +60,11 @@ public class Mark {
return position;
}
/**
* Sets the position of the mark to a specified GPSCoordinate.
* @param position The new GPSCoordinate to use.
*/
public void setPosition(GPSCoordinate position) {
this.position = position;
}
}

@ -0,0 +1,346 @@
package shared.model;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum;
import network.Messages.LatestMessages;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource;
import java.util.List;
/**
* Represents a yacht race.
* This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}.
* Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}.
*/
public abstract class Race implements Runnable {
/**
* The source of race related data.
*/
protected RaceDataSource raceDataSource;
/**
* The source of boat related data.
*/
protected BoatDataSource boatDataSource;
/**
* The source of regatta related data.
*/
protected RegattaDataSource regattaDataSource;
/**
* The collection of latest race messages.
* Can be either read from or written to.
*/
protected LatestMessages latestMessages;
/**
* The sequence number of the latest BoatLocation message sent or received.
*/
protected int boatLocationSequenceNumber = 1;
/**
* The sequence number of the latest RaceStatus message sent or received.
*/
protected int raceStatusSequenceNumber = 1;
/**
* A list of compound marks in the race.
*/
protected List<CompoundMark> compoundMarks;
/**
* A list of legs in the race.
*/
protected List<Leg> legs;
/**
* A list of coordinates describing the boundary of the course.
*/
protected List<GPSCoordinate> boundary;
/**
* The clock which tracks the race's start time, current time, and elapsed duration.
*/
protected RaceClock raceClock;
/**
* The race ID of the course.
*/
protected int raceId;
/**
* The name of the regatta.
*/
protected String regattaName;
/**
* The current status of the race.
*/
protected RaceStatusEnum raceStatusEnum;
/**
* The type of race this is.
*/
protected RaceTypeEnum raceType;
/**
* The current wind direction bearing.
*/
protected Bearing windDirection;
/**
* Wind speed (knots).
* Convert this to millimeters per second before passing to RaceStatus.
*/
protected double windSpeed;
/**
* The number of frames per second.
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset.
*/
private int currentFps = 0;
/**
* The number of frames per second we generated over the last 1 second period.
*/
private IntegerProperty lastFps = new SimpleIntegerProperty(0);
/**
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
*/
private long lastFpsResetTime;
/**
* Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource.
* @param boatDataSource Data source for boat related data (yachts and marker boats).
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
* @param latestMessages The collection of latest messages, which can be written to, or read from.
*/
public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages) {
//Keep a reference to data sources.
this.raceDataSource = raceDataSource;
this.boatDataSource = boatDataSource;
this.regattaDataSource = regattaDataSource;
this.latestMessages = latestMessages;
//Marks.
this.compoundMarks = raceDataSource.getCompoundMarks();
//Boundaries.
this.boundary = raceDataSource.getBoundary();
//Legs.
this.useLegsList(raceDataSource.getLegs());
//Race ID.
this.raceId = raceDataSource.getRaceId();
//Regatta name.
this.regattaName = regattaDataSource.getRegattaName();
//Race clock.
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
//Race status.
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
//Race type.
this.raceType = raceDataSource.getRaceType();
//Wind speed.
this.windSpeed = 0;
//Wind direction.
this.windDirection = Bearing.fromDegrees(0);
}
/**
* Initialise the boats in the race.
* This sets their starting positions and current legs.
*/
protected abstract void initialiseBoats();
/**
* Updates the race to use a new list of legs, and adds a dummy "Finish" leg at the end.
* @param legs The new list of legs to use.
*/
protected void useLegsList(List<Leg> legs) {
//We add a "dummy" leg at the end of the race.
this.legs = legs;
this.legs.add(new Leg("Finish", this.legs.size()));
}
/**
* 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 otherwise.
*/
protected 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;
}
/**
* 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.
*/
protected 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 name of the regatta.
* @return The name of the regatta.
*/
public String getRegattaName() {
return regattaName;
}
/**
* Returns the wind bearing.
* @return The wind bearing.
*/
public Bearing getWindDirection() {
return windDirection;
}
/**
* Returns the wind speed.
* Measured in knots.
* @return The wind speed.
*/
public double getWindSpeed() {
return windSpeed;
}
/**
* Returns the RaceClock for this race.
* This is used to track the start time, current time, and elapsed duration of the race.
* @return The RaceClock for the race.
*/
public RaceClock getRaceClock() {
return raceClock;
}
/**
* Returns the RaceDataSource used for the race.
* @return The RaceDataSource used for the race.
*/
public RaceDataSource getRaceDataSource() {
return raceDataSource;
}
/**
* Returns the number of legs in the race.
* @return The number of legs in the race.
*/
public int getLegCount() {
//We minus one, as we have added an extra "dummy" leg.
return legs.size() - 1;
}
/**
* Returns the race boundary.
* @return The race boundary.
*/
public List<GPSCoordinate> getBoundary() {
return boundary;
}
/**
* Returns the number of frames generated per second.
* @return Frames per second.
*/
public int getFps() {
return lastFps.getValue();
}
/**
* Returns the fps property.
* @return The fps property.
*/
public IntegerProperty fpsProperty() {
return lastFps;
}
/**
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
*/
protected void incrementFps(long timePeriod) {
//Increment.
this.currentFps++;
//Add period to timer.
this.lastFpsResetTime += timePeriod;
//If we have reached 1 second period, snapshot the framerate and reset.
if (this.lastFpsResetTime > 1000) {
this.lastFps.set(this.currentFps);
this.currentFps = 0;
this.lastFpsResetTime = 0;
}
}
}

@ -0,0 +1,305 @@
package shared.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable;
import visualiser.model.ResizableRaceCanvas;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
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 ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
*/
public class RaceClock {
/**
* The time zone of the race.
*/
private final ZoneId zoneId;
/**
* The start time of the race.
*/
private ZonedDateTime startingTime;
/**
* The current time of the race.
*/
private final StringProperty startingTimeProperty = new SimpleStringProperty();
/**
* The current time of the race.
*/
@Nullable
private ZonedDateTime currentTime;
/**
* The current time of the race.
*/
private final StringProperty currentTimeProperty = new SimpleStringProperty();
/**
* The time until the race starts, or elapsed time in the race after it has started.
*/
private StringProperty durationProperty = new SimpleStringProperty();
//Format strings.
/**
* Format string used for starting time.
*/
private String startingTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
/**
* Format string used for current time.
*/
private String currentTimeFormat = "'Current time:' HH:mm dd/MM/YYYY";
/**
* Format string used for duration before it has started.
*/
private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d";
/**
* Format string used for duration once the race has started.
*/
private String durationAfterStartFormat = "Time: %02d:%02d:%02d";
/**
* Constructs a RaceClock using a specified starting ZonedDateTime.
* @param startingTime The ZonedDateTime that the race starts at.
*/
public RaceClock(ZonedDateTime startingTime) {
this.zoneId = startingTime.getZone();
//Set start time.
setStartingTime(startingTime);
}
/**
* 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);
setCurrentTime(utcTime.toInstant().atZone(this.zoneId));
}
/**
* 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);
}
/**
* Returns the starting time of the race.
* @return The starting time of the race.
*/
public ZonedDateTime getStartingTime() {
return startingTime;
}
/**
* Returns the race start time, expressed as the number of milliseconds since the unix epoch.
* @return Start time expressed as milliseconds since unix epoch.
*/
public long getStartingTimeMilli() {
return startingTime.toInstant().toEpochMilli();
}
/**
* Sets the starting time of the race.
* @param startingTime The starting time of the race.
*/
public void setStartingTime(ZonedDateTime startingTime) {
this.startingTime = startingTime;
//Convert time into string.
String startingTimeString = DateTimeFormatter.ofPattern(this.startingTimeFormat).format(startingTime);
//Use it.
setStartingTimeString(startingTimeString);
}
/**
* Returns the starting time of the race, as a string.
* @return The starting time of the race, as a string.
*/
public String getStartingTimeString() {
return startingTimeProperty.get();
}
/**
* Sets the starting time string of the race.
* This should only be called by {@link #setStartingTime(ZonedDateTime)}.
* @param startingTime The new value for the starting time string.
*/
private void setStartingTimeString(String startingTime) {
this.startingTimeProperty.setValue(startingTime);
}
/**
* Returns the starting time property.
* @return The starting time property.
*/
public StringProperty startingTimeProperty() {
return startingTimeProperty;
}
/**
* Returns the race duration, in milliseconds.
* A negative value means that the race has not started.
* @return Race duration in milliseconds.
*/
public long getDurationMilli() {
return getCurrentTimeMilli() - getStartingTimeMilli();
}
/**
* Returns the race duration, as a string.
* @return Duration as a string.
*/
public String getDurationString() {
return durationProperty.get();
}
/**
* Sets the duration time string of the race.
* @param duration The new value for the duration time string.
*/
private void setDurationString(String duration) {
this.durationProperty.setValue(duration);
}
/**
* Returns the duration property.
* @return The duration property.
*/
public StringProperty durationProperty() {
return durationProperty;
}
/**
* Returns the current time of the race.
* @return The current time of the race.
*/
@Nullable
public ZonedDateTime getCurrentTime() {
return currentTime;
}
/**
* Returns the race current time, expressed as the number of milliseconds since the unix epoch.
* @return Current time expressed as milliseconds since unix epoch.
*/
public long getCurrentTimeMilli() {
return currentTime.toInstant().toEpochMilli();
}
/**
* Sets the current time of the race.
* @param currentTime The current time of the race.
*/
private void setCurrentTime(ZonedDateTime currentTime) {
this.currentTime = currentTime;
//Convert time into string.
String currentTimeString = DateTimeFormatter.ofPattern(this.currentTimeFormat).format(currentTime);
//Use it.
setCurrentTimeString(currentTimeString);
//Update the duration string.
updateDurationString();
}
/**
* Updates the duration string based on the start time and current time.
* This requires {@link #currentTime} to be non-null.
*/
private void updateDurationString() {
//Calculates the duration in seconds.
long seconds = Duration.between(startingTime.toLocalDateTime(), currentTime.toLocalDateTime()).getSeconds();
//Check if the race has already started or not. This determines the format string used.
String formatString;
if (seconds < 0) {
//Race hasn't started.
formatString = this.durationBeforeStartFormat;
//The seconds value is negative, so we make it positive.
seconds = seconds * -1;
} else {
//Race has started.
formatString = this.durationAfterStartFormat;
}
//Format the seconds value.
//Hours : minutes : seconds.
String formattedDuration = String.format(formatString, seconds / 3600, (seconds % 3600) / 60, seconds % 60);
//Use it.
setDurationString(formattedDuration);
}
/**
* Returns the current time of the race, as a string.
* @return The current time of the race, as a string.
*/
public String getCurrentTimeString() {
return currentTimeProperty.get();
}
/**
* Sets the current time string of the race.
* @param currentTime The new value for the current time string.
*/
private void setCurrentTimeString(String currentTime) {
this.currentTimeProperty.setValue(currentTime);
}
/**
* Returns the current time property.
* @return The current time property.
*/
public StringProperty currentTimeProperty() {
return currentTimeProperty;
}
/**
* Returns the time zone of the race, as a string.
* @return The race time zone.
*/
public String getTimeZone() {
return zoneId.toString();
}
}

@ -1,4 +1,4 @@
package seng302.Controllers;
package visualiser.Controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -8,7 +8,7 @@ import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import seng302.RaceConnection;
import visualiser.model.RaceConnection;
import java.io.IOException;
import java.net.Socket;
@ -22,7 +22,7 @@ public class ConnectionController extends Controller {
@FXML
private AnchorPane connectionWrapper;
@FXML
private TableView connectionTable;
private TableView<RaceConnection> connectionTable;
@FXML
private TableColumn<RaceConnection, String> hostnameColumn;
@FXML
@ -73,7 +73,7 @@ public class ConnectionController extends Controller {
*/
public void connectSocket() {
try{
RaceConnection connection = (RaceConnection)connectionTable.getSelectionModel().getSelectedItem();
RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
connectionWrapper.setVisible(false);
parent.enterLobby(socket);

@ -1,4 +1,4 @@
package seng302.Controllers;
package visualiser.Controllers;
import javafx.fxml.Initializable;

@ -1,4 +1,4 @@
package seng302.Controllers;
package visualiser.Controllers;
import javafx.collections.ObservableList;
@ -7,14 +7,14 @@ 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 visualiser.model.VisualiserBoat;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Finish Screen for when the race finishs.
* Finish Screen for when the race finishes.
*/
public class FinishController extends Controller {
@ -22,41 +22,67 @@ public class FinishController extends Controller {
AnchorPane finishWrapper;
@FXML
TableView<Boat> boatInfoTable;
TableView<VisualiserBoat> boatInfoTable;
@FXML
TableColumn<Boat, String> boatRankColumn;
TableColumn<VisualiserBoat, String> boatRankColumn;
@FXML
TableColumn<Boat, String> boatNameColumn;
TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML
Label raceWinnerLabel;
/**
* The boats to display on the table.
*/
private ObservableList<VisualiserBoat> boats;
/**
* Ctor.
*/
public FinishController() {
}
@Override
public void initialize(URL location, ResourceBundle resources){
}
/**
* Sets up the finish table
* @param boats Boats to display
*/
private void setFinishTable(ObservableList<Boat> boats){
private void setFinishTable(ObservableList<VisualiserBoat> boats) {
this.boats = boats;
//Set contents.
boatInfoTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
//Name.
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
//Rank/position.
boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty());
//Winner label.
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){
public void enterFinish(ObservableList<VisualiserBoat> boats){
finishWrapper.setVisible(true);
setFinishTable(boats);
}

@ -1,11 +1,11 @@
package seng302.Controllers;
package visualiser.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 visualiser.app.VisualiserInput;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.net.Socket;
import java.net.URL;
@ -15,20 +15,46 @@ 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);
/**
* Ctor.
*/
public MainController() {
}
/**
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserInput The object used to read packets from the race server.
* @param visualiserRace The object modelling the race.
*/
public void beginRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
raceController.startRace(visualiserInput, visualiserRace);
}
/**
* Transitions from the server selection screen to the pre-race lobby for a given server.
* @param socket The server to read data from.
*/
public void enterLobby(Socket socket) {
startController.enterLobby(socket);
}
public void enterFinish(ObservableList<Boat> boats) { finishController.enterFinish(boats); }
/**
* Transitions from the {@link RaceController} screen to the {@link FinishController} screen.
* @param boats The boats to display on the finish screen.
*/
public void enterFinish(ObservableList<VisualiserBoat> boats) {
finishController.enterFinish(boats);
}
/**
* Main Controller for the applications will house the menu and the displayed pane.
@ -38,10 +64,12 @@ public class MainController extends Controller {
*/
@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);

@ -0,0 +1,383 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
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 javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.model.Leg;
import visualiser.app.VisualiserInput;
import visualiser.model.*;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ResourceBundle;
/**
* Controller used to display a running race.
*/
public class RaceController extends Controller {
/**
* The object used to read packets from the connected server.
*/
private VisualiserInput visualiserInput;
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRace visualiserRace;
/**
* An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats.
*/
private ObservableList<VisualiserBoat> tableBoatList;
/**
* The canvas that draws the race.
*/
private ResizableRaceCanvas raceCanvas;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
@FXML private GridPane canvasBase;
@FXML private Pane arrow;
@FXML private SplitPane race;
@FXML private StackPane arrowPane;
@FXML private Label timer;
@FXML private Label FPS;
@FXML private Label timeZone;
@FXML private CheckBox showFPS;
@FXML private TableView<VisualiserBoat> boatInfoTable;
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
*/
public RaceController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
private void initialiseRace() {
//Fps display.
initialiseFps(this.visualiserRace);
//Need to add the included arrow pane to the arrowPane container.
initialiseArrow();
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
//Race clock.
initialiseRaceClock(this.visualiserRace);
//Start the race animation timer.
raceTimer();
}
/**
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property.
* @param visualiserRace The race to connect the fps label to.
*/
private void initialiseFps(VisualiserRace visualiserRace) {
//On/off toggle.
initialiseFpsToggle();
//Label value.
initialiseFpsLabel(visualiserRace);
}
/**
* Initialises a listener for the fps toggle.
*/
private void initialiseFpsToggle() {
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
/**
* Initialises the fps label to update when the race fps changes.
* @param visualiserRace The race to monitor the frame rate of.
*/
private void initialiseFpsLabel(VisualiserRace visualiserRace) {
visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
});
}
/**
* Initialises the information table view to listen to a given race.
* @param race Race to listen to.
*/
public void initialiseInfoTable(VisualiserRace race) {
//Copy list of boats.
this.tableBoatList = FXCollections.observableArrayList(race.getBoats());
//Set up table.
boatInfoTable.setItems(this.tableBoatList);
//Set up each column.
//Name.
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty() );
//Speed.
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty() );
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
//We return a table cell that populates itself with a Number, and formats it.
return new TableCell<VisualiserBoat, Number>(){
//Function to update the cell text.
@Override
protected void updateItem(Number item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(String.format("%.2fkn", item.doubleValue()));
}
}
};
}
} );
//Last mark.
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty() );
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
//We return a table cell that populates itself with a Leg's name.
return new TableCell<VisualiserBoat, Leg>(){
//Function to update the cell text.
@Override
protected void updateItem(Leg item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(item.getName());
}
}
};
}
} );
//Current place within race.
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().positionProperty() );
}
/**
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}.
* @param race The race to listen to.
*/
private void initialiseSparkline(VisualiserRace race) {
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart);
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRace race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0));
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Intialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRace race) {
timeZone.setText(race.getRaceClock().getTimeZone());
}
/**
* Initialises the race clock to listen to the specified race.
* @param race The race to listen to.
*/
private void initialiseRaceClock(VisualiserRace race) {
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Displays a specified race.
* @param visualiserInput Object used to read packets from server.
* @param visualiserRace Object modelling the race.
*/
public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
this.visualiserInput = visualiserInput;
this.visualiserRace = visualiserRace;
initialiseRace();
//Display this controller.
race.setVisible(true);
// set up annotation displays
new Annotations(annotationPane, raceCanvas);
}
/**
* Transition from the race view to the finish view.
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<VisualiserBoat> boats){
race.setVisible(false);
parent.enterFinish(boats);
}
/**
* Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml).
*/
private void initialiseArrow() {
arrowPane.getChildren().add(arrow);
}
/**
* Timer which monitors the race.
*/
private void raceTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
//If the race has finished, go to finish view.
if (raceStatus == RaceStatusEnum.FINISHED) {
//Stop this timer.
stop();
//Hide this, and display the finish controller.
finishRace(visualiserRace.getBoats());
} else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
}
}
}.start();
}
}

@ -0,0 +1,321 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
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 javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import visualiser.app.VisualiserInput;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.*;
/**
* Controller to for waiting for the race to start.
*/
public class StartController extends Controller implements Observer {
@FXML private GridPane start;
@FXML private AnchorPane startWrapper;
/**
* The name of the race/regatta.
*/
@FXML private Label raceTitleLabel;
/**
* The time the race starts at.
*/
@FXML private Label raceStartLabel;
/**
* The current time at the race location.
*/
@FXML private Label timeZoneTime;
/**
* Time until the race starts.
*/
@FXML private Label timer;
@FXML private TableView<VisualiserBoat> boatNameTable;
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
/**
* The status of the race.
*/
@FXML private Label raceStatusLabel;
/**
* The object used to read packets from the connected server.
*/
private VisualiserInput visualiserInput;
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRace visualiserRace;
/**
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
*/
List<Color> colors = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
Color.BLACK,
Color.RED,
Color.ORANGE,
Color.DARKOLIVEGREEN,
Color.LIMEGREEN,
Color.PURPLE,
Color.DARKGRAY,
Color.YELLOW
));
/**
* Ctor.
*/
public StartController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Starts the race.
* Called once we have received all XML files from the server.
* @param latestMessages The set of latest race messages to use for race.
* @throws XMLReaderException Thrown if XML file cannot be parsed.
* @throws InvalidRaceDataException Thrown if XML file cannot be parsed.
* @throws InvalidBoatDataException Thrown if XML file cannot be parsed.
* @throws InvalidRegattaDataException Thrown if XML file cannot be parsed.
*/
private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException {
//Create data sources from latest messages for the race.
RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage(), XMLFileType.Contents);
BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage(), XMLFileType.Contents);
RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage(), XMLFileType.Contents);
//Create race.
this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors);
new Thread(this.visualiserRace).start();
//Initialise the boat table.
initialiseBoatTable(this.visualiserRace);
//Initialise the race name.
initialiseRaceName(this.visualiserRace);
//Initialises the race clock.
initialiseRaceClock(this.visualiserRace);
//Starts the race countdown timer.
countdownTimer();
}
public AnchorPane startWrapper(){
return startWrapper;
}
/**
* Initialises the boat table that is to be shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void initialiseBoatTable(VisualiserRace visualiserRace) {
//Get the boats.
ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();
//Populate table.
boatNameTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
}
/**
* Initialises the race name which is shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceName(VisualiserRace visualiserRace) {
raceTitleLabel.setText(visualiserRace.getRegattaName());
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClock(VisualiserRace visualiserRace) {
//Start time.
initialiseRaceClockStartTime(visualiserRace);
//Current time.
initialiseRaceClockCurrentTime(visualiserRace);
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) {
raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString());
visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
raceStartLabel.setText(newValue);
});
});
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) {
visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
}
/**
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockDuration(VisualiserRace visualiserRace) {
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//TODO instead of having an AnimationTimer checking the race status, we could provide a Property<RaceStatusEnum>, and connect a listener to that.
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
//Display it.
raceStatusLabel.setText("Race Status: " + raceStatus.name());
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
startWrapper.setVisible(false);
start.setVisible(false);
parent.beginRace(visualiserInput, visualiserRace);
}
}
}.start();
}
/**
* Function to handle changes in objects we observe.
* We observe LatestMessages.
* @param o The observed object.
* @param arg The {@link Observable#notifyObservers(Object)} parameter.
*/
@Override
public void update(Observable o, Object arg) {
//Check that we actually have LatestMessages.
if (o instanceof LatestMessages) {
LatestMessages latestMessages = (LatestMessages) o;
//If we've received all of the xml files, start the race. Only start it if it hasn't already been created.
if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) {
//Need to handle it in the javafx thread.
Platform.runLater(() -> {
try {
this.startRace(latestMessages);
} catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) {
//We currently don't handle this in meaningful way, as it should never occur.
//If we reach this point it means that malformed XML files were sent.
e.printStackTrace();
}
});
}
}
}
/**
* Show starting information for a race given a socket.
* @param socket network source of information
*/
public void enterLobby(Socket socket) {
startWrapper.setVisible(true);
try {
//Begin reading packets from the socket/server.
this.visualiserInput = new VisualiserInput(socket);
//Store a reference to latestMessages so that we can observe it.
LatestMessages latestMessages = this.visualiserInput.getLatestMessages();
latestMessages.addObserver(this);
new Thread(this.visualiserInput).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -1,4 +1,4 @@
package seng302;
package visualiser.app;
import javafx.application.Application;
import javafx.application.Platform;
@ -29,7 +29,7 @@ public class App extends Application {
System.exit(0);
}
});
FXMLLoader loader = new FXMLLoader(getClass().getResource("/scenes/main.fxml"));
FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/main.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 1200, 800);
scene.getStylesheets().add("/css/nightMode.css");

@ -0,0 +1,377 @@
package visualiser.app;
import javafx.application.Platform;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.*;
import org.xml.sax.SAXException;
import shared.dataInput.BoatXMLReader;
import shared.dataInput.RaceXMLReader;
import shared.dataInput.RegattaXMLReader;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
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 network.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.
*/
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;
/**
* InputStream (from the socket).
*/
private DataInputStream inStream;
/**
* An object containing the set of latest messages to write to.
* Every server frame, VisualiserInput reads messages from its inputStream, and write them to this.
*/
private LatestMessages latestMessages;
/**
* Ctor.
* @param socket Socket from which we will receive race data.
* @throws IOException If there is something wrong with the socket's input stream.
*/
public VisualiserInput(Socket socket) 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.latestMessages = new LatestMessages();
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Returns the LatestMessages object, which can be queried for any received race related messages.
* @return The LatestMessages object.
*/
public LatestMessages getLatestMessages() {
return latestMessages;
}
/**
* 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);
}
/**
* 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;
}
//Checks which message is being received and does what is needed for that message.
switch (message.getType()) {
//Heartbeat.
case 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);
}
break;
}
//RaceStatus.
case RACESTATUS: {
RaceStatus raceStatus = (RaceStatus) message;
//System.out.println("Race Status Message");
this.latestMessages.setRaceStatus(raceStatus);
for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) {
this.latestMessages.setBoatStatus(boatStatus);
}
break;
}
//DisplayTextMessage.
case DISPLAYTEXTMESSAGE: {
//System.out.println("Display Text Message");
//No decoder for this.
break;
}
//XMLMessage.
case XMLMESSAGE: {
XMLMessage xmlMessage = (XMLMessage) message;
//System.out.println("XML Message!");
this.latestMessages.setXMLMessage(xmlMessage);
break;
}
//RaceStartStatus.
case RACESTARTSTATUS: {
//System.out.println("Race Start Status Message");
break;
}
//YachtEventCode.
case YACHTEVENTCODE: {
//YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("Yacht Event Code!");
//No decoder for this.
break;
}
//YachtActionCode.
case YACHTACTIONCODE: {
//YachtActionCode yachtActionCode = (YachtActionCode) message;
//System.out.println("Yacht Action Code!");
// No decoder for this.
break;
}
//ChatterText.
case CHATTERTEXT: {
//ChatterText chatterText = (ChatterText) message;
//System.out.println("Chatter Text Message!");
//No decoder for this.
break;
}
//BoatLocation.
case BOATLOCATION: {
BoatLocation boatLocation = (BoatLocation) message;
//System.out.println("Boat Location!");
BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID());
if (existingBoatLocation != null) {
//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() > existingBoatLocation.getTime()) {
//If it is, replace the old message.
this.latestMessages.setBoatLocation(boatLocation);
}
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.latestMessages.setBoatLocation(boatLocation);
}
break;
}
//MarkRounding.
case MARKROUNDING: {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Mark Rounding Message!");
MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID());
if (existingMarkRounding != null) {
//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() > existingMarkRounding.getTime()) {
//If it is, replace the old message.
this.latestMessages.setMarkRounding(markRounding);
}
} else {
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.latestMessages.setMarkRounding(markRounding);
}
break;
}
//CourseWinds.
case COURSEWIND: {
//System.out.println("Course Wind Message!");
CourseWinds courseWinds = (CourseWinds) message;
this.latestMessages.setCourseWinds(courseWinds);
break;
}
//AverageWind.
case AVGWIND: {
//System.out.println("Average Wind Message!");
AverageWind averageWind = (AverageWind) message;
this.latestMessages.setAverageWind(averageWind);
break;
}
//Unrecognised message.
default: {
System.out.println("Broken Message!");
break;
}
}
}
}
}

@ -1,4 +1,4 @@
package seng302.Model;
package visualiser.model;
import javafx.fxml.FXML;
import javafx.scene.Node;
@ -14,13 +14,13 @@ 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,
* {@link ResizableRaceCanvas}. These are displayed
* via the {@link visualiser.Controllers.RaceController}. <br>
* Annotation options for a {@link VisualiserBoat} include: its name,
* abbreviation, speed, the time since it passed the last
* {@link seng302.Model.Marker Marker}, estimated time to the next marker,
* {@link shared.model.Mark}, estimated time to the next marker,
* and a path it has travelled made up of
* {@link seng302.Model.TrackPoint TrackPoint}s.
* {@link TrackPoint}s.
*/
public class Annotations {
private ResizableRaceCanvas raceMap;
@ -107,7 +107,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleAnnoName();
storeCurrentAnnotationState(nameCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
@ -117,7 +117,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleAnnoAbbrev();
storeCurrentAnnotationState(abbrevCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
@ -127,7 +127,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleBoatPath();
storeCurrentAnnotationState(pathCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
@ -137,7 +137,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleAnnoSpeed();
storeCurrentAnnotationState(speedCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
@ -147,7 +147,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleAnnoTime();
storeCurrentAnnotationState(timeCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
@ -157,7 +157,7 @@ public class Annotations {
if (old_val != new_val) {
raceMap.toggleAnnoEstTime();
storeCurrentAnnotationState(estTimeCheckAnno, new_val);
raceMap.update();
raceMap.draw();
}
});
}
@ -191,7 +191,7 @@ public class Annotations {
for (Map.Entry<String, CheckBox> checkBox : checkBoxes.entrySet()){
checkBox.getValue().setSelected(false);
}
raceMap.update();
raceMap.draw();
buttonChecked = noBtn;
prevBtnChecked = hideBtn;
selectShow = true;
@ -206,7 +206,7 @@ public class Annotations {
checkBox.getValue().setSelected(
annoShownBeforeHide.get(checkBox.getKey()));
}
raceMap.update();
raceMap.draw();
buttonChecked = noBtn;
prevBtnChecked = showBtn;
}
@ -226,7 +226,7 @@ public class Annotations {
}
else { checkBox.getValue().setSelected(false); }
}
raceMap.update();
raceMap.draw();
buttonChecked = noBtn;
prevBtnChecked = partialBtn;
selectShow = true;
@ -243,6 +243,7 @@ public class Annotations {
(importantAnno.get(checkBox.getKey()));
}
}
raceMap.draw();
buttonChecked = noBtn;
prevBtnChecked = importantBtn;
selectShow = true;

@ -1,13 +1,19 @@
package seng302;
package visualiser.model;
/**
* 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.
* It is a coordinate representing a location
* resizable race canvas
*/
public class GraphCoordinate {
/**
* X (horizontal) coordinate.
*/
private final int x;
/**
* Y (vertical) coordinate.
*/
private final int y;
/**
@ -21,6 +27,7 @@ public class GraphCoordinate {
this.y = y;
}
/**
* Returns the X coordinate.
*

@ -1,4 +1,4 @@
package seng302;
package visualiser.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@ -28,9 +28,10 @@ public class RaceConnection {
*/
@SuppressWarnings("unused")
public boolean check() {
//TODO the connection needs to be moved to its own thread, so it doesn't block fx thread.
InetSocketAddress i = new InetSocketAddress(hostname.get(), port);
try (Socket s = new Socket()){
s.connect(i, 5000);
s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread
status.set("Ready");
return true;
} catch (IOException e) {}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save