Refactored and moved RaceXMLReader to shared/dataInput.

RaceStatus can provide windspeed in knots in addition to mm/sec.
RaceDataSource now provides a list of participating boat sourceIDs instead of boats.
Added a RegattaDataSource interface.
Angle, Azimuth, Bearing and mutable - careful.
Boat has positionInRace.
CompoundMarks have an ID and name.
Marks can be moved (setPosition(...)).

Refactored Mock.Race, Visualiser.StreamedCourse, Visualiser.StreamedRace, into (shared) Race, MockRace, VisualiserRace.

VisualiserBoat has color.
Added xml and polar files into resources folder.
This commit is contained in:
fjc40
2017-07-06 01:30:55 +12:00
parent 7f027c8cc5
commit 8d36d89570
35 changed files with 1806 additions and 1221 deletions
@@ -2,11 +2,13 @@ package shared.dataInput;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
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;
@@ -27,16 +29,44 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
/**
* Constructor for Boat XML
* Constructor for Boat XML using a file read as a resource.
*
* @param filePath Name/path of file to read. Read as a resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
* @throws InvalidBoatDataException Thrown if the file cannot be parsed correctly.
*/
public BoatXMLReader(String filePath) throws XMLReaderException {
public BoatXMLReader(String filePath) throws XMLReaderException, InvalidBoatDataException {
super(filePath);
read();
//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, using an InputStream.
*
* @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(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
*/
@@ -17,10 +17,10 @@ import java.util.List;
*/
public interface RaceDataSource {
/**
* Returns the list of boats competing in the race.
* @return Boats competing in the race.
* Returns the list of sourceIDs for boats competing in the race.
* @return SourceIDs for boats competing in the race.
*/
List<Boat> getBoats();
List<Integer> getParticipants();
/**
* Returns the list of legs in the race.
@@ -36,10 +36,11 @@ public interface RaceDataSource {
/**
* Returns a list of CompoundMarks in the race.
* @return
* @return The sequence of compounds marks in the race.
*/
List<CompoundMark> getCompoundMarks();
/**
* Returns the ID of the race.
* @return The ID of the race.
@@ -52,11 +53,25 @@ public interface RaceDataSource {
*/
RaceTypeEnum getRaceType();
/**
* Returns the start time/date of the race.
* @return The race's start time.
*/
ZonedDateTime getZonedDateTime();
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.
@@ -0,0 +1,465 @@
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.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;
//TODO maybe remove these?
private Map<Integer, Boat> boats;
private Map<Integer, Mark> marks;
/**
* Constructor for Streamed Race XML
* @param filePath file path to read
* @param boatData data of the boats in race
* @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 filePath, BoatDataSource boatData) throws XMLReaderException, InvalidRaceDataException {
super(filePath);
this.boats = boatData.getBoats();
this.marks = boatData.getMarkerBoats();
//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.
*/
private void read() {
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.valueOf(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.
*/
private void readCourse() {
readCompoundMarks();
readCompoundMarkSequence();
readCourseLimits();
readMapTopLeft();
readMapBottomRight();
}
/**
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
* @see CompoundMark
*/
private void readCompoundMarks() {
//Gets the "<Course>...</..>" element.
Element course = (Element) doc.getElementsByTagName("Course").item(0);
//Number of compound marks in the course.
int numberOfCompoundMarks = course.getChildNodes().getLength();
//For each CompoundMark element, create a CompoundMark object.
for(int i = 0; i < numberOfCompoundMarks; i++) {
//Get the CompoundMark element.
Element compoundMarkElement = (Element) course.getChildNodes().item(i);
//If it is actually a CompoundMark element, turn it into a CompoundMark object.
if(compoundMarkElement.getNodeName().equals("CompoundMark")) {
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);
//For each limit element...
for(int i = 0; i < courseLimit.getChildNodes().getLength(); i++) {
//Get the Limit element.
Element limit = (Element) courseLimit.getChildNodes().item(i);
//If it is actually a Limit element, add the limit to boundary list.
if (limit.getNodeName().equals("Limit")) {
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
*/
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();
}
@@ -4,6 +4,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import shared.dataInput.XMLReader;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import shared.model.GPSCoordinate;
@@ -14,7 +15,7 @@ import java.io.InputStream;
/**
* XML reader class for regatta xml file.
*/
public class RegattaXMLReader extends XMLReader {
public class RegattaXMLReader extends XMLReader implements RegattaDataSource {
/**
* The regatta ID.
*/
@@ -63,26 +64,40 @@ public class RegattaXMLReader extends XMLReader {
/**
* Constructor for Regatta XML
* Constructor for Regatta XML using a file read as a resource.
*
* @param filePath path of the file to read. Read as a resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
* @throws InvalidRegattaDataException Thrown if the file cannot be parsed correctly.
*/
public RegattaXMLReader(String filePath) throws XMLReaderException {
public RegattaXMLReader(String filePath) throws XMLReaderException, InvalidRegattaDataException {
super(filePath);
read();
//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
* 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 XMLReaderException {
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);
}
}
@@ -103,6 +118,8 @@ public class RegattaXMLReader extends XMLReader {
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"));
@@ -187,6 +204,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,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 RuntimeException {
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 RuntimeException {
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 RuntimeException {
public InvalidRegattaDataException(String message) {
super(message);
}
public InvalidRegattaDataException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -127,4 +127,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));
}
}
@@ -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());
}
}
@@ -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,12 +1,13 @@
package shared.model;
import javafx.beans.property.StringProperty;
import network.Messages.Enums.BoatStatusEnum;
/**
* Boat Model that is used to store information on the boats that are running in the race.
*/
public class Boat {
public abstract class Boat {
/**
* The name of the boat/team.
*/
@@ -50,6 +51,11 @@ public class Boat {
*/
private double distanceTravelledInLeg;
/**
* The boat's position within the race (e.g., 5th).
*/
private StringProperty positionInRace;
/**
* The time, in milliseconds, that has elapsed during the current leg.
* TODO milliseconds
@@ -69,7 +75,9 @@ public class Boat {
private BoatStatusEnum status;
/**
* The amount of time, in seconds, until the boat reaches the next mark.
*/
private long estimatedTime = 0;
@@ -197,6 +205,19 @@ public class Boat {
}
public StringProperty positionProperty() {
return positionInRace;
}
public void setPosition(String position) {
this.positionInRace.set(position);
}
public String getPosition() {
return this.positionInRace.get();
}
/**
* Returns the current position of the boat.
* @return The current position of the boat.
@@ -6,6 +6,16 @@ package shared.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.
*/
@@ -26,7 +36,9 @@ public class CompoundMark {
* Constructs a compound mark from a single 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();
@@ -38,7 +50,9 @@ public class CompoundMark {
* @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 +60,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.
@@ -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,184 @@
package shared.model;
import javafx.animation.AnimationTimer;
import javafx.collections.FXCollections;
import mock.model.VMG;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import static java.lang.Math.cos;
/**
* Represents a yacht race.
* 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 {
/**
* 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 elapsed time, in milliseconds, of the race.
*/
protected long totalTimeElapsed;
/**
* The starting timestamp, in milliseconds, of the race.
*/
protected long startTime;
/**
* The race ID of the course.
*/
protected int raceId;
/**
* 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;
/**
* 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...).
*/
public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource) {
this.compoundMarks = raceDataSource.getCompoundMarks();
this.boundary = raceDataSource.getBoundary();
//We add a "dummy" leg at the end of the race.
this.legs = raceDataSource.getLegs();
this.legs.add(new Leg("Finish", this.legs.size()));
this.raceId = raceDataSource.getRaceId();
this.startTime = raceDataSource.getStartDateTime().toInstant().toEpochMilli();
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
this.raceType = raceDataSource.getRaceType();
this.windSpeed = 0;
this.windDirection = Bearing.fromDegrees(0);
this.totalTimeElapsed = 0;
}
/**
* Initialise the boats in the race.
* This sets their starting positions and current legs.
*/
protected abstract void initialiseBoats();
/**
* 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;
}
}