Refactored and moved XMLReader, RaceDataSource, BoatXMLReader, BoatDataSource, RegattaXMLReader to shared/dataInput.

main
fjc40 9 years ago
parent d0d63ca236
commit 7f027c8cc5

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

@ -4,6 +4,7 @@ package mock.dataInput;
import mock.exceptions.InvalidPolarFileException;
import mock.model.Polars;
import shared.model.Bearing;
import java.io.*;
import java.util.ArrayList;

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

@ -6,6 +6,8 @@ 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;

@ -1,6 +1,7 @@
package mock.model;
import javafx.util.Pair;
import shared.model.Bearing;
import java.util.ArrayList;
import java.util.HashMap;

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

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

@ -1,44 +1,39 @@
package mock.dataInput;
package shared.dataInput;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import seng302.Model.Boat;
import seng302.Model.GPSCoordinate;
import seng302.Model.Mark;
import seng302.Model.Polars;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import shared.exceptions.XMLReaderException;
import shared.model.Boat;
import shared.model.GPSCoordinate;
import shared.model.Mark;
import java.util.HashMap;
import java.util.Map;
/**
* Xml Reader class for Boat XML used for the race
* Xml Reader class for Boat XML used for the race.
*/
public class BoatXMLReader extends XMLReader implements BoatDataSource {
/**
* A map of source ID to boat for all boats in the race.
*/
private final Map<Integer, Boat> boatMap = new HashMap<>();
private final Map<Integer, Mark> markerMap = new HashMap<>();
/**
* Polars table to assign to each boat.
* A map of source ID to mark for all marks in the race.
*/
Polars boatPolars;
private final Map<Integer, Mark> markerMap = new HashMap<>();
/**
* Constructor for Boat XML
*
* @param filePath Name/path of file to read. Read as a resource.
* @param boatPolars polars used by the boats
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
* @throws XMLReaderException Thrown if the file cannot be parsed.
*/
public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {
public BoatXMLReader(String filePath) throws XMLReaderException {
super(filePath);
this.boatPolars = boatPolars;
read();
}
@ -109,9 +104,9 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
String shortName = boatNode.getAttributes().getNamedItem("ShortName").getTextContent();
if (exists(boatNode, "Country")) {
String country = boatNode.getAttributes().getNamedItem("Country").getTextContent();
boatMap.put(sourceID, new Boat(sourceID, name, country, this.boatPolars));
boatMap.put(sourceID, new Boat(sourceID, name, country));
} else {
boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars));
boatMap.put(sourceID, new Boat(sourceID, name, shortName));
}
}

@ -0,0 +1,72 @@
package shared.dataInput;
import network.Messages.Enums.RaceTypeEnum;
import shared.model.Boat;
import shared.model.CompoundMark;
import shared.model.GPSCoordinate;
import shared.model.Leg;
import java.time.ZonedDateTime;
import java.util.List;
/**
* An object that holds relevant data for a race. <br>
* Information includes: {@link shared.model.Boat Boat}s,
* {@link shared.model.Leg Leg}s, {@link shared.model.CompoundMark CompoundMark}s and
* the {@link shared.model.GPSCoordinate GPSCoordinate}s.
*/
public interface RaceDataSource {
/**
* Returns the list of boats competing in the race.
* @return Boats competing in the race.
*/
List<Boat> getBoats();
/**
* Returns the list of legs in the race.
* @return The list of legs in the race.
*/
List<Leg> getLegs();
/**
* Returns a list of coordinates representing the boundary of the race.
* @return The boundary of the race.
*/
List<GPSCoordinate> getBoundary();
/**
* Returns a list of CompoundMarks in the race.
* @return
*/
List<CompoundMark> getCompoundMarks();
/**
* Returns the ID of the race.
* @return The ID of the race.
*/
int getRaceId();
/**
* Returns the type of race.
* @return The type of race.
*/
RaceTypeEnum getRaceType();
/**
* Returns the start time/date of the race.
* @return The race's start time.
*/
ZonedDateTime getZonedDateTime();
/**
* Returns the GPS coordinate of the top left of the race map area.
* @return Top left GPS coordinate.
*/
GPSCoordinate getMapTopLeft();
/**
* Returns the GPS coordinate of the bottom right of the race map area.
* @return Bottom right GPS coordinate.
*/
GPSCoordinate getMapBottomRight();
}

@ -1,68 +1,91 @@
package visualiser.dataInput;
package shared.dataInput;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.GPSCoordinate;
import shared.dataInput.XMLReader;
import shared.exceptions.XMLReaderException;
import shared.model.GPSCoordinate;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by jjg64 on 19/04/17.
* XML reader class for regatta xml file.
*/
public class RegattaXMLReader extends XMLReader {
/**
* The regatta ID.
*/
private int regattaID;
/**
* The regatta name.
*/
private String regattaName;
/**
* The race ID.
*/
private int raceID = 0;
/**
* The course name.
*/
private String courseName;
/**
* The central latitude of the course.
*/
private double centralLatitude;
/**
* The central longitude of the course.
*/
private double centralLongitude;
/**
* The central altitude of the course.
*/
private double centralAltitude;
/**
* The UTC offset of the course.
*/
private float utcOffset;
private float magneticVariation;
/**
* Constructor for Regatta XML
*
* @param filePath path of the file
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
* The magnetic variation of the course.
*/
public RegattaXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
this(filePath, true);
}
private float magneticVariation;
/**
* Constructor for Regatta XML
*
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
* @param filePath path of the file to read. Read as a resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
*/
private RegattaXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
public RegattaXMLReader(String filePath) throws XMLReaderException {
super(filePath);
if (read) {
read();
}
read();
}
/**
* Alternate Constructor that takes in an inputstream instead
* @param xmlString Input stream of the XML
* @throws IOException Error with input
* @throws SAXException Error with XML Format
* @throws ParserConfigurationException Error with XMl contents
* @throws XMLReaderException Thrown if the input stream cannot be parsed.
*/
public RegattaXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException {
public RegattaXMLReader(InputStream xmlString) throws XMLReaderException {
super(xmlString);
read();
}
/**
* Read the XML
*/
@ -77,14 +100,19 @@ public class RegattaXMLReader extends XMLReader {
* @param attributes attributes to extract information form.
*/
private void makeRegatta(Element attributes) {
this.regattaID = Integer.parseInt(getTextValueOfNode(attributes, "RegattaID"));
this.regattaName = getTextValueOfNode(attributes, "RegattaName");
this.courseName = getTextValueOfNode(attributes, "CourseName");
this.centralLatitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLatitude"));
this.centralLongitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLongitude"));
this.centralAltitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralAltitude"));
this.utcOffset = Float.parseFloat(getTextValueOfNode(attributes, "UtcOffset"));
this.magneticVariation = Float.parseFloat(getTextValueOfNode(attributes, "MagneticVariation"));
}
public int getRegattaID() {

@ -1,10 +1,10 @@
package mock.dataInput;
package shared.dataInput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import shared.exceptions.XMLReaderException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -16,7 +16,7 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.InputStream;
import java.io.StringWriter;
/**
@ -27,38 +27,61 @@ public abstract class XMLReader {
protected Document doc;
/**
* Read in XML file
* @param filePath filepath for XML file
* @throws ParserConfigurationException If a document builder cannot be created.
* @throws IOException If any IO errors occur while parsing the XML file.
* @throws SAXException If any parse error occurs while parsing.
* Read an XML file by name as a resource.
* @param filePath filepath for XML file. Loaded as a resource.
* @throws XMLReaderException Thrown if the file cannot be parsed.
*/
public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException {
public XMLReader(String filePath) throws XMLReaderException {
InputSource fXmlFile;
if (filePath.contains("<")) {
fXmlFile = new InputSource();
fXmlFile.setCharacterStream(new StringReader(filePath));
//Read file as resource.
InputStream xmlInputStream = getClass().getClassLoader().getResourceAsStream(filePath);
} else {
fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath));
}
this.doc = parseInputStream(xmlInputStream);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
}
/**
* Alternate constructor
* @param xmlFile File to be read
* @param isWholeFile boolean value whether entire file is being passed
* Reads an XML file from an input stream.
* @param xmlInputStream The input stream to parse.
* @throws XMLReaderException Thrown if the input stream cannot be parsed.
*/
public XMLReader(String xmlFile, Boolean isWholeFile) {
public XMLReader(InputStream xmlInputStream) throws XMLReaderException {
this.doc = parseInputStream(xmlInputStream);
}
/**
* Parses an input stream into a document.
* @param inputStream The xml input stream to parse.
* @return The parsed document.
* @throws XMLReaderException Thrown when a document builder cannot be constructed, or the stream cannot be parsed.
*/
private Document parseInputStream(InputStream inputStream) throws XMLReaderException {
//Create document builder.
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new XMLReaderException("Could not create a DocumentBuilder.", e);
}
//Parse document.
Document document = null;
try {
document = dBuilder.parse(inputStream);
} catch (SAXException | IOException e) {
throw new XMLReaderException("Could not parse the xml input stream.", e);
}
document.getDocumentElement().normalize();
return document;
}
/**
* Return Document data of the read-in XML
* @return XML document

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

@ -1,156 +0,0 @@
package visualiser.dataInput;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import seng302.Model.Boat;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* XML Reader that reads in a file and initializes
* {@link seng302.Mock.StreamedBoat StreamedBoat}s that will be participating
* in a race.
*/
public class BoatXMLReader extends XMLReader {
private final Map<Integer, StreamedBoat> streamedBoatMap = new HashMap<>();
private Map<Integer, StreamedBoat> participants = new HashMap<>();
/**
* Constructor for Boat XML Reader
* @param filePath path of the file
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
this(filePath, true);
}
/**
* Constructor for Boat XML Reader
* @param filePath file path to read
* @param read whether or not to read and store the files straight away.
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException {
super(filePath);
if (read) {
read();
}
}
/**
* Constructor for Boat XML Reader
* @param xmlString sting to read
* @throws IOException error
* @throws SAXException error
* @throws ParserConfigurationException error
*/
public BoatXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException {
super(xmlString);
read();
}
public void read() {
readSettings();
readShapes();
readBoats();
}
/**
* Reads boats settings.
* INFORMATION FROM HERE IS IGNORED FOR NOW
*/
private void readSettings() {
}
/**
* Reads different kinds of boat.
* INFORMATION FROM HERE IS IGNORED FOR NOW
*/
private void readShapes() {
}
/**
* Reads the boats in the race
*/
private void readBoats() {
Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0);
for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) {
Node boat = nBoats.getChildNodes().item(i);
if (boat.getNodeName().equals("Boat") && boat.getAttributes().getNamedItem("Type").getTextContent().equals("Yacht")) {
readSingleBoat(boat);
}
}
}
/**
* Reads the information about one boat
* Ignored values: ShapeID, StoweName, HullNum, Skipper, Type
* @param boat The node to read boat data from.
*/
private void readSingleBoat(Node boat) {
StreamedBoat streamedBoat;
String country = null;
int sourceID = Integer.parseInt(boat.getAttributes().getNamedItem("SourceID").getTextContent());
String boatName = boat.getAttributes().getNamedItem("BoatName").getTextContent();
String shortName = boat.getAttributes().getNamedItem("ShortName").getTextContent();
if (exists(boat, "Country")) country = boat.getAttributes().getNamedItem("Country").getTextContent();
// Ignore all non participating boats
if (participants.containsKey(sourceID)) {
if (!streamedBoatMap.containsKey(sourceID)) {
if (country != null) {
streamedBoat = new StreamedBoat(sourceID, boatName, country);
} else {
streamedBoat = new StreamedBoat(sourceID, boatName, shortName);
}
streamedBoatMap.put(sourceID, streamedBoat);
// Override boat with new boat
participants.put(sourceID, streamedBoat);
}
for (int i = 0; i < boat.getChildNodes().getLength(); i++) {
Node GPSposition = boat.getChildNodes().item(i);
if (GPSposition.getNodeName().equals("GPSposition"))
readBoatPositionInformation(sourceID, GPSposition);
}
}
}
/**
* Reads the positional information about a boat
* Ignored values: FlagPosition, MastTop, Z value of GPSPosition
* @param sourceID The source ID of the boat.
* @param GPSposition The relative GPS position of the boat.
*/
private void readBoatPositionInformation(int sourceID, Node GPSposition) {
// TODO Get relative point before implementing. (GPSposition is based
// off a relative point).
}
/**
* Sets the participants
* @param participants boats participating the race mapped by their source ID's
*/
public void setParticipants(Map<Integer, StreamedBoat> participants) {
this.participants = participants;
}
public List<Boat> getBoats() {
return new ArrayList<>(streamedBoatMap.values());
}
}

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

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