Refactored Race, MockRace, and VisualiserRace to use RaceClock instead of keeping their own timers.

Moved FPS tracking to Race class, so both VisualiserRace and MockRace can monitor their FPS.
LatestMessages is now observable. It notifies observers when an XMLMessage is received.
Boat now has StringProperty for name and country/abbreviation.
Moved the MockRace timescale value to Constants.RaceTimeScale. This is passed in to MockRace on construction.
Tidied up StartController.
Copied the visualiser's resources into the resources folder.
Refactored RaceClock. Added comments. Tidied code a bit. Moved to shared.model.

Started work on RaceController.
main
fjc40 9 years ago
parent f057ad58b7
commit abbbf70146

@ -32,11 +32,11 @@ public class App extends Application {
@Override
public void start(Stage primaryStage) {
try {
Polars boatPolars = PolarParser.parse("polars/acc_polars.csv");
Polars boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
String regattaXML = readFile("mockXML/regattaTest.xml", StandardCharsets.UTF_8);
String raceXML = readFile("mockXML/raceTest.xml", StandardCharsets.UTF_8);
String boatXML = readFile("mockXML/boatTest.xml", StandardCharsets.UTF_8);
String regattaXML = readFile("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8);
String raceXML = readFile("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8);
String boatXML = readFile("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8);
Event raceEvent = new Event(raceXML, regattaXML, boatXML, boatPolars);
raceEvent.start();

@ -66,7 +66,7 @@ public class Event {
RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML);
//Create and start race.
MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.boatPolars, this.latestMessages);
MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale);
new Thread(newRace).start();
@ -97,6 +97,8 @@ public class Event {
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();

@ -41,10 +41,9 @@ public class MockRace extends Race {
/**
* 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.
* See {@link Constants#RaceTimeScale}.
*/
private int scaleFactor = 5;
private int scaleFactor;
/**
@ -83,13 +82,15 @@ public class MockRace extends Race {
* @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 latestMessages The LatestMessages to send events to.
* @param polars The polars table to be used for boat simulation.
* @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}.
*/
public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, LatestMessages latestMessages) {
public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) {
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
this.scaleFactor = timeScale;
this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars);
@ -138,7 +139,7 @@ public class MockRace extends Race {
public void run() {
initialiseBoats();
initialiseWindDirection();
countdownTimer.start();
this.countdownTimer.start();
}
@ -175,12 +176,12 @@ public class MockRace extends Race {
mark.getSourceID(),
mark.getPosition().getLatitude(),
mark.getPosition().getLongitude(),
boatLocationSequenceNumber,
this.boatLocationSequenceNumber,
0, 0,
totalTimeElapsed + startTime);
this.raceClock.getCurrentTimeMilli());
//Iterates the sequence number.
boatLocationSequenceNumber++;
this.boatLocationSequenceNumber++;
this.latestMessages.setBoatLocation(boatLocation);
@ -211,13 +212,13 @@ public class MockRace extends Race {
boat.getSourceID(),
boat.getCurrentPosition().getLatitude(),
boat.getCurrentPosition().getLongitude(),
boatLocationSequenceNumber,
this.boatLocationSequenceNumber,
boat.getBearing().degrees(),
boat.getCurrentSpeed(),
startTime + totalTimeElapsed);
this.raceClock.getCurrentTimeMilli());
//Iterates the sequence number.
boatLocationSequenceNumber++;
this.boatLocationSequenceNumber++;
this.latestMessages.setBoatLocation(boatLocation);
@ -225,27 +226,32 @@ public class MockRace extends Race {
/**
* Updates the race status enumeration based on the current time, in milliseconds.
* @param currentTime The current time, in milliseconds.
* Updates the race time to a specified value, in milliseconds since the unix epoch.
* @param currentTime Milliseconds since unix epoch.
*/
private void updateRaceStatusEnum(long currentTime) {
private void updateRaceTime(long currentTime) {
this.raceClock.setUTCTime(currentTime);
}
//The amount of milliseconds until the race starts.
long timeToStart = this.startTime - currentTime;
//Scale the time to start based on the scale factor.
long timeToStartScaled = timeToStart / this.scaleFactor;
/**
* Updates the race status enumeration based on the current time.
*/
private void updateRaceStatusEnum() {
//The amount of milliseconds until the race starts.
long timeToStart = this.raceClock.getDurationMilli();
if (timeToStartScaled > Constants.RacePreStartTime) {
if (timeToStart > Constants.RacePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
} else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) {
} else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) {
//Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
} else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) {
} else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) {
//Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
@ -267,7 +273,7 @@ public class MockRace extends Race {
List<BoatStatus> boatStatuses = new ArrayList<>();
//Add each boat status to the status list.
for (MockBoat boat : boats) {
for (MockBoat boat : this.boats) {
BoatStatus boatStatus = new BoatStatus(
boat.getSourceID(),
@ -281,14 +287,14 @@ public class MockRace extends Race {
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees());
int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond);
int windSpeedInt = (int) (this.windSpeed * Constants.KnotsToMMPerSecond);
//Create race status object, and send it.
RaceStatus raceStatus = new RaceStatus(
System.currentTimeMillis(),
this.raceId,
this.getRaceStatusEnum().getValue(),
this.startTime,
this.raceClock.getStartingTimeMilli(),
windDirectionInt,
windSpeedInt,
this.getRaceType().getValue(),
@ -323,8 +329,11 @@ public class MockRace extends Race {
@Override
public void handle(long arg0) {
//Update race time.
updateRaceTime(currentTime);
//Update the race status based on the current time.
updateRaceStatusEnum(this.currentTime);
updateRaceStatusEnum();
//Parse the boat locations.
parseBoatLocations();
@ -361,6 +370,11 @@ public class MockRace extends Race {
*/
long timeRaceStarted = System.currentTimeMillis();
/**
* Current time during a loop iteration.
*/
long currentTime = System.currentTimeMillis();
/**
* The time of the previous frame, in milliseconds.
*/
@ -370,19 +384,17 @@ public class MockRace extends Race {
public void handle(long arg0) {
//Get the current time.
long currentTime = System.currentTimeMillis();
currentTime = System.currentTimeMillis();
//Update race time.
updateRaceTime(currentTime);
//Update the total elapsed time.
totalTimeElapsed = currentTime - this.timeRaceStarted;
//As long as there is at least one boat racing, we still simulate the race.
if (getNumberOfActiveBoats() != 0) {
//Get the time period of this frame.
long framePeriod = currentTime - lastFrameTime;
//We actually simulate 20ms 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 (MockBoat boat : boats) {
@ -390,7 +402,7 @@ public class MockRace extends Race {
//If it is still racing, update its position.
if (boat.getStatus() == BoatStatusEnum.RACING) {
updatePosition(boat, framePeriod, totalTimeElapsed);
updatePosition(boat, framePeriod, raceClock.getDurationMilli());
}
@ -654,7 +666,7 @@ public class MockRace extends Race {
//Check the boats position (update leg and stuff).
this.checkPosition(boat, totalTimeElapsed);
this.checkPosition(boat, totalElapsedMilliseconds);
}
@ -899,7 +911,7 @@ public class MockRace extends Race {
if (velocityToMark > 0) {
long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow);
boat.setEstimatedTime(this.raceClock.getCurrentTimeMilli() + timeFromNow);
}
}

@ -13,6 +13,7 @@ import static network.Utils.AC35UnitConverter.convertGPSToInt;
*/
public class BoatLocation extends AC35Data {
//TODO move these to an enum.
public static final byte Unknown = 0;
public static final byte RacingYacht = 1;
public static final byte CommitteeBoat = 2;
@ -27,6 +28,7 @@ public class BoatLocation extends AC35Data {
public static final byte WeatherStation = 11;
public static final byte Helicopter = 12;
public static final byte DataProcessingApplication = 13;
///Version number of the message - is always 1.
private byte messageVersionNumber = 1;
///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int.

@ -20,6 +20,10 @@ public enum RaceStatusEnum {
* Less than 1:00 minutes before start.
*/
PREPARATORY(2),
/**
* Race has started.
*/
STARTED(3),
/**

@ -5,11 +5,13 @@ import shared.dataInput.RaceDataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
/**
* This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...).
* Currently, LatestMessage only notifies observers of change when a new XMLMessage is received.
*/
public class LatestMessages {
public class LatestMessages extends Observable {
/**
* The latest RaceStatus message.
@ -218,6 +220,8 @@ public class LatestMessages {
*/
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
this.raceXMLMessage = raceXMLMessage;
this.notifyObservers();
}
@ -235,6 +239,8 @@ public class LatestMessages {
*/
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
this.boatXMLMessage = boatXMLMessage;
this.notifyObservers();
}
@ -252,6 +258,8 @@ public class LatestMessages {
*/
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
this.regattaXMLMessage = regattaXMLMessage;
this.notifyObservers();
}
/**

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

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

@ -3,7 +3,7 @@ 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 class InvalidRegattaDataException extends Exception {
public InvalidRegattaDataException(String message) {
super(message);

@ -1,6 +1,7 @@
package shared.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import network.Messages.Enums.BoatStatusEnum;
@ -11,7 +12,7 @@ public class Boat {
/**
* The name of the boat/team.
*/
private String name;
private StringProperty name = new SimpleStringProperty();
/**
* The current speed of the boat, in knots.
@ -32,7 +33,7 @@ public class Boat {
/**
* The country or team abbreviation of the boat.
*/
private String country;
private StringProperty country = new SimpleStringProperty();
/**
* The source ID of the boat.
@ -89,9 +90,10 @@ public class Boat {
* @param country The abbreviation or country code for the boat.
*/
public Boat(int sourceID, String name, String country) {
this.country = country;
this.name = name;
this.sourceID = sourceID;
this.setName(name);
this.setCountry(country);
this.bearing = Bearing.fromDegrees(0d);
@ -106,7 +108,7 @@ public class Boat {
* @return Name of the boat/team.
*/
public String getName() {
return name;
return name.getValue();
}
/**
@ -114,9 +116,16 @@ public class Boat {
* @param name Name of the boat/team.
*/
public void setName(String name) {
this.name = name;
this.name.setValue(name);
}
/**
* Returns the name property of the boat.
* @return The name of the boat, in a StringProperty.
*/
public StringProperty nameProperty() {
return name;
}
/**
* Returns the current speed of the boat, in knots.
@ -140,7 +149,7 @@ public class Boat {
* @return The country/team abbreviation of the boat.
*/
public String getCountry() {
return country;
return country.getValue();
}
/**
@ -148,9 +157,16 @@ public class Boat {
* @param country The new country/team abbreviation for the boat.
*/
public void setCountry(String country) {
this.country = country;
this.country.setValue(country);
}
/**
* Returns the country/abbreviation property of the boat.
* @return The country/abbreviation of the boat, in a StringProperty.
*/
public StringProperty countryProperty() {
return country;
}
/**
* Returns the source ID of the boat.

@ -27,6 +27,14 @@ public class Constants {
public static final double KnotsToMMPerSecond = 514.444;
/**
* The scale factor of the race.
* Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/
public static final int RaceTimeScale = 1;
/**
* The race pre-start time, in milliseconds. 3 minutes.
*/

@ -1,8 +1,5 @@
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 network.Messages.LatestMessages;
@ -10,12 +7,7 @@ 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;
/**
@ -75,15 +67,11 @@ public abstract class Race implements Runnable {
protected List<GPSCoordinate> boundary;
/**
* The elapsed time, in milliseconds, of the race.
*/
protected long totalTimeElapsed;
/**
* The starting timestamp, in milliseconds, of the race.
* The clock which tracks the race's start time, current time, and elapsed duration.
*/
protected long startTime;
protected RaceClock raceClock;
/**
@ -91,6 +79,11 @@ public abstract class Race implements Runnable {
*/
protected int raceId;
/**
* The name of the regatta.
*/
protected String regattaName;
/**
* The current status of the race.
*/
@ -114,6 +107,23 @@ public abstract class Race implements Runnable {
protected double windSpeed;
/**
* The number of frames per second.
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset.
*/
private int currentFps = 0;
/**
* The number of frames per second we generated over the last 1 second period.
*/
private int lastFps = 0;
/**
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
*/
private long lastFpsResetTime;
/**
* Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource.
@ -132,28 +142,37 @@ public abstract class Race implements Runnable {
this.latestMessages = latestMessages;
//Marks.
this.compoundMarks = raceDataSource.getCompoundMarks();
//Boundaries.
this.boundary = raceDataSource.getBoundary();
//Legs.
this.useLegsList(raceDataSource.getLegs());
//Race ID.
this.raceId = raceDataSource.getRaceId();
//Regatta name.
this.regattaName = regattaDataSource.getRegattaName();
this.startTime = raceDataSource.getStartDateTime().toInstant().toEpochMilli();
//Race clock.
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
//this.raceClock.run();//TODO looks like we may not actually need this.
//Race status.
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
//Race type.
this.raceType = raceDataSource.getRaceType();
//Wind speed.
this.windSpeed = 0;
//Wind direction.
this.windDirection = Bearing.fromDegrees(0);
this.totalTimeElapsed = 0;
}
@ -226,6 +245,67 @@ public abstract class Race implements Runnable {
return raceType;
}
/**
* Returns the name of the regatta.
* @return The name of the regatta.
*/
public String getRegattaName() {
return regattaName;
}
/**
* Returns the wind bearing.
* @return The wind bearing.
*/
public Bearing getWindDirection() {
return windDirection;
}
/**
* Returns the wind speed.
* Measured in knots.
* @return The wind speed.
*/
public double getWindSpeed() {
return windSpeed;
}
/**
* Returns the RaceClock for this race.
* This is used to track the start time, current time, and elapsed duration of the race.
* @return The RaceClock for the race.
*/
public RaceClock getRaceClock() {
return raceClock;
}
/**
* Returns the number of frames generated per second.
* @return Frames per second.
*/
public int getFps() {
return lastFps;
}
/**
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
*/
protected void incrementFps(long timePeriod) {
//Increment.
this.currentFps++;
//Add period to timer.
this.lastFpsResetTime += timePeriod;
//If we have reached 1 second period, snapshot the framerate and reset.
if (this.lastFpsResetTime > 1000) {
this.lastFps = this.currentFps;
this.currentFps = 0;
this.lastFpsResetTime = 0;
}
}
}

@ -0,0 +1,356 @@
package shared.model;
import com.github.bfsmith.geotimezone.TimeZoneLookup;
import com.github.bfsmith.geotimezone.TimeZoneResult;
import com.sun.istack.internal.Nullable;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import shared.model.GPSCoordinate;
import visualiser.model.ResizableRaceCanvas;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
*/
public class RaceClock implements Runnable {
/**
* The time that we last updated the current race time at.
*/
private long lastTime;
/**
* The time zone of the race.
*/
private final ZoneId zoneId;
/**
* The start time of the race.
*/
private ZonedDateTime startingTime;
/**
* The current time of the race.
*/
private final StringProperty startingTimeProperty = new SimpleStringProperty();
/**
* The current time of the race.
*/
@Nullable
private ZonedDateTime currentTime;
/**
* The current time of the race.
*/
private final StringProperty currentTimeProperty = new SimpleStringProperty();
/**
* The time until the race starts, or elapsed time in the race after it has started.
*/
private StringProperty durationProperty = new SimpleStringProperty();
//Format strings.
/**
* Format string used for starting time.
*/
private String startingTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
/**
* Format string used for current time.
*/
private String currentTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
/**
* Format string used for duration before it has started.
*/
private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d";
/**
* Format string used for duration once the race has started.
*/
private String durationAfterStartFormat = "Time: %02d:%02d:%02d";
/**
* Constructs a RaceClock using a specified starting ZonedDateTime.
* @param startingTime The ZonedDateTime that the race starts at.
*/
public RaceClock(ZonedDateTime startingTime) {
this.zoneId = startingTime.getZone();
//Set start time.
setStartingTime(startingTime);
}
/**
* Returns the ZonedDateTime corresponding to a specified GPSCoordinate.
* @param gpsCoordinate The GPSCoordinate to lookup.
* @return The ZonedDateTime for the coordinate.
*/
public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
ZoneId zone = ZoneId.of(timeZoneResult.getResult());
return LocalDateTime.now(zone).atZone(zone);
}
/**
* Starts the race clock.
*/
public void run() {
new AnimationTimer() {
@Override
public void handle(long now) {
updateTime();
}
}.start();
}
/**
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
* @param time UTC time.
*/
public void setUTCTime(long time) {
Date utcTime = new Date(time);
setCurrentTime(utcTime.toInstant().atZone(this.zoneId));
}
/**
* Get ZonedDateTime corresponding to local time zone and given UTC time.
* @param time time in mills
* @return local date time
*/
public ZonedDateTime getLocalTime(long time) {
Date utcTime = new Date(time);
return utcTime.toInstant().atZone(this.zoneId);
}
/**
* Updates time by duration elapsed since last update.
*/
private void updateTime() {
//Get duration elapsed since last update.
Duration duration = Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS);
//Add this duration to the current time.
ZonedDateTime newCurrentTime = this.currentTime.plus(duration);
setCurrentTime(newCurrentTime);
}
/**
* Returns the starting time of the race.
* @return The starting time of the race.
*/
public ZonedDateTime getStartingTime() {
return startingTime;
}
/**
* Returns the race start time, expressed as the number of milliseconds since the unix epoch.
* @return Start time expressed as milliseconds since unix epoch.
*/
public long getStartingTimeMilli() {
return startingTime.toInstant().toEpochMilli();
}
/**
* Sets the starting time of the race.
* @param startingTime The starting time of the race.
*/
public void setStartingTime(ZonedDateTime startingTime) {
this.startingTime = startingTime;
//Convert time into string.
String startingTimeString = DateTimeFormatter.ofPattern(this.startingTimeFormat).format(startingTime);
//Use it.
setStartingTimeString(startingTimeString);
}
/**
* Returns the starting time of the race, as a string.
* @return The starting time of the race, as a string.
*/
public String getStartingTimeString() {
return startingTimeProperty.get();
}
/**
* Sets the starting time string of the race.
* This should only be called by {@link #setStartingTime(ZonedDateTime)}.
* @param startingTime The new value for the starting time string.
*/
private void setStartingTimeString(String startingTime) {
this.startingTimeProperty.setValue(startingTime);
}
/**
* Returns the starting time property.
* @return The starting time property.
*/
public StringProperty startingTimeProperty() {
return startingTimeProperty;
}
/**
* Returns the race duration, in milliseconds.
* A negative value means that the race has not started.
* @return Race duration in milliseconds.
*/
public long getDurationMilli() {
return getCurrentTimeMilli() - getStartingTimeMilli();
}
/**
* Returns the race duration, as a string.
* @return Duration as a string.
*/
public String getDurationString() {
return durationProperty.get();
}
/**
* Sets the duration time string of the race.
* @param duration The new value for the duration time string.
*/
private void setDurationString(String duration) {
this.durationProperty.setValue(duration);
}
/**
* Returns the duration property.
* @return The duration property.
*/
public StringProperty durationProperty() {
return durationProperty;
}
/**
* Returns the current time of the race.
* @return The current time of the race.
*/
public ZonedDateTime getCurrentTime() {
return currentTime;
}
/**
* Returns the race current time, expressed as the number of milliseconds since the unix epoch.
* @return Current time expressed as milliseconds since unix epoch.
*/
public long getCurrentTimeMilli() {
return currentTime.toInstant().toEpochMilli();
}
/**
* Sets the current time of the race.
* @param currentTime The current time of the race.
*/
private void setCurrentTime(ZonedDateTime currentTime) {
this.currentTime = currentTime;
//Convert time into string.
String currentTimeString = DateTimeFormatter.ofPattern(this.currentTimeFormat).format(currentTime);
//Use it.
setCurrentTimeString(currentTimeString);
//Store the last time we updated the current time at.
this.lastTime = System.currentTimeMillis();
//Update the duration string.
updateDurationString();
}
/**
* Updates the duration string based on the start time and current time.
* This requires {@link #currentTime} to be non-null.
*/
private void updateDurationString() {
//Calculates the duration in seconds.
long seconds = Duration.between(startingTime.toLocalDateTime(), currentTime.toLocalDateTime()).getSeconds();
//Check if the race has already started or not. This determines the format string used.
String formatString;
if (seconds < 0) {
//Race hasn't started.
formatString = this.durationBeforeStartFormat;
//The seconds value is negative, so we make it positive.
seconds = seconds * -1;
} else {
//Race has started.
formatString = this.durationAfterStartFormat;
}
//Format the seconds value.
//Hours : minutes : seconds.
String formattedDuration = String.format(formatString, seconds / 3600, (seconds % 3600) / 60, seconds % 60);
//Use it.
setDurationString(formattedDuration);
}
/**
* Returns the current time of the race, as a string.
* @return The current time of the race, as a string.
*/
public String getCurrentTimeString() {
return currentTimeProperty.get();
}
/**
* Sets the current time string of the race.
* @param currentTime The new value for the current time string.
*/
private void setCurrentTimeString(String currentTime) {
this.currentTimeProperty.setValue(currentTime);
}
/**
* Returns the current time property.
* @return The current time property.
*/
public StringProperty currentTimeProperty() {
return currentTimeProperty;
}
/**
* Returns the time zone of the race, as a string.
* @return The race time zone.
*/
public String getTimeZone() {
return zoneId.toString();
}
}

@ -3,10 +3,9 @@ package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
import seng302.Model.Boat;
import seng302.Model.RaceClock;
import seng302.VisualiserInput;
import visualiser.app.VisualiserInput;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.net.Socket;
import java.net.URL;
@ -21,8 +20,13 @@ public class MainController extends Controller {
@FXML private ConnectionController connectionController;
@FXML private FinishController finishController;
public void beginRace(VisualiserInput visualiserInput, RaceClock raceClock) {
raceController.startRace(visualiserInput, raceClock);
/**
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserInput The object used to read packets from the race server.
* @param visualiserRace The object modelling the race.
*/
public void beginRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
raceController.startRace(visualiserInput, visualiserRace);
}
public void enterLobby(Socket socket) {
@ -41,10 +45,12 @@ public class MainController extends Controller {
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
startController.setParent(this);
raceController.setParent(this);
connectionController.setParent(this);
finishController.setParent(this);
AnchorPane.setTopAnchor(startController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);

@ -10,45 +10,84 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import visualiser.model.RaceClock;
import visualiser.app.VisualiserInput;
import shared.model.RaceClock;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
/**
* Created by fwy13 on 15/03/2017.
* Controller used to display a running race.
*/
public class RaceController extends Controller {
/**
* The object used to read packets from the connected server.
*/
private VisualiserInput visualiserInput;
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRace visualiserRace;
private ResizableRaceCanvas raceMap;
private ResizableRaceMap raceBoundaries;
private RaceClock raceClock;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
private int legNum;
@FXML GridPane canvasBase;
@FXML Pane arrow;
@FXML SplitPane race;
@FXML StackPane arrowPane;
@FXML Label timer;
@FXML Label FPS;
@FXML Label timeZone;
@FXML CheckBox showFPS;
@FXML TableView<Boat> boatInfoTable;
@FXML TableColumn<Boat, String> boatPlacingColumn;
@FXML TableColumn<Boat, String> boatTeamColumn;
@FXML TableColumn<Boat, String> boatMarkColumn;
@FXML TableColumn<Boat, String> boatSpeedColumn;
@FXML LineChart<Number, Number> sparklineChart;
@FXML AnchorPane annotationPane;
@FXML private GridPane canvasBase;
@FXML private Pane arrow;
@FXML private SplitPane race;
@FXML private StackPane arrowPane;
@FXML private Label timer;
@FXML private Label FPS;
@FXML private Label timeZone;
@FXML private CheckBox showFPS;
@FXML private TableView<VisualiserBoat> boatInfoTable;
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
@FXML private TableColumn<VisualiserBoat, String> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, String> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
*/
public RaceController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
initialiseFpsToggle();
}
/**
* Initialises a listener for the fps toggle.
*/
private void initialiseFpsToggle() {
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
/**
* Updates the ResizableRaceCanvas (raceMap) with most recent data
@ -79,17 +118,7 @@ public class RaceController extends Controller {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
//listener for fps
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
/**
* Creates and sets initial display for Sparkline for race positions.
@ -107,14 +136,16 @@ public class RaceController extends Controller {
sparkline.updateSparkline(boatsInRace);
}
/**
* Initializes and runs the race, based on the user's chosen scale factor
* Currently uses an example racecourse
*
* @param visualiserInput input from network
* @param raceClock The RaceClock to use for the race's countdown/elapsed duration + timezone.
* Displays a specified race.
* @param visualiserInput Object used to read packets from server.
* @param visualiserRace Object modelling the race.
*/
public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) {
public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
this.visualiserInput = visualiserInput;
this.visualiserRace = visualiserRace;
legNum = visualiserInput.getCourse().getLegs().size()-1;
@ -154,17 +185,7 @@ public class RaceController extends Controller {
raceMap.setRaceClock(raceClock);
//TODO move this list of colors somewhere more sensible.
List<Color> colours = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
Color.BLACK,
Color.RED,
Color.ORANGE,
Color.DARKOLIVEGREEN,
Color.LIMEGREEN,
Color.PURPLE,
Color.DARKGRAY,
Color.YELLOW
));
StreamedRace newRace = new StreamedRace(visualiserInput, colours, this);
initializeFPS();

@ -2,51 +2,72 @@ package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import visualiser.app.VisualiserInput;
import visualiser.model.RaceClock;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRace;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;
import java.util.*;
/**
* Controller to for waiting for the race to start
* Controller to for waiting for the race to start.
*/
public class StartController extends Controller implements Observer {
@FXML private GridPane start;
@FXML private AnchorPane startWrapper;
/**
* The name of the race/regatta.
*/
@FXML private Label raceTitleLabel;
/**
* The time the race starts at.
*/
@FXML private Label raceStartLabel;
/**
* The current time at the race location.
*/
@FXML private Label timeZoneTime;
/**
* Time until the race starts.
*/
@FXML private Label timer;
@FXML private TableView<VisualiserBoat> boatNameTable;
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
@FXML private Label timeZoneTime;
@FXML private Label timer;
@FXML private Label raceStatusLabel;
/**
* The status of the race.
*/
@FXML private Label raceStatusLabel;
private RaceClock raceClock;
private int raceStat;
/**
* The object used to read packets from the connected server.
*/
private VisualiserInput visualiserInput;
/**
@ -54,147 +75,223 @@ public class StartController extends Controller implements Observer {
*/
private VisualiserRace visualiserRace;
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
private boolean hasRaceStarted = false;
/**
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
*/
List<Color> colors = new ArrayList<>(Arrays.asList(
Color.BLUEVIOLET,
Color.BLACK,
Color.RED,
Color.ORANGE,
Color.DARKOLIVEGREEN,
Color.LIMEGREEN,
Color.PURPLE,
Color.DARKGRAY,
Color.YELLOW
));
//Tracks whether or not a clock has been created and setup, which occurs after receiving enough information.
private boolean hasCreatedClock = false;
/**
* Begins the race with a scale factor of 1
* Ctor.
*/
private void startRaceNoScaling() {
//while(visualiserInput.getRaceStatus() == null);//TODO probably remove this.
countdownTimer();
public StartController() {
}
@Override
public void initialize(URL location, ResourceBundle resources){
this.visualiserRace = new VisualiserRace();
raceData.addObserver(this);
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Starts the race.
* Called once we have received all XML files from the server.
* @param latestMessages The set of latest race messages to use for race.
* @throws XMLReaderException Thrown if XML file cannot be parsed.
* @throws InvalidRaceDataException Thrown if XML file cannot be parsed.
* @throws InvalidBoatDataException Thrown if XML file cannot be parsed.
* @throws InvalidRegattaDataException Thrown if XML file cannot be parsed.
*/
private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException {
//Create data sources from latest messages for the race.
RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage());
BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage());
RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage());
//Create race.
this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors);
//Initialise the boat table.
initialiseBoatTable(this.visualiserRace);
//Initialise the race name.
initialiseRaceName(this.visualiserRace);
//Initialises the race clock.
initialiseRaceClock(this.visualiserRace);
//Starts the race countdown timer.
countdownTimer();
}
public AnchorPane startWrapper(){
return startWrapper;
}
/**
* Initiliases the tables that are to be shown on the pane
* Initialises the boat table that is to be shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void initialiseTables() {
List<VisualiserBoat> boats = raceData.getBoats();
ObservableList<VisualiserBoat> observableBoats = FXCollections.observableArrayList(boats);
private void initialiseBoatTable(VisualiserRace visualiserRace) {
boatNameTable.setItems(observableBoats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
boatCodeColumn.setCellValueFactory(new PropertyValueFactory<>("abbrev"));
//Get the boats.
ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();
//Populate table.
boatNameTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
}
/**
* Countdown timer until race starts.
* Initialises the race name which is shown on the pane.
* @param visualiserRace The race to get data from.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
raceStat = visualiserInput.getRaceStatus().getRaceStatus();
raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus());
if (raceStat==2 || raceStat == 3) {
stop();
private void initialiseRaceName(VisualiserRace visualiserRace) {
startWrapper.setVisible(false);
start.setVisible(false);
raceTitleLabel.setText(visualiserRace.getRegattaName());
parent.beginRace(visualiserInput, raceClock);
}
}
}.start();
}
/**
* Sets the clock that displays the time of at the current race venue.
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void setRaceClock() {
raceClock = new RaceClock(raceData.getZonedDateTime());
private void initialiseRaceClock(VisualiserRace visualiserRace) {
raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> {
//Start time.
initialiseRaceClockStartTime(visualiserRace);
//Current time.
initialiseRaceClockCurrentTime(visualiserRace);
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) {
visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
raceStartLabel.setText(newValue);
});
});
}
/**
* Initialises the race current time label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) {
visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
//TEMP REMOVE
//timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
raceClock.run();
}
/**
* Sets the time that the race is going to start.
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void setStartingTime() {
String dateFormat = "'Starting time:' HH:mm dd/MM/YYYY";
Platform.runLater(()-> {
long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime();
raceClock.setStartingTime(raceClock.getLocalTime(utcTime));
raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime()));
private void initialiseRaceClockDuration(VisualiserRace visualiserRace) {
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* set the current time, may be used to update the time on the clock.
* Countdown timer until race starts.
*/
private void setCurrentTime() {
Platform.runLater(()->
raceClock.setUTCTime(visualiserInput.getRaceStatus().getCurrentTime())
);
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
//Display it.
raceStatusLabel.setText("Race Status: " + raceStatus.name());
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
startWrapper.setVisible(false);
start.setVisible(false);
parent.beginRace(visualiserInput, visualiserRace);
}
}
}.start();
}
/**
* Function to handle changes in objects we observe.
* We observe LatestMessages.
* @param o The observed object.
* @param arg The {@link Observable#notifyObservers(Object)} parameter.
*/
@Override
public void update(Observable o, Object arg) {
if(o instanceof StreamedCourse) {
StreamedCourse streamedCourse = (StreamedCourse) o;
if(streamedCourse.hasReadBoats()) {
initialiseTables();
}
if(streamedCourse.hasReadRegatta()) {
Platform.runLater(() -> raceTitleLabel.setText(streamedCourse.getRegattaName()));
}
if (streamedCourse.hasReadCourse()) {
Platform.runLater(() -> {
if (!this.hasCreatedClock) {
this.hasCreatedClock = true;
setRaceClock();
if (visualiserInput.getRaceStatus() == null) {
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
}// TODO - replace with observer on VisualiserInput
setStartingTime();
setCurrentTime();
}
});
}
//TODO this is a somewhat temporary fix for when not all of the race data (boats, course, regatta) is received in time.
//Previously, startRaceNoScaling was called in the enterLobby function after the visualiserInput was started, but when connecting to the official data source it sometimes didn't send all of the race data, causing startRaceNoScaling to start, even though we didn't have enough information to start it.
if (streamedCourse.hasReadBoats() && streamedCourse.hasReadCourse() && streamedCourse.hasReadRegatta()) {
//Check that we actually have LatestMessages.
if (o instanceof LatestMessages) {
LatestMessages latestMessages = (LatestMessages) o;
//If we've received all of the xml files, start the race. Only start it if it hasn't already been created.
if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) {
//Need to handle it in the javafx thread.
Platform.runLater(() -> {
if (!this.hasRaceStarted) {
if(visualiserInput.getRaceStatus() == null) {
}
else {
this.hasRaceStarted = true;
startRaceNoScaling();
}
try {
this.startRace(latestMessages);
} catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) {
//We currently don't handle this in meaningful way, as it should never occur.
//If we reach this point it means that malformed XML files were sent.
e.printStackTrace();
}
});
}
}
}
/**
@ -204,8 +301,12 @@ public class StartController extends Controller implements Observer {
public void enterLobby(Socket socket) {
startWrapper.setVisible(true);
try {
visualiserInput = new VisualiserInput(socket, raceData);
new Thread(visualiserInput).start();
//Begin reading packets from the socket/server.
this.visualiserInput = new VisualiserInput(socket);
//Store a reference to latestMessages so that we can observe it.
LatestMessages latestMessages = this.visualiserInput.getLatestMessages();
latestMessages.addObserver(this);
new Thread(this.visualiserInput).start();
} catch (IOException e) {
e.printStackTrace();

@ -78,8 +78,13 @@ public class VisualiserInput implements Runnable {
}
/**
* Returns the LatestMessages object, which can be queried for any received race related messages.
* @return The LatestMessages object.
*/
public LatestMessages getLatestMessages() {
return latestMessages;
}
/**
* Calculates the time since last heartbeat, in milliseconds.

@ -1,133 +0,0 @@
package visualiser.model;
import com.github.bfsmith.geotimezone.TimeZoneLookup;
import com.github.bfsmith.geotimezone.TimeZoneResult;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import shared.model.GPSCoordinate;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
*/
public class RaceClock implements Runnable {
private long lastTime;
private final ZoneId zoneId;
private ZonedDateTime time;
private ZonedDateTime startingTime;
private final StringProperty timeString;
private StringProperty duration;
public RaceClock(ZonedDateTime zonedDateTime) {
this.zoneId = zonedDateTime.getZone();
this.timeString = new SimpleStringProperty();
this.duration = new SimpleStringProperty();
this.time = zonedDateTime;
setTime(time);
}
public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
ZoneId zone = ZoneId.of(timeZoneResult.getResult());
return LocalDateTime.now(zone).atZone(zone);
}
public void run() {
new AnimationTimer() {
@Override
public void handle(long now) {
updateTime();
}
}.start();
}
/**
* Sets time to arbitrary zoned time.
*
* @param time arbitrary time with timezone.
*/
private void setTime(ZonedDateTime time) {
this.time = time;
this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
this.lastTime = System.currentTimeMillis();
if(startingTime != null) {
long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
if(seconds < 0)
duration.set(String.format("Starting in: %02d:%02d:%02d", -seconds/3600, -(seconds%3600)/60, -seconds%60));
else
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
}
}
/**
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
* @param time UTC time
*/
public void setUTCTime(long time) {
Date utcTime = new Date(time);
setTime(utcTime.toInstant().atZone(this.zoneId));
}
public ZonedDateTime getStartingTime() {
return startingTime;
}
public void setStartingTime(ZonedDateTime startingTime) {
this.startingTime = startingTime;
}
/**
* Get ZonedDateTime corresponding to local time zone and given UTC time.
* @param time time in mills
* @return local date time
*/
public ZonedDateTime getLocalTime(long time) {
Date utcTime = new Date(time);
return utcTime.toInstant().atZone(this.zoneId);
}
/**
* Updates time by duration elapsed since last update.
*/
private void updateTime() {
this.time = this.time.plus(Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS));
this.lastTime = System.currentTimeMillis();
setTime(time);
}
public String getDuration() {
return duration.get();
}
public StringProperty durationProperty() {
return duration;
}
public String getTimeZone() {
return zoneId.toString();
}
public ZonedDateTime getTime() {
return time;
}
public StringProperty timeStringProperty() {
return timeString;
}
}

@ -9,6 +9,7 @@ import javafx.scene.transform.Rotate;
import seng302.Mock.StreamedCourse;
import seng302.RaceDataSource;
import seng302.RaceMap;
import shared.model.RaceClock;
import java.time.Duration;
import java.time.ZonedDateTime;

@ -22,7 +22,7 @@ import java.util.Map;
*/
public class Sparkline {
private ArrayList<String> colours;
private ArrayList<Boat> startBoats = new ArrayList<>();
private ArrayList<VisualiserBoat> startBoats = new ArrayList<>();
private Map<Integer, String> boatColours = new HashMap<>();
private Integer legNum;
private Integer sparkLineNumber = 0;

@ -15,9 +15,7 @@ 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;
@ -41,10 +39,6 @@ public class VisualiserRace extends Race {
*/
private ObservableList<Mark> boatMarkers;
//TODO remove these controller references once refactored
private RaceController controller;
protected FinishController finishController;
/**
@ -52,10 +46,10 @@ public class VisualiserRace extends Race {
* @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 colors A collection of colors used to assign a color to each boat.
* @param latestMessages The LatestMessages to send events to.
* @param colors A collection of colors used to assign a color to each boat.
*/
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, List<Color> colors, LatestMessages latestMessages, RaceController controller) {
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, List<Color> colors) {
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
@ -64,9 +58,6 @@ public class VisualiserRace extends Race {
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
this.controller = controller;
}
@ -148,7 +139,7 @@ public class VisualiserRace extends Race {
for (VisualiserBoat boat : boats) {
boat.setCurrentLeg(startingLeg);
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
boat.setTimeSinceLastMark(this.raceClock.getCurrentTime());
}
@ -209,7 +200,7 @@ public class VisualiserRace extends Race {
if (legNumber >= 1 && legNumber < legs.size()) {
if (boat.getCurrentLeg() != legs.get(legNumber)) {
boat.setCurrentLeg(legs.get(legNumber));
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
boat.setTimeSinceLastMark(this.raceClock.getCurrentTime());
}
}
@ -279,54 +270,41 @@ public class VisualiserRace extends Race {
//Wind speed.
this.windSpeed = raceStatus.getWindSpeedKnots();
//Current race time.
this.raceClock.setUTCTime(raceStatus.getCurrentTime());
}
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.
* Starts the race.
* This updates the race based on {@link #latestMessages}.
*/
private void startRaceStream() {
System.setProperty("javafx.animation.fullspeed", "true");
new AnimationTimer() {
final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
//final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
int fps = 0; //init fps value
long timeCurrent = System.currentTimeMillis(); //current time
//long timeCurrent = System.currentTimeMillis(); //current time
@Override
public void handle(long arg0) {
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
//totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
@ -343,22 +321,11 @@ public class VisualiserRace extends Race {
updateRaceStatus(latestMessages.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();
}
@ -400,12 +367,7 @@ public class VisualiserRace extends Race {
}
/**
* Update call for the controller.
*/
private void setControllerListeners() {
if (controller != null) controller.setInfoTable(this);
}
/**
* Returns the boats participating in the race.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2012-05-17T07:49:40+0200</Modified>
<Version>12</Version>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.659"/>
<Vtx Seq="2" Y="18.359" X="-2.659"/>
<Vtx Seq="3" Y="18.359" X="2.659"/>
<Vtx Seq="4" Y="0" X="2.659"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="1">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.278"/>
<Vtx Seq="2" Y="8.876" X="-1.278"/>
<Vtx Seq="3" Y="8.876" X="1.278"/>
<Vtx Seq="4" Y="0" X="1.278"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="2">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.1"/>
<Vtx Seq="2" Y="8.3" X="-1.1"/>
<Vtx Seq="3" Y="8.3" X="1.1"/>
<Vtx Seq="4" Y="0" X="1.1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="3">
<Vertices>
<Vtx Seq="1" Y="0" X="-0.75"/>
<Vtx Seq="2" Y="3" X="-0.75"/>
<Vtx Seq="3" Y="3" X="0.75"/>
<Vtx Seq="4" Y="0" X="0.75"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="4">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="5"/>
</BoatShapes>
<Boats>
<Boat Type="RC" SourceID="121" ShapeID="0" HullNum="RG01" StoweName="PRO" ShortName="PRO"
BoatName="Regardless">
<GPSposition Z="6.840" Y="7.800" X="0.000"/>
<FlagPosition Z="0.000" Y="7.800" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="122" ShapeID="1" HullNum="LC05" StoweName="CON" ShortName="Constellation"
BoatName="Constellation">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="123" ShapeID="1" HullNum="LC04" StoweName="MIS" ShortName="Mischief"
BoatName="Mischief">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="124" ShapeID="1" HullNum="LC03" ShortName="Atalanta" BoatName="Atalanta">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat SourceID="125" ShapeID="1" StoweName="VOL" HullNum="LC01" ShortName="Volunteer"
BoatName="Volunteer">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="126" ShapeID="1" HullNum="LC13" StoweName="MS2" ShortName="Defender"
BoatName="Defender">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Mark" SourceID="128" ShapeID="1" HullNum="LC01" ShortName="Shamrock" BoatName="Shamrock">
<GPSposition Z="5.334" Y="3.804" X="0.000"/>
<FlagPosition Z="0.000" Y="3.426" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="101" ShapeID="4" HullNum="AC4501" StoweName="KOR" ShortName="TEAM KOREA"
BoatName="TEAM KOREA" Country="KOR">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
</Boats>
</BoatConfig>

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>11080703</RaceID>
<RaceType>Match</RaceType>
<CreationTimeDate>2011-08-06T13:25:00-0000</CreationTimeDate>
<RaceStartTime Time="2011-08-06T13:30:00-0700" Postpone="false"/>
<Participants>
<Yacht SourceID="101" Entry="Port"/>
<Yacht SourceID="108" Entry="Stbd"/>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<Mark SeqID="1" Name="PRO" TargetLat="-36.83" TargetLng="174.83" SourceID="101"/>
<Mark SeqID="2" Name="PIN" TargetLat="-36.84" TargetLng="174.81" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="M1">
<Mark Name="M1" TargetLat="-36.63566590" TargetLng="174.88543944" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="M2">
<Mark Name="M2" TargetLat="-36.83" TargetLng="174.80" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Gate">
<Mark SeqID="1" Name="G1" TargetLat="-36.63566590" TargetLng="174.97205159" SourceID="104"/>
<Mark SeqID="2" Name="G2" TargetLat="-36.64566590" TargetLng="174.98205159" SourceID="105"/>
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Stbd" ZoneSize="6"/>
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="6"/>
<Corner SeqID="5" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="-36.8325" Lon="174.8325"/>
<Limit SeqID="2" Lat="-36.82883" Lon="174.81983"/>
<Limit SeqID="3" Lat="-36.82067" Lon="174.81983"/>
<Limit SeqID="4" Lat="-36.811" Lon="174.8265"/>
<Limit SeqID="5" Lat="-36.81033" Lon="174.83833"/>
<Limit SeqID="6" Lat="-36.81533" Lon="174.8525"/>
<Limit SeqID="7" Lat="-36.81533" Lon="174.86733"/>
<Limit SeqID="8" Lat="-36.81633" Lon="174.88217"/>
<Limit SeqID="9" Lat="-36.83383" Lon="174.87117"/>
<Limit SeqID="10" Lat="-36.83417" Lon="174.84767"/>
</CourseLimit>
</Race>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<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>

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2017-04-19T15:49:40+1200</Modified>
<Version>1</Version>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.659"/>
<Vtx Seq="2" Y="18.359" X="-2.659"/>
<Vtx Seq="3" Y="18.359" X="2.659"/>
<Vtx Seq="4" Y="0" X="2.659"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="1">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.278"/>
<Vtx Seq="2" Y="8.876" X="-1.278"/>
<Vtx Seq="3" Y="8.876" X="1.278"/>
<Vtx Seq="4" Y="0" X="1.278"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="2">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.1"/>
<Vtx Seq="2" Y="8.3" X="-1.1"/>
<Vtx Seq="3" Y="8.3" X="1.1"/>
<Vtx Seq="4" Y="0" X="1.1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="3">
<Vertices>
<Vtx Seq="1" Y="0" X="-0.75"/>
<Vtx Seq="2" Y="3" X="-0.75"/>
<Vtx Seq="3" Y="3" X="0.75"/>
<Vtx Seq="4" Y="0" X="0.75"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="4">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="5"/>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="101" ShapeID="4" HullNum="AC4501" ShortName="USA"
BoatName="ORACLE TEAM USA" Country="USA">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="102" ShapeID="4" HullNum="AC4502" ShortName="GBR"
BoatName="Land Rover BAR" Country="United Kingdom">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="103" ShapeID="4" HullNum="AC4503" ShortName="JPN"
BoatName="SoftBank Team Japan" Country="Japan">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="104" ShapeID="4" HullNum="AC4504" ShortName="FRA"
BoatName="Groupama Team France" Country="France">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="105" ShapeID="4" HullNum="AC4505" ShortName="SWE"
BoatName="Artemis Racing" Country="Sweden">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
<Boat Type="Yacht" SourceID="106" ShapeID="4" HullNum="AC4506" ShortName="NZL"
BoatName="Emirates Team New Zealand" Country="New Zealand">
<GPSposition Z="1.738" Y="0.625" X="0.001"/>
<MastTop Z="21.496" Y="4.233" X="0.000"/>
</Boat>
</Boats>
</BoatConfig>

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>17041901</RaceID>
<RaceType>Fleet</RaceType>
<CreationTimeDate>2017-04-19T15:30:00+1200</CreationTimeDate>
<RaceStartTime Time="2019-06-01T13:30:00-0400" Postpone="false"/>
<Participants>
<Yacht SourceID="001" Entry="Port"/>
<Yacht SourceID="002" Entry="Port"/>
<Yacht SourceID="003" Entry="Port"/>
<Yacht SourceID="004" Entry="Port"/>
<Yacht SourceID="005" Entry="Port"/>
<Yacht SourceID="006" Entry="Port"/>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<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="M1">
<Mark Name="M1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="WindwardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.284680" TargetLng="-64.850045" SourceID="104"/>
<Mark SeqID="2" Name="G2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="LeewardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
<Mark SeqID="2" Name="G2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="FinishLine">
<Mark SeqID="1" Name="PRO" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
<Mark SeqID="2" Name="PIN" TargetLat="32.317257" TargetLng="-64.836260" SourceID="109"/>
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="4" CompoundMarkID="4" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="32.313922" Lon="-64.837168"/>
<Limit SeqID="2" Lat="32.317379" Lon="-64.839291"/>
<Limit SeqID="3" Lat="32.317911" Lon="-64.836996"/>
<Limit SeqID="4" Lat="32.317257" Lon="-64.836260"/>
<Limit SeqID="5" Lat="32.304273" Lon="-64.822834"/>
<Limit SeqID="6" Lat="32.279097" Lon="-64.841545"/>
<Limit SeqID="7" Lat="32.279604" Lon="-64.849871"/>
<Limit SeqID="8" Lat="32.289545" Lon="-64.854162"/>
<Limit SeqID="9" Lat="32.290198" Lon="-64.858711"/>
<Limit SeqID="10" Lat="32.297164" Lon="-64.856394"/>
<Limit SeqID="11" Lat="32.296148" Lon="-64.849184"/>
</CourseLimit>
</Race>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RegattaConfig>
<RegattaID>1</RegattaID>
<RegattaName>Seng302 Mock Test</RegattaName>
<CourseName>Bermuda AC35</CourseName>
<CentralLatitude>-32.296577</CentralLatitude>
<CentralLongitude>64.854304</CentralLongitude>
<CentralAltitude>0.00</CentralAltitude>
<UtcOffset>-4</UtcOffset>
<MagneticVariation>-14.78</MagneticVariation>
</RegattaConfig>

@ -0,0 +1,269 @@
<race>
<raceId>5326</raceId>
<boats>
<boat>
<name>ORACLE TEAM USA</name>
<speed>20</speed>
<abbr>USA</abbr>
<colour>BLUEVIOLET</colour>
</boat>
<boat>
<name>Land Rover BAR</name>
<speed>30</speed>
<abbr>GBR</abbr>
<colour>BLACK</colour>
</boat>
<boat>
<name>SoftBank Team Japan</name>
<speed>25</speed>
<abbr>JPN</abbr>
<colour>RED</colour>
</boat>
<boat>
<name>Groupama Team France</name>
<speed>20</speed>
<abbr>FRA</abbr>
<colour>ORANGE</colour>
</boat>
<boat>
<name>Artemis Racing</name>
<speed>29</speed>
<abbr>SWE</abbr>
<colour>DARKOLIVEGREEN</colour>
</boat>
<boat>
<name>Emirates Team New Zealand</name>
<speed>62</speed>
<abbr>NZL</abbr>
<colour>LIMEGREEN</colour>
</boat>
</boats>
<legs>
<leg>
<name>Start to Mark 1</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</coordinate>
<coordinate>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Mark 1 to Leeward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Leeward Gate to Windward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Windward Gate to Leeward Gate</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
<leg>
<name>Leeward Gate to Finish</name>
<start>
<compoundMark>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
</start>
<finish>
<compoundMark>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
</compoundMark>
</finish>
</leg>
</legs>
<course>
<boundaries>
<coordinate>
<latitude>32.313922</latitude>
<longitude>-64.837168</longitude>
</coordinate>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317911</latitude>
<longitude>-64.836996</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
<coordinate>
<latitude>32.304273</latitude>
<longitude>-64.822834</longitude>
</coordinate>
<coordinate>
<latitude>32.279097</latitude>
<longitude>-64.841545</longitude>
</coordinate>
<coordinate>
<latitude>32.279604</latitude>
<longitude>-64.849871</longitude>
</coordinate>
<coordinate>
<latitude>32.289545</latitude>
<longitude>-64.854162</longitude>
</coordinate>
<coordinate>
<latitude>32.290198</latitude>
<longitude>-64.858711</longitude>
</coordinate>
<coordinate>
<latitude>32.297164</latitude>
<longitude>-64.856394</longitude>
</coordinate>
<coordinate>
<latitude>32.296148</latitude>
<longitude>-64.849184</longitude>
</coordinate>
</boundaries>
<compoundMark>
<name>Start Line</name>
<coordinate>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</coordinate>
<coordinate>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Mark</name>
<coordinate>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Windward Gate</name>
<coordinate>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</coordinate>
<coordinate>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Leeward Gate</name>
<coordinate>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</coordinate>
<coordinate>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</coordinate>
</compoundMark>
<compoundMark>
<name>Finish Line</name>
<coordinate>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</coordinate>
<coordinate>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</coordinate>
</compoundMark>
</course>
</race>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<StackPane fx:id="arrow" prefHeight="125.0" prefWidth="125.0">
<children>
<ImageView fitHeight="75.0" fitWidth="75.0">
<image>
<Image url="@../images/arrow.png" />
</image>
</ImageView>
</children>
</StackPane>
<Circle fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
<Label layoutX="55.0" layoutY="1.0" text="N">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Label layoutX="42.0" layoutY="99.0" text="Wind">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
</children>
</Pane>

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.ConnectionController">
<children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="182.0" minHeight="10.0" prefHeight="182.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="434.0" minHeight="10.0" prefHeight="434.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="174.0" minHeight="10.0" prefHeight="174.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="50.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="connectionTable" prefHeight="200.0" prefWidth="1080.0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<columns>
<TableColumn fx:id="hostnameColumn" prefWidth="453.99998474121094" text="Host" />
<TableColumn fx:id="statusColumn" prefWidth="205.0" text="Status" />
</columns>
<GridPane.margin>
<Insets left="50.0" right="50.0" />
</GridPane.margin>
</TableView>
<Button mnemonicParsing="false" onAction="#checkConnections" text="Refresh" GridPane.halignment="RIGHT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets right="20.0" />
</GridPane.margin>
</Button>
<Button fx:id="connectButton" mnemonicParsing="false" onAction="#connectSocket" text="Connect" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label text="Welcome to RaceVision" GridPane.columnSpan="2" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<GridPane GridPane.columnSpan="2" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="urlField" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="portField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button mnemonicParsing="false" onAction="#addConnection" text="Add New Connection" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Label text="Host Name:" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
<Label text="Port:" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="finishWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.FinishController">
<children>
<GridPane fx:id="start" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="372.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="394.0" minWidth="10.0" prefWidth="250.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="416.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="241.0" minHeight="10.0" prefHeight="116.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="383.0" minHeight="10.0" prefHeight="48.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="261.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="53.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="82.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="boatInfoTable" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<placeholder>
<Label text="Initial lineup..." />
</placeholder>
<columns>
<TableColumn fx:id="boatRankColumn" prefWidth="120.0" style="-fx-font-size: 16;" text="Ranking" />
<TableColumn fx:id="boatNameColumn" prefWidth="367.0" style="-fx-font-size: 16;" text="Team" />
</columns>
</TableView>
<Label fx:id="raceFinishLabel" alignment="CENTER" text="Race Finished" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<Label fx:id="raceWinnerLabel" alignment="CENTER" maxWidth="1.7976931348623157E308" text="Winner" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
</children>
</GridPane>
</children>
</AnchorPane>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="main" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.MainController">
<children>
<fx:include fx:id="race" source="race.fxml" />
<fx:include fx:id="start" source="start.fxml" />
<fx:include fx:id="connection" source="connect.fxml" />
<fx:include fx:id="finish" source="finish.fxml" />
</children>
</AnchorPane>

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="race" dividerPositions="0.7" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.RaceController">
<items>
<GridPane fx:id="canvasBase">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<fx:include fx:id="arrow" source="arrow.fxml" />
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<children>
<Accordion>
<panes>
<TitledPane animated="false" prefHeight="395.0" prefWidth="222.0" text="Annotation Control">
<content>
<AnchorPane fx:id="annotationPane" minHeight="0.0" minWidth="0.0">
<children>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false" selected="true" text="Show Boat Name" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false" selected="true" text="Show Boat Abbreviation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" />
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false" selected="true" text="Show Boat Speed" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" />
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true" text="Show Boat Paths" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="75.0" />
<CheckBox fx:id="showTime" mnemonicParsing="false" selected="true" text="Show Boat Leg Time" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="100.0" />
<CheckBox fx:id="showEstTime" mnemonicParsing="false" selected="true" text="Show Est. Time to Next Mark" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<Separator layoutX="19.6" layoutY="175.6" prefHeight="0.0" prefWidth="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="150.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="150.0" />
<RadioButton fx:id="hideAnnoRBtn" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="175.0">
<toggleGroup>
<ToggleGroup fx:id="annoToggleGroup" />
</toggleGroup></RadioButton>
<RadioButton fx:id="showAnnoRBtn" mnemonicParsing="false" text="Visible" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="200.0" />
<RadioButton fx:id="partialAnnoRBtn" mnemonicParsing="false" text="Partial" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="225.0" />
<RadioButton fx:id="importantAnnoRBtn" mnemonicParsing="false" text="Important" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="250.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="126.0" mnemonicParsing="false" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="275.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane animated="false" text="FPS Control">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</children>
</Pane>
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" maxHeight="20.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="20.0" />
</GridPane.margin>
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false" />
</children>
</GridPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="265.0" prefWidth="242.0" AnchorPane.bottomAnchor="164.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
<TableColumn fx:id="boatTeamColumn" prefWidth="100.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="130.0" text="Mark" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
</columns>
</TableView>
<AnchorPane layoutY="265.0" prefHeight="167.0" prefWidth="178.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<LineChart fx:id="sparklineChart" layoutX="-211.0" layoutY="-186.0" mouseTransparent="true" prefHeight="167.0" prefWidth="178.0" titleSide="LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
</children>
</AnchorPane>
</items>
</SplitPane>

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="startWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.Controllers.StartController">
<children>
<GridPane fx:id="start" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="372.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="394.0" minWidth="10.0" prefWidth="250.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="416.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" prefWidth="200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="241.0" minHeight="10.0" prefHeight="116.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="383.0" minHeight="10.0" prefHeight="48.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="261.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="369.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="53.5" vgrow="SOMETIMES" />
<RowConstraints maxHeight="191.5" minHeight="10.0" prefHeight="82.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="boatNameTable" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="2">
<placeholder>
<Label text="Initial lineup..." />
</placeholder>
<columns>
<TableColumn fx:id="boatNameColumn" prefWidth="360.0" style="-fx-font-size: 16;" text="Team Name" />
<TableColumn fx:id="boatCodeColumn" prefWidth="133.0" style="-fx-font-size: 16;" text="Code" />
</columns>
</TableView>
<Label fx:id="timeZoneTime" contentDisplay="CENTER" text="Local time..." GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
<Label fx:id="timer" text=" " GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="5" />
<Label fx:id="raceStartLabel" text="Starting time..." GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
<Label fx:id="raceTitleLabel" text="Welcome to RaceVision" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<Label fx:id="raceStatusLabel" alignment="CENTER" text="Race Status:" textAlignment="CENTER" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
</children>
</GridPane>
</children>
</AnchorPane>
Loading…
Cancel
Save