mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Merge branch 'wake_remake' into Story30b_correcting_boat_movement
# Conflicts: # src/main/java/seng302/App.java # src/main/java/seng302/controllers/CanvasController.java # src/main/java/seng302/models/BoatGroup.java # src/main/java/seng302/models/RaceObject.java # src/main/java/seng302/models/mark/MarkGroup.java # src/main/java/seng302/models/parsers/StreamParser.java
This commit is contained in:
@@ -16,4 +16,4 @@
|
|||||||
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
||||||
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
||||||
|
|
||||||
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz> <michael@michaelrausch.net>
|
||||||
@@ -20,6 +20,11 @@
|
|||||||
<version>4.12</version>
|
<version>4.12</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>1.3.2</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.googlecode.json-simple</groupId>
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
<artifactId>json-simple</artifactId>
|
<artifactId>json-simple</artifactId>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import javafx.scene.Scene;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import seng302.models.parsers.StreamParser;
|
import seng302.models.parsers.StreamParser;
|
||||||
import seng302.models.parsers.StreamReceiver;
|
import seng302.models.parsers.StreamReceiver;
|
||||||
|
import seng302.server.ServerThread;
|
||||||
|
|
||||||
public class App extends Application
|
public class App extends Application
|
||||||
{
|
{
|
||||||
@@ -22,16 +23,24 @@ public class App extends Application
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
StreamReceiver sr;
|
StreamReceiver sr;
|
||||||
|
|
||||||
|
new ServerThread("Racevision Test Server");
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
if (args.length > 1){
|
if (args.length > 1){
|
||||||
sr = new StreamReceiver("localhost", 8085, "TestThread1");
|
sr = new StreamReceiver("localhost", 8085, "RaceStream");
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
|
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"RaceStream");
|
||||||
//sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1");
|
// sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
||||||
|
// sr = new StreamReceiver("localhost", 8085, "RaceStream");
|
||||||
}
|
}
|
||||||
|
|
||||||
sr.start();
|
sr.start();
|
||||||
StreamParser streamParser = new StreamParser("TestThread2");
|
StreamParser streamParser = new StreamParser("StreamParser");
|
||||||
streamParser.start();
|
streamParser.start();
|
||||||
|
|
||||||
launch(args);
|
launch(args);
|
||||||
|
|||||||
@@ -17,10 +17,13 @@ import javafx.scene.layout.Pane;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import seng302.models.Boat;
|
import seng302.models.Boat;
|
||||||
import seng302.models.parsers.StreamParser;
|
import seng302.models.parsers.StreamParser;
|
||||||
|
import seng302.models.parsers.XMLParser;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.xml.crypto.dsig.XMLObject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@@ -66,10 +69,11 @@ public class Controller implements Initializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Running a timer to update the livestream status on welcome screen. Update interval is 500 miliseconds.
|
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
|
||||||
*/
|
*/
|
||||||
public void startStream() {
|
public void startStream() {
|
||||||
if (StreamParser.isStreamStatus()) {
|
if (StreamParser.isStreamStatus()) {
|
||||||
|
XMLParser xmlParser = StreamParser.getXmlObject();
|
||||||
streamButton.setVisible(false);
|
streamButton.setVisible(false);
|
||||||
timeTillLive.setVisible(true);
|
timeTillLive.setVisible(true);
|
||||||
timeTillLive.setTextFill(Color.GREEN);
|
timeTillLive.setTextFill(Color.GREEN);
|
||||||
@@ -83,26 +87,32 @@ public class Controller implements Initializable {
|
|||||||
timeTillLive.setTextFill(Color.RED);
|
timeTillLive.setTextFill(Color.RED);
|
||||||
timeTillLive.setText("Race finished! Waiting for new race...");
|
timeTillLive.setText("Race finished! Waiting for new race...");
|
||||||
switchToRaceViewButton.setDisable(true);
|
switchToRaceViewButton.setDisable(true);
|
||||||
} else if (StreamParser.getTimeSinceStart() > 0 && StreamParser.getTimeSinceStart() % 10 == 0) {
|
} else if (StreamParser.getTimeSinceStart() > 0) {
|
||||||
updateTeamList();
|
updateTeamList();
|
||||||
timeTillLive.setTextFill(Color.RED);
|
timeTillLive.setTextFill(Color.RED);
|
||||||
switchToRaceViewButton.setDisable(false);
|
switchToRaceViewButton.setDisable(false);
|
||||||
Long timerMinute = StreamParser.getTimeSinceStart() / 60;
|
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
Long timerSecond = StreamParser.getTimeSinceStart() % 60;
|
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
||||||
String timerString = "-" + timerMinute + "." + timerSecond + " minutes";
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
String timerString = "-" + timerMinute + ":" + timerSecond + " minutes";
|
||||||
timeTillLive.setText(timerString);
|
timeTillLive.setText(timerString);
|
||||||
} else if (StreamParser.getTimeSinceStart() % 10 == 0) {
|
} else {
|
||||||
updateTeamList();
|
updateTeamList();
|
||||||
timeTillLive.setTextFill(Color.BLACK);
|
timeTillLive.setTextFill(Color.BLACK);
|
||||||
switchToRaceViewButton.setDisable(false);
|
switchToRaceViewButton.setDisable(false);
|
||||||
Long timerMinute = -1 * StreamParser.getTimeSinceStart() / 60;
|
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
||||||
Long timerSecond = -1 * StreamParser.getTimeSinceStart() % 60;
|
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
||||||
String timerString = timerMinute + "." + timerSecond + " minutes";
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
String timerString = timerMinute + ":" + timerSecond + " minutes";
|
||||||
timeTillLive.setText(timerString);
|
timeTillLive.setText(timerString);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 0, 500);
|
}, 0, 1000);
|
||||||
} else {
|
} else {
|
||||||
timeTillLive.setText("Stream not available.");
|
timeTillLive.setText("Stream not available.");
|
||||||
timeTillLive.setTextFill(Color.RED);
|
timeTillLive.setTextFill(Color.RED);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import seng302.models.Boat;
|
|||||||
import seng302.models.Race;
|
import seng302.models.Race;
|
||||||
import seng302.models.parsers.ConfigParser;
|
import seng302.models.parsers.ConfigParser;
|
||||||
import seng302.models.parsers.CourseParser;
|
import seng302.models.parsers.CourseParser;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
import seng302.models.parsers.TeamsParser;
|
import seng302.models.parsers.TeamsParser;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
@@ -38,7 +39,7 @@ public class RaceController {
|
|||||||
|
|
||||||
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
|
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
|
||||||
Race race = new Race();
|
Race race = new Race();
|
||||||
|
// StreamParser.xmlObject
|
||||||
// Read team names from file
|
// Read team names from file
|
||||||
TeamsParser tp = new TeamsParser(teamsConfigFile);
|
TeamsParser tp = new TeamsParser(teamsConfigFile);
|
||||||
|
|
||||||
|
|||||||
@@ -293,14 +293,20 @@ public class RaceViewController extends Thread{
|
|||||||
|
|
||||||
private String currentTimer() {
|
private String currentTimer() {
|
||||||
String timerString = "0:00 minutes";
|
String timerString = "0:00 minutes";
|
||||||
if (StreamParser.getTimeSinceStart() > 0 && StreamParser.getTimeSinceStart() % 10 == 0) {
|
if (StreamParser.getTimeSinceStart() > 0) {
|
||||||
Long timerMinute = StreamParser.getTimeSinceStart() / 60;
|
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
Long timerSecond = StreamParser.getTimeSinceStart() % 60;
|
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
||||||
timerString = "-" + timerMinute + "." + timerSecond + " minutes";
|
if (timerSecond.length() == 1) {
|
||||||
} else if (StreamParser.getTimeSinceStart() % 10 == 0) {
|
timerSecond = "0" + timerSecond;
|
||||||
Long timerMinute = -1 * StreamParser.getTimeSinceStart() / 60;
|
}
|
||||||
Long timerSecond = -1 * StreamParser.getTimeSinceStart() % 60;
|
timerString = "-" + timerMinute + ":" + timerSecond + " minutes";
|
||||||
timerString = timerMinute + "." + timerSecond + " minutes";
|
} else {
|
||||||
|
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
timerString = timerMinute + ":" + timerSecond + " minutes";
|
||||||
}
|
}
|
||||||
return timerString;
|
return timerString;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,10 +179,10 @@ public class BoatGroup extends RaceObject{
|
|||||||
* @param rotation Rotation to move graphics to.
|
* @param rotation Rotation to move graphics to.
|
||||||
* @param raceIds RaceID of the object to move.
|
* @param raceIds RaceID of the object to move.
|
||||||
*/
|
*/
|
||||||
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) {
|
public void setDestination (double newXValue, double newYValue, double rotation, double speed, int... raceIds) {
|
||||||
if (hasRaceId(raceIds)) {
|
if (hasRaceId(raceIds)) {
|
||||||
destinationSet = true;
|
destinationSet = true;
|
||||||
boat.setVelocity(groundSpeed);
|
boat.setVelocity(speed);
|
||||||
if (currentRotation < 0)
|
if (currentRotation < 0)
|
||||||
currentRotation = 360 - currentRotation;
|
currentRotation = 360 - currentRotation;
|
||||||
double dx = newXValue - boatPoly.getLayoutX();
|
double dx = newXValue - boatPoly.getLayoutX();
|
||||||
@@ -226,22 +226,20 @@ public class BoatGroup extends RaceObject{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDestination (double newXValue, double newYValue, int... raceIDs) {
|
public void setDestination (double newXValue, double newYValue, double speed, int... raceIDs) {
|
||||||
|
destinationSet = true;
|
||||||
|
|
||||||
|
if (hasRaceId(raceIDs)) {
|
||||||
|
double rotation = Math.abs(
|
||||||
|
Math.toDegrees(
|
||||||
|
Math.atan(
|
||||||
|
(newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setDestination(newXValue, newYValue, rotation, speed, raceIDs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// public void setDestination (double newXValue, double newYValue, int... raceIDs) {
|
|
||||||
// destinationSet = true;
|
|
||||||
//
|
|
||||||
// if (hasRaceId(raceIDs)) {
|
|
||||||
// double rotation = Math.abs(
|
|
||||||
// Math.toDegrees(
|
|
||||||
// Math.atan(
|
|
||||||
// (newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// setDestination(newXValue, newYValue, rotation, raceIDs);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public void rotateTo (double rotation) {
|
public void rotateTo (double rotation) {
|
||||||
currentRotation = rotation;
|
currentRotation = rotation;
|
||||||
|
|||||||
@@ -59,10 +59,9 @@ public abstract class RaceObject extends Group {
|
|||||||
* @param x X co-ordinate to move the graphics to.
|
* @param x X co-ordinate to move the graphics to.
|
||||||
* @param y Y co-ordinate to move the graphics to.
|
* @param y Y co-ordinate to move the graphics to.
|
||||||
* @param rotation Rotation to move graphics to.
|
* @param rotation Rotation to move graphics to.
|
||||||
* @param groundSpeed boat groundspeed.
|
|
||||||
* @param raceIds RaceID of the object to move.
|
* @param raceIds RaceID of the object to move.
|
||||||
*/
|
*/
|
||||||
public abstract void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds);
|
public abstract void setDestination (double x, double y, double rotation, double speed, int... raceIds);
|
||||||
/**
|
/**
|
||||||
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
|
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
|
||||||
* set to the co-ordinates (x, y).
|
* set to the co-ordinates (x, y).
|
||||||
@@ -70,7 +69,7 @@ public abstract class RaceObject extends Group {
|
|||||||
* @param y Y co-ordinate to move the graphic to.
|
* @param y Y co-ordinate to move the graphic to.
|
||||||
* @param raceIds RaceID to the object to move.
|
* @param raceIds RaceID to the object to move.
|
||||||
*/
|
*/
|
||||||
public abstract void setDestination (double x, double y, int... raceIds);
|
public abstract void setDestination (double x, double y, double speed, int... raceIds);
|
||||||
|
|
||||||
public abstract void updatePosition (long timeInterval);
|
public abstract void updatePosition (long timeInterval);
|
||||||
|
|
||||||
|
|||||||
@@ -102,21 +102,21 @@ public class MarkGroup extends RaceObject {
|
|||||||
//moveTo(points[0].getX(), points[0].getY());
|
//moveTo(points[0].getX(), points[0].getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) {
|
public void setDestination (double x, double y, double rotation, double speed, int... raceIds) {
|
||||||
setDestination(x, y, raceIds);
|
setDestination(x, y, 0, raceIds);
|
||||||
this.rotationalGoal = rotation;
|
this.rotationalGoal = rotation;
|
||||||
calculateRotationalVelocity();
|
calculateRotationalVelocity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDestination (double x, double y, int... raceIds) {
|
public void setDestination (double x, double y, double speed, int... raceIds) {
|
||||||
for (int i = 0; i < marks.size(); i++)
|
for (int i = 0; i < marks.size(); i++)
|
||||||
for (int id : raceIds)
|
for (int id : raceIds)
|
||||||
if (id == marks.get(i).getId())
|
if (id == marks.get(i).getId())
|
||||||
setDestinationChild(x, y, Math.max(0, i-1));
|
setDestinationChild(x, y, 0, Math.max(0, i-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setDestinationChild (double x, double y, int childIndex) {
|
private void setDestinationChild (double x, double y, double speed, int childIndex) {
|
||||||
//double relativeX = x - super.getLayoutX();
|
//double relativeX = x - super.getLayoutX();
|
||||||
//double relativeY = y - super.getLayoutY();
|
//double relativeY = y - super.getLayoutY();
|
||||||
Circle markCircle = (Circle) super.getChildren().get(childIndex);
|
Circle markCircle = (Circle) super.getChildren().get(childIndex);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class StreamParser extends Thread{
|
|||||||
private String threadName;
|
private String threadName;
|
||||||
private Thread t;
|
private Thread t;
|
||||||
private static boolean raceStarted = false;
|
private static boolean raceStarted = false;
|
||||||
|
public static XMLParser xmlObject;
|
||||||
private static boolean raceFinished = false;
|
private static boolean raceFinished = false;
|
||||||
private static boolean streamStatus = false;
|
private static boolean streamStatus = false;
|
||||||
private static long timeSinceStart = -1;
|
private static long timeSinceStart = -1;
|
||||||
@@ -50,6 +51,7 @@ public class StreamParser extends Thread{
|
|||||||
try {
|
try {
|
||||||
System.out.println("START OF STREAM");
|
System.out.println("START OF STREAM");
|
||||||
streamStatus = true;
|
streamStatus = true;
|
||||||
|
xmlObject = new XMLParser();
|
||||||
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
|
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
}
|
}
|
||||||
@@ -151,15 +153,15 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractRaceStatus(StreamPacket packet){
|
private static void extractRaceStatus(StreamPacket packet){
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
long currentTime = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
|
long currentTime = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11));
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11));
|
||||||
int raceStatus = payload[11];
|
int raceStatus = payload[11];
|
||||||
// System.out.println("raceStatus = " + raceStatus);
|
// System.out.println("raceStatus = " + raceStatus);
|
||||||
long expectedStartTime = extractTimeStamp(Arrays.copyOfRange(payload,12,18), 6);
|
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
|
||||||
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
|
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
|
||||||
if (timeTillStart > 0 && timeTillStart % 10 == 0) {
|
if (timeTillStart > 0) {
|
||||||
timeSinceStart = timeTillStart;
|
timeSinceStart = timeTillStart;
|
||||||
System.out.println("Time till start: " + timeTillStart + " Seconds");
|
System.out.println("Time till start: " + timeTillStart + " Seconds");
|
||||||
} else {
|
} else {
|
||||||
@@ -172,10 +174,8 @@ public class StreamParser extends Thread{
|
|||||||
raceFinished = false;
|
raceFinished = false;
|
||||||
System.out.println("RACE HAS STARTED");
|
System.out.println("RACE HAS STARTED");
|
||||||
}
|
}
|
||||||
if (timeTillStart % 10 == 0){
|
System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
|
||||||
System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
|
timeSinceStart = timeTillStart;
|
||||||
timeSinceStart = timeTillStart;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
||||||
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
||||||
@@ -188,8 +188,8 @@ public class StreamParser extends Thread{
|
|||||||
boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
|
boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
|
||||||
boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
|
boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
|
||||||
boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
|
boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
|
||||||
boatStatus += "\nEstTimeAtNextMark: " + extractTimeStamp(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)), 6);
|
boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
|
||||||
boatStatus += "\nEstTimeAtFinish: " + extractTimeStamp(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)), 6);
|
boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
|
||||||
boatStatuses.add(boatStatus);
|
boatStatuses.add(boatStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,41 +219,24 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractXmlMessage(StreamPacket packet){
|
private static void extractXmlMessage(StreamPacket packet){
|
||||||
|
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
String xmlMessage = "";
|
|
||||||
|
|
||||||
ByteArrayInputStream payloadStream = new ByteArrayInputStream(payload);
|
int messageType = payload[9];
|
||||||
|
long messagelength = bytesToLong(Arrays.copyOfRange(payload,12,14));
|
||||||
//Bunch of data we don't want (Message Version Number, AckNumber, Timestamp)
|
String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messagelength)))).trim();
|
||||||
payloadStream.skip(9);
|
//System.out.println("xmlMessage2 = " + xmlMessage);
|
||||||
int xmlMessageSubType = payloadStream.read();
|
|
||||||
payloadStream.skip(2);
|
|
||||||
|
|
||||||
//checks the length of the xml message itself
|
|
||||||
int xmlMessageLength = payloadStream.read() | payloadStream.read() << 8;
|
|
||||||
|
|
||||||
//Converts XML message to string to be parsed
|
|
||||||
int currentChar;
|
|
||||||
while (payloadStream.available() > 0 && (currentChar = payloadStream.read()) != 0) {
|
|
||||||
xmlMessage += (char)currentChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse boat xml from server
|
|
||||||
if (xmlMessageSubType == 7) {
|
|
||||||
BoatsParser boatsParser = new BoatsParser(xmlMessage);
|
|
||||||
boats = boatsParser.getBoats();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create XML document Object
|
//Create XML document Object
|
||||||
// DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
// DocumentBuilder db = null;
|
DocumentBuilder db = null;
|
||||||
// try {
|
Document doc = null;
|
||||||
// db = dbf.newDocumentBuilder();
|
try {
|
||||||
// Document doc = db.parse(new InputSource(new StringReader(xmlMessage)));
|
db = dbf.newDocumentBuilder();
|
||||||
// // TODO: 25/04/17 ajm412: Check that the object matches expected structure and return Document object.
|
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
|
||||||
// } catch (ParserConfigurationException | IOException | SAXException e) {
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
// e.printStackTrace();
|
e.printStackTrace();
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
xmlObject.constructXML(doc, messageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,8 +247,8 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractRaceStartStatus(StreamPacket packet){
|
private static void extractRaceStartStatus(StreamPacket packet){
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
long raceStartTime = extractTimeStamp(Arrays.copyOfRange(payload,9,15), 6);
|
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload,9,15));
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload,15,19));
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,15,19));
|
||||||
int notificationType = payload[19];
|
int notificationType = payload[19];
|
||||||
}
|
}
|
||||||
@@ -278,7 +261,7 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractYachtEventCode(StreamPacket packet){
|
private static void extractYachtEventCode(StreamPacket packet){
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
||||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
||||||
long incidentId = bytesToLong(Arrays.copyOfRange(payload,17,21));
|
long incidentId = bytesToLong(Arrays.copyOfRange(payload,17,21));
|
||||||
@@ -359,7 +342,7 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractMarkRounding(StreamPacket packet){
|
private static void extractMarkRounding(StreamPacket packet){
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
||||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
||||||
int boatStatus = payload[17];
|
int boatStatus = payload[17];
|
||||||
@@ -376,7 +359,7 @@ public class StreamParser extends Thread{
|
|||||||
ArrayList<String> windInfo = new ArrayList<>();
|
ArrayList<String> windInfo = new ArrayList<>();
|
||||||
for (int i = 0; i < loopCount; i++){
|
for (int i = 0; i < loopCount; i++){
|
||||||
String wind = "WindId: " + payload[3 + (20 * i)];
|
String wind = "WindId: " + payload[3 + (20 * i)];
|
||||||
wind += "\nTime: " + extractTimeStamp(Arrays.copyOfRange(payload,4 + (20 * i),10 + (20 * i)), 6);
|
wind += "\nTime: " + bytesToLong(Arrays.copyOfRange(payload,4 + (20 * i),10 + (20 * i)));
|
||||||
wind += "\nRaceId: " + bytesToLong(Arrays.copyOfRange(payload,10 + (20 * i),14 + (20 * i)));
|
wind += "\nRaceId: " + bytesToLong(Arrays.copyOfRange(payload,10 + (20 * i),14 + (20 * i)));
|
||||||
wind += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i)));
|
wind += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i)));
|
||||||
wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i)));
|
wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i)));
|
||||||
@@ -390,7 +373,7 @@ public class StreamParser extends Thread{
|
|||||||
private static void extractAvgWind(StreamPacket packet){
|
private static void extractAvgWind(StreamPacket packet){
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6);
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload,7,9));
|
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload,7,9));
|
||||||
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload,9,11));
|
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload,9,11));
|
||||||
long period2 = bytesToLong(Arrays.copyOfRange(payload,11,13));
|
long period2 = bytesToLong(Arrays.copyOfRange(payload,11,13));
|
||||||
@@ -401,16 +384,6 @@ public class StreamParser extends Thread{
|
|||||||
long speed4 = bytesToLong(Arrays.copyOfRange(payload,21,23));
|
long speed4 = bytesToLong(Arrays.copyOfRange(payload,21,23));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long extractTimeStamp(byte[] timeStampBytes, int noOfBytes){
|
|
||||||
long timeStamp = 0;
|
|
||||||
long multiplier=1;
|
|
||||||
for(int i = 0;i < noOfBytes;i++) {
|
|
||||||
timeStamp += timeStampBytes[i]*multiplier;
|
|
||||||
multiplier *= 256;
|
|
||||||
}
|
|
||||||
return timeStamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takes an array of up to 7 bytes and returns a positive
|
* takes an array of up to 7 bytes and returns a positive
|
||||||
* long constructed from the input bytes
|
* long constructed from the input bytes
|
||||||
@@ -474,5 +447,9 @@ public class StreamParser extends Thread{
|
|||||||
public static List<Boat> getBoats() {
|
public static List<Boat> getBoats() {
|
||||||
return boats;
|
return boats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static XMLParser getXmlObject() {
|
||||||
|
return xmlObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,35 +9,51 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to create an XML object from the XML Packet Messages.
|
* Class to create an XML object from the XML Packet Messages.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* Document doc; // some xml document
|
||||||
|
* Integer xmlMessageType; // an Integer of value 5, 6, 7
|
||||||
|
*
|
||||||
|
* xmlP = new XMLParser(doc, xmlMessageType);
|
||||||
|
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
class XMLParser {
|
public class XMLParser {
|
||||||
|
|
||||||
/**
|
private Document xmlDoc;
|
||||||
* Creates a Regatta XML Object from the data in a Regatta XML Message
|
|
||||||
* @param doc XML Document Object
|
private RaceXMLObject raceXML;
|
||||||
* @return A new RegattaXMLObject from the input Document.
|
private RegattaXMLObject regattaXML;
|
||||||
*/
|
private BoatXMLObject boatXML;
|
||||||
RegattaXMLObject createRegattaXML(Document doc) {
|
|
||||||
return new RegattaXMLObject(doc);
|
public XMLParser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Race XML Object from the data in a Regatta XML Message
|
* Constructor for XMLParser
|
||||||
* @param doc XML Document Object
|
* @param doc Document to create XML object.
|
||||||
* @return A new RaceXMLObject from the input Document.
|
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
|
||||||
*/
|
*/
|
||||||
RaceXMLObject createRaceXML(Document doc) {
|
public void constructXML(Document doc, Integer messageType) {
|
||||||
return new RaceXMLObject(doc);
|
this.xmlDoc = doc;
|
||||||
|
switch (messageType) {
|
||||||
|
case 5:
|
||||||
|
regattaXML = new RegattaXMLObject(this.xmlDoc);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
raceXML = new RaceXMLObject(this.xmlDoc);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
boatXML = new BoatXMLObject(this.xmlDoc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public RaceXMLObject getRaceXML() { return raceXML; }
|
||||||
* Creates a Boat XML Object from the data in a Regatta XML Message
|
public RegattaXMLObject getRegattaXML() { return regattaXML; }
|
||||||
* @param doc XML Document Object
|
public BoatXMLObject getBoatXML() { return boatXML; }
|
||||||
* @return A new BoatXMLObject from the input Document.
|
|
||||||
*/
|
|
||||||
BoatXMLObject createBoatXML(Document doc) {
|
|
||||||
return new BoatXMLObject(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
||||||
@@ -129,7 +145,7 @@ class XMLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegattaXMLObject {
|
public class RegattaXMLObject {
|
||||||
//Regatta Info
|
//Regatta Info
|
||||||
private Integer regattaID;
|
private Integer regattaID;
|
||||||
private String regattaName;
|
private String regattaName;
|
||||||
@@ -163,7 +179,7 @@ class XMLParser {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RaceXMLObject {
|
public class RaceXMLObject {
|
||||||
|
|
||||||
// Race Info
|
// Race Info
|
||||||
private Integer raceID;
|
private Integer raceID;
|
||||||
@@ -266,7 +282,7 @@ class XMLParser {
|
|||||||
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
|
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
|
||||||
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
|
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
|
||||||
|
|
||||||
class Participant {
|
public class Participant {
|
||||||
Integer sourceID;
|
Integer sourceID;
|
||||||
String entry;
|
String entry;
|
||||||
|
|
||||||
@@ -279,7 +295,7 @@ class XMLParser {
|
|||||||
public String getEntry() { return entry; }
|
public String getEntry() { return entry; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompoundMark {
|
public class CompoundMark {
|
||||||
private Integer markID;
|
private Integer markID;
|
||||||
private String cMarkName;
|
private String cMarkName;
|
||||||
private ArrayList<Mark> marks;
|
private ArrayList<Mark> marks;
|
||||||
@@ -302,7 +318,7 @@ class XMLParser {
|
|||||||
public String getcMarkName() { return cMarkName; }
|
public String getcMarkName() { return cMarkName; }
|
||||||
public ArrayList<Mark> getMarks() { return marks; }
|
public ArrayList<Mark> getMarks() { return marks; }
|
||||||
|
|
||||||
class Mark {
|
public class Mark {
|
||||||
private Integer seqID;
|
private Integer seqID;
|
||||||
private Integer sourceID;
|
private Integer sourceID;
|
||||||
private String markName;
|
private String markName;
|
||||||
@@ -310,13 +326,11 @@ class XMLParser {
|
|||||||
private Double targetLng;
|
private Double targetLng;
|
||||||
|
|
||||||
Mark(Node markNode) {
|
Mark(Node markNode) {
|
||||||
|
|
||||||
this.seqID = getNodeAttributeInt(markNode, "SeqID");
|
this.seqID = getNodeAttributeInt(markNode, "SeqID");
|
||||||
this.sourceID = getNodeAttributeInt(markNode, "SourceID");
|
this.sourceID = getNodeAttributeInt(markNode, "SourceID");
|
||||||
this.markName = getNodeAttributeString(markNode, "Name");
|
this.markName = getNodeAttributeString(markNode, "Name");
|
||||||
this.targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
this.targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
||||||
this.targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
this.targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSeqID() { return seqID; }
|
public Integer getSeqID() { return seqID; }
|
||||||
@@ -327,7 +341,7 @@ class XMLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Corner {
|
public class Corner {
|
||||||
private Integer seqID;
|
private Integer seqID;
|
||||||
private Integer compoundMarkID;
|
private Integer compoundMarkID;
|
||||||
private String rounding;
|
private String rounding;
|
||||||
@@ -346,7 +360,7 @@ class XMLParser {
|
|||||||
public Integer getZoneSize() { return zoneSize; }
|
public Integer getZoneSize() { return zoneSize; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Limit {
|
public class Limit {
|
||||||
private Integer seqID;
|
private Integer seqID;
|
||||||
private Double lat;
|
private Double lat;
|
||||||
private Double lng;
|
private Double lng;
|
||||||
@@ -364,7 +378,7 @@ class XMLParser {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoatXMLObject {
|
public class BoatXMLObject {
|
||||||
|
|
||||||
private String lastModified;
|
private String lastModified;
|
||||||
private Integer version;
|
private Integer version;
|
||||||
@@ -429,7 +443,7 @@ class XMLParser {
|
|||||||
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
|
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
|
||||||
public ArrayList<Boat> getBoats() { return boats; }
|
public ArrayList<Boat> getBoats() { return boats; }
|
||||||
|
|
||||||
class Boat {
|
public class Boat {
|
||||||
|
|
||||||
private String boatType;
|
private String boatType;
|
||||||
private Integer sourceID;
|
private Integer sourceID;
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.Simulator;
|
||||||
|
import sun.misc.IOUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerThread implements Runnable, Observer {
|
||||||
|
private Thread runner;
|
||||||
|
private StreamingServerSocket server;
|
||||||
|
private long startTime;
|
||||||
|
boolean raceStarted = false;
|
||||||
|
Map<Integer,Boolean> boatsFinished = new HashMap<>();
|
||||||
|
private List<Boat> boats;
|
||||||
|
private Simulator raceSimulator;
|
||||||
|
|
||||||
|
private final int HEARTBEAT_PERIOD = 5000;
|
||||||
|
private final int RACE_STATUS_PERIOD = 1000;
|
||||||
|
private final int RACE_START_STATUS_PERIOD = 1000;
|
||||||
|
private final int BOAT_LOCATION_PERIOD = 1000/5;
|
||||||
|
private final int PORT_NUMBER = 8085;
|
||||||
|
private final int TIME_TILL_RACE_START = 20*1000;
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
|
||||||
|
public ServerThread(String threadName){
|
||||||
|
runner = new Thread(this, threadName);
|
||||||
|
serverLog("Spawning Server", 0);
|
||||||
|
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
||||||
|
boats = raceSimulator.getBoats();
|
||||||
|
|
||||||
|
for (Boat b : boats){
|
||||||
|
boatsFinished.put(b.getSourceID(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void serverLog(String message, int logLevel){
|
||||||
|
if(logLevel <= LOG_LEVEL){
|
||||||
|
System.out.println("[SERVER] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns an XML Message from the file specified
|
||||||
|
* @param fileName The source XML file
|
||||||
|
* @param type The XML Message type
|
||||||
|
* @return The XML Message
|
||||||
|
*/
|
||||||
|
public Message getXmlMessage(String fileName, XMLMessageSubType type){
|
||||||
|
String fileContents = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream thisStream = this.getClass().getResourceAsStream(fileName);
|
||||||
|
fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContents != null){
|
||||||
|
return new XMLMessage(fileContents, type, server.getSequenceNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Get a race status message for the current race
|
||||||
|
*/
|
||||||
|
public Message getRaceStatusMessage(){
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<BoatSubMessage>();
|
||||||
|
BoatStatus boatStatus;
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
boolean thereAreBoatsNotFinished = false;
|
||||||
|
|
||||||
|
for (Boat b : boats){
|
||||||
|
if (!raceStarted){
|
||||||
|
boatStatus = BoatStatus.PRESTART;
|
||||||
|
thereAreBoatsNotFinished = true;
|
||||||
|
}
|
||||||
|
else if(boatsFinished.get(b.getSourceID())){
|
||||||
|
boatStatus = BoatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
boatStatus = BoatStatus.PRESTART;
|
||||||
|
thereAreBoatsNotFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, 0, 0);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thereAreBoatsNotFinished){
|
||||||
|
if (raceStarted){
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long timeDifference = startTime - currentTime;
|
||||||
|
|
||||||
|
if (timeDifference > 60*3){
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
}
|
||||||
|
else if (timeDifference > 60){
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
raceStatus = RaceStatus.TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST,
|
||||||
|
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an instance of the race simulator
|
||||||
|
*/
|
||||||
|
private void startRaceSim(){
|
||||||
|
serverLog("Starting Race Simulator", 0);
|
||||||
|
raceSimulator.addObserver(this);
|
||||||
|
new Thread(raceSimulator).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts sending heartbeat messages to the client
|
||||||
|
*/
|
||||||
|
private void startSendingHeartbeats(){
|
||||||
|
serverLog("Sending Heartbeats", 0);
|
||||||
|
Timer t = new Timer();
|
||||||
|
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Message heartbeat = new Heartbeat(server.getSequenceNumber());
|
||||||
|
|
||||||
|
try {
|
||||||
|
server.send(heartbeat);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, HEARTBEAT_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending race start status messages until race starts
|
||||||
|
*/
|
||||||
|
private void startSendingRaceStartStatusMessages(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1,
|
||||||
|
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
try {
|
||||||
|
if (startTime < System.currentTimeMillis() && !raceStarted){
|
||||||
|
startRaceSim();
|
||||||
|
raceStarted = true;
|
||||||
|
serverLog("Race Started", 0);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
server.send(raceStartStatusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, RACE_START_STATUS_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending race start status messages until race starts
|
||||||
|
*/
|
||||||
|
private void startSendingRaceStatusMessages(){
|
||||||
|
serverLog("Sending race status messages", 0);
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Message raceStatusMessage = getRaceStatusMessage();
|
||||||
|
try {
|
||||||
|
server.send(raceStatusMessage);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, RACE_STATUS_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the race, boat, and regatta XML files to the client
|
||||||
|
*/
|
||||||
|
private void sendXml(){
|
||||||
|
try{
|
||||||
|
Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE);
|
||||||
|
Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT);
|
||||||
|
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
|
||||||
|
|
||||||
|
if (raceData != null){
|
||||||
|
server.send(raceData);
|
||||||
|
serverLog("Sending race data", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boatData != null){
|
||||||
|
server.send(boatData);
|
||||||
|
serverLog("Sending boat data", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regatta != null){
|
||||||
|
server.send(regatta);
|
||||||
|
serverLog("Sending regatta data", 0);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try{
|
||||||
|
server = new StreamingServerSocket(PORT_NUMBER);
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
serverLog("Failed to bind socket: " + e.getMessage(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client to connect
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
|
||||||
|
|
||||||
|
startSendingHeartbeats();
|
||||||
|
sendXml();
|
||||||
|
startSendingRaceStartStatusMessages();
|
||||||
|
startSendingRaceStatusMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a boat location message when they are updated by the simulator
|
||||||
|
* @param o .
|
||||||
|
* @param arg .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
// Only send if server started
|
||||||
|
if(!server.isStarted()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Boat b : ((Simulator) o).getBoats()){
|
||||||
|
try {
|
||||||
|
Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(),
|
||||||
|
b.getLng(), b.getHeadingCorner().getBearingToNextCorner(),
|
||||||
|
((long) b.getSpeed()));
|
||||||
|
server.send(m);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Couldn't send a boat status message", 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (NullPointerException e){
|
||||||
|
serverLog("Boat " + b.getSourceID() + " finished the race", 1);
|
||||||
|
boatsFinished.put(b.getSourceID(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import seng302.server.messages.Message;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class StreamingServerSocket {
|
||||||
|
private ServerSocketChannel socket;
|
||||||
|
private SocketChannel client;
|
||||||
|
private short seqNum;
|
||||||
|
private boolean isServerStarted;
|
||||||
|
|
||||||
|
StreamingServerSocket(int port) throws IOException{
|
||||||
|
socket = ServerSocketChannel.open();
|
||||||
|
socket.socket().bind(new InetSocketAddress("localhost", port));
|
||||||
|
//socket.setSoTimeout(10000);
|
||||||
|
seqNum = 0;
|
||||||
|
isServerStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(){
|
||||||
|
ServerThread.serverLog("Listening For Connections",0);
|
||||||
|
try {
|
||||||
|
client = socket.accept();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.getMessage();
|
||||||
|
}
|
||||||
|
if (client.socket() == null){
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
isServerStarted = true;
|
||||||
|
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(Message message) throws IOException{
|
||||||
|
if (client == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.send(client);
|
||||||
|
|
||||||
|
seqNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getSequenceNumber(){
|
||||||
|
return seqNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStarted(){
|
||||||
|
return isServerStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
|
||||||
|
public class BoatLocationMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 56;
|
||||||
|
|
||||||
|
private long messageVersionNumber;
|
||||||
|
private long time;
|
||||||
|
private long sourceId;
|
||||||
|
private long sequenceNum;
|
||||||
|
private DeviceType deviceType;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
private long altitude;
|
||||||
|
private Double heading;
|
||||||
|
private long pitch;
|
||||||
|
private long roll;
|
||||||
|
private long boatSpeed;
|
||||||
|
private long COG;
|
||||||
|
private long SOG;
|
||||||
|
private long apparentWindSpeed;
|
||||||
|
private long apparentWindAngle;
|
||||||
|
private long trueWindSpeed;
|
||||||
|
private long trueWindDirection;
|
||||||
|
private long trueWindAngle;
|
||||||
|
private long currentDrift;
|
||||||
|
private long currentSet;
|
||||||
|
private long rudderAngle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the location, altitude and sensor data from the boat.
|
||||||
|
* @param sourceId ID of the boat
|
||||||
|
* @param sequenceNum Sequence number of the message
|
||||||
|
* @param latitude The boats latitude
|
||||||
|
* @param longitude The boats longitude
|
||||||
|
* @param heading The boats heading
|
||||||
|
* @param boatSpeed The boats speed
|
||||||
|
*/
|
||||||
|
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||||
|
messageVersionNumber = 1;
|
||||||
|
time = System.currentTimeMillis() / 1000L;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.sequenceNum = sequenceNum;
|
||||||
|
this.deviceType = DeviceType.RACING_YACHT;
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.altitude = 0;
|
||||||
|
this.heading = heading;
|
||||||
|
this.pitch = 0;
|
||||||
|
this.roll = 0;
|
||||||
|
this.boatSpeed = boatSpeed;
|
||||||
|
this.COG = 0;
|
||||||
|
this.SOG = 0;
|
||||||
|
this.apparentWindSpeed = 0;
|
||||||
|
this.apparentWindAngle = 0;
|
||||||
|
this.trueWindSpeed = 0;
|
||||||
|
this.trueWindDirection = 0;
|
||||||
|
this.trueWindAngle = 0;
|
||||||
|
this.currentDrift = 0;
|
||||||
|
this.currentSet = 0;
|
||||||
|
this.rudderAngle = 0;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary latitude or longitude to floating point number
|
||||||
|
* @param binaryPackedLatLon Binary packed lat OR lon
|
||||||
|
* @return Floating point lat/lon
|
||||||
|
*/
|
||||||
|
public static double binaryPackedToLatLon(long binaryPackedLatLon){
|
||||||
|
return (double)binaryPackedLatLon * 180.0 / 2147483648.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary packed heading to floating point number
|
||||||
|
* @param binaryPackedHeading Binary packed heading
|
||||||
|
* @return heading as a decimal
|
||||||
|
*/
|
||||||
|
public static double binaryPackedHeadingToDouble(long binaryPackedHeading){
|
||||||
|
return (double)binaryPackedHeading * 360.0 / 65536.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary packed wind angle to floating point number
|
||||||
|
* @param binaryPackedWindAngle Binary packed wind angle
|
||||||
|
* @return wind angle as a decimal
|
||||||
|
*/
|
||||||
|
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){
|
||||||
|
return (double)binaryPackedWindAngle*180.0/32768.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a latitude or longitude to a binary packed long
|
||||||
|
* @param latLon A floating point latitude/longitude
|
||||||
|
* @return A binary packed lat/lon
|
||||||
|
*/
|
||||||
|
public static long latLonToBinaryPackedLong(double latLon){
|
||||||
|
return (long)((536870912 * latLon) / 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a heading to a binary packed long
|
||||||
|
* @param heading A floating point heading
|
||||||
|
* @return A binary packed heading
|
||||||
|
*/
|
||||||
|
public static long headingToBinaryPackedLong(double heading){
|
||||||
|
return (long)((8192*heading)/45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a wind angle to a binary packed long
|
||||||
|
* @param windAngle Floating point wind angle
|
||||||
|
* @return A binary packed wind angle
|
||||||
|
*/
|
||||||
|
public static long windAngleToBinaryPackedLong(double windAngle){
|
||||||
|
return (long)((8192*windAngle)/45);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(SocketChannel outputStream) throws IOException{
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
long headingToSend = (long)((heading/360.0) * 65535.0);
|
||||||
|
|
||||||
|
putByte((byte) messageVersionNumber);
|
||||||
|
putInt(time, 6);
|
||||||
|
putInt((int) sourceId, 4);
|
||||||
|
putUnsignedInt((int) sequenceNum, 4);
|
||||||
|
putByte((byte) deviceType.getCode());
|
||||||
|
putInt((int) latLonToBinaryPackedLong(latitude), 4);
|
||||||
|
putInt((int) latLonToBinaryPackedLong(longitude), 4);
|
||||||
|
putInt((int) altitude, 4);
|
||||||
|
putInt(headingToSend, 2);
|
||||||
|
putInt((int) pitch, 2);
|
||||||
|
putInt((int) roll, 2);
|
||||||
|
putUnsignedInt((int) boatSpeed, 2);
|
||||||
|
putUnsignedInt((int) COG, 2);
|
||||||
|
putUnsignedInt((int) SOG, 2);
|
||||||
|
putUnsignedInt((int) apparentWindSpeed, 2);
|
||||||
|
putInt((int) apparentWindAngle, 2);
|
||||||
|
putUnsignedInt((int) trueWindSpeed, 2);
|
||||||
|
putUnsignedInt((int) trueWindDirection, 2);
|
||||||
|
putInt((int) trueWindAngle, 2);
|
||||||
|
putUnsignedInt((int) currentDrift, 2);
|
||||||
|
putUnsignedInt((int) currentSet, 2);
|
||||||
|
putInt((int) rudderAngle, 2);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of a boat
|
||||||
|
*/
|
||||||
|
public enum BoatStatus {
|
||||||
|
UNDEFINED(0),
|
||||||
|
PRESTART(1),
|
||||||
|
RACING(2),
|
||||||
|
FINISHED(3),
|
||||||
|
DNS(4),
|
||||||
|
DNF(5),
|
||||||
|
DSQ(6),
|
||||||
|
CS(7);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
BoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of each boat, sent within a race status message
|
||||||
|
*/
|
||||||
|
public class BoatSubMessage{
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long sourceId;
|
||||||
|
private BoatStatus boatStatus;
|
||||||
|
private long legNumber;
|
||||||
|
private long numberPenaltiesAwarded;
|
||||||
|
private long numberPenaltiesServed;
|
||||||
|
private long estimatedTimeAtNextMark;
|
||||||
|
private long estimatedTimeAtFinish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boat Sub message from section 4.2 of the AC35 streaming data interface spec
|
||||||
|
* @param sourceId The source ID of the boat
|
||||||
|
* @param boatStatus The boats status
|
||||||
|
* @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc)
|
||||||
|
* @param numberPenaltiesAwarded The number of penalties awarded to the boat
|
||||||
|
* @param numberPenaltiesServed The number of penalties served to the boat
|
||||||
|
* @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race
|
||||||
|
* @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark
|
||||||
|
*/
|
||||||
|
public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed,
|
||||||
|
long estimatedTimeAtFinish, long estimatedTimeAtNextMark){
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
this.numberPenaltiesAwarded = numberPenaltiesAwarded;
|
||||||
|
this.numberPenaltiesServed = numberPenaltiesServed;
|
||||||
|
this.estimatedTimeAtFinish = estimatedTimeAtFinish;
|
||||||
|
this.estimatedTimeAtNextMark = estimatedTimeAtNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The size of this message in bytes
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing this boat status message
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
ByteBuffer buff = ByteBuffer.allocate(getSize());
|
||||||
|
int buffPos = 0;
|
||||||
|
|
||||||
|
// Source ID, 4 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(4).putInt((int) sourceId).array());
|
||||||
|
buffPos += 4;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Boat Status, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Leg number, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Number of penalties awarded, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Number of penalties served, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Estimated time at next mark, 6 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtNextMark).array());
|
||||||
|
buffPos += 6;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Estimated time at finish, 6 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtFinish).array());
|
||||||
|
buffPos += 6;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
public enum DeviceType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING_YACHT(1);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
DeviceType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class Header {
|
||||||
|
// From API spec
|
||||||
|
private final int syncByte1 = 0x47;
|
||||||
|
private final int syncByte2 = 0x83;
|
||||||
|
|
||||||
|
private MessageType messageType;
|
||||||
|
private int timeStamp;
|
||||||
|
private int sourceId;
|
||||||
|
private short messageLength;
|
||||||
|
private static final int MESSAGE_LEN = 15;
|
||||||
|
private ByteBuffer buff;
|
||||||
|
private int buffPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message Header from section 3.2 of the AC35 Streaming
|
||||||
|
* Data spec
|
||||||
|
* @param messageType The type of the message following this header
|
||||||
|
* @param sourceId The message source (as defined in the spec)
|
||||||
|
* @param messageLength The length of the message following this header
|
||||||
|
*/
|
||||||
|
public Header(MessageType messageType, int sourceId, Short messageLength){
|
||||||
|
this.messageType = messageType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
timeStamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
|
buff = ByteBuffer.allocate(MESSAGE_LEN);
|
||||||
|
buffPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putInBuffer(byte[] bytes, long val){
|
||||||
|
byte[] tmp = bytes.clone();
|
||||||
|
Message.reverse(tmp);
|
||||||
|
|
||||||
|
buff.put(tmp);
|
||||||
|
buffPos += tmp.length;
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing the message header
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode());
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(sourceId, 4), sourceId);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(messageLength, 2), messageLength);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of this message
|
||||||
|
* @return the size of the message
|
||||||
|
*/
|
||||||
|
public static Integer getSize(){
|
||||||
|
return MESSAGE_LEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class Heartbeat extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 4;
|
||||||
|
private int seqNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat from the AC35 Streaming data spec
|
||||||
|
* @param seqNo Increment every time a message is sent
|
||||||
|
*/
|
||||||
|
public Heartbeat(int seqNo){
|
||||||
|
this.seqNo = seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(SocketChannel outputStream) throws IOException {
|
||||||
|
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
||||||
|
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putUnsignedInt(seqNo, 4);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
|
||||||
|
public class MarkRoundingMessage extends Message{
|
||||||
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
|
private final int MESSAGE_SIZE = 21;
|
||||||
|
|
||||||
|
private long time;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceId;
|
||||||
|
private long sourceId;
|
||||||
|
private RoundingBoatStatus boatStatus;
|
||||||
|
private RoundingSide roundingSide;
|
||||||
|
private long markId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message is sent when a boat passes a mark, start line, or finish line
|
||||||
|
* The purpose of this is to record the time when yachts cross marks
|
||||||
|
*/
|
||||||
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
|
RoundingSide roundingSide, int markId){
|
||||||
|
this.time = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = roundingBoatStatus;
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
this.markId = markId;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(SocketChannel outputStream) throws IOException {
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
|
putInt((int) time, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putInt((int) sourceId, 4);
|
||||||
|
putByte((byte) boatStatus.getCode());
|
||||||
|
putByte((byte) roundingSide.getCode());
|
||||||
|
putByte((byte) markId);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of marks boats can round
|
||||||
|
*/
|
||||||
|
public enum MarkType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
ROUNDING_MARK(1),
|
||||||
|
GATE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
MarkType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public abstract class Message {
|
||||||
|
private final int CRC_SIZE = 4;
|
||||||
|
private Header header;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
private int bufferPosition;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param header Set the header for this message
|
||||||
|
*/
|
||||||
|
void setHeader(Header header){
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the header specified for this message
|
||||||
|
*/
|
||||||
|
Header getHeader(){
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of the message
|
||||||
|
*/
|
||||||
|
public abstract int getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the message as through the outputStream
|
||||||
|
*/
|
||||||
|
public abstract void send(SocketChannel outputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate byte buffer to correct size
|
||||||
|
*/
|
||||||
|
void allocateBuffer(){
|
||||||
|
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bufferPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the set header to the byte buffer
|
||||||
|
*/
|
||||||
|
void writeHeaderToBuffer(){
|
||||||
|
buffer.put(getHeader().getByteBuffer().array());
|
||||||
|
bufferPosition += Header.getSize();
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the buffer position by n bytes
|
||||||
|
* @param size Number of bytes to move the buffer by
|
||||||
|
*/
|
||||||
|
private void moveBufferPositionBy(int size){
|
||||||
|
bufferPosition += size;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an unsigned byte in the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an signed byte in the buffer
|
||||||
|
*/
|
||||||
|
void putByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put(b).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Place an unsigned integer of the specified length in the buffer
|
||||||
|
* @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers)
|
||||||
|
* @param size The size of the int to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedInt(long val, int size){
|
||||||
|
if (size <= 1){
|
||||||
|
putUnsignedByte((byte) val);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (size < 4){
|
||||||
|
// Use short
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array();
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Use int
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a signed int of a specified length in the buffer
|
||||||
|
* @param val The integer value to add
|
||||||
|
* @param size The size of the integer to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putInt(long val, int size){
|
||||||
|
if (size < 4){
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write an array of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
*/
|
||||||
|
void putBytes(byte[] bytes){
|
||||||
|
buffer.put(bytes);
|
||||||
|
moveBufferPositionBy(bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a ByteBuffer of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
* @param size number of bytes
|
||||||
|
*/
|
||||||
|
void putBytes(ByteBuffer bytes, int size){
|
||||||
|
buffer.put(bytes);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the CRC of the buffer and append it to the end of the buffer
|
||||||
|
*/
|
||||||
|
void writeCRC(){
|
||||||
|
crc = new CRC32();
|
||||||
|
|
||||||
|
buffer.position(0);
|
||||||
|
|
||||||
|
byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE);
|
||||||
|
crc.update(data);
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
|
||||||
|
putInt((int) crc.getValue(), CRC_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current buffer
|
||||||
|
*/
|
||||||
|
public ByteBuffer getBuffer(){
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the buffer to the beginning
|
||||||
|
*/
|
||||||
|
void rewind(){
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an integer to an array of bytes
|
||||||
|
* @param val The value to add
|
||||||
|
* @param len The width of the integer in the buffer
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] intToByteArray(long val, int len){
|
||||||
|
long vor = val;
|
||||||
|
int index = 0;
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++){
|
||||||
|
data[len - index - 1] = (byte) (val & 0xFF);
|
||||||
|
val >>>= 8;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse an array of bytes
|
||||||
|
* @param data The byte[] to reverse
|
||||||
|
*/
|
||||||
|
public static void reverse(byte[] data) {
|
||||||
|
for (int left = 0, right = data.length - 1; left < right; left++, right--) {
|
||||||
|
byte temp = (byte) (data[left] & 0xff);
|
||||||
|
data[left] = (byte) (data[right] & 0xff);
|
||||||
|
data[right] = (byte) (temp & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of messages
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum MessageType {
|
||||||
|
HEARTBEAT(1),
|
||||||
|
RACE_STATUS(12),
|
||||||
|
DISPLAY_TEXT_MESSAGE(20),
|
||||||
|
XML_MESSAGE(26),
|
||||||
|
RACE_START_STATUS(27),
|
||||||
|
YACHT_EVENT_CODE(29),
|
||||||
|
YACHT_ACTION_CODE(31),
|
||||||
|
CHATTER_TEXT(36),
|
||||||
|
BOAT_LOCATION(37),
|
||||||
|
MARK_ROUNDING(38),
|
||||||
|
COURSE_WIND(44),
|
||||||
|
AVERAGE_WIND(47);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
MessageType(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message code (From the API Spec)
|
||||||
|
* @return the message code
|
||||||
|
*/
|
||||||
|
int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types of race start status messages
|
||||||
|
*/
|
||||||
|
public enum RaceStartNotificationType {
|
||||||
|
SET_RACE_START_TIME(1),
|
||||||
|
RACE_POSTPONED(2),
|
||||||
|
RACE_ABANDONED(3),
|
||||||
|
RACE_TERMINATED(4);
|
||||||
|
|
||||||
|
private final long type;
|
||||||
|
|
||||||
|
RaceStartNotificationType(long type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getType(){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
|
||||||
|
public class RaceStartStatusMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long version;
|
||||||
|
private long timeStamp;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceStartTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStartNotificationType notificationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent to clients with the expected start time of the race
|
||||||
|
* @param ackNumber Sequence number of message.
|
||||||
|
* @param raceStartTime Expected race start time
|
||||||
|
* @param raceId Race ID#
|
||||||
|
* @param notificationType Type of this notification
|
||||||
|
*/
|
||||||
|
public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){
|
||||||
|
this.version = 1;
|
||||||
|
this.timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceStartTime = raceStartTime;
|
||||||
|
this.notificationType = notificationType;
|
||||||
|
this.raceId = raceId;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(SocketChannel outputStream) throws IOException {
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putUnsignedByte((byte) version);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceStartTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putUnsignedByte((byte) notificationType.getType());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of the race
|
||||||
|
*/
|
||||||
|
public enum RaceStatus {
|
||||||
|
NOTACTIVE(0),
|
||||||
|
WARNING(1), // Between 3:00 and 1:00 before start
|
||||||
|
PREPARATORY(2), // Less than 1:00 before start
|
||||||
|
STARTED(3),
|
||||||
|
ABANDONED(6),
|
||||||
|
POSTPONED(7),
|
||||||
|
TERMINATED(8),
|
||||||
|
RACE_START_TIME_NOT_SET(9),
|
||||||
|
PRESTART(10); // More than 3:00 before start
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
RaceStatus(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class RaceStatusMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
||||||
|
private final int MESSAGE_VERSION = 2; //Always set to 1
|
||||||
|
private final int MESSAGE_BASE_SIZE = 24;
|
||||||
|
|
||||||
|
private long currentTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStatus raceStatus;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private WindDirection raceWindDirection;
|
||||||
|
private long windSpeed;
|
||||||
|
private long numBoatsInRace;
|
||||||
|
private RaceType raceType;
|
||||||
|
private List<BoatSubMessage> boats;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message containing the current status of the race
|
||||||
|
* @param raceId The ID of the current race
|
||||||
|
* @param raceStatus The status of the race
|
||||||
|
* @param expectedStartTime The expected start time
|
||||||
|
* @param raceWindDirection The wind direction (north, east, south)
|
||||||
|
* @param windSpeed The wind speed in mm/sec
|
||||||
|
* @param numBoatsInRace The number of boats in the race
|
||||||
|
* @param raceType The race type (Match/fleet)
|
||||||
|
* @param sourceId The source of this message
|
||||||
|
* @param boats A list of boat status sub messages
|
||||||
|
*/
|
||||||
|
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
|
||||||
|
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.raceStatus = raceStatus;
|
||||||
|
this.expectedStartTime = expectedStartTime;
|
||||||
|
this.raceWindDirection = raceWindDirection;
|
||||||
|
this.windSpeed = windSpeed;
|
||||||
|
this.numBoatsInRace = numBoatsInRace;
|
||||||
|
this.raceType = raceType;
|
||||||
|
this.boats = boats;
|
||||||
|
crc = new CRC32();
|
||||||
|
|
||||||
|
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of this message in bytes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send this message as a stream of bytes
|
||||||
|
* @param outputStream The output stream to send the message
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void send(SocketChannel outputStream) throws IOException {
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt(currentTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putByte((byte) raceStatus.getCode());
|
||||||
|
putInt(expectedStartTime, 6);
|
||||||
|
putInt((int) raceWindDirection.getCode(), 2);
|
||||||
|
putInt((int) windSpeed, 2);
|
||||||
|
putByte((byte) numBoatsInRace);
|
||||||
|
putByte((byte) raceType.getCode());
|
||||||
|
|
||||||
|
for (BoatSubMessage boatSubMessage : boats){
|
||||||
|
putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of races
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum RaceType {
|
||||||
|
MATCH_RACE(1),
|
||||||
|
FLEET_RACE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RaceType(long code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of a boat rounding a mark
|
||||||
|
*/
|
||||||
|
public enum RoundingBoatStatus {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING(1),
|
||||||
|
DSQ(2),
|
||||||
|
WITHDRAWN(3);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingBoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The side the boat rounded the mark
|
||||||
|
*/
|
||||||
|
public enum RoundingSide {
|
||||||
|
UNKNOWN(0),
|
||||||
|
PORT(1),
|
||||||
|
STARBOARD(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingSide(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the supported wind directions
|
||||||
|
*/
|
||||||
|
public enum WindDirection {
|
||||||
|
NORTH(0x0000L),
|
||||||
|
EAST(0x4000L),
|
||||||
|
SOUTH(0x8000L);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
WindDirection(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class XMLMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||||
|
private final int MESSAGE_VERSION = 1; //Always set to 1
|
||||||
|
private final int MESSAGE_SIZE = 14;
|
||||||
|
|
||||||
|
// Message fields
|
||||||
|
private long timeStamp;
|
||||||
|
private long ack = 0x00; //Unused
|
||||||
|
private XMLMessageSubType xmlMessageSubType;
|
||||||
|
private long length;
|
||||||
|
private long sequence;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML Message from the AC35 Streaming data spec
|
||||||
|
* @param content The XML content
|
||||||
|
* @param type The XML Message Sub Type
|
||||||
|
*/
|
||||||
|
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||||
|
this.content = content;
|
||||||
|
this.xmlMessageSubType = type;
|
||||||
|
timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
ack = 0;
|
||||||
|
length = this.content.length();
|
||||||
|
sequence = sequenceNum;
|
||||||
|
|
||||||
|
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The length of this message
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE + content.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send this message as a stream of bytes
|
||||||
|
* @param outputStream The output stream to send the message
|
||||||
|
*/
|
||||||
|
public void send(SocketChannel outputStream) throws IOException {
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
// Write message fields
|
||||||
|
putUnsignedByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt((int) ack, 2);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putByte((byte)xmlMessageSubType.getType());
|
||||||
|
putInt((int) sequence, 2);
|
||||||
|
putInt((int) length, 2);
|
||||||
|
putBytes(content.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of XML messages
|
||||||
|
*/
|
||||||
|
public enum XMLMessageSubType {
|
||||||
|
REGATTA(5),
|
||||||
|
RACE(6),
|
||||||
|
BOAT(7);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
XMLMessageSubType(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType(){
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
public class Boat {
|
||||||
|
|
||||||
|
private int sourceID;
|
||||||
|
private double lat;
|
||||||
|
private double lng;
|
||||||
|
private double speed; // in mm/sec
|
||||||
|
private String boatName, shortName, shorterName;
|
||||||
|
|
||||||
|
private Corner lastPassedCorner, headingCorner;
|
||||||
|
|
||||||
|
public Boat(int sourceID, String boatName) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.boatName = boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves boat to the heading direction for a given time duration
|
||||||
|
* @param heading moving direction in degree.
|
||||||
|
* @param duration moving duration in millisecond.
|
||||||
|
*/
|
||||||
|
public void move(double heading, double duration) {
|
||||||
|
Double distance = speed * duration / 1000000; // convert mm to meter
|
||||||
|
Position originPos = new Position(lat, lng);
|
||||||
|
Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance);
|
||||||
|
this.lat = newPos.getLat();
|
||||||
|
this.lng = newPos.getLng();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeed(double speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatName(String boatName) {
|
||||||
|
this.boatName = boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShortName(String shortName) {
|
||||||
|
this.shortName = shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShorterName() {
|
||||||
|
return shorterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShorterName(String shorterName) {
|
||||||
|
this.shorterName = shorterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getLastPassedCorner() {
|
||||||
|
return lastPassedCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastPassedCorner(Corner lastPassedCorner) {
|
||||||
|
this.lastPassedCorner = lastPassedCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getHeadingCorner() {
|
||||||
|
return headingCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeadingCorner(Corner headingCorner) {
|
||||||
|
this.headingCorner = headingCorner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
public class GeoUtility {
|
||||||
|
|
||||||
|
private static double EARTH_RADIUS = 6378.137;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the euclidean distance between two markers on the canvas using xy coordinates
|
||||||
|
*
|
||||||
|
* @param p1 first geographical position
|
||||||
|
* @param p2 second geographical position
|
||||||
|
* @return the distance in meter between two points in meters
|
||||||
|
*/
|
||||||
|
public static Double getDistance(Position p1, Position p2) {
|
||||||
|
|
||||||
|
double dLat = Math.toRadians(p2.getLat() - p1.getLat());
|
||||||
|
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
|
||||||
|
|
||||||
|
double a = Math.pow(Math.sin(dLat / 2), 2.0)
|
||||||
|
+ Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat()))
|
||||||
|
* Math.pow(Math.sin(dLon / 2), 2.0);
|
||||||
|
|
||||||
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
double d = EARTH_RADIUS * c;
|
||||||
|
|
||||||
|
return d * 1000; // distance from km to meter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the angle between to angular co-ordinates on a sphere.
|
||||||
|
*
|
||||||
|
* @param p1 the first geographical position, start point
|
||||||
|
* @param p2 the second geographical position, end point
|
||||||
|
* @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.).
|
||||||
|
* vertical up is 0 deg. horizontal right is 90 deg.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* The final bearing will differ from the initial bearing by varying degrees
|
||||||
|
* according to distance and latitude (if you were to go from say 35°N,45°E
|
||||||
|
* (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60°
|
||||||
|
* and end up on a heading of 120°
|
||||||
|
*/
|
||||||
|
public static Double getBearing(Position p1, Position p2) {
|
||||||
|
|
||||||
|
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
|
||||||
|
|
||||||
|
double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat()));
|
||||||
|
double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat()))
|
||||||
|
- Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon);
|
||||||
|
|
||||||
|
double bearing = Math.toDegrees(Math.atan2(y, x));
|
||||||
|
|
||||||
|
return (bearing + 360.0) % 360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an existing point in lat/lng, distance in (in meter) and bearing
|
||||||
|
* (in degrees), calculates the new lat/lng.
|
||||||
|
*
|
||||||
|
* @param origin the original position within lat / lng
|
||||||
|
* @param bearing the bearing in degree, from original position to the new position
|
||||||
|
* @param distance the distance in meter, from original position to the new position
|
||||||
|
* @return the new position
|
||||||
|
*/
|
||||||
|
public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) {
|
||||||
|
double b = Math.toRadians(bearing); // bearing to radians
|
||||||
|
double d = distance / 1000.0; // distance to km
|
||||||
|
|
||||||
|
double originLat = Math.toRadians(origin.getLat());
|
||||||
|
double originLng = Math.toRadians(origin.getLng());
|
||||||
|
|
||||||
|
double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS)
|
||||||
|
+ Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b));
|
||||||
|
double endLng = originLng
|
||||||
|
+ Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat),
|
||||||
|
Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat));
|
||||||
|
|
||||||
|
return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Mark;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
import seng302.server.simulator.parsers.RaceParser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class Simulator extends Observable implements Runnable {
|
||||||
|
|
||||||
|
private List<Corner> course;
|
||||||
|
private List<Boat> boats;
|
||||||
|
private long lapse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a simulator instance with given time lapse.
|
||||||
|
* @param lapse time duration in millisecond.
|
||||||
|
*/
|
||||||
|
public Simulator(long lapse) {
|
||||||
|
RaceParser rp = new RaceParser("/server_config/race.xml");
|
||||||
|
course = rp.getCourse();
|
||||||
|
boats = rp.getBoats();
|
||||||
|
this.lapse = lapse;
|
||||||
|
|
||||||
|
setLegs();
|
||||||
|
|
||||||
|
// set start line's coordinate to boats
|
||||||
|
Double startLat = course.get(0).getCompoundMark().getMark1().getLat();
|
||||||
|
Double startLng = course.get(0).getCompoundMark().getMark1().getLng();
|
||||||
|
for (Boat boat : boats) {
|
||||||
|
boat.setLat(startLat);
|
||||||
|
boat.setLng(startLng);
|
||||||
|
boat.setLastPassedCorner(course.get(0));
|
||||||
|
boat.setHeadingCorner(course.get(1));
|
||||||
|
boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
int numOfFinishedBoats = 0;
|
||||||
|
|
||||||
|
while (numOfFinishedBoats < boats.size()) {
|
||||||
|
for (Boat boat : boats) {
|
||||||
|
numOfFinishedBoats += moveBoat(boat, lapse);
|
||||||
|
}
|
||||||
|
//System.out.println(boats.get(0));
|
||||||
|
|
||||||
|
setChanged();
|
||||||
|
notifyObservers(boats);
|
||||||
|
|
||||||
|
// sleep for 1 second.
|
||||||
|
try {
|
||||||
|
Thread.sleep(lapse);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a boat with given time duration.
|
||||||
|
*
|
||||||
|
* @param boat the boat to be moved
|
||||||
|
* @param duration the moving duration in milliseconds
|
||||||
|
* @return 1 if the boat has reached the final line, otherwise return 0
|
||||||
|
*/
|
||||||
|
private int moveBoat(Boat boat, double duration) {
|
||||||
|
if (boat.getHeadingCorner() != null) {
|
||||||
|
|
||||||
|
boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration);
|
||||||
|
|
||||||
|
Position boatPos = new Position(boat.getLat(), boat.getLng());
|
||||||
|
Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1();
|
||||||
|
|
||||||
|
double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos);
|
||||||
|
// if a boat passes its heading mark
|
||||||
|
while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) {
|
||||||
|
double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner();
|
||||||
|
boat.setLastPassedCorner(boat.getHeadingCorner());
|
||||||
|
boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner());
|
||||||
|
|
||||||
|
// heading corner == null means boat has reached the final mark
|
||||||
|
if (boat.getHeadingCorner() == null) return 1;
|
||||||
|
|
||||||
|
// move compensate distance for the mark just passed
|
||||||
|
Position pos = GeoUtility.getGeoCoordinate(
|
||||||
|
boat.getLastPassedCorner().getCompoundMark().getMark1(),
|
||||||
|
boat.getLastPassedCorner().getBearingToNextCorner(),
|
||||||
|
compensateDistance);
|
||||||
|
boat.setLat(pos.getLat());
|
||||||
|
boat.setLng(pos.getLng());
|
||||||
|
distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()),
|
||||||
|
boat.getLastPassedCorner().getCompoundMark().getMark1());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link all the corners in the course list so that every corner knows its next
|
||||||
|
* corner, as well as the distance and bearing to its next corner. However,
|
||||||
|
* the last corner's heading is null, which means it is the final line.
|
||||||
|
*/
|
||||||
|
private void setLegs() {
|
||||||
|
// get the bearing from one mark to the next heading mark
|
||||||
|
for (int i = 0; i < course.size() - 1; i++) {
|
||||||
|
|
||||||
|
Mark mark1 = course.get(i).getCompoundMark().getMark1();
|
||||||
|
Mark mark2 = course.get(i + 1).getCompoundMark().getMark1();
|
||||||
|
course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2));
|
||||||
|
|
||||||
|
course.get(i).setNextCorner(course.get(i + 1));
|
||||||
|
|
||||||
|
course.get(i).setBearingToNextCorner(
|
||||||
|
GeoUtility.getBearing(course.get(i).getCompoundMark().getMark1(),
|
||||||
|
course.get(i + 1).getCompoundMark().getMark1()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Boat> getBoats(){
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class CompoundMark {
|
||||||
|
|
||||||
|
private int markID;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Mark mark1;
|
||||||
|
private Mark mark2;
|
||||||
|
|
||||||
|
public CompoundMark(int markID, String name) {
|
||||||
|
this.markID = markID;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMark(int seqId, Mark mark) {
|
||||||
|
if (seqId == 1) {
|
||||||
|
setMark1(mark);
|
||||||
|
} else if (seqId == 2) {
|
||||||
|
setMark2(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out compoundMark's info and its marks, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
if (mark2 == null)
|
||||||
|
return String.format("CompoundMark: %d (%s), [%s]",
|
||||||
|
markID, name, mark1.toString());
|
||||||
|
return String.format("CompoundMark: %d (%s), [%s; %s]",
|
||||||
|
markID, name, mark1.toString(), mark2.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMarkID() {
|
||||||
|
return markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkID(int markID) {
|
||||||
|
this.markID = markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getMark1() {
|
||||||
|
return mark1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMark1(Mark mark1) {
|
||||||
|
this.mark1 = mark1;
|
||||||
|
mark1.setSeqID(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getMark2() {
|
||||||
|
return mark2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMark2(Mark mark2) {
|
||||||
|
this.mark2 = mark2;
|
||||||
|
mark2.setSeqID(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class Corner {
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private CompoundMark compoundMark;
|
||||||
|
//private int CompoundMarkID;
|
||||||
|
private RoundingType roundingType;
|
||||||
|
private int zoneSize; // size of the zone around a mark in boat-lengths.
|
||||||
|
|
||||||
|
// TODO: this shouldn't be used in the future!!!!
|
||||||
|
private double bearingToNextCorner, distanceToNextCorner;
|
||||||
|
private Corner nextCorner;
|
||||||
|
|
||||||
|
public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
this.compoundMark = compoundMark;
|
||||||
|
this.roundingType = roundingType;
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out corner's info and its compound mark, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Corner: %d - %s - %d, %s\n",
|
||||||
|
seqID, roundingType.getType(), zoneSize, compoundMark.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeqID(int seqID) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark getCompoundMark() {
|
||||||
|
return compoundMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompoundMark(CompoundMark compoundMark) {
|
||||||
|
this.compoundMark = compoundMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundingType getRoundingType() {
|
||||||
|
return roundingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingType(RoundingType roundingType) {
|
||||||
|
this.roundingType = roundingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoneSize() {
|
||||||
|
return zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoneSize(int zoneSize) {
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: next six setters & getters shouldn't be used in the future.
|
||||||
|
public double getBearingToNextCorner() {
|
||||||
|
return bearingToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBearingToNextCorner(double bearingToNextCorner) {
|
||||||
|
this.bearingToNextCorner = bearingToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDistanceToNextCorner() {
|
||||||
|
return distanceToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistanceToNextCorner(double distanceToNextCorner) {
|
||||||
|
this.distanceToNextCorner = distanceToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getNextCorner() {
|
||||||
|
return nextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextCorner(Corner nextCorner) {
|
||||||
|
this.nextCorner = nextCorner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public class Mark extends Position {
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private String name;
|
||||||
|
private int sourceID;
|
||||||
|
|
||||||
|
public Mark(String name, double lat, double lng, int sourceID) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.name = name;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out mark's info and its geo location, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeqID(int seqID) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class Position {
|
||||||
|
|
||||||
|
double lat, lng;
|
||||||
|
|
||||||
|
public Position(double lat, double lng) {
|
||||||
|
this.lat = lat;
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Position at lat:%f lng:%f.", lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public enum RoundingType {
|
||||||
|
|
||||||
|
// the mark should be rounded to port (boat's left)
|
||||||
|
PORT("Port"),
|
||||||
|
|
||||||
|
// the mark should be rounded to starboard (boat's right)
|
||||||
|
STARBOARD("Stbd"),
|
||||||
|
|
||||||
|
// the boat within the compound mark with the SeqID of 1 should be rounded
|
||||||
|
// to starboard and the boat within the compound mark with the SeqID of 2
|
||||||
|
// should be rounded to port.
|
||||||
|
SP("SP"),
|
||||||
|
|
||||||
|
// the opposite of SP
|
||||||
|
PS("PS");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
RoundingType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoundingType typeOf(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "Port":
|
||||||
|
return PORT;
|
||||||
|
case "Stbd":
|
||||||
|
return STARBOARD;
|
||||||
|
case "SP":
|
||||||
|
return SP;
|
||||||
|
case "PS":
|
||||||
|
return PS;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class BoatsParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
|
||||||
|
public BoatsParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.server.simulator.mark.CompoundMark;
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Mark;
|
||||||
|
import seng302.server.simulator.mark.RoundingType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class CourseParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private Map<Integer, CompoundMark> compoundMarksMap;
|
||||||
|
|
||||||
|
public CourseParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should handle error / invalid file gracefully
|
||||||
|
protected List<Corner> getCourse() {
|
||||||
|
compoundMarksMap = getCompoundMarks(doc.getDocumentElement());
|
||||||
|
List<Corner> corners = new ArrayList<>();
|
||||||
|
NodeList cMarksSequence = doc.getElementsByTagName("Corner");
|
||||||
|
|
||||||
|
for (int i = 0; i < cMarksSequence.getLength(); i++) {
|
||||||
|
corners.add(getCorner(cMarksSequence.item(i)));
|
||||||
|
}
|
||||||
|
return corners;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Corner getCorner(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
|
||||||
|
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
|
||||||
|
Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID"));
|
||||||
|
CompoundMark cMark = compoundMarksMap.get(cMarkId);
|
||||||
|
RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding"));
|
||||||
|
Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize"));
|
||||||
|
|
||||||
|
return new Corner(seqId, cMark, roundingType, zoneSize);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, CompoundMark> getCompoundMarks(Node node) {
|
||||||
|
Map<Integer, CompoundMark> compoundMarksMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
NodeList cMarks = element.getElementsByTagName("CompoundMark");
|
||||||
|
|
||||||
|
// loop through all compound marks who are the children of course node
|
||||||
|
for (int i = 0; i < cMarks.getLength(); i++) {
|
||||||
|
CompoundMark cMark = getCompoundMark(cMarks.item(i));
|
||||||
|
if (cMark != null)
|
||||||
|
compoundMarksMap.put(cMark.getMarkID(), cMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundMarksMap;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private CompoundMark getCompoundMark(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID"));
|
||||||
|
|
||||||
|
String name = e.getAttribute("Name");
|
||||||
|
CompoundMark cMark = new CompoundMark(markID, name);
|
||||||
|
|
||||||
|
NodeList marks = e.getElementsByTagName("Mark");
|
||||||
|
for (int i = 0; i < marks.getLength(); i++) {
|
||||||
|
Mark mark = getMark(marks.item(i));
|
||||||
|
if (mark != null)
|
||||||
|
cMark.addMark(mark.getSeqID(), mark);
|
||||||
|
}
|
||||||
|
return cMark;
|
||||||
|
}
|
||||||
|
System.out.println("Failed to create compound mark.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Mark getMark(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
|
||||||
|
String name = e.getAttribute("Name");
|
||||||
|
Double lat = Double.valueOf(e.getAttribute("TargetLat"));
|
||||||
|
Double lng = Double.valueOf(e.getAttribute("TargetLng"));
|
||||||
|
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
||||||
|
|
||||||
|
Mark mark = new Mark(name, lat, lng, sourceId);
|
||||||
|
mark.setSeqID(seqId);
|
||||||
|
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
System.out.println("Failed to create mark.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public abstract class FileParser {
|
||||||
|
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
public FileParser() {}
|
||||||
|
|
||||||
|
public FileParser(String path) {
|
||||||
|
this.filePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Document parseFile() {
|
||||||
|
try {
|
||||||
|
InputStream is = getClass().getResourceAsStream(this.filePath);
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(is);
|
||||||
|
// optional, in order to recover info from broken line.
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
return doc;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Document parseFile(String xmlString) {
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
|
||||||
|
// optional, in order to recover info from broken line.
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
return doc;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class RaceParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
public RaceParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.path = path;
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and returns a list of corner which is the race course.
|
||||||
|
* @return a list of ordered corner to represent the course.
|
||||||
|
*/
|
||||||
|
public List<Corner> getCourse() {
|
||||||
|
CourseParser cp = new CourseParser(path);
|
||||||
|
return cp.getCourse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and return a list of boats which will compete in the
|
||||||
|
* race.
|
||||||
|
* @return a list of boats that are going to compete in the race.
|
||||||
|
*/
|
||||||
|
public List<Boat> getBoats() {
|
||||||
|
NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht");
|
||||||
|
List<Boat> boats = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < yachts.getLength(); i++) {
|
||||||
|
boats.add(getBoat(yachts.item(i)));
|
||||||
|
}
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a single boat from the given node
|
||||||
|
* @param node a node within a boat tag
|
||||||
|
* @return a boat instance parsed from the given node
|
||||||
|
*/
|
||||||
|
private Boat getBoat(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
|
||||||
|
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
||||||
|
return new Boat(sourceId, "Test Boat");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
<course>
|
<markers>
|
||||||
<marks>
|
<marks>
|
||||||
<gate>
|
<gate>
|
||||||
<name type="start-line">Start</name>
|
<name type="start-line">Start</name>
|
||||||
@@ -77,4 +77,4 @@
|
|||||||
<five>Leeward Gate</five>
|
<five>Leeward Gate</five>
|
||||||
<six>Finish</six>
|
<six>Finish</six>
|
||||||
</order>
|
</order>
|
||||||
</course>
|
</markers>
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<BoatConfig>
|
||||||
|
<Modified>2015-08-28T17:32:59+0100</Modified>
|
||||||
|
<Version>12</Version>
|
||||||
|
<Snapshot>219</Snapshot>
|
||||||
|
<Settings>
|
||||||
|
<RaceBoatType Type="AC45"/>
|
||||||
|
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
|
||||||
|
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
|
||||||
|
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
|
||||||
|
</Settings>
|
||||||
|
<BoatShapes>
|
||||||
|
<BoatShape ShapeID="0">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="3" Y="25" X="0"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="14">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1"/>
|
||||||
|
<Vtx Seq="2" Y="0.75" X="-1"/>
|
||||||
|
<Vtx Seq="3" Y="0.75" X="-0.25"/>
|
||||||
|
<Vtx Seq="4" Y="3.5" X="-0.25"/>
|
||||||
|
<Vtx Seq="5" Y="4.5" X="-1"/>
|
||||||
|
<Vtx Seq="6" Y="6.5" X="-1"/>
|
||||||
|
<Vtx Seq="7" Y="7" X="-0.5"/>
|
||||||
|
<Vtx Seq="8" Y="7" X="0.5"/>
|
||||||
|
<Vtx Seq="9" Y="6.5" X="1"/>
|
||||||
|
<Vtx Seq="10" Y="4.5" X="1"/>
|
||||||
|
<Vtx Seq="11" Y="3.5" X="0.25"/>
|
||||||
|
<Vtx Seq="12" Y="0.75" X="0.25"/>
|
||||||
|
<Vtx Seq="13" Y="0.75" X="1"/>
|
||||||
|
<Vtx Seq="14" Y="0" X="1"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="15">
|
||||||
|
<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="18">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.04"/>
|
||||||
|
<Vtx Seq="2" Y="0.11" X="-1.18"/>
|
||||||
|
<Vtx Seq="3" Y="0.42" X="-1.28"/>
|
||||||
|
<Vtx Seq="4" Y="3.74" X="-1.29"/>
|
||||||
|
<Vtx Seq="5" Y="5.36" X="-1.21"/>
|
||||||
|
<Vtx Seq="6" Y="6.29" X="-1.08"/>
|
||||||
|
<Vtx Seq="7" Y="7.15" X="-0.84"/>
|
||||||
|
<Vtx Seq="8" Y="7.63" X="-0.62"/>
|
||||||
|
<Vtx Seq="9" Y="7.94" X="-0.34"/>
|
||||||
|
<Vtx Seq="10" Y="8.06" X="0"/>
|
||||||
|
<Vtx Seq="11" Y="7.94" X="0.34"/>
|
||||||
|
<Vtx Seq="12" Y="7.63" X="0.62"/>
|
||||||
|
<Vtx Seq="13" Y="7.15" X="0.84"/>
|
||||||
|
<Vtx Seq="14" Y="6.29" X="1.08"/>
|
||||||
|
<Vtx Seq="15" Y="5.36" X="1.21"/>
|
||||||
|
<Vtx Seq="16" Y="3.74" X="1.29"/>
|
||||||
|
<Vtx Seq="17" Y="0.42" X="1.28"/>
|
||||||
|
<Vtx Seq="18" Y="0.11" X="1.18"/>
|
||||||
|
<Vtx Seq="19" Y="0" X="1.04"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="24">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-2.5"/>
|
||||||
|
<Vtx Seq="2" Y="7" X="-2.5"/>
|
||||||
|
<Vtx Seq="3" Y="12.6" X="-2.2"/>
|
||||||
|
<Vtx Seq="4" Y="12.6" X="2.2"/>
|
||||||
|
<Vtx Seq="5" Y="7" X="2.5"/>
|
||||||
|
<Vtx Seq="6" Y="0" X="2.5"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="34">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.16"/>
|
||||||
|
<Vtx Seq="2" Y="5.51" X="-1.16"/>
|
||||||
|
<Vtx Seq="3" Y="5.846" X="-0.84"/>
|
||||||
|
<Vtx Seq="4" Y="5.846" X="0.84"/>
|
||||||
|
<Vtx Seq="5" Y="5.51" X="1.16"/>
|
||||||
|
<Vtx Seq="6" Y="0" X="1.16"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="35">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.461"/>
|
||||||
|
<Vtx Seq="2" Y="6" X="-1.461"/>
|
||||||
|
<Vtx Seq="3" Y="7" X="-1.44"/>
|
||||||
|
<Vtx Seq="4" Y="8" X="-1.38"/>
|
||||||
|
<Vtx Seq="5" Y="9" X="-1.17"/>
|
||||||
|
<Vtx Seq="6" Y="10" X="-0.76"/>
|
||||||
|
<Vtx Seq="7" Y="10.6" X="-0.34"/>
|
||||||
|
<Vtx Seq="8" Y="10.61" X="0"/>
|
||||||
|
<Vtx Seq="9" Y="10.6" X="0.34"/>
|
||||||
|
<Vtx Seq="10" Y="10" X="0.76"/>
|
||||||
|
<Vtx Seq="11" Y="9" X="1.17"/>
|
||||||
|
<Vtx Seq="12" Y="8" X="1.38"/>
|
||||||
|
<Vtx Seq="13" Y="7" X="1.44"/>
|
||||||
|
<Vtx Seq="14" Y="6" X="1.461"/>
|
||||||
|
<Vtx Seq="15" Y="0" X="1.461"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
</BoatShapes>
|
||||||
|
<Boats>
|
||||||
|
<Boat Type="Yacht" SourceID="101" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
|
||||||
|
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="102" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
|
||||||
|
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="103" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
|
||||||
|
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="104" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
|
||||||
|
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="105" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
|
||||||
|
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="106" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
|
||||||
|
<GPSposition Z="1.734" Y="-0.352" X="0"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
</Boats>
|
||||||
|
</BoatConfig>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Race>
|
||||||
|
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
|
||||||
|
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
|
||||||
|
<RaceID>15082901</RaceID>
|
||||||
|
<RaceType>Fleet</RaceType>
|
||||||
|
<Participants>
|
||||||
|
<Yacht SourceID="101" />
|
||||||
|
<Yacht SourceID="102" />
|
||||||
|
<Yacht SourceID="103" />
|
||||||
|
<Yacht SourceID="104" />
|
||||||
|
<Yacht SourceID="105" />
|
||||||
|
<Yacht SourceID="106" />
|
||||||
|
</Participants>
|
||||||
|
<Course>
|
||||||
|
<CompoundMark CompoundMarkID="1" Name="Mark0">
|
||||||
|
<Mark SeqID="1" Name="Start Line 1" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="122" />
|
||||||
|
<Mark SeqID="2" Name="Start Line 2" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="123" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="2" Name="Mark1">
|
||||||
|
<Mark SeqID="1" Name="Mark1" TargetLat="57.6675700" TargetLng="11.8359880" SourceID="131" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="3" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="4" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="5" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="6" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="7" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="8" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="9" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="10" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="11" Name="Mark4">
|
||||||
|
<Mark SeqID="1" Name="Finish Line 1" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="128" />
|
||||||
|
<Mark SeqID="2" Name="Finish Line 2" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="129" />
|
||||||
|
</CompoundMark>
|
||||||
|
</Course>
|
||||||
|
<CompoundMarkSequence>
|
||||||
|
<Corner SeqID="1" CompoundMarkID="1" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
|
||||||
|
<Corner SeqID="3" CompoundMarkID="3" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="6" CompoundMarkID="6" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="7" CompoundMarkID="7" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="8" CompoundMarkID="8" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="9" CompoundMarkID="9" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="10" CompoundMarkID="10" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="11" CompoundMarkID="11" Rounding="PS" ZoneSize="3" />
|
||||||
|
</CompoundMarkSequence>
|
||||||
|
<CourseLimit>
|
||||||
|
<Limit SeqID="1" Lat="57.6739450" Lon="11.8417100" />
|
||||||
|
<Limit SeqID="2" Lat="57.6709520" Lon="11.8485010" />
|
||||||
|
<Limit SeqID="3" Lat="57.6690260" Lon="11.8472790" />
|
||||||
|
<Limit SeqID="4" Lat="57.6693140" Lon="11.8457610" />
|
||||||
|
<Limit SeqID="5" Lat="57.6665370" Lon="11.8432910" />
|
||||||
|
<Limit SeqID="6" Lat="57.6641400" Lon="11.8385840" />
|
||||||
|
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
|
||||||
|
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
|
||||||
|
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
|
||||||
|
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" />
|
||||||
|
</CourseLimit>
|
||||||
|
</Race>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RegattaConfig>
|
||||||
|
<RegattaID>24</RegattaID>
|
||||||
|
<RegattaName>Gothenburg World Series 2015</RegattaName>
|
||||||
|
<CourseName>Gothenburg</CourseName>
|
||||||
|
<CentralLatitude>57.6679590</CentralLatitude>
|
||||||
|
<CentralLongitude>11.8503233</CentralLongitude>
|
||||||
|
<CentralAltitude>6.95</CentralAltitude>
|
||||||
|
<UtcOffset>2</UtcOffset>
|
||||||
|
<MagneticVariation>3.2</MagneticVariation>
|
||||||
|
<ShorelineName>gothenburg_shoreline</ShorelineName>
|
||||||
|
</RegattaConfig>
|
||||||
@@ -9,18 +9,18 @@
|
|||||||
|
|
||||||
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
|
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
|
||||||
<children>
|
<children>
|
||||||
<GridPane alignment="CENTER" prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
<GridPane nodeOrientation="LEFT_TO_RIGHT" prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
<RowConstraints maxHeight="403.0" minHeight="0.0" prefHeight="170.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="170.0" minHeight="170.0" prefHeight="170.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="444.0" minHeight="0.0" prefHeight="52.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="432.0" minHeight="2.0" prefHeight="102.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="102.0" minHeight="102.0" prefHeight="102.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="635.0" minHeight="0.0" prefHeight="60.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="60.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="635.0" minHeight="10.0" prefHeight="365.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="365.0" minHeight="365.0" prefHeight="365.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="635.0" minHeight="10.0" prefHeight="93.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="93.0" minHeight="93.0" prefHeight="93.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="599.0" minHeight="10.0" prefHeight="262.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="262.0" minHeight="262.0" prefHeight="262.0" vgrow="SOMETIMES" />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<Label alignment="CENTER" text="Welcome to Race Vision" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
<Label alignment="CENTER" text="Welcome to Race Vision" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="3" />
|
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="3" />
|
||||||
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="6" />
|
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="6" GridPane.valignment="TOP" />
|
||||||
<TableView fx:id="teamList" maxWidth="500.0" prefHeight="200.0" prefWidth="200.0" GridPane.halignment="CENTER" GridPane.rowIndex="4">
|
<TableView fx:id="teamList" maxWidth="500.0" prefHeight="200.0" prefWidth="200.0" GridPane.halignment="CENTER" GridPane.rowIndex="4">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn fx:id="boatNameCol" editable="false" prefWidth="250.0" sortable="false" text="Boat Name" />
|
<TableColumn fx:id="boatNameCol" editable="false" prefWidth="250.0" sortable="false" text="Boat Name" />
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
|
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
|
||||||
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
|
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
|
||||||
<children>
|
<children>
|
||||||
<Text fx:id="timerLabel" layoutX="6.0" layoutY="37.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="181.0">
|
<Text fx:id="timerLabel" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
|
||||||
<font>
|
<font>
|
||||||
<Font size="34.0" />
|
<Font size="25.0" />
|
||||||
</font>
|
</font>
|
||||||
</Text>
|
</Text>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.BoatLocationMessage;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test conversions used by the boat location messages
|
||||||
|
*/
|
||||||
|
public class TestConversions {
|
||||||
|
@Test
|
||||||
|
public void testLatLonConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.latLonToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedToLatLon(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWindAngleConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.windAngleToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedWindAngleToDouble(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeadingConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.headingToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedHeadingToDouble(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests message header
|
||||||
|
*/
|
||||||
|
public class TestHeader {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeaderSizeEqualsActualSize(){
|
||||||
|
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
|
||||||
|
assertTrue(h.getSize() == h.getByteBuffer().array().length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerSizeIsSameAsSpec(){
|
||||||
|
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
|
||||||
|
assertTrue(h.getSize() == 15); // Spec specifies 15 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
public class TestMessage {
|
||||||
|
private static int XML_MESSAGE_LEN = 14;
|
||||||
|
private static int CRC_LEN = 4;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test output expected is the same as the spec
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testXmlMessageSize(){
|
||||||
|
Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1);
|
||||||
|
assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessageBytesReverse(){
|
||||||
|
byte[] bytes = {1,2,3,4,5};
|
||||||
|
Message.reverse(bytes);
|
||||||
|
|
||||||
|
int testValue = 1;
|
||||||
|
for (int i = 5; i > 0; i--){
|
||||||
|
assertEquals((byte) testValue, bytes[i-1]);
|
||||||
|
testValue++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntToByteArray(){
|
||||||
|
long originalValue = 0x5050;
|
||||||
|
long testValue = 0;
|
||||||
|
|
||||||
|
byte[] bytes = Message.intToByteArray(originalValue, 6);
|
||||||
|
Message.reverse(bytes);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length; i++){
|
||||||
|
testValue += ((long) bytes[i] & 0xffL) << (8 * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(originalValue, testValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To test methods in GeoUtility.
|
||||||
|
* Created by Haoming on 28/04/17.
|
||||||
|
*/
|
||||||
|
public class GeoUtilityTest {
|
||||||
|
|
||||||
|
private Position p1 = new Position(57.670333, 11.827833);
|
||||||
|
private Position p2 = new Position(57.671524, 11.844495);
|
||||||
|
private Position p3 = new Position(57.670822, 11.843392);
|
||||||
|
private Position p4 = new Position(25.694829, 98.392049);
|
||||||
|
|
||||||
|
private double toleranceRate = 0.01;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDistance() throws Exception {
|
||||||
|
double expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p1, p2);
|
||||||
|
expected = 1000;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p1, p3);
|
||||||
|
expected = 927;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p2, p4);
|
||||||
|
expected = 7430180;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getBearing() throws Exception {
|
||||||
|
double expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p1, p2);
|
||||||
|
expected = 82;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p1, p3);
|
||||||
|
expected = 86;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p2, p4);
|
||||||
|
expected = 78;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getGeoCoordinate() throws Exception {
|
||||||
|
Position expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0);
|
||||||
|
expected = p2;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0);
|
||||||
|
expected = p3;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0);
|
||||||
|
expected = p4;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user