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.main
parent
7f027c8cc5
commit
8d36d89570
@ -1,289 +0,0 @@
|
|||||||
package mock.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 shared.dataInput.BoatDataSource;
|
|
||||||
import shared.dataInput.RaceDataSource;
|
|
||||||
|
|
||||||
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,7 +0,0 @@
|
|||||||
package mock.exceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cbt24 on 25/04/17.
|
|
||||||
*/
|
|
||||||
public class StreamedCourseXMLException extends Throwable {
|
|
||||||
}
|
|
||||||
@ -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();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,14 +1,15 @@
|
|||||||
package mock.exceptions;
|
package shared.exceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when we cannot generate Boats.xml and send an XML message.
|
* 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 class InvalidBoatDataException extends RuntimeException {
|
||||||
|
|
||||||
public InvalidBoatDataException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidBoatDataException(String message) {
|
public InvalidBoatDataException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InvalidBoatDataException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +1,15 @@
|
|||||||
package mock.exceptions;
|
package shared.exceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when we cannot generate Race.xml data, and send an XML message.
|
* 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 class InvalidRaceDataException extends RuntimeException {
|
||||||
public InvalidRaceDataException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidRaceDataException(String message) {
|
public InvalidRaceDataException(String message) {
|
||||||
super(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,290 +0,0 @@
|
|||||||
package visualiser.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.GPSCoordinate;
|
|
||||||
import seng302.Model.Leg;
|
|
||||||
import seng302.Model.Marker;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XML Read for the Course that is being received
|
|
||||||
*/
|
|
||||||
public class StreamedCourseXMLReader extends XMLReader {
|
|
||||||
private static final double COORDINATEPADDING = 0.000;
|
|
||||||
private GPSCoordinate mapTopLeft, mapBottomRight;
|
|
||||||
private final List<GPSCoordinate> boundary = new ArrayList<>();
|
|
||||||
private final Map<Integer,Element> compoundMarks = new HashMap<>();
|
|
||||||
private final Map<Integer, StreamedBoat> participants = new HashMap<>();
|
|
||||||
private final List<Leg> legs = new ArrayList<>();
|
|
||||||
private final List<Marker> markers = new ArrayList<>();
|
|
||||||
private ZonedDateTime creationTimeDate;
|
|
||||||
private ZonedDateTime raceStartTime;
|
|
||||||
private int raceID;
|
|
||||||
private String raceType;
|
|
||||||
private boolean postpone;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for Streamed Race XML
|
|
||||||
* @param filePath file path to read
|
|
||||||
* @param read whether or not to read and store the files straight away.
|
|
||||||
* @throws IOException error
|
|
||||||
* @throws SAXException error
|
|
||||||
* @throws ParserConfigurationException error
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
public StreamedCourseXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException {
|
|
||||||
super(filePath);
|
|
||||||
if (read) {
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for Streamed Race XML
|
|
||||||
* @param xmlString string to read
|
|
||||||
* @throws IOException error
|
|
||||||
* @throws SAXException error
|
|
||||||
* @throws ParserConfigurationException error
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
public StreamedCourseXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException {
|
|
||||||
super(xmlString);
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
private void read() throws StreamedCourseXMLException {
|
|
||||||
readRace();
|
|
||||||
readParticipants();
|
|
||||||
readCourse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads a race
|
|
||||||
*/
|
|
||||||
private void readRace() {
|
|
||||||
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
|
||||||
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
|
|
||||||
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
|
|
||||||
|
|
||||||
if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
|
||||||
|
|
||||||
|
|
||||||
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
|
|
||||||
raceType = getTextValueOfNode(settings, "RaceType");
|
|
||||||
|
|
||||||
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
|
|
||||||
|
|
||||||
if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
|
|
||||||
else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
|
|
||||||
|
|
||||||
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the participants of the race.
|
|
||||||
*/
|
|
||||||
private void readParticipants() {
|
|
||||||
Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0);
|
|
||||||
nParticipants.getChildNodes().getLength();
|
|
||||||
for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) {
|
|
||||||
int sourceID;
|
|
||||||
Node yacht = nParticipants.getChildNodes().item(i);
|
|
||||||
if (yacht.getNodeName().equals("Yacht")) {
|
|
||||||
if (exists(yacht, "SourceID")) {
|
|
||||||
sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
|
|
||||||
participants.put(sourceID, new StreamedBoat(sourceID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reads a course
|
|
||||||
* @throws StreamedCourseXMLException error
|
|
||||||
*/
|
|
||||||
private void readCourse() throws StreamedCourseXMLException {
|
|
||||||
readCompoundMarks();
|
|
||||||
readCompoundMarkSequence();
|
|
||||||
readCourseLimit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
|
|
||||||
* @throws StreamedCourseXMLException if a CompoundMark element contains an unhandled number of compoundMarks.
|
|
||||||
* @see seng302.Model.Marker
|
|
||||||
*/
|
|
||||||
private void readCompoundMarks() throws StreamedCourseXMLException {
|
|
||||||
Element nCourse = (Element) doc.getElementsByTagName("Course").item(0);
|
|
||||||
for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) {
|
|
||||||
Node compoundMark = nCourse.getChildNodes().item(i);
|
|
||||||
if(compoundMark.getNodeName().equals("CompoundMark")) {
|
|
||||||
int compoundMarkID = getCompoundMarkID((Element) compoundMark);
|
|
||||||
compoundMarks.put(compoundMarkID, (Element)compoundMark);
|
|
||||||
markers.add(getMarker(compoundMarkID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Marker from the CompoundMark element with given ID.
|
|
||||||
* @param compoundMarkID index of required CompoundMark element
|
|
||||||
* @return generated Marker
|
|
||||||
* @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks
|
|
||||||
* @see seng302.Model.Marker
|
|
||||||
*/
|
|
||||||
private Marker getMarker(int compoundMarkID) throws StreamedCourseXMLException {
|
|
||||||
Element compoundMark = compoundMarks.get(compoundMarkID);
|
|
||||||
NodeList nMarks = compoundMark.getElementsByTagName("Mark");
|
|
||||||
Marker marker;
|
|
||||||
|
|
||||||
switch(nMarks.getLength()) {
|
|
||||||
case 1: marker = new Marker(getCoordinate((Element)nMarks.item(0)),getSourceId((Element)nMarks.item(0))); break;
|
|
||||||
case 2: marker = new Marker(getCoordinate((Element)nMarks.item(0)), getCoordinate((Element)nMarks.item(1)),
|
|
||||||
getSourceId((Element)nMarks.item(0)), getSourceId((Element)nMarks.item(1))); break;
|
|
||||||
default: throw new StreamedCourseXMLException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the GPS Coordinates from a XMl Element
|
|
||||||
* @param mark Element to Extract from
|
|
||||||
* @return the GPS coordinate that it reflects
|
|
||||||
*/
|
|
||||||
private GPSCoordinate getCoordinate(Element mark) {
|
|
||||||
double lat = Double.parseDouble(mark.getAttribute("TargetLat"));
|
|
||||||
double lon = Double.parseDouble(mark.getAttribute("TargetLng"));
|
|
||||||
return new GPSCoordinate(lat,lon);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the SourceID from a XML ELement
|
|
||||||
* @param mark Element to Extract from
|
|
||||||
* @return the Source ID of the Extracted Element.
|
|
||||||
*/
|
|
||||||
private int getSourceId(Element mark) {
|
|
||||||
String sourceId = mark.getAttribute("SourceID");
|
|
||||||
if (sourceId.isEmpty()){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Integer.parseInt(sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads "compoundMarkID" attribute of CompoundMark or Corner element
|
|
||||||
* @param element with "compoundMarkID" attribute
|
|
||||||
* @return value of "compoundMarkID" attribute
|
|
||||||
*/
|
|
||||||
private int getCompoundMarkID(Element element) {
|
|
||||||
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID
|
|
||||||
* @param compoundMarkID unique ID for CompoundMark element
|
|
||||||
* @return value of "name" attribute
|
|
||||||
*/
|
|
||||||
private String getCompoundMarkName(int compoundMarkID) {
|
|
||||||
return compoundMarks.get(compoundMarkID).getAttribute("Name");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
|
|
||||||
* @throws StreamedCourseXMLException if markers cannot be resolved from CompoundMark
|
|
||||||
*/
|
|
||||||
private void readCompoundMarkSequence() throws StreamedCourseXMLException {
|
|
||||||
Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
|
|
||||||
NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner");
|
|
||||||
Element markXML = (Element)nCorners.item(0);
|
|
||||||
Marker lastMarker = getMarker(getCompoundMarkID(markXML));
|
|
||||||
String legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
|
||||||
for(int i = 1; i < nCorners.getLength(); i++) {
|
|
||||||
markXML = (Element)nCorners.item(i);
|
|
||||||
Marker currentMarker = getMarker(getCompoundMarkID(markXML));
|
|
||||||
legs.add(new Leg(legName, lastMarker, currentMarker, i-1));
|
|
||||||
lastMarker = currentMarker;
|
|
||||||
legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the course boundary limitations of the course recieved.
|
|
||||||
*/
|
|
||||||
private void readCourseLimit() {
|
|
||||||
Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
|
|
||||||
for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) {
|
|
||||||
Node limit = nCourseLimit.getChildNodes().item(i);
|
|
||||||
if (limit.getNodeName().equals("Limit")) {
|
|
||||||
double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent());
|
|
||||||
double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent());
|
|
||||||
boundary.add(new GPSCoordinate(lat, lon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
|
||||||
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
|
||||||
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
|
||||||
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
|
||||||
|
|
||||||
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
|
|
||||||
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GPSCoordinate> getBoundary() {
|
|
||||||
return boundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapTopLeft() {
|
|
||||||
return mapTopLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapBottomRight() {
|
|
||||||
return mapBottomRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Leg> getLegs() {
|
|
||||||
return legs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Marker> getMarkers() { return markers; }
|
|
||||||
|
|
||||||
public Double getPadding() {
|
|
||||||
return COORDINATEPADDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZonedDateTime getRaceStartTime() {
|
|
||||||
return raceStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRaceID() {
|
|
||||||
return raceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRaceType() {
|
|
||||||
return raceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPostpone() {
|
|
||||||
return postpone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Integer, StreamedBoat> getParticipants() {
|
|
||||||
return participants;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package visualiser.exceptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cbt24 on 25/04/17.
|
|
||||||
*/
|
|
||||||
public class StreamedCourseXMLException extends Throwable {
|
|
||||||
}
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
package visualiser.model;
|
|
||||||
|
|
||||||
import seng302.GPSCoordinate;
|
|
||||||
import seng302.Model.Boat;
|
|
||||||
import seng302.Model.Leg;
|
|
||||||
import seng302.Model.Marker;
|
|
||||||
import seng302.RaceDataSource;
|
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Observable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* COurse that the is being received.
|
|
||||||
*/
|
|
||||||
public class StreamedCourse extends Observable implements RaceDataSource {
|
|
||||||
private StreamedCourseXMLReader streamedCourseXMLReader = null;
|
|
||||||
private BoatXMLReader boatXMLReader = null;
|
|
||||||
private RegattaXMLReader regattaXMLReader = null;
|
|
||||||
private double windDirection = 0;
|
|
||||||
|
|
||||||
public StreamedCourse() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read and set the new XML that has been received.
|
|
||||||
* @param boatXMLReader new XMl of the boats.
|
|
||||||
*/
|
|
||||||
public void setBoatXMLReader(BoatXMLReader boatXMLReader) {
|
|
||||||
this.boatXMLReader = boatXMLReader;
|
|
||||||
if (streamedCourseXMLReader != null && boatXMLReader != null) {
|
|
||||||
this.boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
|
|
||||||
|
|
||||||
boatXMLReader.read();
|
|
||||||
}
|
|
||||||
setChanged();
|
|
||||||
notifyObservers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamedCourseXMLReader getStreamedCourseXMLReader() {
|
|
||||||
return streamedCourseXMLReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read and sets the new Course that has been received
|
|
||||||
* @param streamedCourseXMLReader COurse XML that has been received
|
|
||||||
*/
|
|
||||||
public void setStreamedCourseXMLReader(StreamedCourseXMLReader streamedCourseXMLReader) {
|
|
||||||
this.streamedCourseXMLReader = streamedCourseXMLReader;
|
|
||||||
if (streamedCourseXMLReader != null && boatXMLReader != null) {
|
|
||||||
boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
|
|
||||||
boatXMLReader.read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads and sets the new Regatta that has been received
|
|
||||||
* @param regattaXMLReader Regatta XMl that has been received.
|
|
||||||
*/
|
|
||||||
public void setRegattaXMLReader(RegattaXMLReader regattaXMLReader) {
|
|
||||||
this.regattaXMLReader = regattaXMLReader;
|
|
||||||
setChanged();
|
|
||||||
notifyObservers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWindDirection(double windDirection) {
|
|
||||||
this.windDirection = windDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getWindDirection() {
|
|
||||||
return windDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasReadRegatta() { return regattaXMLReader != null; }
|
|
||||||
|
|
||||||
public boolean hasReadBoats() { return boatXMLReader != null; }
|
|
||||||
|
|
||||||
public boolean hasReadCourse() { return streamedCourseXMLReader != null; }
|
|
||||||
|
|
||||||
public String getRegattaName() { return regattaXMLReader.getRegattaName(); }
|
|
||||||
|
|
||||||
public List<Boat> getBoats() {
|
|
||||||
return boatXMLReader.getBoats();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Leg> getLegs() {
|
|
||||||
return streamedCourseXMLReader.getLegs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Marker> getMarkers() { return streamedCourseXMLReader.getMarkers(); }
|
|
||||||
|
|
||||||
public List<GPSCoordinate> getBoundary() {
|
|
||||||
return streamedCourseXMLReader.getBoundary();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZonedDateTime getZonedDateTime() {
|
|
||||||
return streamedCourseXMLReader.getRaceStartTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapTopLeft() {
|
|
||||||
return streamedCourseXMLReader.getMapTopLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
public GPSCoordinate getMapBottomRight() {
|
|
||||||
return streamedCourseXMLReader.getMapBottomRight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
package visualiser.model;
|
|
||||||
|
|
||||||
import javafx.animation.AnimationTimer;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import seng302.Controllers.FinishController;
|
|
||||||
import seng302.Controllers.RaceController;
|
|
||||||
import seng302.GPSCoordinate;
|
|
||||||
import seng302.Model.Boat;
|
|
||||||
import seng302.Model.Leg;
|
|
||||||
import seng302.Model.Marker;
|
|
||||||
import seng302.Networking.Messages.BoatLocation;
|
|
||||||
import seng302.Networking.Messages.BoatStatus;
|
|
||||||
import seng302.Networking.Messages.Enums.BoatStatusEnum;
|
|
||||||
import seng302.VisualiserInput;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Class used to view the race streamed.
|
|
||||||
*/
|
|
||||||
public class StreamedRace implements Runnable {
|
|
||||||
private final VisualiserInput visualiserInput;
|
|
||||||
private final ObservableList<Boat> startingBoats;
|
|
||||||
private final ObservableList<Marker> boatMarkers;
|
|
||||||
private final List<Leg> legs;
|
|
||||||
private RaceController controller;
|
|
||||||
protected FinishController finishController;
|
|
||||||
private int boatsFinished = 0;
|
|
||||||
private long totalTimeElapsed;
|
|
||||||
|
|
||||||
private int lastFPS = 20;
|
|
||||||
|
|
||||||
public StreamedRace(VisualiserInput visualiserInput, RaceController controller) {
|
|
||||||
StreamedCourse course = visualiserInput.getCourse();
|
|
||||||
|
|
||||||
this.startingBoats = FXCollections.observableArrayList(course.getBoats());
|
|
||||||
this.boatMarkers = FXCollections.observableArrayList(course.getMarkers());
|
|
||||||
this.legs = course.getLegs();
|
|
||||||
this.legs.add(new Leg("Finish", this.legs.size()));
|
|
||||||
this.controller = controller;
|
|
||||||
if (startingBoats != null && startingBoats.size() > 0) {
|
|
||||||
initialiseBoats();
|
|
||||||
}
|
|
||||||
this.visualiserInput = visualiserInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseBoats() {
|
|
||||||
Leg officialStart = legs.get(0);
|
|
||||||
String name = officialStart.getName();
|
|
||||||
Marker endCompoundMark = officialStart.getEndMarker();
|
|
||||||
|
|
||||||
for (Boat boat : startingBoats) {
|
|
||||||
if (boat != null) {
|
|
||||||
Leg startLeg = new Leg(name, 0);
|
|
||||||
startLeg.setEndMarker(endCompoundMark);
|
|
||||||
boat.setCurrentLeg(startLeg, controller.getRaceClock());
|
|
||||||
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the boat cannot finish the race
|
|
||||||
* @return True if boat cannot finish the race
|
|
||||||
*/
|
|
||||||
protected boolean doNotFinish() {
|
|
||||||
// DNF is no longer random and is now determined by a dnf packet
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the position of the boat.
|
|
||||||
*
|
|
||||||
* @param boat Boat that the position is to be updated for.
|
|
||||||
* @param timeElapsed Time that has elapse since the start of the the race.
|
|
||||||
*/
|
|
||||||
private void checkPosition(Boat boat, long timeElapsed) {
|
|
||||||
boolean legChanged = false;
|
|
||||||
StreamedCourse raceData = visualiserInput.getCourse();
|
|
||||||
BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID());
|
|
||||||
if (boatStatusMessage != null) {
|
|
||||||
BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatusMessage.getBoatStatus());
|
|
||||||
|
|
||||||
int legNumber = boatStatusMessage.getLegNumber();
|
|
||||||
|
|
||||||
|
|
||||||
if (legNumber >= 1 && legNumber < legs.size()) {
|
|
||||||
if (boat.getCurrentLeg() != legs.get(legNumber)){
|
|
||||||
boat.setCurrentLeg(legs.get(legNumber), controller.getRaceClock());
|
|
||||||
legChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boatStatusEnum == BoatStatusEnum.RACING) {
|
|
||||||
boat.addTrackPoint(boat.getCurrentPosition());
|
|
||||||
} else if (boatStatusEnum == BoatStatusEnum.DNF) {
|
|
||||||
boat.setDnf(true);
|
|
||||||
} else if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == raceData.getLegs().size()) {
|
|
||||||
boatsFinished++;
|
|
||||||
boat.setTimeFinished(timeElapsed);
|
|
||||||
boat.setFinished(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (legChanged) {
|
|
||||||
//Update the boat display table in the GUI to reflect the leg change
|
|
||||||
updatePositions();
|
|
||||||
controller.updateSparkline(startingBoats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the boat's gps coordinates
|
|
||||||
*
|
|
||||||
* @param boat to be updated
|
|
||||||
*/
|
|
||||||
private void updatePosition(Boat boat) {
|
|
||||||
int sourceID = boat.getSourceID();
|
|
||||||
BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID);
|
|
||||||
BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID);
|
|
||||||
if(boatLocation != null) {
|
|
||||||
double lat = boatLocation.getLatitudeDouble();
|
|
||||||
double lon = boatLocation.getLongitudeDouble();
|
|
||||||
boat.setCurrentPosition(new GPSCoordinate(lat, lon));
|
|
||||||
boat.setHeading(boatLocation.getHeadingDegrees());
|
|
||||||
boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime()));
|
|
||||||
double MMPS_TO_KN = 0.001944;
|
|
||||||
boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the boat's gps coordinates
|
|
||||||
*
|
|
||||||
* @param mark to be updated
|
|
||||||
*/
|
|
||||||
private void updateMarker(Marker mark) {
|
|
||||||
int sourceID = mark.getSourceId1();
|
|
||||||
BoatLocation boatLocation1 = visualiserInput.getBoatLocationMessage(sourceID);
|
|
||||||
if(boatLocation1 != null) {
|
|
||||||
double lat = boatLocation1.getLatitudeDouble();
|
|
||||||
double lon = boatLocation1.getLongitudeDouble();
|
|
||||||
mark.setCurrentPosition1(new GPSCoordinate(lat, lon));
|
|
||||||
}
|
|
||||||
int sourceID2 = mark.getSourceId2();
|
|
||||||
BoatLocation boatLocation2 = visualiserInput.getBoatLocationMessage(sourceID2);
|
|
||||||
if(boatLocation2 != null) {
|
|
||||||
double lat = boatLocation2.getLatitudeDouble();
|
|
||||||
double lon = boatLocation2.getLongitudeDouble();
|
|
||||||
mark.setCurrentPosition2(new GPSCoordinate(lat, lon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setController(RaceController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runnable for the thread.
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
setControllerListeners();
|
|
||||||
Platform.runLater(() -> controller.createSparkLine(startingBoats));
|
|
||||||
initialiseBoats();
|
|
||||||
startRaceStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the calculated fps to the fps label
|
|
||||||
*
|
|
||||||
* @param fps The new calculated fps value
|
|
||||||
*/
|
|
||||||
private void updateFPS(int fps) {
|
|
||||||
Platform.runLater(() -> controller.setFrames("FPS: " + fps));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the Race Simulation, playing the race start to finish with the timescale.
|
|
||||||
* This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
|
|
||||||
*/
|
|
||||||
private void startRaceStream() {
|
|
||||||
|
|
||||||
System.setProperty("javafx.animation.fullspeed", "true");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
new AnimationTimer() {
|
|
||||||
|
|
||||||
final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
|
|
||||||
int fps = 0; //init fps value
|
|
||||||
long timeCurrent = System.currentTimeMillis(); //current time
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(long arg0) {
|
|
||||||
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
|
|
||||||
|
|
||||||
|
|
||||||
//Check if the race has actually started.
|
|
||||||
if (visualiserInput.getRaceStatus().isStarted()) {
|
|
||||||
//Set all boats to started.
|
|
||||||
for (Boat boat : startingBoats) {
|
|
||||||
boat.setStarted(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Boat boat : startingBoats) {
|
|
||||||
if (boat != null && !boat.isFinished()) {
|
|
||||||
updatePosition(boat);
|
|
||||||
checkPosition(boat, totalTimeElapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
for (Marker mark: boatMarkers){
|
|
||||||
if (mark != null){
|
|
||||||
updateMarker(mark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visualiserInput.getRaceStatus().isFinished()) {
|
|
||||||
controller.finishRace(startingBoats);
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
controller.updateMap(startingBoats, boatMarkers);
|
|
||||||
fps++;
|
|
||||||
if ((System.currentTimeMillis() - timeCurrent) > 1000) {
|
|
||||||
updateFPS(fps);
|
|
||||||
lastFPS = fps;
|
|
||||||
fps = 0;
|
|
||||||
timeCurrent = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update position of boats in race, no position if on starting leg or DNF.
|
|
||||||
*/
|
|
||||||
private void updatePositions() {
|
|
||||||
FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber());
|
|
||||||
for(Boat boat: startingBoats) {
|
|
||||||
if(boat != null) {
|
|
||||||
boat.setPosition(Integer.toString(startingBoats.indexOf(boat) + 1));
|
|
||||||
if (boat.isDnf() || !boat.isStarted() || boat.getCurrentLeg().getLegNumber() < 0)
|
|
||||||
boat.setPosition("-");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update call for the controller.
|
|
||||||
*/
|
|
||||||
private void setControllerListeners() {
|
|
||||||
if (controller != null) controller.setInfoTable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boats that have started the race.
|
|
||||||
*
|
|
||||||
* @return ObservableList of Boat class that participated in the race.
|
|
||||||
* @see ObservableList
|
|
||||||
* @see Boat
|
|
||||||
*/
|
|
||||||
public ObservableList<Boat> getStartingBoats() {
|
|
||||||
return startingBoats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an estimated time an event will occur, and converts it to the
|
|
||||||
* number of seconds before the event will occur.
|
|
||||||
*
|
|
||||||
* @param estTimeMillis estimated time in milliseconds
|
|
||||||
* @return int difference between time the race started and the estimated time
|
|
||||||
*/
|
|
||||||
private int convertEstTime(long estTimeMillis, long currentTime) {
|
|
||||||
|
|
||||||
long estElapsedMillis = estTimeMillis - currentTime;
|
|
||||||
int estElapsedSecs = Math.round(estElapsedMillis/1000);
|
|
||||||
return estElapsedSecs;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,398 @@
|
|||||||
|
package visualiser.model;
|
||||||
|
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import network.Messages.BoatLocation;
|
||||||
|
import network.Messages.BoatStatus;
|
||||||
|
import network.Messages.Enums.BoatStatusEnum;
|
||||||
|
import network.Messages.Enums.RaceStatusEnum;
|
||||||
|
import network.Messages.RaceStatus;
|
||||||
|
import shared.dataInput.BoatDataSource;
|
||||||
|
import shared.dataInput.RaceDataSource;
|
||||||
|
import shared.dataInput.RegattaDataSource;
|
||||||
|
import shared.model.*;
|
||||||
|
import visualiser.Controllers.FinishController;
|
||||||
|
import visualiser.Controllers.RaceController;
|
||||||
|
import visualiser.app.VisualiserInput;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class used to view the race streamed.
|
||||||
|
* Has a course, boats, boundaries, etc...
|
||||||
|
* Observes LatestMessages and updates its state based on new messages.
|
||||||
|
*/
|
||||||
|
public class VisualiserRace extends Race {
|
||||||
|
|
||||||
|
|
||||||
|
//TODO replace with LatestMessages
|
||||||
|
private final VisualiserInput visualiserInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable list of boats in the race.
|
||||||
|
*/
|
||||||
|
private final ObservableList<VisualiserBoat> boats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable list of marker boats in the race.
|
||||||
|
*/
|
||||||
|
private final ObservableList<Mark> boatMarkers;
|
||||||
|
|
||||||
|
//TODO remove these controller references once refactored
|
||||||
|
private RaceController controller;
|
||||||
|
protected FinishController finishController;
|
||||||
|
|
||||||
|
//TODO remove?
|
||||||
|
private int lastFPS = 20;
|
||||||
|
|
||||||
|
|
||||||
|
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, List<Color> colors, VisualiserInput visualiserInput, RaceController controller) {
|
||||||
|
|
||||||
|
super(boatDataSource, raceDataSource, regattaDataSource);
|
||||||
|
|
||||||
|
|
||||||
|
this.boats = FXCollections.observableArrayList(this.generateVisualiserBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), colors));
|
||||||
|
|
||||||
|
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.controller = controller;
|
||||||
|
|
||||||
|
this.visualiserInput = visualiserInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a list of VisualiserBoats 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 colors The list of colors to be used for the boats.
|
||||||
|
* @return A list of MockBoats that are participating in the race.
|
||||||
|
*/
|
||||||
|
private List<VisualiserBoat> generateVisualiserBoats(Map<Integer, Boat> boats, List<Integer> sourceIDs, List<Color> colors) {
|
||||||
|
|
||||||
|
List<VisualiserBoat> visualiserBoats = new ArrayList<>(sourceIDs.size());
|
||||||
|
|
||||||
|
//For each sourceID participating...
|
||||||
|
int colorIndex = 0;
|
||||||
|
for (int sourceID : sourceIDs) {
|
||||||
|
|
||||||
|
//Get the boat associated with the sourceID.
|
||||||
|
Boat boat = boats.get(sourceID);
|
||||||
|
|
||||||
|
//Get a color for the boat.
|
||||||
|
Color color = colors.get(colorIndex);
|
||||||
|
|
||||||
|
//Construct a VisualiserBoat using the Boat and Polars.
|
||||||
|
VisualiserBoat visualiserBoat = new VisualiserBoat(boat, color);
|
||||||
|
|
||||||
|
visualiserBoats.add(visualiserBoat);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return visualiserBoats;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the boats in the race.
|
||||||
|
* This sets their current leg.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void initialiseBoats() {
|
||||||
|
|
||||||
|
Leg startingLeg = legs.get(0);
|
||||||
|
|
||||||
|
for (VisualiserBoat boat : boats) {
|
||||||
|
|
||||||
|
boat.setCurrentLeg(startingLeg);
|
||||||
|
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all of the racing boats based on messages received.
|
||||||
|
* @param boats The list of racing boats.
|
||||||
|
* @param boatLocationMap A map between boat sourceIDs and BoatLocation messages.
|
||||||
|
* @param boatStatusMap A map between boat sourceIDs and BoatStatus messages.
|
||||||
|
*/
|
||||||
|
private void updateBoats(ObservableList<VisualiserBoat> boats, Map<Integer, BoatLocation> boatLocationMap, Map<Integer, BoatStatus> boatStatusMap) {
|
||||||
|
|
||||||
|
for (VisualiserBoat boat : boats) {
|
||||||
|
BoatLocation boatLocation = boatLocationMap.get(boat.getSourceID());
|
||||||
|
BoatStatus boatStatus = boatStatusMap.get(boat.getSourceID());
|
||||||
|
updateBoat(boat, boatLocation, boatStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an individual racing boat based on messages received.
|
||||||
|
* @param boat The boat to update.
|
||||||
|
* @param boatLocation The BoatLocation message to use.
|
||||||
|
* @param boatStatus The BoatStatus message to use.
|
||||||
|
*/
|
||||||
|
private void updateBoat(VisualiserBoat boat, BoatLocation boatLocation, BoatStatus boatStatus) {
|
||||||
|
|
||||||
|
if (boatLocation != null && boatStatus != null) {
|
||||||
|
|
||||||
|
//Get the new position.
|
||||||
|
double latitude = boatLocation.getLatitudeDouble();
|
||||||
|
double longitude = boatLocation.getLongitudeDouble();
|
||||||
|
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
|
||||||
|
|
||||||
|
boat.setCurrentPosition(gpsCoordinate);
|
||||||
|
|
||||||
|
//Bearing.
|
||||||
|
boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees()));
|
||||||
|
|
||||||
|
//Time until next mark.
|
||||||
|
boat.setEstimatedTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime()));
|
||||||
|
|
||||||
|
//Speed.
|
||||||
|
boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond);
|
||||||
|
|
||||||
|
|
||||||
|
//Boat status.
|
||||||
|
BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus());
|
||||||
|
boat.setStatus(boatStatusEnum);
|
||||||
|
|
||||||
|
|
||||||
|
//Leg.
|
||||||
|
int legNumber = boatStatus.getLegNumber();
|
||||||
|
|
||||||
|
if (legNumber >= 1 && legNumber < legs.size()) {
|
||||||
|
if (boat.getCurrentLeg() != legs.get(legNumber)) {
|
||||||
|
boat.setCurrentLeg(legs.get(legNumber));
|
||||||
|
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Add a track point.
|
||||||
|
if (boatStatusEnum == BoatStatusEnum.RACING) {
|
||||||
|
boat.addTrackPoint(boat.getCurrentPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set finish time if boat finished.
|
||||||
|
if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == this.legs.size()) {
|
||||||
|
boat.setTimeFinished(boatLocation.getTime());
|
||||||
|
boat.setStatus(BoatStatusEnum.FINISHED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all of the marker boats based on messages received.
|
||||||
|
* @param boatMarkers The list of marker boats.
|
||||||
|
* @param boatLocationMap A map between boat sourceIDs and BoatLocation messages.
|
||||||
|
* @param boatStatusMap A map between boat sourceIDs and BoatStatus messages.
|
||||||
|
*/
|
||||||
|
private void updateMarkers(ObservableList<Mark> boatMarkers, Map<Integer, BoatLocation> boatLocationMap, Map<Integer, BoatStatus> boatStatusMap) {
|
||||||
|
|
||||||
|
for (Mark mark : boatMarkers) {
|
||||||
|
BoatLocation boatLocation = boatLocationMap.get(mark.getSourceID());
|
||||||
|
updateMark(mark, boatLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an individual marker boat based on messages received.
|
||||||
|
* @param mark The marker boat to be updated.
|
||||||
|
* @param boatLocation The message describing the boat's new location.
|
||||||
|
*/
|
||||||
|
private void updateMark(Mark mark, BoatLocation boatLocation) {
|
||||||
|
|
||||||
|
if (boatLocation != null) {
|
||||||
|
|
||||||
|
//We only update the boat's position.
|
||||||
|
double latitude = boatLocation.getLatitudeDouble();
|
||||||
|
double longitude = boatLocation.getLongitudeDouble();
|
||||||
|
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
|
||||||
|
|
||||||
|
mark.setPosition(gpsCoordinate);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the race status (RaceStatusEnum, wind bearing, wind speed) based on received messages.
|
||||||
|
* @param raceStatus The RaceStatus message received.
|
||||||
|
*/
|
||||||
|
private void updateRaceStatus(RaceStatus raceStatus) {
|
||||||
|
|
||||||
|
//Race status enum.
|
||||||
|
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
|
||||||
|
|
||||||
|
//Wind bearing.
|
||||||
|
this.windDirection.setDegrees(raceStatus.getScaledWindDirection());
|
||||||
|
|
||||||
|
//Wind speed.
|
||||||
|
this.windSpeed = raceStatus.getWindSpeedKnots();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setController(RaceController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable for the thread.
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
setControllerListeners();
|
||||||
|
Platform.runLater(() -> controller.createSparkLine(boats));
|
||||||
|
initialiseBoats();
|
||||||
|
startRaceStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the calculated fps to the fps label
|
||||||
|
*
|
||||||
|
* @param fps The new calculated fps value
|
||||||
|
*/
|
||||||
|
private void updateFPS(int fps) {
|
||||||
|
Platform.runLater(() -> controller.setFrames("FPS: " + fps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the Race Simulation, playing the race start to finish with the timescale.
|
||||||
|
* This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
|
||||||
|
*/
|
||||||
|
private void startRaceStream() {
|
||||||
|
|
||||||
|
System.setProperty("javafx.animation.fullspeed", "true");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
new AnimationTimer() {
|
||||||
|
|
||||||
|
final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
|
||||||
|
int fps = 0; //init fps value
|
||||||
|
long timeCurrent = System.currentTimeMillis(); //current time
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long arg0) {
|
||||||
|
|
||||||
|
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Update racing boats.
|
||||||
|
updateBoats(boats, visualiserInput.getBoatLocationMap(), visualiserInput.getBoatStatusMap());
|
||||||
|
//And their positions (e.g., 5th).
|
||||||
|
updateBoatPositions(boats);
|
||||||
|
|
||||||
|
//Update marker boats.
|
||||||
|
updateMarkers(boatMarkers, visualiserInput.getBoatLocationMap(), visualiserInput.getBoatStatusMap());
|
||||||
|
|
||||||
|
|
||||||
|
//Update race status.
|
||||||
|
updateRaceStatus(visualiserInput.getRaceStatus());
|
||||||
|
|
||||||
|
|
||||||
|
//TODO tidy this circular dependency up
|
||||||
|
if (getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
|
||||||
|
controller.finishRace(boats);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.updateMap(boats, boatMarkers);
|
||||||
|
|
||||||
|
fps++;
|
||||||
|
|
||||||
|
if ((System.currentTimeMillis() - timeCurrent) > 1000) {
|
||||||
|
updateFPS(fps);
|
||||||
|
lastFPS = fps;
|
||||||
|
fps = 0;
|
||||||
|
timeCurrent = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of boats in race (e.g, 5th), no position if on starting leg or DNF.
|
||||||
|
*/
|
||||||
|
private void updateBoatPositions(ObservableList<VisualiserBoat> boats) {
|
||||||
|
|
||||||
|
sortBoatsByPosition(boats);
|
||||||
|
|
||||||
|
for (VisualiserBoat boat: boats) {
|
||||||
|
boat.setPosition(Integer.toString(boats.indexOf(boat) + 1));
|
||||||
|
|
||||||
|
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0))
|
||||||
|
boat.setPosition("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the list of boats by their position within the race.
|
||||||
|
* @param boats The list of boats in the race.
|
||||||
|
*/
|
||||||
|
private void sortBoatsByPosition(ObservableList<VisualiserBoat> boats) {
|
||||||
|
|
||||||
|
FXCollections.sort(boats, (a, b) -> {
|
||||||
|
//Get the difference in leg numbers.
|
||||||
|
int legNumberDelta = b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber();
|
||||||
|
|
||||||
|
//If they're on the same leg, we need to compare time to finish leg.
|
||||||
|
if (legNumberDelta == 0) {
|
||||||
|
return (int) (b.getEstimatedTime() - a.getEstimatedTime());
|
||||||
|
} else {
|
||||||
|
return legNumberDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update call for the controller.
|
||||||
|
*/
|
||||||
|
private void setControllerListeners() {
|
||||||
|
if (controller != null) controller.setInfoTable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the boats participating in the race.
|
||||||
|
* @return ObservableList of boats participating in the race.
|
||||||
|
*/
|
||||||
|
public ObservableList<VisualiserBoat> getBoats() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an estimated time an event will occur, and converts it to the
|
||||||
|
* number of seconds before the event will occur.
|
||||||
|
*
|
||||||
|
* @param estTimeMillis estimated time in milliseconds
|
||||||
|
* @return int difference between time the race started and the estimated time
|
||||||
|
*/
|
||||||
|
private int convertEstTime(long estTimeMillis, long currentTime) {
|
||||||
|
|
||||||
|
long estElapsedMillis = estTimeMillis - currentTime;
|
||||||
|
int estElapsedSecs = Math.round(estElapsedMillis/1000);
|
||||||
|
return estElapsedSecs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
|
||||||
|
<BoatConfig>
|
||||||
|
<Boats>
|
||||||
|
|
||||||
|
<!--Mark Boats-->
|
||||||
|
<Boat Type="Mark" BoatName="PRO" SourceID="101" >
|
||||||
|
<GPSposition X= "-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="PIN" SourceID="102" >
|
||||||
|
<GPSposition X= "-64.855242" Y="32.293771" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="Marker1" SourceID="103" >
|
||||||
|
<GPSposition X= "-64.843983" Y="32.293039" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="WGL" SourceID="104" >
|
||||||
|
<GPSposition X= "-64.850045" Y="32.28468" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="WGR" SourceID="105" >
|
||||||
|
<GPSposition X= "-64.847591" Y="32.280164" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="LGL" SourceID="106" >
|
||||||
|
<GPSposition X= "-64.835249" Y="32.309693" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="LGR" SourceID="107" >
|
||||||
|
<GPSposition X= "-64.831785" Y="32.308046" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="FL" SourceID="108" >
|
||||||
|
<GPSposition X= "-64.839291" Y="32.317379" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Mark" BoatName="FR" SourceID="109" >
|
||||||
|
<GPSposition X= "-64.83626" Y="32.317257" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
|
||||||
|
<!--Participants-->
|
||||||
|
<Boat BoatName="Team ORACLE USA" HullNum="RG01" ShapeID="0" ShortName="USA" SourceID="121" StoweName="USA" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat BoatName="Land Rover BAR" HullNum="RG01" ShapeID="0" ShortName="GBR" SourceID="122" StoweName="GBR" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat BoatName="SoftBank Team Japan" HullNum="RG01" ShapeID="0" ShortName="JPN" SourceID="123" StoweName="JPN" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat BoatName="Groupama Team France" HullNum="RG01" ShapeID="0" ShortName="FRA" SourceID="124" StoweName="FRA" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat BoatName="Artemis Racing" HullNum="RG01" ShapeID="0" ShortName="SWE" SourceID="125" StoweName="SWE" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat BoatName="Emirates Team New Zealand" HullNum="RG01" ShapeID="0" ShortName="NZL" SourceID="126" StoweName="NZL" Type="Yacht">
|
||||||
|
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
|
||||||
|
</Boat>
|
||||||
|
</Boats>
|
||||||
|
</BoatConfig>
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<Race>
|
||||||
|
<RaceID>5326</RaceID>
|
||||||
|
<RaceType>FLEET</RaceType>
|
||||||
|
<CreationTimeDate>CREATION_TIME</CreationTimeDate>
|
||||||
|
<RaceStartTime Postpone="false" Time="START_TIME"/>
|
||||||
|
<Participants>
|
||||||
|
<Yacht SourceID="121"/>
|
||||||
|
<Yacht SourceID="122"/>
|
||||||
|
<Yacht SourceID="123"/>
|
||||||
|
<Yacht SourceID="124"/>
|
||||||
|
<Yacht SourceID="125"/>
|
||||||
|
<Yacht SourceID="126"/>
|
||||||
|
</Participants>
|
||||||
|
<CompoundMarkSequence>
|
||||||
|
<Corner CompoundMarkID="1" SeqID="1"/>
|
||||||
|
<Corner CompoundMarkID="2" SeqID="2"/>
|
||||||
|
<Corner CompoundMarkID="4" SeqID="3"/>
|
||||||
|
<Corner CompoundMarkID="3" SeqID="4"/>
|
||||||
|
<Corner CompoundMarkID="4" SeqID="5"/>
|
||||||
|
<Corner CompoundMarkID="5" SeqID="6"/>
|
||||||
|
</CompoundMarkSequence>
|
||||||
|
<Course>
|
||||||
|
<CompoundMark CompoundMarkID="1" Name="Start Line">
|
||||||
|
<Mark SeqId="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101"/>
|
||||||
|
<Mark SeqId="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102"/>
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="2" Name="Marker 1">
|
||||||
|
<Mark Name="Marker1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="3" Name="Windward Gate">
|
||||||
|
<Mark Name="WGL" SeqId="1" TargetLat="32.28468" TargetLng="-64.850045" SourceID="104"/>
|
||||||
|
<Mark Name="WGR" SeqId="2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="4" Name="Leeward Gate">
|
||||||
|
<Mark Name="LGL" SeqId="1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
|
||||||
|
<Mark Name="LGR" SeqId="2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="5" Name="Finish Line">
|
||||||
|
<Mark Name="FL" SeqId="1" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
|
||||||
|
<Mark Name="FR" SeqId="2" TargetLat="32.317257" TargetLng="-64.83626" SourceID="109"/>
|
||||||
|
</CompoundMark>
|
||||||
|
</Course>
|
||||||
|
<CourseLimit>
|
||||||
|
<Limit Lat="32.313922" Lon="-64.837168" SeqID="1"/>
|
||||||
|
<Limit Lat="32.317379" Lon="-64.839291" SeqID="2"/>
|
||||||
|
<Limit Lat="32.317911" Lon="-64.836996" SeqID="3"/>
|
||||||
|
<Limit Lat="32.317257" Lon="-64.83626" SeqID="4"/>
|
||||||
|
<Limit Lat="32.304273" Lon="-64.822834" SeqID="5"/>
|
||||||
|
<Limit Lat="32.279097" Lon="-64.841545" SeqID="6"/>
|
||||||
|
<Limit Lat="32.279604" Lon="-64.849871" SeqID="7"/>
|
||||||
|
<Limit Lat="32.289545" Lon="-64.854162" SeqID="8"/>
|
||||||
|
<Limit Lat="32.290198" Lon="-64.858711" SeqID="9"/>
|
||||||
|
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
|
||||||
|
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>
|
||||||
|
</CourseLimit>
|
||||||
|
</Race>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<RegattaConfig>
|
||||||
|
<RegattaID>3</RegattaID>
|
||||||
|
<RegattaName>New Zealand Test</RegattaName>
|
||||||
|
<CourseName>North Head</CourseName>
|
||||||
|
<CentralLatitude>-36.82791529</CentralLatitude>
|
||||||
|
<CentralLongitude>174.81218919</CentralLongitude>
|
||||||
|
<CentralAltitude>0.00</CentralAltitude>
|
||||||
|
<UtcOffset>12</UtcOffset>
|
||||||
|
<MagneticVariation>14.1</MagneticVariation>
|
||||||
|
</RegattaConfig>
|
||||||
|
Loading…
Reference in new issue