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:
@@ -3,6 +3,7 @@ package mock.app;
|
||||
import mock.model.Polars;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import org.xml.sax.SAXException;
|
||||
import shared.model.Constants;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
@@ -66,15 +67,24 @@ public class Event {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the xml description of the race to show the race was created now, and starts in 3 minutes
|
||||
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
|
||||
* @param raceXML
|
||||
* @return String containing edited xml
|
||||
*/
|
||||
private String getRaceXMLAtCurrentTime(String raceXML) {
|
||||
|
||||
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
|
||||
long secondsToAdd = millisecondsToAdd / 1000;
|
||||
|
||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
ZonedDateTime creationTime = ZonedDateTime.now();
|
||||
return raceXML.replace("CREATION_TIME", dateFormat.format(creationTime))
|
||||
.replace("START_TIME", dateFormat.format(creationTime.plusMinutes(3)));
|
||||
raceXML.replace("CREATION_TIME", dateFormat.format(creationTime));
|
||||
|
||||
raceXML.replace("START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
|
||||
|
||||
return raceXML;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,14 +0,0 @@
|
||||
package mock.exceptions;
|
||||
|
||||
/**
|
||||
* An exception thrown when we cannot generate Boats.xml and send an XML message.
|
||||
*/
|
||||
public class InvalidBoatDataException extends RuntimeException {
|
||||
|
||||
public InvalidBoatDataException() {
|
||||
}
|
||||
|
||||
public InvalidBoatDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package mock.exceptions;
|
||||
|
||||
/**
|
||||
* Exception thrown when we cannot generate Race.xml data, and send an XML message.
|
||||
*/
|
||||
public class InvalidRaceDataException extends RuntimeException {
|
||||
public InvalidRaceDataException() {
|
||||
}
|
||||
|
||||
public InvalidRaceDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package mock.exceptions;
|
||||
|
||||
/**
|
||||
* Created by cbt24 on 25/04/17.
|
||||
*/
|
||||
public class StreamedCourseXMLException extends Throwable {
|
||||
}
|
||||
@@ -19,7 +19,6 @@ public class MockBoat extends Boat {
|
||||
|
||||
/**
|
||||
* This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds.
|
||||
* TODO milliseconds
|
||||
*/
|
||||
private long timeSinceTackChange = 0;
|
||||
|
||||
@@ -40,6 +39,19 @@ public class MockBoat extends Boat {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a mock boat object from a given boat and polars table.
|
||||
*
|
||||
* @param boat The boat to convert into a MockBoat.
|
||||
* @param polars The polars table to use for this boat.
|
||||
*/
|
||||
public MockBoat(Boat boat, Polars polars) {
|
||||
super(boat.getSourceID(), boat.getName(), boat.getCountry());
|
||||
|
||||
this.polars = polars;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+154
-181
@@ -1,20 +1,18 @@
|
||||
package mock.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import mock.app.MockOutput;
|
||||
import network.Messages.BoatStatus;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import network.Messages.RaceStatus;
|
||||
import network.Utils.AC35UnitConverter;
|
||||
import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.Enums.RaceTypeEnum;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Constants;
|
||||
import shared.model.GPSCoordinate;
|
||||
import shared.model.Leg;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
import shared.model.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.*;
|
||||
|
||||
import static java.lang.Math.cos;
|
||||
|
||||
@@ -24,42 +22,20 @@ import static java.lang.Math.cos;
|
||||
* Has a course, boats, boundaries, etc...
|
||||
* Is responsible for simulating the race, and sending messages to a MockOutput instance.
|
||||
*/
|
||||
public class Race implements Runnable {
|
||||
public class MockRace extends Race {
|
||||
|
||||
/**
|
||||
* An observable list of boats in the race.
|
||||
*/
|
||||
private ObservableList<Boat> boats;
|
||||
private List<MockBoat> boats;
|
||||
|
||||
/**
|
||||
* An observable list of compound marks in the race.
|
||||
*/
|
||||
private ObservableList<CompoundMark> compoundMarks;
|
||||
|
||||
/**
|
||||
* A list of legs in the race.
|
||||
*/
|
||||
private List<Leg> legs;
|
||||
|
||||
/**
|
||||
* A list of coordinates describing the boundary of the course.
|
||||
*/
|
||||
private List<GPSCoordinate> boundary;
|
||||
|
||||
/**
|
||||
* A copy of the boundary list, except "shrunk" inwards by 50m.
|
||||
*/
|
||||
private List<GPSCoordinate> shrinkBoundary;
|
||||
|
||||
/**
|
||||
* The elapsed time, in milliseconds, of the race.
|
||||
*/
|
||||
private long totalTimeElapsed;
|
||||
|
||||
/**
|
||||
* The starting timestamp, in milliseconds, of the race.
|
||||
*/
|
||||
private long startTime;
|
||||
|
||||
/**
|
||||
* The scale factor of the race.
|
||||
@@ -68,20 +44,6 @@ public class Race implements Runnable {
|
||||
*/
|
||||
private int scaleFactor = 5;
|
||||
|
||||
/**
|
||||
* The race ID of the course.
|
||||
*/
|
||||
private int raceId;
|
||||
|
||||
/**
|
||||
* The current status of the race.
|
||||
*/
|
||||
private RaceStatusEnum raceStatusEnum;
|
||||
|
||||
/**
|
||||
* The type of race this is.
|
||||
*/
|
||||
private RaceTypeEnum raceType;
|
||||
|
||||
/**
|
||||
* The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint.
|
||||
@@ -96,50 +58,49 @@ public class Race implements Runnable {
|
||||
private MockOutput mockOutput;
|
||||
|
||||
|
||||
/**
|
||||
* Wind direction bearing.
|
||||
*/
|
||||
private Bearing windDirection;
|
||||
|
||||
/**
|
||||
* Wind speed (knots).
|
||||
* Convert this to millimeters per second before passing to RaceStatus.
|
||||
* Used to generate random numbers when changing the wind direction.
|
||||
*/
|
||||
private double windSpeed;
|
||||
|
||||
private double windDirDegrees;
|
||||
private double windDir;
|
||||
private int changeWind = 4;
|
||||
private static final int windUpperBound = 235;
|
||||
private static final int windLowerBound = 215;
|
||||
|
||||
/**
|
||||
* The bearing the wind direction starts at.
|
||||
*/
|
||||
private static final Bearing windBaselineBearing = Bearing.fromDegrees(225);
|
||||
|
||||
/**
|
||||
* The lower bearing angle that the wind may have.
|
||||
*/
|
||||
private static final Bearing windLowerBound = Bearing.fromDegrees(215);
|
||||
|
||||
/**
|
||||
* The upper bearing angle that the wind may have.
|
||||
*/
|
||||
private static final Bearing windUpperBound = Bearing.fromDegrees(235);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a race object with a given RaceDataSource and sends events to the given mockOutput.
|
||||
* @param raceData Data source for race related data (boats, legs, etc...).
|
||||
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
|
||||
* @param boatDataSource Data source for boat related data (yachts and marker boats).
|
||||
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
|
||||
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
|
||||
* @param polars The polars table to be used for boat simulation.
|
||||
* @param mockOutput The mockOutput to send events to.
|
||||
*/
|
||||
public Race(RaceDataSource raceData, MockOutput mockOutput) {
|
||||
public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, MockOutput mockOutput) {
|
||||
|
||||
super(boatDataSource, raceDataSource, regattaDataSource);
|
||||
|
||||
this.mockOutput = mockOutput;
|
||||
|
||||
this.boats = FXCollections.observableArrayList(raceData.getBoats());
|
||||
this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks());
|
||||
this.boundary = raceData.getBoundary();
|
||||
this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars);
|
||||
|
||||
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary);
|
||||
|
||||
|
||||
this.legs = raceData.getLegs();
|
||||
this.legs.add(new Leg("Finish", this.legs.size()));
|
||||
|
||||
this.raceId = raceData.getRaceId();
|
||||
|
||||
//The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another.
|
||||
this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor);
|
||||
|
||||
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
|
||||
this.raceType = raceData.getRaceType();
|
||||
|
||||
this.windSpeed = 12;
|
||||
this.windDirection = Bearing.fromDegrees(180);
|
||||
@@ -147,15 +108,45 @@ public class Race implements Runnable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of MockBoats given a list of Boats, and a list of participating boats.
|
||||
* @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat.
|
||||
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
|
||||
* @param polars The polars table to be used for boat simulation.
|
||||
* @return A list of MockBoats that are participating in the race.
|
||||
*/
|
||||
private List<MockBoat> generateMockBoats(Map<Integer, Boat> boats, List<Integer> sourceIDs, Polars polars) {
|
||||
|
||||
List<MockBoat> mockBoats = new ArrayList<>(sourceIDs.size());
|
||||
|
||||
//For each sourceID participating...
|
||||
for (int sourceID : sourceIDs) {
|
||||
|
||||
//Get the boat associated with the sourceID.
|
||||
Boat boat = boats.get(sourceID);
|
||||
|
||||
//Construct a MockBoat using the Boat and Polars.
|
||||
MockBoat mockBoat = new MockBoat(boat, polars);
|
||||
|
||||
mockBoats.add(mockBoat);
|
||||
|
||||
}
|
||||
|
||||
return mockBoats;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runnable for the thread.
|
||||
*/
|
||||
public void run() {
|
||||
initialiseBoats();
|
||||
initialiseWindDir();
|
||||
initialiseWindDirection();
|
||||
countdownTimer.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the compound marker boats through mock output.
|
||||
*/
|
||||
@@ -184,7 +175,14 @@ public class Race implements Runnable {
|
||||
*/
|
||||
private void parseIndividualMark(Mark mark) {
|
||||
|
||||
this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime);
|
||||
this.mockOutput.parseBoatLocation(
|
||||
mark.getSourceID(),
|
||||
mark.getPosition().getLatitude(),
|
||||
mark.getPosition().getLongitude(),
|
||||
0,
|
||||
0,
|
||||
this.totalTimeElapsed + this.startTime
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -194,7 +192,7 @@ public class Race implements Runnable {
|
||||
private void parseBoatLocations() {
|
||||
|
||||
//Parse each boat.
|
||||
for (Boat boat : this.boats) {
|
||||
for (MockBoat boat : this.boats) {
|
||||
|
||||
this.parseIndividualBoatLocation(boat);
|
||||
|
||||
@@ -206,7 +204,7 @@ public class Race implements Runnable {
|
||||
* Parses an individual boat, and sends it to mockOutput.
|
||||
* @param boat The boat to parse.
|
||||
*/
|
||||
private void parseIndividualBoatLocation(Boat boat) {
|
||||
private void parseIndividualBoatLocation(MockBoat boat) {
|
||||
|
||||
this.mockOutput.parseBoatLocation(
|
||||
boat.getSourceID(),
|
||||
@@ -263,9 +261,13 @@ public class Race implements Runnable {
|
||||
List<BoatStatus> boatStatuses = new ArrayList<>();
|
||||
|
||||
//Add each boat status to the status list.
|
||||
for (Boat boat : boats) {
|
||||
for (MockBoat boat : boats) {
|
||||
|
||||
BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime());
|
||||
BoatStatus boatStatus = new BoatStatus(
|
||||
boat.getSourceID(),
|
||||
boat.getStatus(),
|
||||
boat.getCurrentLeg().getLegNumber(),
|
||||
boat.getEstimatedTime() );
|
||||
|
||||
boatStatuses.add(boatStatus);
|
||||
}
|
||||
@@ -277,7 +279,14 @@ public class Race implements Runnable {
|
||||
int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond);
|
||||
|
||||
//Create race status object, and send it.
|
||||
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses);
|
||||
RaceStatus raceStatus = new RaceStatus(
|
||||
System.currentTimeMillis(),
|
||||
this.raceId,
|
||||
this.getRaceStatusEnum().getValue(),
|
||||
this.startTime, windDirectionInt,
|
||||
windSpeedInt,
|
||||
this.getRaceType().getValue(),
|
||||
boatStatuses );
|
||||
|
||||
mockOutput.parseRaceStatus(raceStatus);
|
||||
|
||||
@@ -290,7 +299,7 @@ public class Race implements Runnable {
|
||||
*/
|
||||
private void setBoatsStatusToRacing() {
|
||||
|
||||
for (Boat boat : this.boats) {
|
||||
for (MockBoat boat : this.boats) {
|
||||
boat.setStatus(BoatStatusEnum.RACING);
|
||||
}
|
||||
}
|
||||
@@ -317,14 +326,13 @@ public class Race implements Runnable {
|
||||
parseMarks();
|
||||
|
||||
// Change wind direction
|
||||
changeWindDir();
|
||||
changeWindDirection();
|
||||
|
||||
//Parse the race status.
|
||||
parseRaceStatus();
|
||||
|
||||
|
||||
if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
|
||||
System.setProperty("javafx.animation.fullspeed", "true");
|
||||
setBoatsStatusToRacing();
|
||||
raceTimer.start();
|
||||
this.stop();
|
||||
@@ -365,12 +373,12 @@ public class Race implements Runnable {
|
||||
|
||||
//Get the time period of this frame.
|
||||
long framePeriod = currentTime - lastFrameTime;
|
||||
//We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues.
|
||||
//We actually simulate 20ms instead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues.
|
||||
framePeriod = 20;
|
||||
|
||||
|
||||
//For each boat, we update its position, and generate a BoatLocationMessage.
|
||||
for (Boat boat : boats) {
|
||||
for (MockBoat boat : boats) {
|
||||
|
||||
//If it is still racing, update its position.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
@@ -390,7 +398,7 @@ public class Race implements Runnable {
|
||||
|
||||
if (getNumberOfActiveBoats() != 0) {
|
||||
// Change wind direction
|
||||
changeWindDir();
|
||||
changeWindDirection();
|
||||
|
||||
//Parse the boat locations.
|
||||
parseBoatLocations();
|
||||
@@ -415,9 +423,10 @@ public class Race implements Runnable {
|
||||
int iters = 0;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>());
|
||||
mockOutput.parseRaceStatus(raceStatus);
|
||||
if (iters > 500){
|
||||
|
||||
parseRaceStatus();
|
||||
|
||||
if (iters > 500) {
|
||||
mockOutput.stop();
|
||||
stop();
|
||||
}
|
||||
@@ -425,24 +434,26 @@ public class Race implements Runnable {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the boats in the race.
|
||||
* This sets their starting positions and current legs.
|
||||
*/
|
||||
public void initialiseBoats() {
|
||||
@Override
|
||||
protected void initialiseBoats() {
|
||||
|
||||
//Gets the starting positions of the boats.
|
||||
List<GPSCoordinate> startingPositions = getSpreadStartingPositions();
|
||||
|
||||
//Get iterators for our boat and position lists.
|
||||
Iterator<Boat> boatIt = this.boats.iterator();
|
||||
Iterator<MockBoat> boatIt = this.boats.iterator();
|
||||
Iterator<GPSCoordinate> startPositionIt = startingPositions.iterator();
|
||||
|
||||
//Iterate over the pair of lists.
|
||||
while (boatIt.hasNext() && startPositionIt.hasNext()) {
|
||||
|
||||
//Get the next boat and position.
|
||||
Boat boat = boatIt.next();
|
||||
MockBoat boat = boatIt.next();
|
||||
GPSCoordinate startPosition = startPositionIt.next();
|
||||
|
||||
|
||||
@@ -522,7 +533,7 @@ public class Race implements Runnable {
|
||||
* @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course.
|
||||
* @return VMG for the specified boat.
|
||||
*/
|
||||
private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {
|
||||
private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) {
|
||||
|
||||
//Get the lower and upper acceptable bounds.
|
||||
Bearing lowerAcceptableBound = bearingBounds[0];
|
||||
@@ -533,7 +544,6 @@ public class Race implements Runnable {
|
||||
VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound);
|
||||
|
||||
|
||||
|
||||
return bestVMG;
|
||||
|
||||
}
|
||||
@@ -572,7 +582,7 @@ public class Race implements Runnable {
|
||||
* @param vmg The new VMG to test.
|
||||
* @return True if the new VMG is improves velocity, false otherwise.
|
||||
*/
|
||||
private boolean improvesVelocity(Boat boat, VMG vmg) {
|
||||
private boolean improvesVelocity(MockBoat boat, VMG vmg) {
|
||||
|
||||
//Get the boats "current" VMG.
|
||||
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
|
||||
@@ -590,7 +600,7 @@ public class Race implements Runnable {
|
||||
* @param updatePeriodMilliseconds The time, in milliseconds, since the last update.
|
||||
* @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race.
|
||||
*/
|
||||
protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
|
||||
protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {
|
||||
|
||||
//Checks if the current boat has finished the race or not.
|
||||
boolean finish = this.isLastLeg(boat.getCurrentLeg());
|
||||
@@ -649,7 +659,7 @@ public class Race implements Runnable {
|
||||
* @param boat The boat to check.
|
||||
* @return An array of bearings. The first is the lower bound, the second is the upper bound.
|
||||
*/
|
||||
private Bearing[] calculateBearingBounds(Boat boat) {
|
||||
private Bearing[] calculateBearingBounds(MockBoat boat) {
|
||||
|
||||
Bearing[] bearings = new Bearing[2];
|
||||
|
||||
@@ -732,7 +742,7 @@ public class Race implements Runnable {
|
||||
* @param boat The boat to check.
|
||||
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
|
||||
*/
|
||||
protected void checkPosition(Boat boat, long timeElapsed) {
|
||||
protected void checkPosition(MockBoat boat, long timeElapsed) {
|
||||
|
||||
//The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
|
||||
double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10.
|
||||
@@ -764,6 +774,7 @@ public class Race implements Runnable {
|
||||
boat.setTimeFinished(timeElapsed);
|
||||
boat.setCurrentSpeed(0);
|
||||
boat.setStatus(BoatStatusEnum.FINISHED);
|
||||
|
||||
} else if (doNotFinish()) {
|
||||
//Boat has pulled out of race.
|
||||
boat.setTimeFinished(timeElapsed);
|
||||
@@ -778,26 +789,6 @@ public class Race implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not a specific leg is the last leg in the race.
|
||||
* @param leg The leg to check.
|
||||
* @return Returns true if it is the last, false otherwse.
|
||||
*/
|
||||
private boolean isLastLeg(Leg leg) {
|
||||
|
||||
//Get the last leg.
|
||||
Leg lastLeg = this.legs.get(this.legs.size() - 1);
|
||||
|
||||
//Check its ID.
|
||||
int lastLegID = lastLeg.getLegNumber();
|
||||
|
||||
//Get the specified leg's ID.
|
||||
int legID = leg.getLegNumber();
|
||||
|
||||
|
||||
//Check if they are the same.
|
||||
return legID == lastLegID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -821,31 +812,6 @@ public class Race implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current race status.
|
||||
* @return The current race status.
|
||||
*/
|
||||
public RaceStatusEnum getRaceStatusEnum() {
|
||||
return raceStatusEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current race status.
|
||||
* @param raceStatusEnum The new status of the race.
|
||||
*/
|
||||
private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
|
||||
this.raceStatusEnum = raceStatusEnum;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of race this is.
|
||||
* @return The type of race this is.
|
||||
*/
|
||||
public RaceTypeEnum getRaceType() {
|
||||
return raceType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of boats that are still active in the race.
|
||||
@@ -854,74 +820,81 @@ public class Race implements Runnable {
|
||||
*/
|
||||
protected int getNumberOfActiveBoats() {
|
||||
|
||||
int numberofActiveBoats = 0;
|
||||
int numberOfActiveBoats = 0;
|
||||
|
||||
for (Boat boat : this.boats) {
|
||||
for (MockBoat boat : this.boats) {
|
||||
|
||||
//If the boat is currently racing, count it.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
numberofActiveBoats++;
|
||||
numberOfActiveBoats++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return numberofActiveBoats;
|
||||
return numberOfActiveBoats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an observable list of boats in the race.
|
||||
* Returns a list of boats in the race.
|
||||
* @return List of boats in the race.
|
||||
*/
|
||||
public ObservableList<Boat> getBoats() {
|
||||
public List<MockBoat> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
protected void initialiseWindDir(){
|
||||
windDirDegrees = 225;
|
||||
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
|
||||
/*windDir = new Random().nextInt(65535+1);
|
||||
windDir = BoatLocation.convertHeadingIntToDouble(255);*/
|
||||
this.windDirection = new Bearing((int)windDir);
|
||||
|
||||
/**
|
||||
* Initialises the wind bearing with the value of the windBaselineBearing.
|
||||
*/
|
||||
protected void initialiseWindDirection() {
|
||||
//Set the starting bearing.
|
||||
this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees());
|
||||
}
|
||||
|
||||
protected void changeWindDir(){
|
||||
int r = new Random().nextInt(changeWind)+1;
|
||||
if(r==1){
|
||||
windDirDegrees = (0.5 + windDirDegrees) % 360;
|
||||
} else if (r==2){
|
||||
windDirDegrees = ((windDirDegrees - 0.5) + 360) % 360;///keep the degrees positive when below 0
|
||||
}
|
||||
if (windDirDegrees > windUpperBound){
|
||||
windDirDegrees = windUpperBound;
|
||||
}
|
||||
if (windDirDegrees < windLowerBound){
|
||||
windDirDegrees = windLowerBound;
|
||||
|
||||
/**
|
||||
* Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound].
|
||||
*/
|
||||
protected void changeWindDirection() {
|
||||
|
||||
//Randomly add or remove 0.5 degrees.
|
||||
int r = new Random().nextInt(changeWind) + 1;
|
||||
|
||||
if (r == 1) {
|
||||
//Add 0.5 degrees to the wind bearing.
|
||||
this.windDirection.setDegrees(this.windDirection.degrees() + 0.5);
|
||||
|
||||
} else if (r == 2) {
|
||||
//Minus 0.5 degrees from the wind bearing.
|
||||
this.windDirection.setDegrees(this.windDirection.degrees() - 0.5);
|
||||
|
||||
}
|
||||
|
||||
windDir = AC35UnitConverter.convertHeading(windDirDegrees);
|
||||
this.windDirection = new Bearing(windDirDegrees);
|
||||
}
|
||||
//Ensure that the wind is in the correct bounds.
|
||||
if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) {
|
||||
this.windDirection.setBearing(MockRace.windUpperBound);
|
||||
|
||||
} else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) {
|
||||
this.windDirection.setBearing(MockRace.windLowerBound);
|
||||
|
||||
protected void setChangeWind(int changeVal){
|
||||
if (changeVal>=0){
|
||||
changeWind = changeVal;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getWind(){
|
||||
return (int)windDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the boat's estimated time to next mark if positive
|
||||
* @param boat to estimate time given its velocity
|
||||
*/
|
||||
private void updateEstimatedTime(Boat boat) {
|
||||
private void updateEstimatedTime(MockBoat boat) {
|
||||
|
||||
double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond;
|
||||
|
||||
if (velocityToMark > 0) {
|
||||
long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark);
|
||||
long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
|
||||
boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user