mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Compare commits
284 Commits
sprint_1.0
...
sprint_3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 24667991f1 | |||
| f2c4929656 | |||
| 4c038a823a | |||
| 3080c1bf27 | |||
| 59809c39ea | |||
| 9b063190ce | |||
| 49f2398669 | |||
| d7a4d20ceb | |||
| 0855e268c8 | |||
| 49b8d75aea | |||
| b1a9a7845b | |||
| b1e749bafb | |||
| f6fc6c0693 | |||
| ad1371bce0 | |||
| e77df0a5dd | |||
| 6935bd514e | |||
| a4cc5f222c | |||
| a0bb7b85b4 | |||
| fd8ed92f88 | |||
| c7b6261602 | |||
| 3aefb14faf | |||
| 8521b68855 | |||
| e37b244f45 | |||
| 7f40fb6283 | |||
| 83316f7a17 | |||
| a56e55ae70 | |||
| 07234ee33a | |||
| 145d59df45 | |||
| 7e8c3af9ce | |||
| aa6ef72670 | |||
| ed8781b382 | |||
| 7a5f4e8f8c | |||
| d992422efd | |||
| f0d6312fa5 | |||
| 772ece25a0 | |||
| d063a41ad4 | |||
| 6d02f05f05 | |||
| b3a89279d6 | |||
| 4432ba26e5 | |||
| fe824a8f71 | |||
| 8233b75e05 | |||
| 3af15b2b95 | |||
| a4dfcca302 | |||
| 04ce6f6103 | |||
| 178af141f0 | |||
| c5c2d4375d | |||
| 3aa183042f | |||
| 944755fde1 | |||
| cd78c35bf6 | |||
| f8d003002b | |||
| 56fab768f3 | |||
| c92744f21c | |||
| 1f71fd1967 | |||
| 33ae7beeb4 | |||
| 130efa3a51 | |||
| 7df55fc1a3 | |||
| 3adadcc1e1 | |||
| 5b027a29d8 | |||
| 2a9d0fb82c | |||
| ee6a543f8d | |||
| d927531354 | |||
| 1d47df09eb | |||
| a9709c4f84 | |||
| a06806c42d | |||
| 57de058582 | |||
| 978493853d | |||
| a5ca9218da | |||
| 5fe330bfbb | |||
| c80cff87f7 | |||
| 9a864cc2bd | |||
| c07f13180f | |||
| f672eafd6d | |||
| 6a361c0d4b | |||
| b597f010dc | |||
| a670f677e9 | |||
| a77423b937 | |||
| 50083a9297 | |||
| d5aa430d4a | |||
| e7f9954970 | |||
| ec57851de2 | |||
| 0eb767b615 | |||
| 45b77c05d4 | |||
| b9900925b8 | |||
| d94290c58d | |||
| f50aabff7b | |||
| d07c660eb9 | |||
| 6491efec4c | |||
| 25038da2a1 | |||
| 85f461c88c | |||
| 1cf55f3e96 | |||
| 9a995ddcc1 | |||
| 0b2ef3de00 | |||
| 99e50aa7ac | |||
| a898290c0b | |||
| 6cbff1097b | |||
| 246083460e | |||
| 1d28334346 | |||
| 3e97f016d5 | |||
| ab0d4634d6 | |||
| 1e1e482b79 | |||
| a0624cfef6 | |||
| 02a35b4c02 | |||
| 80409c08a6 | |||
| 6149f7be60 | |||
| 474f0ee427 | |||
| f3ee618900 | |||
| b939086e10 | |||
| ffdfc24e65 | |||
| 07bbd7e06d | |||
| 765f27f987 | |||
| d204bee55d | |||
| 0f4ad48de0 | |||
| fe480d5cb6 | |||
| 8a04a0e5b7 | |||
| 705a0a2eaf | |||
| d1289b0de1 | |||
| e1de5e0989 | |||
| f5b9160304 | |||
| 0a22812165 | |||
| 104fd86179 | |||
| 67a702ffcd | |||
| 65c0e6f77d | |||
| 245bd184b4 | |||
| 8c8f253233 | |||
| 8b8422de3a | |||
| 7bf2d4c40e | |||
| 2a67f04d15 | |||
| b2ea8196d5 | |||
| 7f38191d03 | |||
| f6b7a3042f | |||
| 3bdc6ce5cc | |||
| bc31987f96 | |||
| eaff4c5aac | |||
| 95bafdc0d1 | |||
| c776d22941 | |||
| 749c6b7fef | |||
| 912c081606 | |||
| c73bf7dd3e | |||
| a3ae015be8 | |||
| 1f8f1f0f86 | |||
| 5eebab2748 | |||
| 8cbd1cc4aa | |||
| 00c1a89f58 | |||
| d51825ffb7 | |||
| 42569e6ad7 | |||
| ef874b4245 | |||
| 46037b5aea | |||
| 6874f288ee | |||
| b6fd90e9d7 | |||
| f078c34bf9 | |||
| c1e4a6156c | |||
| 71e14259f6 | |||
| 403dc480c4 | |||
| 3dc1a7f9c0 | |||
| 672194adb4 | |||
| fdb84b6675 | |||
| ba352183bf | |||
| dd480080c9 | |||
| 4047978ea2 | |||
| a649b11bbf | |||
| 247560ee43 | |||
| 50e7ece477 | |||
| b5129c5c80 | |||
| 6a27dedd74 | |||
| edc306da22 | |||
| 15ded667fe | |||
| 34872a822b | |||
| 9817fc9093 | |||
| dde4b2fcba | |||
| 623600a8a9 | |||
| bff4986242 | |||
| c689530068 | |||
| 7022be1979 | |||
| 971a3920a3 | |||
| b252797e9b | |||
| c758afe3e3 | |||
| f8d3f53158 | |||
| 4a8672a20b | |||
| 32109e8565 | |||
| 5d6060c690 | |||
| a95d030817 | |||
| ffa84c6e87 | |||
| 4a6978ff79 | |||
| 1497858cc0 | |||
| 2c125d4ce0 | |||
| 8bf5455f42 | |||
| 48f7e41905 | |||
| b50ac62a4b | |||
| 6fc55bb82c | |||
| 65ac864bf2 | |||
| cf6bbdd1f1 | |||
| bbe7cbee8f | |||
| 55ba343426 | |||
| e6ace5fb2f | |||
| 550ab59231 | |||
| 213303d674 | |||
| 74c81eb7b3 | |||
| c33586e7f5 | |||
| 5dd5e50738 | |||
| 304f30ece6 | |||
| e8b1720fee | |||
| e0cea098c1 | |||
| 50ab481b18 | |||
| d39aacba83 | |||
| 798fe4da0e | |||
| e19f110f19 | |||
| 59a4a74a97 | |||
| 590ef557d3 | |||
| 7bc5c8f8a4 | |||
| bf8244ce49 | |||
| a860cc0ec1 | |||
| bb8c681270 | |||
| 9872095e50 | |||
| 403aaa76ae | |||
| 8578bc4a5b | |||
| ed004d1423 | |||
| a91b2b4f7e | |||
| 24f9607e5a | |||
| 2384013139 | |||
| 42ffd1b1f8 | |||
| a2d06909c9 | |||
| c8b7b95df8 | |||
| 9e22eac4d8 | |||
| a41f2e4bde | |||
| 039e61def6 | |||
| 54d329c5cf | |||
| f46a98fad9 | |||
| 6a6816dda1 | |||
| f6ea2953e9 | |||
| 7591a79323 | |||
| c12760b70a | |||
| a526b0d65a | |||
| ef098e63d7 | |||
| 29a0b23670 | |||
| 44b5b0b771 | |||
| 00f9cc4698 | |||
| 3d4d5a3dab | |||
| ed577fad6a | |||
| ae61260665 | |||
| 0e4bb0f942 | |||
| ee34e5028f | |||
| 3b8dd11758 | |||
| cc04e2dd6d | |||
| b88cf6a101 | |||
| d10985f890 | |||
| c08504293b | |||
| 4bc49da10d | |||
| 683f4ba94e | |||
| 8fd06c84ac | |||
| 23b163e6c1 | |||
| 6383b9a6f8 | |||
| 0b3ebf229f | |||
| d6fe155d4d | |||
| 8829728f19 | |||
| a6c9156ae5 | |||
| 121f996a43 | |||
| 44d4f25413 | |||
| 16abfcffda | |||
| e7ba9d962d | |||
| b7631c0b46 | |||
| 0039703f03 | |||
| d439c9673f | |||
| f255b0ea6d | |||
| 9d01ed8eb3 | |||
| 94e4e853c3 | |||
| ffa39e6a9c | |||
| 9e4ae60885 | |||
| 2cd4366d07 | |||
| 11c5e1e9ba | |||
| 550812d8e1 | |||
| 9ca5f5e7fd | |||
| abc5df7837 | |||
| debe2c0cca | |||
| cfa851b968 | |||
| 37f4b55b04 | |||
| c1aa38c1b0 | |||
| 260bf06219 | |||
| 8d85557e10 | |||
| d33a88d313 | |||
| d3b71c21e5 | |||
| d10c6a54f5 | |||
| 0a86dde7e4 | |||
| ae80b434f6 | |||
| b0cd7c8c08 |
@@ -84,6 +84,9 @@ nbactions.xml
|
|||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
# User-specific stuff:
|
# User-specific stuff:
|
||||||
|
# get rid of these annoying files
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/tasks.xml
|
.idea/tasks.xml
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
|
|||||||
@@ -16,4 +16,10 @@
|
|||||||
# 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>
|
||||||
|
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
||||||
|
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
||||||
|
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
||||||
|
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,16 @@
|
|||||||
# Design Decisions
|
# Design Decisions
|
||||||
|
- Code structure
|
||||||
|
|
||||||
|
App creates a race instance which can:
|
||||||
|
instantiate a file parser to extract race setting and team information;
|
||||||
|
creates and passes legs and teams/boats into event generator to create events;
|
||||||
|
runs a race and iterates all events that returned from the generator;
|
||||||
|
prints out event details, including time, involved boats and legs.
|
||||||
|
|
||||||
|
- Configuration file
|
||||||
|
|
||||||
|
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file.
|
||||||
|
|
||||||
|
To read external files, "Json-simple" library has been used to parse information.
|
||||||
|
By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"race-name": "AC35",
|
|
||||||
"time-scale": 1.0,
|
|
||||||
"race-size": 4,
|
|
||||||
"teams": [
|
|
||||||
{"team-name": "Oracle Team USA", "velocity": 20.9},
|
|
||||||
{"team-name": "Artemis Racing", "velocity": 18.3},
|
|
||||||
{"team-name": "Emirates Team New Zealand", "velocity": 21.5},
|
|
||||||
{"team-name": "Groupama Team France","velocity": 19.9},
|
|
||||||
{"team-name": "Land Rover BAR", "velocity": 17.6},
|
|
||||||
{"team-name": "SoftBank Team Japan", "velocity": 16.6}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +1,15 @@
|
|||||||
# User Manual
|
# User Manual
|
||||||
|
|
||||||
|
## Running the application
|
||||||
|
|
||||||
|
When you execute the application, it will try to load a configuration file called config.json located in doc/examples/.
|
||||||
|
|
||||||
|
You can specify a config file using the using the -f flag, for example 'java -jar app.jar -f doc/examples/config1.json'
|
||||||
|
|
||||||
|
## The config file
|
||||||
|
|
||||||
|
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second).
|
||||||
|
|
||||||
|
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
|
||||||
|
|
||||||
|
The 'race-size' option lets you specify how many boats will be selected to compete in each race. There must be at least this many teams defined.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>seng302</groupId>
|
<groupId>seng302</groupId>
|
||||||
@@ -20,11 +20,22 @@
|
|||||||
<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>
|
||||||
<version>1.1.1</version>
|
<version>1.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>2.7.13</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -40,7 +51,8 @@
|
|||||||
<version>2.4.3</version>
|
<version>2.4.3</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
<Main-Class>seng302.App</Main-Class>
|
<Main-Class>seng302.App</Main-Class>
|
||||||
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
|
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
|
||||||
|
|||||||
@@ -1,87 +1,79 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
import java.util.*;
|
import javafx.application.Application;
|
||||||
import java.lang.reflect.Array;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
|
import seng302.models.parsers.StreamReceiver;
|
||||||
|
import seng302.server.ServerThread;
|
||||||
|
|
||||||
public class App
|
public class App extends Application
|
||||||
{
|
{
|
||||||
/**
|
@Override
|
||||||
* Builds a race object for the AC35 course
|
public void start(Stage primaryStage) throws Exception {
|
||||||
*/
|
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
||||||
public static Race createRace() throws Exception{
|
primaryStage.setTitle("RaceVision");
|
||||||
Race race = new Race();
|
primaryStage.setScene(new Scene(root));
|
||||||
|
primaryStage.setMaximized(true);
|
||||||
|
|
||||||
// Read team names from file
|
primaryStage.show();
|
||||||
FileParser fp = new FileParser("doc/examples/config.json");
|
primaryStage.setOnCloseRequest(e -> {
|
||||||
ArrayList<String> boatNames = new ArrayList<>();
|
StreamParser.appClose();
|
||||||
ArrayList<Map<String, Object>> teams = fp.getTeams();
|
StreamReceiver.noMoreBytes();
|
||||||
|
System.out.println("[CLIENT] Exiting program");
|
||||||
|
System.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
//get race size
|
}
|
||||||
int numberOfBoats = (int) fp.getRaceSize();
|
|
||||||
|
|
||||||
//get time scale
|
public static void main(String[] args) {
|
||||||
double timeScale = fp.getTimeScale();
|
StreamReceiver sr = null;
|
||||||
race.setTimeScale(timeScale);
|
|
||||||
|
|
||||||
for (Map<String, Object> team : teams) {
|
new ServerThread("Racevision Test Server");
|
||||||
boatNames.add((String) team.get("team-name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle team names
|
try {
|
||||||
long seed = System.nanoTime();
|
Thread.sleep(2000);
|
||||||
Collections.shuffle(boatNames, new Random(seed));
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
if (numberOfBoats > Array.getLength(boatNames.toArray())){
|
if (args.length == 1 && args[0].equals("-standalone")){
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < numberOfBoats; i++) {
|
if (args.length == 3 && args[0].equals("-server")){
|
||||||
race.addBoat(new Boat(boatNames.get(i), (Double)(teams.get(i).get("velocity"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
race.addLeg(new Leg(35, 100, "Start"));
|
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
|
||||||
race.addLeg(new Leg(10, 300, "Marker 1"));
|
|
||||||
race.addLeg(new Leg(350, 400, "Leeward Gate"));
|
|
||||||
race.addLeg(new Leg(10, 400, "Windward Gate"));
|
|
||||||
|
|
||||||
Leg finishingLeg = new Leg(10, 400, "Leeward Gate");
|
} else if(args.length == 2 && args[0].equals("-server")){
|
||||||
finishingLeg.setFinishingLeg(true);
|
switch (args[1]) {
|
||||||
|
case "internal":
|
||||||
|
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||||
|
break;
|
||||||
|
case "staffserver":
|
||||||
|
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
|
||||||
|
break;
|
||||||
|
case "official":
|
||||||
|
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Change the StreamReceiver in this else block to change the default data source.
|
||||||
|
else{
|
||||||
|
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||||
|
}
|
||||||
|
|
||||||
race.addLeg(finishingLeg);
|
sr.start();
|
||||||
|
StreamParser streamParser = new StreamParser("StreamParser");
|
||||||
|
streamParser.start();
|
||||||
|
|
||||||
return race;
|
launch(args);
|
||||||
}
|
|
||||||
|
|
||||||
public static void main( String[] args )
|
|
||||||
{
|
|
||||||
Race race = null;
|
|
||||||
|
|
||||||
try{
|
|
||||||
race = createRace();
|
|
||||||
}
|
|
||||||
catch (Exception e){
|
|
||||||
System.out.println(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If race was created
|
|
||||||
if (race != null){
|
|
||||||
race.displayStartingBoats();
|
|
||||||
|
|
||||||
System.out.println("\n\n");
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Live Race Updates ");
|
|
||||||
System.out.println("######################");
|
|
||||||
race.startRace();
|
|
||||||
|
|
||||||
System.out.println("\n\n");
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Race Results ");
|
|
||||||
System.out.println("######################");
|
|
||||||
race.showRaceMarkerResults();
|
|
||||||
race.displayFinishingOrder();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
System.out.println("There was an error creating the race.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a boat in the race.
|
|
||||||
*
|
|
||||||
* @param teamName The name of the team sailing the boat
|
|
||||||
* @param boatVelocity The speed of the boat in meters/second
|
|
||||||
*/
|
|
||||||
public class Boat
|
|
||||||
{
|
|
||||||
|
|
||||||
private String teamName; // The name of the team, this is also the name of the boat
|
|
||||||
private double velocity; // In meters/second
|
|
||||||
|
|
||||||
public Boat(String teamName) {
|
|
||||||
this.teamName = teamName;
|
|
||||||
this.velocity = 10; // Default velocity
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boat(String teamName, double boatVelocity) {
|
|
||||||
this.teamName = teamName;
|
|
||||||
this.velocity = boatVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the team sailing the boat
|
|
||||||
* @return The name of the team
|
|
||||||
*/
|
|
||||||
public String getTeamName(){
|
|
||||||
return this.teamName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the team sailing the boat
|
|
||||||
* @param teamName The name of the team
|
|
||||||
*/
|
|
||||||
public void setTeamName(String teamName){
|
|
||||||
this.teamName = teamName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets velocity of the boat
|
|
||||||
* @param velocity The velocity of boat
|
|
||||||
*/
|
|
||||||
public void setVelocity(float velocity) {
|
|
||||||
this.velocity = velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets velocity of the boat
|
|
||||||
* @return a float number of the boat velocity
|
|
||||||
*/
|
|
||||||
public double getVelocity() {
|
|
||||||
return this.velocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event class containing the time of specific event, related team/boat, and
|
|
||||||
* event location such as leg.
|
|
||||||
*
|
|
||||||
* @param eventTime, what time the event happens
|
|
||||||
* @param eventBoat, the boat that the event belongs to
|
|
||||||
* @param eventLeg, the leg the event happens on
|
|
||||||
*/
|
|
||||||
public class Event {
|
|
||||||
|
|
||||||
private long time;
|
|
||||||
private Boat boat;
|
|
||||||
private Leg leg;
|
|
||||||
private boolean isFinishingEvent = false;
|
|
||||||
|
|
||||||
public Event(long eventTime, Boat eventBoat, Leg eventLeg) {
|
|
||||||
this.time = eventTime;
|
|
||||||
this.boat = eventBoat;
|
|
||||||
this.leg = eventLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Event(long eventTime, Boat eventBoat, Leg eventLeg, boolean isFinishingEvent) {
|
|
||||||
this.time = eventTime;
|
|
||||||
this.boat = eventBoat;
|
|
||||||
this.leg = eventLeg;
|
|
||||||
this.isFinishingEvent = isFinishingEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the time for the event
|
|
||||||
* @param eventTime the time for event in millisecond
|
|
||||||
*/
|
|
||||||
public void setTime(long eventTime) {
|
|
||||||
this.time = eventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the time for the event
|
|
||||||
* @return the time for event in millisecond
|
|
||||||
*/
|
|
||||||
public long getTime() {
|
|
||||||
return this.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the time in a formatted string
|
|
||||||
* @return the string of time
|
|
||||||
*/
|
|
||||||
public String getTimeString() {
|
|
||||||
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the involved boat
|
|
||||||
* @param eventBoat the involved boat
|
|
||||||
*/
|
|
||||||
public void setBoat(Boat eventBoat) {
|
|
||||||
this.boat = eventBoat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the involved boat
|
|
||||||
* @return the boat involved in the event
|
|
||||||
*/
|
|
||||||
public Boat getBoat() {
|
|
||||||
return this.boat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the involved location/leg
|
|
||||||
* @param eventLeg the involved leg
|
|
||||||
*/
|
|
||||||
public void setLeg(Leg eventLeg) {
|
|
||||||
this.leg = eventLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the involved location/leg
|
|
||||||
* @return the leg involved in the event
|
|
||||||
*/
|
|
||||||
public Leg getLeg() {
|
|
||||||
return this.leg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call when the boat reaches the marker, this will tell the marker the order
|
|
||||||
* in which boats pass it
|
|
||||||
*/
|
|
||||||
public void addBoatToMarker(){
|
|
||||||
this.leg.addBoatToMarker(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this event is the boat finishing the race
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public boolean getIsFinishingEvent(){
|
|
||||||
return this.isFinishingEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a string that contains the timestamp and course information for this event
|
|
||||||
* @return A string that contains the timestamp and course information for this event
|
|
||||||
*/
|
|
||||||
public String getEventString(){
|
|
||||||
String currentHeading = Integer.toString(this.getLeg().getHeading());
|
|
||||||
|
|
||||||
if (this.isFinishingEvent){
|
|
||||||
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.getLeg().getMarkerLabel() + " going heading " + currentHeading + "°");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read team name from a given Json file. So that user can extract information
|
|
||||||
* efficiently from external files.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FileParser {
|
|
||||||
|
|
||||||
private String filePath;
|
|
||||||
private JSONObject content;
|
|
||||||
/** used to construct an instance of file parser
|
|
||||||
*
|
|
||||||
* @param filePath a string like path to show location of desired file to
|
|
||||||
* be parsed
|
|
||||||
*/
|
|
||||||
public FileParser(String filePath) throws Exception {
|
|
||||||
this.filePath = filePath;
|
|
||||||
this.readFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads content from a given file, and return the content as JSONObject.
|
|
||||||
* Throws FileNotFoundException, if the given file cannot be found.
|
|
||||||
*/
|
|
||||||
private void readFile() throws FileNotFoundException{
|
|
||||||
JSONParser parser = new JSONParser();
|
|
||||||
try {
|
|
||||||
this.content = (JSONObject) parser.parse(new FileReader(filePath));
|
|
||||||
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets time scale setting parameter.
|
|
||||||
* @return long time scale. -1 if parameter is invalid (eg. scale is
|
|
||||||
* negative number, or containing non numeric character) or cannot be found.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public double getTimeScale() {
|
|
||||||
try {
|
|
||||||
double timeScale = (double) this.content.get("time-scale");
|
|
||||||
return timeScale >= 0 ? timeScale : -1;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets race name in the setting file.
|
|
||||||
* @return a string of race name. null if race name is invalid or cannot
|
|
||||||
* be found.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public String getRaceName() {
|
|
||||||
try {
|
|
||||||
return (String) this.content.get("race-name");
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an array of teams who participate the race.
|
|
||||||
* @return an ArrayList containing strings of team names. null if teams
|
|
||||||
* setting is invalid or there is no team.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ArrayList<Map<String, Object>> getTeams() {
|
|
||||||
try {
|
|
||||||
return (ArrayList<Map<String, Object>>) this.content.get("teams");
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the total number of teams.
|
|
||||||
* @return the number of teams. 0 if no teams or anything goes wrong.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public long getTotalNumberOfTeams() {
|
|
||||||
ArrayList<Map<String, Object>> teams = getTeams();
|
|
||||||
try {
|
|
||||||
return teams.size();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of boats that would compete during a race. Returns the
|
|
||||||
* total number of race size if parameter is invalid or cannot be found.
|
|
||||||
* @return an int of the race size.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public long getRaceSize() {
|
|
||||||
long totalTeams = this.getTotalNumberOfTeams();
|
|
||||||
try {
|
|
||||||
long raceSize = (long) this.content.get("race-size");
|
|
||||||
return raceSize >= 0 && raceSize <= totalTeams? raceSize : totalTeams;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return totalTeams;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
public class Leg {
|
|
||||||
private int heading;
|
|
||||||
private int distance;
|
|
||||||
private boolean isFinishingLeg;
|
|
||||||
private Marker startingMarker;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create a new leg
|
|
||||||
|
|
||||||
@param heading, the magnetic heading of this leg
|
|
||||||
@param distance, the total distance of this leg in meters
|
|
||||||
@param marker, the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, Marker marker){
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingMarker = marker;
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create a new leg
|
|
||||||
|
|
||||||
@param heading, the magnetic heading of this leg
|
|
||||||
@param distance, the total distance of this leg in meters
|
|
||||||
@param markerName, the name of the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, String markerName){
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingMarker = new Marker(markerName);
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the heading for this leg
|
|
||||||
*/
|
|
||||||
public void setHeading(int heading){
|
|
||||||
this.heading = heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get the heading of this leg
|
|
||||||
*/
|
|
||||||
public int getHeading(){
|
|
||||||
return this.heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the distance of this leg in meters
|
|
||||||
*/
|
|
||||||
public void setDistance(int distance){
|
|
||||||
this.distance = distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get the total distance of this leg in meters
|
|
||||||
*/
|
|
||||||
public int getDistance(){
|
|
||||||
return this.distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public void setMarker(Marker marker){
|
|
||||||
this.startingMarker = marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the marker this leg started on
|
|
||||||
*/
|
|
||||||
public Marker getMarker(){
|
|
||||||
return this.startingMarker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the name of the marker this leg started on
|
|
||||||
*/
|
|
||||||
public String getMarkerLabel(){
|
|
||||||
return this.startingMarker.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Tell the marker that the boat has passed it
|
|
||||||
*/
|
|
||||||
public void addBoatToMarker(Boat boat){
|
|
||||||
this.startingMarker.addBoat(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Specify whether or not the race finishes on this leg
|
|
||||||
|
|
||||||
@param isFinishingLeg whether or not the race finishes on this leg
|
|
||||||
*/
|
|
||||||
public void setFinishingLeg(boolean isFinishingLeg){
|
|
||||||
this.isFinishingLeg = isFinishingLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@returns true if this the race finishes after this leg
|
|
||||||
*/
|
|
||||||
public boolean getIsFinishingLeg(){
|
|
||||||
return this.isFinishingLeg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
class Marker{
|
|
||||||
private String name;
|
|
||||||
private ArrayList<Boat> boatOrder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the marker at the beginning of a leg
|
|
||||||
*
|
|
||||||
* @param name, the name of the marker
|
|
||||||
*/
|
|
||||||
public Marker(String name){
|
|
||||||
this.name = name;
|
|
||||||
this.boatOrder = new ArrayList<Boat>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the name of the marker
|
|
||||||
*
|
|
||||||
* @param name, the name of the marker
|
|
||||||
*/
|
|
||||||
public void setName(String name){
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the marker
|
|
||||||
*
|
|
||||||
* @return the name of the marker
|
|
||||||
*/
|
|
||||||
public String getName(){
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a boat as they pass the marker
|
|
||||||
*
|
|
||||||
* @param boat, the boat that passed the marker
|
|
||||||
*/
|
|
||||||
public void addBoat(Boat boat){
|
|
||||||
this.boatOrder.add(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of boats in the order they passed the marker
|
|
||||||
*
|
|
||||||
* @return An array of boats in the order they passed the marker
|
|
||||||
*/
|
|
||||||
public Boat[] getBoats(){
|
|
||||||
return this.boatOrder.toArray(new Boat[this.boatOrder.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class Race {
|
|
||||||
private ArrayList<Boat> boats; // The boats in the race
|
|
||||||
private ArrayList<Leg> legs; // The legs in the race
|
|
||||||
private ArrayList<Boat> finishingOrder; // The order in which the boats finish the race
|
|
||||||
private PriorityQueue<Event> events; // The events that occur in the race
|
|
||||||
private int numberOfBoats = 0;
|
|
||||||
private long startTime = 0;
|
|
||||||
private double timeScale = 1;
|
|
||||||
|
|
||||||
public Race() {
|
|
||||||
this.boats = new ArrayList<Boat>();
|
|
||||||
this.legs = new ArrayList<Leg>();
|
|
||||||
this.finishingOrder = new ArrayList<Boat>();
|
|
||||||
|
|
||||||
// create a priority queue with a custom Comparator to order events
|
|
||||||
this.events = new PriorityQueue<Event>(new Comparator<Event>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Event o1, Event o2) {
|
|
||||||
Long time1 = o1.getTime();
|
|
||||||
Long time2 = o2.getTime();
|
|
||||||
|
|
||||||
// order event asc. by time. if tie appears, then order team
|
|
||||||
// name alphabetically.
|
|
||||||
if (time1 != time2) {
|
|
||||||
return time1.compareTo(time2);
|
|
||||||
} else {
|
|
||||||
return o1.getBoat().getTeamName().compareTo(o2.getBoat().getTeamName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add a boat to the race
|
|
||||||
@param boat, the boat to add
|
|
||||||
*/
|
|
||||||
public void addBoat(Boat boat) {
|
|
||||||
boats.add(boat);
|
|
||||||
numberOfBoats += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns a list of boats in a random order
|
|
||||||
|
|
||||||
@returns a list of boats
|
|
||||||
*/
|
|
||||||
public Boat[] getShuffledBoats() {
|
|
||||||
// Shuffle the list of boats
|
|
||||||
long seed = System.nanoTime();
|
|
||||||
Collections.shuffle(this.boats, new Random(seed));
|
|
||||||
|
|
||||||
return boats.toArray(new Boat[boats.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns a list of boats in the order that they
|
|
||||||
finished the race (position 0 is first place)
|
|
||||||
|
|
||||||
@returns a list of boats
|
|
||||||
*/
|
|
||||||
public Boat[] getFinishedBoats() {
|
|
||||||
return this.finishingOrder.toArray(new Boat[this.finishingOrder.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the number of boats in the race
|
|
||||||
|
|
||||||
@returns the number of boats in the race
|
|
||||||
*/
|
|
||||||
public int getNumberOfBoats() {
|
|
||||||
return numberOfBoats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns a list of boats in the race
|
|
||||||
|
|
||||||
@returns a list of the boats competing in the race
|
|
||||||
*/
|
|
||||||
public Boat[] getBoats() {
|
|
||||||
return boats.toArray(new Boat[boats.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prints the order in which the boats finished
|
|
||||||
*/
|
|
||||||
public void displayFinishingOrder() {
|
|
||||||
int numberOfBoats = this.getNumberOfBoats();
|
|
||||||
Boat[] boats = this.getFinishedBoats();
|
|
||||||
|
|
||||||
System.out.println("\n\n");
|
|
||||||
System.out.println("--- Finishing Order ---");
|
|
||||||
|
|
||||||
for (int i = 0; i < Array.getLength(boats); i++) {
|
|
||||||
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prints the list of boats competing in the race
|
|
||||||
*/
|
|
||||||
public void displayStartingBoats() {
|
|
||||||
int numberOfBoats = this.getNumberOfBoats();
|
|
||||||
Boat[] boats = this.getBoats();
|
|
||||||
|
|
||||||
System.out.println("######################");
|
|
||||||
System.out.println("# Competing Boats ");
|
|
||||||
System.out.println("######################");
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfBoats; i++) {
|
|
||||||
String velocityKnots = String.format("%1.2f", boats[i].getVelocity() * 1.943844492);
|
|
||||||
|
|
||||||
System.out.println(boats[i].getTeamName() + " Velocity: " + velocityKnots + " knots.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Adds a leg to the race
|
|
||||||
|
|
||||||
@param leg, the leg to add to the race
|
|
||||||
*/
|
|
||||||
public void addLeg(Leg leg) {
|
|
||||||
this.legs.add(leg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets legs array
|
|
||||||
*
|
|
||||||
* @return an array of legs
|
|
||||||
*/
|
|
||||||
public ArrayList<Leg> getLegs() {
|
|
||||||
return this.legs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets time scale
|
|
||||||
* @param timeScale
|
|
||||||
*/
|
|
||||||
public void setTimeScale(double timeScale) {
|
|
||||||
this.timeScale = timeScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary method used to generated all the events.
|
|
||||||
*/
|
|
||||||
private void generateEvents() {
|
|
||||||
|
|
||||||
//calculate the time for every boat passes each leg, and create an event
|
|
||||||
for (Boat boat : this.boats) {
|
|
||||||
long totalDistance = 0;
|
|
||||||
for (Leg leg : this.legs) {
|
|
||||||
long time = (long) (1000 * totalDistance / boat.getVelocity());
|
|
||||||
Event event = new Event(time, boat, leg);
|
|
||||||
events.add(event);
|
|
||||||
totalDistance += leg.getDistance();
|
|
||||||
|
|
||||||
// If finishing leg, add another event for when the boat finishes the race
|
|
||||||
if (leg.getIsFinishingLeg()){
|
|
||||||
time = (long) (1000 * totalDistance / boat.getVelocity());
|
|
||||||
event = new Event(time, boat, leg, true);
|
|
||||||
events.add(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: this function is useless so far
|
|
||||||
* Calculates how far a boat has travelled in meter
|
|
||||||
*
|
|
||||||
* @param velocity the velocity of boat
|
|
||||||
* @return a float number of distance the boat has been travelled
|
|
||||||
*/
|
|
||||||
public float getDistanceTravelled(long velocity) {
|
|
||||||
long timeDiff = System.currentTimeMillis() - this.startTime;
|
|
||||||
long timeElapse = (long) (timeDiff / 1000 * this.timeScale);
|
|
||||||
return timeElapse * velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over events in the race and print the
|
|
||||||
* event string for each event
|
|
||||||
*/
|
|
||||||
public void iterateEvents() {
|
|
||||||
// iterates all events. ends when no event in events.
|
|
||||||
while (!events.isEmpty()) {
|
|
||||||
Event peekEvent = events.peek();
|
|
||||||
long currentTime = (long) ((System.currentTimeMillis() - this.startTime) * this.timeScale);
|
|
||||||
|
|
||||||
if (currentTime > peekEvent.getTime()) {
|
|
||||||
// pull out the event
|
|
||||||
Event nextEvent = events.poll();
|
|
||||||
|
|
||||||
// I just simply print it out for testing
|
|
||||||
System.out.println(nextEvent.getEventString());
|
|
||||||
nextEvent.addBoatToMarker();
|
|
||||||
|
|
||||||
if (nextEvent.getIsFinishingEvent()){
|
|
||||||
this.finishingOrder.add(nextEvent.getBoat());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for 100ms to slow down the while loop
|
|
||||||
try{
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
catch(java.lang.InterruptedException e){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Start the race and print each marker with the order
|
|
||||||
in which the boats passed that marker
|
|
||||||
*/
|
|
||||||
public void startRace() {
|
|
||||||
// record start time.
|
|
||||||
generateEvents();
|
|
||||||
this.startTime = System.currentTimeMillis();
|
|
||||||
iterateEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Print the order in which the boats passed each marker
|
|
||||||
*/
|
|
||||||
public void showRaceMarkerResults(){
|
|
||||||
for (Leg leg : this.legs) {
|
|
||||||
Boat[] boats = leg.getMarker().getBoats();
|
|
||||||
|
|
||||||
System.out.println("--- " + leg.getMarkerLabel() + " ---");
|
|
||||||
|
|
||||||
// Print the order in which the boats passed the marker
|
|
||||||
for (int i = 0; i < this.getNumberOfBoats(); i++) {
|
|
||||||
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,539 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Polygon;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import seng302.models.*;
|
||||||
|
import seng302.models.mark.*;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
|
import seng302.models.parsers.StreamReceiver;
|
||||||
|
import seng302.models.parsers.packets.BoatPositionPacket;
|
||||||
|
import seng302.models.parsers.XMLParser;
|
||||||
|
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
|
||||||
|
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ptg19 on 15/03/17.
|
||||||
|
* Modified by Haoming Yin (hyi25) on 20/3/2017.
|
||||||
|
*/
|
||||||
|
public class CanvasController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private AnchorPane canvasPane;
|
||||||
|
|
||||||
|
private RaceViewController raceViewController;
|
||||||
|
private ResizableCanvas canvas;
|
||||||
|
private Group group;
|
||||||
|
private GraphicsContext gc;
|
||||||
|
|
||||||
|
private final int MARK_SIZE = 10;
|
||||||
|
private final int BUFFER_SIZE = 50;
|
||||||
|
private final int CANVAS_WIDTH = 1000;
|
||||||
|
private final int CANVAS_HEIGHT = 1000;
|
||||||
|
private final int LHS_BUFFER = BUFFER_SIZE;
|
||||||
|
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
|
||||||
|
private final int TOP_BUFFER = BUFFER_SIZE;
|
||||||
|
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
|
||||||
|
|
||||||
|
private double distanceScaleFactor;
|
||||||
|
private ScaleDirection scaleDirection;
|
||||||
|
private Mark minLatPoint;
|
||||||
|
private Mark minLonPoint;
|
||||||
|
private Mark maxLatPoint;
|
||||||
|
private Mark maxLonPoint;
|
||||||
|
private double referencePointX;
|
||||||
|
private double referencePointY;
|
||||||
|
private double metersToPixels;
|
||||||
|
private List<RaceObject> raceObjects = new ArrayList<>();
|
||||||
|
private List<Mark> raceMarks = new ArrayList<>();
|
||||||
|
|
||||||
|
//FRAME RATE
|
||||||
|
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
|
||||||
|
private final long[] frameTimes = new long[30];
|
||||||
|
private int frameTimeIndex = 0;
|
||||||
|
private boolean arrayFilled = false;
|
||||||
|
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
|
||||||
|
|
||||||
|
public AnimationTimer timer;
|
||||||
|
|
||||||
|
private enum ScaleDirection {
|
||||||
|
HORIZONTAL,
|
||||||
|
VERTICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup(RaceViewController raceViewController){
|
||||||
|
this.raceViewController = raceViewController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
raceViewController = new RaceViewController();
|
||||||
|
canvas = new ResizableCanvas();
|
||||||
|
group = new Group();
|
||||||
|
|
||||||
|
canvasPane.getChildren().add(canvas);
|
||||||
|
canvasPane.getChildren().add(group);
|
||||||
|
// Bind canvas size to stack pane size.
|
||||||
|
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
|
||||||
|
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
|
||||||
|
//group.minWidth(CANVAS_WIDTH);
|
||||||
|
//group.minHeight(CANVAS_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeCanvas (){
|
||||||
|
|
||||||
|
gc = canvas.getGraphicsContext2D();
|
||||||
|
gc.save();
|
||||||
|
gc.setFill(Color.SKYBLUE);
|
||||||
|
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||||
|
gc.restore();
|
||||||
|
fitMarksToCanvas();
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
||||||
|
drawBoats();
|
||||||
|
timer = new AnimationTimer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
|
|
||||||
|
//fps stuff
|
||||||
|
long oldFrameTime = frameTimes[frameTimeIndex] ;
|
||||||
|
frameTimes[frameTimeIndex] = now ;
|
||||||
|
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
|
||||||
|
if (frameTimeIndex == 0) {
|
||||||
|
arrayFilled = true ;
|
||||||
|
}
|
||||||
|
long elapsedNanos;
|
||||||
|
if (arrayFilled) {
|
||||||
|
elapsedNanos = now - oldFrameTime ;
|
||||||
|
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
||||||
|
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
||||||
|
drawFps(frameRate.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
|
||||||
|
elapsedNanos = 1000 / 60;
|
||||||
|
updateRaceObjects();
|
||||||
|
if (StreamParser.isRaceFinished()) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds border marks to the canvas, taken from the XML file
|
||||||
|
*
|
||||||
|
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
|
||||||
|
* named the same as those in the model package but are, however not the same, so they do not have things such as
|
||||||
|
* a type and must be derived from the number of marks in a compound mark etc..
|
||||||
|
*/
|
||||||
|
private void addRaceBorder() {
|
||||||
|
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
|
||||||
|
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
||||||
|
gc.setStroke(Color.DARKBLUE);
|
||||||
|
gc.setLineWidth(3);
|
||||||
|
double[] xBoundaryPoints = new double[courseLimits.size()];
|
||||||
|
double[] yBoundaryPoints = new double[courseLimits.size()];
|
||||||
|
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
||||||
|
Limit thisPoint1 = courseLimits.get(i);
|
||||||
|
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
|
||||||
|
Limit thisPoint2 = courseLimits.get(i+1);
|
||||||
|
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
|
||||||
|
Point2D borderPoint1 = findScaledXY(thisMark1);
|
||||||
|
Point2D borderPoint2 = findScaledXY(thisMark2);
|
||||||
|
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||||
|
borderPoint2.getX(), borderPoint2.getY());
|
||||||
|
xBoundaryPoints[i] = borderPoint1.getX();
|
||||||
|
yBoundaryPoints[i] = borderPoint1.getY();
|
||||||
|
}
|
||||||
|
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
||||||
|
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
|
||||||
|
Limit thisPoint2 = courseLimits.get(0);
|
||||||
|
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
|
||||||
|
Point2D borderPoint1 = findScaledXY(thisMark1);
|
||||||
|
Point2D borderPoint2 = findScaledXY(thisMark2);
|
||||||
|
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||||
|
borderPoint2.getX(), borderPoint2.getY());
|
||||||
|
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
||||||
|
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
||||||
|
gc.setFill(Color.LIGHTBLUE);
|
||||||
|
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the course marks to the canvas, taken from the XMl file
|
||||||
|
*
|
||||||
|
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
|
||||||
|
* named the same as those in the model package but are, however not the same, so they do not have things such as
|
||||||
|
* a type and must be derived from the number of marks in a compound mark etc..
|
||||||
|
*/
|
||||||
|
private void addCourseMarks() {
|
||||||
|
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
|
||||||
|
ArrayList<CompoundMark> compoundMarks = raceXMLObject.getCompoundMarks();
|
||||||
|
RaceObject markGroup;
|
||||||
|
|
||||||
|
for (CompoundMark compoundMark : compoundMarks) {
|
||||||
|
|
||||||
|
//If the compound mark has 2 marks then its a gate mark
|
||||||
|
if (compoundMark.getMarks().size() == 2) {
|
||||||
|
CompoundMark.Mark mark1 = compoundMark.getMarks().get(0);
|
||||||
|
CompoundMark.Mark mark2 = compoundMark.getMarks().get(1);
|
||||||
|
SingleMark singleMark1 = new SingleMark(mark1.getMarkName(), mark1.getTargetLat(), mark1.getTargetLng(), mark1.getSourceID());
|
||||||
|
SingleMark singleMark2 = new SingleMark(mark1.getMarkName(), mark2.getTargetLat(), mark2.getTargetLng(), mark2.getSourceID());
|
||||||
|
GateMark thisGateMark = new GateMark(compoundMark.getcMarkName(),
|
||||||
|
(compoundMark.getMarkID().equals(1)) ? MarkType.OPEN_GATE : MarkType.CLOSED_GATE,
|
||||||
|
singleMark1,
|
||||||
|
singleMark2,
|
||||||
|
singleMark1.getLatitude(),
|
||||||
|
singleMark1.getLongitude());
|
||||||
|
|
||||||
|
markGroup = new MarkGroup(thisGateMark,
|
||||||
|
findScaledXY(thisGateMark.getSingleMark1()),
|
||||||
|
findScaledXY(thisGateMark.getSingleMark2()));
|
||||||
|
|
||||||
|
raceObjects.add(markGroup);
|
||||||
|
raceMarks.add(thisGateMark);
|
||||||
|
|
||||||
|
//Otherwise its a single mark
|
||||||
|
} else {
|
||||||
|
CompoundMark.Mark singleMark = compoundMark.getMarks().get(0);
|
||||||
|
Mark thisSingleMark = new SingleMark(singleMark.getMarkName(),
|
||||||
|
singleMark.getTargetLat(),
|
||||||
|
singleMark.getTargetLng(),
|
||||||
|
singleMark.getSourceID());
|
||||||
|
|
||||||
|
markGroup = new MarkGroup(thisSingleMark, findScaledXY(thisSingleMark));
|
||||||
|
raceObjects.add(markGroup);
|
||||||
|
raceMarks.add(thisSingleMark);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRaceObjects(){
|
||||||
|
for (RaceObject raceObject : raceObjects) {
|
||||||
|
raceObject.updatePosition(1000 / 60);
|
||||||
|
// some raceObjects will have multiply ID's (for instance gate marks)
|
||||||
|
for (long id : raceObject.getRaceIds()) {
|
||||||
|
//checking if the current "ID" has any updates associated with it
|
||||||
|
if (StreamParser.boatPositions.containsKey(id)) {
|
||||||
|
move(id, raceObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void move(long id, RaceObject raceObject){
|
||||||
|
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(id);
|
||||||
|
if (movementQueue.size() > 0){
|
||||||
|
BoatPositionPacket positionPacket = movementQueue.peek();
|
||||||
|
|
||||||
|
//this code adds a delay to reading from the movementQueue
|
||||||
|
//in case things being put into the movement queue are slightly
|
||||||
|
//out of order
|
||||||
|
int delayTime = 1000;
|
||||||
|
int loopTime = delayTime * 10;
|
||||||
|
long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime);
|
||||||
|
if (timeDiff < 0){
|
||||||
|
timeDiff = loopTime + timeDiff;
|
||||||
|
}
|
||||||
|
if (timeDiff > delayTime) {
|
||||||
|
try {
|
||||||
|
positionPacket = movementQueue.take();
|
||||||
|
Point2D p2d = latLonToXY(positionPacket.getLat(), positionPacket.getLon());
|
||||||
|
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
||||||
|
raceObject.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), (int) id);
|
||||||
|
} catch (InterruptedException e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResizableCanvas extends Canvas {
|
||||||
|
|
||||||
|
ResizableCanvas() {
|
||||||
|
// Redraw canvas when size changes.
|
||||||
|
widthProperty().addListener(evt -> draw());
|
||||||
|
heightProperty().addListener(evt -> draw());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw() {
|
||||||
|
double width = getWidth();
|
||||||
|
double height = getHeight();
|
||||||
|
|
||||||
|
GraphicsContext gc = getGraphicsContext2D();
|
||||||
|
gc.clearRect(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResizable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double prefWidth(double height) {
|
||||||
|
return getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double prefHeight(double width) {
|
||||||
|
return getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawFps(int fps){
|
||||||
|
if (raceViewController.isDisplayFps()){
|
||||||
|
gc.clearRect(5,5,50,20);
|
||||||
|
gc.setFill(Color.SKYBLUE);
|
||||||
|
gc.fillRect(4,4,51,21);
|
||||||
|
gc.setFill(Color.BLACK);
|
||||||
|
gc.setFont(new Font(14));
|
||||||
|
gc.setLineWidth(3);
|
||||||
|
gc.fillText(fps + " FPS", 5, 20);
|
||||||
|
} else {
|
||||||
|
gc.clearRect(5,5,50,20);
|
||||||
|
gc.setFill(Color.SKYBLUE);
|
||||||
|
gc.fillRect(4,4,51,21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws all the boats.
|
||||||
|
*/
|
||||||
|
private void drawBoats() {
|
||||||
|
// Map<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
|
||||||
|
// List<Boat> boats = raceViewController.getStartingBoats();
|
||||||
|
Map<Integer, Yacht> boats = StreamParser.getBoats();
|
||||||
|
Double startingX = raceObjects.get(0).getLayoutX();
|
||||||
|
Double startingY = raceObjects.get(0).getLayoutY();
|
||||||
|
Group boatAnnotations = new Group();
|
||||||
|
|
||||||
|
for (Yacht boat : boats.values()) {
|
||||||
|
// for (Boat boat : boats) {
|
||||||
|
boat.setColour(Colors.getColor());
|
||||||
|
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
||||||
|
boatGroup.moveTo(startingX, startingY, 0d);
|
||||||
|
//boatGroup.setStage(raceViewController.getStage());
|
||||||
|
raceObjects.add(boatGroup);
|
||||||
|
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
|
||||||
|
}
|
||||||
|
group.getChildren().add(boatAnnotations);
|
||||||
|
group.getChildren().addAll(raceObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
|
||||||
|
*/
|
||||||
|
private void fitMarksToCanvas() {
|
||||||
|
findMinMaxPoint();
|
||||||
|
double minLonToMaxLon = scaleRaceExtremities();
|
||||||
|
calculateReferencePointLocation(minLonToMaxLon);
|
||||||
|
givePointsXY();
|
||||||
|
addRaceBorder();
|
||||||
|
findMetersToPixels();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost
|
||||||
|
* marker, rightmost marker, southern most marker and northern most marker respectively.
|
||||||
|
*/
|
||||||
|
private void findMinMaxPoint() {
|
||||||
|
List<Limit> sortedPoints = new ArrayList<>();
|
||||||
|
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) {
|
||||||
|
sortedPoints.add(limit);
|
||||||
|
}
|
||||||
|
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
||||||
|
Limit minLatMark = sortedPoints.get(0);
|
||||||
|
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
||||||
|
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID());
|
||||||
|
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID());
|
||||||
|
|
||||||
|
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
||||||
|
//If the course is on a point on the earth where longitudes wrap around.
|
||||||
|
Limit minLonMark = sortedPoints.get(0);
|
||||||
|
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
||||||
|
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
|
||||||
|
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
|
||||||
|
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around.
|
||||||
|
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) {
|
||||||
|
SingleMark temp = thisMinLon;
|
||||||
|
thisMinLon = thisMaxLon;
|
||||||
|
thisMaxLon = temp;
|
||||||
|
}
|
||||||
|
minLonPoint = thisMinLon;
|
||||||
|
maxLonPoint = thisMaxLon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the location of a reference point, this is always the point with minimum latitude, in relation to the
|
||||||
|
* canvas.
|
||||||
|
*
|
||||||
|
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to maximum longitude.
|
||||||
|
*/
|
||||||
|
private void calculateReferencePointLocation (double minLonToMaxLon) {
|
||||||
|
Mark referencePoint = minLatPoint;
|
||||||
|
double referenceAngle;
|
||||||
|
|
||||||
|
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
||||||
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||||
|
referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||||
|
|
||||||
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
||||||
|
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER);
|
||||||
|
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||||
|
referencePointY = referencePointY / 2;
|
||||||
|
referencePointY += TOP_BUFFER;
|
||||||
|
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||||
|
} else {
|
||||||
|
referencePointY = CANVAS_HEIGHT - BOT_BUFFER;
|
||||||
|
|
||||||
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||||
|
referencePointX = LHS_BUFFER;
|
||||||
|
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||||
|
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor
|
||||||
|
* Returns the max horizontal distance of the map.
|
||||||
|
*/
|
||||||
|
private double scaleRaceExtremities () {
|
||||||
|
|
||||||
|
double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint));
|
||||||
|
double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint);
|
||||||
|
double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
||||||
|
|
||||||
|
if (horiAngle <= (Math.PI / 2))
|
||||||
|
horiAngle = (Math.PI / 2) - horiAngle;
|
||||||
|
else
|
||||||
|
horiAngle = horiAngle - (Math.PI / 2);
|
||||||
|
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
|
||||||
|
|
||||||
|
double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance;
|
||||||
|
|
||||||
|
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) {
|
||||||
|
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance;
|
||||||
|
scaleDirection = ScaleDirection.HORIZONTAL;
|
||||||
|
} else {
|
||||||
|
distanceScaleFactor = vertScale;
|
||||||
|
scaleDirection = ScaleDirection.VERTICAL;
|
||||||
|
}
|
||||||
|
return horiDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances
|
||||||
|
* are scaled according to the distanceScaleFactor variable.
|
||||||
|
*/
|
||||||
|
private void givePointsXY() {
|
||||||
|
List<Mark> allPoints = new ArrayList<>(raceViewController.getRace().getCourse());
|
||||||
|
List<Mark> processed = new ArrayList<>();
|
||||||
|
RaceObject markGroup;
|
||||||
|
|
||||||
|
for (Mark mark : allPoints) {
|
||||||
|
if (!processed.contains(mark)) {
|
||||||
|
if (mark.getMarkType() != MarkType.SINGLE_MARK) {
|
||||||
|
GateMark gateMark = (GateMark) mark;
|
||||||
|
markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2()));
|
||||||
|
raceObjects.add(markGroup);
|
||||||
|
} else {
|
||||||
|
markGroup = new MarkGroup(mark, findScaledXY(mark));
|
||||||
|
raceObjects.add(markGroup);
|
||||||
|
}
|
||||||
|
processed.add(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D findScaledXY (Mark unscaled) {
|
||||||
|
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(),
|
||||||
|
unscaled.getLatitude(), unscaled.getLongitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) {
|
||||||
|
double distanceFromReference;
|
||||||
|
double angleFromReference;
|
||||||
|
int xAxisLocation = (int) referencePointX;
|
||||||
|
int yAxisLocation = (int) referencePointY;
|
||||||
|
|
||||||
|
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB);
|
||||||
|
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB);
|
||||||
|
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
||||||
|
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
|
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
|
} else if (angleFromReference >= 0) {
|
||||||
|
angleFromReference = angleFromReference - Math.PI / 2;
|
||||||
|
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
|
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
|
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
|
||||||
|
angleFromReference = Math.abs(angleFromReference);
|
||||||
|
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
|
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
|
} else {
|
||||||
|
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
|
||||||
|
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
|
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
|
}
|
||||||
|
return new Point2D(xAxisLocation, yAxisLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the number of meters per pixel.
|
||||||
|
*/
|
||||||
|
private void findMetersToPixels () {
|
||||||
|
Double angularDistance;
|
||||||
|
Double angle;
|
||||||
|
Double straightLineDistance;
|
||||||
|
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
||||||
|
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint);
|
||||||
|
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
||||||
|
if (angle > Math.PI / 2) {
|
||||||
|
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance;
|
||||||
|
} else {
|
||||||
|
straightLineDistance = Math.cos(angle) * angularDistance;
|
||||||
|
}
|
||||||
|
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance;
|
||||||
|
} else {
|
||||||
|
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint);
|
||||||
|
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint);
|
||||||
|
if (angle < Math.PI / 2) {
|
||||||
|
straightLineDistance = Math.cos(angle) * angularDistance;
|
||||||
|
} else {
|
||||||
|
straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance;
|
||||||
|
}
|
||||||
|
metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D latLonToXY (double latitude, double longitude) {
|
||||||
|
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RaceObject> getRaceObjects() {
|
||||||
|
return raceObjects;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
|
import seng302.models.parsers.XMLParser;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class Controller implements Initializable {
|
||||||
|
@FXML
|
||||||
|
private AnchorPane contentPane;
|
||||||
|
@FXML
|
||||||
|
private Label timeTillLive;
|
||||||
|
@FXML
|
||||||
|
private Button streamButton;
|
||||||
|
@FXML
|
||||||
|
private Button switchToRaceViewButton;
|
||||||
|
@FXML
|
||||||
|
private TableView<Yacht> teamList;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> boatNameCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> shortNameCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> countryCol;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<Yacht, String> posCol;
|
||||||
|
@FXML
|
||||||
|
private Label realTime;
|
||||||
|
|
||||||
|
private XMLParser xmlParser;
|
||||||
|
|
||||||
|
private void setContentPane(String jfxUrl){
|
||||||
|
try{
|
||||||
|
contentPane.getChildren().removeAll();
|
||||||
|
contentPane.getChildren().clear();
|
||||||
|
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||||
|
}
|
||||||
|
catch(javafx.fxml.LoadException e){
|
||||||
|
System.err.println(e.getCause());
|
||||||
|
}
|
||||||
|
catch(IOException e){
|
||||||
|
System.err.println(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
//format.setTimeZone(TimeZone.getTimeZone("GMT-8"));
|
||||||
|
//realTime.setText(format.format(new Date()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
|
||||||
|
*/
|
||||||
|
public void startStream() {
|
||||||
|
if (StreamParser.isStreamStatus()) {
|
||||||
|
xmlParser = StreamParser.getXmlObject();
|
||||||
|
streamButton.setVisible(false);
|
||||||
|
realTime.setVisible(true);
|
||||||
|
timeTillLive.setVisible(true);
|
||||||
|
timeTillLive.setTextFill(Color.GREEN);
|
||||||
|
timeTillLive.setText("Connecting...");
|
||||||
|
Timer timer = new Timer();
|
||||||
|
timer.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (StreamParser.isRaceFinished()) {
|
||||||
|
realTime.setText(StreamParser.getCurrentTimeString());
|
||||||
|
timeTillLive.setTextFill(Color.RED);
|
||||||
|
timeTillLive.setText("Race finished! Waiting for new race...");
|
||||||
|
switchToRaceViewButton.setDisable(true);
|
||||||
|
} else if (StreamParser.getTimeSinceStart() > 0) {
|
||||||
|
realTime.setText(StreamParser.getCurrentTimeString());
|
||||||
|
updateTeamList();
|
||||||
|
timeTillLive.setTextFill(Color.RED);
|
||||||
|
switchToRaceViewButton.setDisable(false);
|
||||||
|
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
String timerString = "-" + timerMinute + ":" + timerSecond;
|
||||||
|
timeTillLive.setText(timerString);
|
||||||
|
} else {
|
||||||
|
realTime.setText(StreamParser.getCurrentTimeString());
|
||||||
|
updateTeamList();
|
||||||
|
timeTillLive.setTextFill(Color.BLACK);
|
||||||
|
switchToRaceViewButton.setDisable(false);
|
||||||
|
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
String timerString = timerMinute + ":" + timerSecond;
|
||||||
|
timeTillLive.setText(timerString);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
} else {
|
||||||
|
timeTillLive.setText("Stream not available.");
|
||||||
|
timeTillLive.setTextFill(Color.RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchToRaceView() {
|
||||||
|
setContentPane("/views/RaceView.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTeamList() {
|
||||||
|
ObservableList<Yacht> data = FXCollections.observableArrayList();
|
||||||
|
teamList.setItems(data);
|
||||||
|
boatNameCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("boatName")
|
||||||
|
);
|
||||||
|
shortNameCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("shortName")
|
||||||
|
);
|
||||||
|
countryCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("country")
|
||||||
|
);
|
||||||
|
posCol.setCellValueFactory(
|
||||||
|
new PropertyValueFactory<>("position")
|
||||||
|
);
|
||||||
|
// if (StreamParser.isRaceStarted()) {
|
||||||
|
data.addAll(StreamParser.getBoatsPos().values());
|
||||||
|
// } else {
|
||||||
|
// for (Yacht boat : StreamParser.getBoats().values()) {
|
||||||
|
// boat.setPosition("-");
|
||||||
|
// data.add(boat);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
teamList.refresh();
|
||||||
|
|
||||||
|
// posCol.setSortType(TableColumn.SortType.ASCENDING);
|
||||||
|
// teamList.getSortOrder().add(posCol);
|
||||||
|
// posCol.setSortable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import seng302.models.Race;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.parsers.ConfigParser;
|
||||||
|
import seng302.models.parsers.CourseParser;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by zyt10 on 17/03/17.
|
||||||
|
* run before CanvasController to initialize race events
|
||||||
|
* the CanvasController then uses the event data to make the animations
|
||||||
|
*/
|
||||||
|
public class RaceController {
|
||||||
|
Race race = null;
|
||||||
|
|
||||||
|
public void initializeRace() {
|
||||||
|
String raceConfigFile = "/config/config.xml";
|
||||||
|
String teamsConfigFile = "/config/teams.xml";
|
||||||
|
|
||||||
|
try {
|
||||||
|
race = createRace(raceConfigFile, teamsConfigFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("There was an error creating the race.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (race != null) {
|
||||||
|
race.startRace();
|
||||||
|
} else {
|
||||||
|
System.out.println("There was an error creating the race. Exiting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
|
||||||
|
Race race = new Race();
|
||||||
|
// StreamParser.xmlObject
|
||||||
|
// Read team names from file
|
||||||
|
// TeamsParser tp = new TeamsParser(teamsConfigFile);
|
||||||
|
|
||||||
|
// Read course from file
|
||||||
|
// ConfigParser config = new ConfigParser(configFile);
|
||||||
|
|
||||||
|
ArrayList<String> boatNames = new ArrayList<>();
|
||||||
|
// ArrayList<Boat> teams = tp.getBoats();
|
||||||
|
Map<Long, Yacht> teams = StreamParser.getBoatsPos();
|
||||||
|
|
||||||
|
//get race size
|
||||||
|
int numberOfBoats = teams.size();
|
||||||
|
|
||||||
|
//get time scale
|
||||||
|
// double timeScale = config.getTimeScale();
|
||||||
|
// race.setTimeScale(timeScale);
|
||||||
|
|
||||||
|
for (Yacht boat : teams.values()) {
|
||||||
|
boatNames.add(boat.getBoatName());
|
||||||
|
race.addBoat(boat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle team names
|
||||||
|
long seed = System.nanoTime();
|
||||||
|
Collections.shuffle(boatNames, new Random(seed));
|
||||||
|
|
||||||
|
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CourseParser course = new CourseParser("/config/course.xml");
|
||||||
|
race.addCourse(course.getCourse());
|
||||||
|
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Race getRace() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import seng302.models.Race;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ptg19 on 20/03/17.
|
||||||
|
*/
|
||||||
|
public class RaceResultController implements Initializable{
|
||||||
|
@FXML private AnchorPane window;
|
||||||
|
@FXML private VBox resultsVBox;
|
||||||
|
private Race race;
|
||||||
|
|
||||||
|
RaceResultController(Race race){
|
||||||
|
this.race = race;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
int boatPosition = this.race.getFinishedBoats().length;
|
||||||
|
|
||||||
|
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
|
||||||
|
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getBoatName()));
|
||||||
|
boatPosition--;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import seng302.models.*;
|
||||||
|
import seng302.models.parsers.ConfigParser;
|
||||||
|
import seng302.models.parsers.StreamParser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ptg19 on 29/03/17.
|
||||||
|
*/
|
||||||
|
public class RaceViewController extends Thread{
|
||||||
|
@FXML
|
||||||
|
private VBox positionVbox;
|
||||||
|
@FXML
|
||||||
|
private CheckBox toggleFps;
|
||||||
|
@FXML
|
||||||
|
private Text timerLabel;
|
||||||
|
@FXML
|
||||||
|
private AnchorPane contentAnchorPane;
|
||||||
|
@FXML
|
||||||
|
private Text windArrowText, windDirectionText;
|
||||||
|
@FXML
|
||||||
|
private Slider annotationSlider;
|
||||||
|
@FXML
|
||||||
|
private CanvasController includedCanvasController;
|
||||||
|
|
||||||
|
private ArrayList<Yacht> startingBoats = new ArrayList<>();
|
||||||
|
private boolean displayFps;
|
||||||
|
private Timeline timerTimeline;
|
||||||
|
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
|
||||||
|
private ArrayList<Yacht> boatOrder = new ArrayList<>();
|
||||||
|
private Race race;
|
||||||
|
private Stage stage;
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
|
||||||
|
RaceController raceController = new RaceController();
|
||||||
|
raceController.initializeRace();
|
||||||
|
race = raceController.getRace();
|
||||||
|
for (Yacht boat : race.getBoats()) {
|
||||||
|
startingBoats.add(boat);
|
||||||
|
}
|
||||||
|
// try{
|
||||||
|
// initializeTimelines();
|
||||||
|
// }
|
||||||
|
// catch (Exception e){
|
||||||
|
// e.printStackTrace();
|
||||||
|
// }
|
||||||
|
|
||||||
|
includedCanvasController.setup(this);
|
||||||
|
includedCanvasController.initializeCanvas();
|
||||||
|
initializeTimer();
|
||||||
|
initializeSettings();
|
||||||
|
initialiseWindDirection();
|
||||||
|
initialisePositionVBox();
|
||||||
|
//set wind direction!!!!!!! can't find another place to put my code --haoming
|
||||||
|
// double windDirection = new ConfigParser("/config/config.xml").getWindDirection();
|
||||||
|
// windDirectionText.setText(String.format("%.1f°", windDirection));
|
||||||
|
// windArrowText.setRotate(windDirection);
|
||||||
|
includedCanvasController.timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void initializeSettings() {
|
||||||
|
displayFps = true;
|
||||||
|
|
||||||
|
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||||
|
displayFps = !displayFps;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//SLIFER STUFF BELOW
|
||||||
|
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Double n) {
|
||||||
|
if (n == 0) return "None";
|
||||||
|
if (n == 1) return "Low";
|
||||||
|
if (n == 2) return "Medium";
|
||||||
|
if (n == 3) return "All";
|
||||||
|
|
||||||
|
return "All";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double fromString(String s) {
|
||||||
|
switch (s) {
|
||||||
|
case "None":
|
||||||
|
return 0d;
|
||||||
|
case "Low":
|
||||||
|
return 1d;
|
||||||
|
case "Medium":
|
||||||
|
return 2d;
|
||||||
|
case "All":
|
||||||
|
return 3d;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 3d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
|
||||||
|
setAnnotations((int)annotationSlider.getValue()));
|
||||||
|
|
||||||
|
annotationSlider.setValue(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeTimer(){
|
||||||
|
timerTimeline = new Timeline();
|
||||||
|
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
|
// Run timer update every second
|
||||||
|
timerTimeline.getKeyFrames().add(
|
||||||
|
new KeyFrame(Duration.seconds(1),
|
||||||
|
event -> {
|
||||||
|
if (StreamParser.isRaceFinished()) {
|
||||||
|
timerLabel.setFill(Color.RED);
|
||||||
|
timerLabel.setText("Race Finished!");
|
||||||
|
} else {
|
||||||
|
timerLabel.setText(currentTimer());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start the timer
|
||||||
|
timerTimeline.playFromStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialiseWindDirection() {
|
||||||
|
Timeline windDirTimeline = new Timeline();
|
||||||
|
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
|
windDirTimeline.getKeyFrames().add(
|
||||||
|
new KeyFrame(Duration.seconds(1),
|
||||||
|
event -> {
|
||||||
|
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
||||||
|
windArrowText.setRotate(StreamParser.getWindDirection());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
windDirTimeline.playFromStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialisePositionVBox() {
|
||||||
|
|
||||||
|
Timeline posVBoxTimeline = new Timeline();
|
||||||
|
posVBoxTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
|
posVBoxTimeline.getKeyFrames().add(
|
||||||
|
new KeyFrame(Duration.seconds(1),
|
||||||
|
event -> {
|
||||||
|
showOrder();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
posVBoxTimeline.playFromStart();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates time line for each boat, and stores time time into timelineInfos hash map
|
||||||
|
*/
|
||||||
|
private void initializeTimelines() {
|
||||||
|
HashMap<Yacht, List> boat_events = race.getEvents();
|
||||||
|
for (Yacht boat : boat_events.keySet()) {
|
||||||
|
startingBoats.add(boat);
|
||||||
|
// // x, y are the real time coordinates
|
||||||
|
// DoubleProperty x = new SimpleDoubleProperty();
|
||||||
|
// DoubleProperty y = new SimpleDoubleProperty();
|
||||||
|
//
|
||||||
|
// List<KeyFrame> keyFrames = new ArrayList<>();
|
||||||
|
// List<Event> events = boat_events.get(boat);
|
||||||
|
//
|
||||||
|
// // iterates all events and convert each event to keyFrame, then add them into a list
|
||||||
|
// for (Event event : events) {
|
||||||
|
// if (event.getIsFinishingEvent()) {
|
||||||
|
// keyFrames.add(
|
||||||
|
// new KeyFrame(Duration.seconds(event.getTime()),
|
||||||
|
// onFinished -> {race.setBoatFinished(boat); handleEvent(event);},
|
||||||
|
// new KeyValue(x, event.getThisMark().getLatitude()),
|
||||||
|
// new KeyValue(y, event.getThisMark().getLongitude())
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// keyFrames.add(
|
||||||
|
// new KeyFrame(Duration.seconds(event.getTime()),
|
||||||
|
// onFinished ->{
|
||||||
|
// handleEvent(event);
|
||||||
|
// boat.setHeading(event.getBoatHeading());
|
||||||
|
// },
|
||||||
|
// new KeyValue(x, event.getThisMark().getLatitude()),
|
||||||
|
// new KeyValue(y, event.getThisMark().getLongitude())
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y));
|
||||||
|
}
|
||||||
|
setRaceDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRaceDuration(){
|
||||||
|
Double maxDuration = 0.0;
|
||||||
|
Timeline maxTimeline = null;
|
||||||
|
|
||||||
|
for (TimelineInfo timelineInfo : timelineInfos.values()) {
|
||||||
|
|
||||||
|
Timeline timeline = timelineInfo.getTimeline();
|
||||||
|
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
|
||||||
|
maxDuration = timeline.getTotalDuration().toMillis();
|
||||||
|
maxTimeline = timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timelines are paused by default
|
||||||
|
timeline.play();
|
||||||
|
timeline.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTimeline.setOnFinished(event -> {
|
||||||
|
race.setRaceFinished();
|
||||||
|
loadRaceResultView();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play each boats timerTimeline
|
||||||
|
*/
|
||||||
|
public void playTimelines(){
|
||||||
|
for (TimelineInfo timelineInfo : timelineInfos.values()){
|
||||||
|
Timeline timeline = timelineInfo.getTimeline();
|
||||||
|
|
||||||
|
if (timeline.getStatus() == Animation.Status.PAUSED){
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause each boats timerTimeline
|
||||||
|
*/
|
||||||
|
public void pauseTimelines(){
|
||||||
|
for (TimelineInfo timelineInfo : timelineInfos.values()){
|
||||||
|
Timeline timeline = timelineInfo.getTimeline();
|
||||||
|
|
||||||
|
if (timeline.getStatus() == Animation.Status.RUNNING){
|
||||||
|
timeline.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the list of boats in the order they finished the race
|
||||||
|
*/
|
||||||
|
private void loadRaceResultView() {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||||
|
loader.setController(new RaceResultController(race));
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentAnchorPane.getChildren().removeAll();
|
||||||
|
contentAnchorPane.getChildren().clear();
|
||||||
|
contentAnchorPane.getChildren().addAll((Pane) loader.load());
|
||||||
|
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
|
System.err.println(e.getCause());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleEvent(Event event) {
|
||||||
|
Yacht boat = event.getBoat();
|
||||||
|
boatOrder.remove(boat);
|
||||||
|
boat.setMarkLastPast(event.getMarkPosInRace());
|
||||||
|
boatOrder.add(boat);
|
||||||
|
boatOrder.sort(new Comparator<Yacht>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Yacht b1, Yacht b2) {
|
||||||
|
return b2.getMarkLastPast() - b1.getMarkLastPast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
showOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showOrder() {
|
||||||
|
positionVbox.getChildren().clear();
|
||||||
|
positionVbox.getChildren().removeAll();
|
||||||
|
|
||||||
|
// for (Boat boat : boatOrder) {
|
||||||
|
// positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots"));
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
||||||
|
// System.out.println(boat.getBoatStatus());
|
||||||
|
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
||||||
|
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " (Finished)"));
|
||||||
|
} else {
|
||||||
|
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert seconds to a string of the format mm:ss
|
||||||
|
*
|
||||||
|
* @param time the time in seconds
|
||||||
|
* @return a formatted string
|
||||||
|
*/
|
||||||
|
public String convertTimeToMinutesSeconds(int time) {
|
||||||
|
if (time < 0) {
|
||||||
|
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
|
||||||
|
}
|
||||||
|
return String.format("%02d:%02d", time / 60, time % 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String currentTimer() {
|
||||||
|
String timerString = "0:00";
|
||||||
|
if (StreamParser.getTimeSinceStart() > 0) {
|
||||||
|
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
||||||
|
if (timerSecond.length() == 1) {
|
||||||
|
timerSecond = "0" + timerSecond;
|
||||||
|
}
|
||||||
|
timerString = "-" + timerMinute + ":" + timerSecond;
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
return timerString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopTimer() {
|
||||||
|
timerTimeline.stop();
|
||||||
|
}
|
||||||
|
public void startTimer() {
|
||||||
|
timerTimeline.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisplayFps() {
|
||||||
|
return displayFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Race getRace() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Yacht, TimelineInfo> getTimelineInfos() {
|
||||||
|
return timelineInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Yacht> getStartingBoats(){
|
||||||
|
return startingBoats;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setAnnotations(Integer annotationLevel) {
|
||||||
|
switch (annotationLevel) {
|
||||||
|
case 0:
|
||||||
|
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
||||||
|
if(ro instanceof BoatGroup) {
|
||||||
|
BoatGroup bg = (BoatGroup) ro;
|
||||||
|
bg.setTeamNameObjectVisible(false);
|
||||||
|
bg.setVelocityObjectVisible(false);
|
||||||
|
bg.setLineGroupVisible(false);
|
||||||
|
bg.setWakeVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
||||||
|
if(ro instanceof BoatGroup) {
|
||||||
|
BoatGroup bg = (BoatGroup) ro;
|
||||||
|
bg.setTeamNameObjectVisible(true);
|
||||||
|
bg.setVelocityObjectVisible(false);
|
||||||
|
bg.setLineGroupVisible(false);
|
||||||
|
bg.setWakeVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
||||||
|
if(ro instanceof BoatGroup) {
|
||||||
|
BoatGroup bg = (BoatGroup) ro;
|
||||||
|
bg.setTeamNameObjectVisible(true);
|
||||||
|
bg.setVelocityObjectVisible(false);
|
||||||
|
bg.setLineGroupVisible(true);
|
||||||
|
bg.setWakeVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
||||||
|
if(ro instanceof BoatGroup) {
|
||||||
|
BoatGroup bg = (BoatGroup) ro;
|
||||||
|
bg.setTeamNameObjectVisible(true);
|
||||||
|
bg.setVelocityObjectVisible(true);
|
||||||
|
bg.setLineGroupVisible(true);
|
||||||
|
bg.setWakeVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStage (Stage stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage getStage () {
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Line;
|
||||||
|
import javafx.scene.shape.Polygon;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat.
|
||||||
|
* It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to
|
||||||
|
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime
|
||||||
|
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them
|
||||||
|
* when the window is maximised.
|
||||||
|
*/
|
||||||
|
public class BoatGroup extends RaceObject{
|
||||||
|
|
||||||
|
//Constants for drawing
|
||||||
|
private static final double TEAMNAME_X_OFFSET = 10d;
|
||||||
|
private static final double TEAMNAME_Y_OFFSET = -15d;
|
||||||
|
private static final double VELOCITY_X_OFFSET = 10d;
|
||||||
|
private static final double VELOCITY_Y_OFFSET = -5d;
|
||||||
|
private static final double BOAT_HEIGHT = 15d;
|
||||||
|
private static final double BOAT_WIDTH = 10d;
|
||||||
|
//Variables for boat logic.
|
||||||
|
private Point2D lastPoint;
|
||||||
|
private int wakeGenerationDelay = 10;
|
||||||
|
private double distanceTravelled;
|
||||||
|
//Graphical objects
|
||||||
|
private Yacht boat;
|
||||||
|
private Group lineGroup = new Group();
|
||||||
|
private Polygon boatPoly;
|
||||||
|
private Text teamNameObject;
|
||||||
|
private Text velocityObject;
|
||||||
|
private Wake wake;
|
||||||
|
//Handles boat moving when connecting to a stream
|
||||||
|
private boolean setToInitialLocation = false;
|
||||||
|
private boolean destinationSet;
|
||||||
|
//Variables for handling minimization
|
||||||
|
private Stage stage;
|
||||||
|
private boolean isMaximized= true;
|
||||||
|
private List<Line> lineStorage = new ArrayList<>();
|
||||||
|
private int setCallCount = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BoatGroup with the default triangular boat polygon.
|
||||||
|
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
|
||||||
|
* BoatGroup to update.
|
||||||
|
* @param color The colour of the boat polygon and the trailing line.
|
||||||
|
*/
|
||||||
|
public BoatGroup (Yacht boat, Color color){
|
||||||
|
this.boat = boat;
|
||||||
|
initChildren(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0).
|
||||||
|
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
|
||||||
|
* BoatGroup to update.
|
||||||
|
* @param color The colour of the boat polygon and the trailing line.
|
||||||
|
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
|
||||||
|
*/
|
||||||
|
public BoatGroup (Yacht boat, Color color, double... points)
|
||||||
|
{
|
||||||
|
this.boat = boat;
|
||||||
|
initChildren(color, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the javafx objects that will be the in the group by default.
|
||||||
|
* @param color The colour of the boat polygon and the trailing line.
|
||||||
|
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
|
||||||
|
*/
|
||||||
|
private void initChildren (Color color, double... points) {
|
||||||
|
boatPoly = new Polygon(points);
|
||||||
|
boatPoly.setFill(color);
|
||||||
|
|
||||||
|
teamNameObject = new Text(boat.getShortName());
|
||||||
|
velocityObject = new Text(String.valueOf(boat.getVelocity()));
|
||||||
|
|
||||||
|
teamNameObject.setX(TEAMNAME_X_OFFSET);
|
||||||
|
teamNameObject.setY(TEAMNAME_Y_OFFSET);
|
||||||
|
teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY());
|
||||||
|
|
||||||
|
velocityObject.setX(VELOCITY_X_OFFSET);
|
||||||
|
velocityObject.setY(VELOCITY_Y_OFFSET);
|
||||||
|
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
|
||||||
|
destinationSet = false;
|
||||||
|
|
||||||
|
wake = new Wake(0, -BOAT_HEIGHT);
|
||||||
|
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the javafx objects that will be the in the group by default.
|
||||||
|
* @param color The colour of the boat polygon and the trailing line.
|
||||||
|
*/
|
||||||
|
private void initChildren (Color color) {
|
||||||
|
initChildren(color,
|
||||||
|
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||||
|
0.0, -BOAT_HEIGHT / 2,
|
||||||
|
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat and its children annotations from its current coordinates by specified amounts.
|
||||||
|
* @param dx The amount to move the X coordinate by
|
||||||
|
* @param dy The amount to move the Y coordinate by
|
||||||
|
*/
|
||||||
|
public void moveGroupBy(double dx, double dy, double rotation) {
|
||||||
|
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
||||||
|
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
||||||
|
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
|
||||||
|
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
|
||||||
|
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
|
||||||
|
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
|
||||||
|
wake.setLayoutX(wake.getLayoutX() + dx);
|
||||||
|
wake.setLayoutY(wake.getLayoutY() + dy);
|
||||||
|
rotateTo(rotation + currentRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat and its children annotations to coordinates specified
|
||||||
|
* @param x The X coordinate to move the boat to
|
||||||
|
* @param y The Y coordinate to move the boat to
|
||||||
|
* @param rotation The heading in degrees from north the boat should rotate to.
|
||||||
|
*/
|
||||||
|
public void moveTo (double x, double y, double rotation) {
|
||||||
|
rotateTo(rotation);
|
||||||
|
moveTo(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the boat and its children annotations to coordinates specified
|
||||||
|
* @param x The X coordinate to move the boat to
|
||||||
|
* @param y The Y coordinate to move the boat to
|
||||||
|
*/
|
||||||
|
public void moveTo (double x, double y) {
|
||||||
|
boatPoly.setLayoutX(x);
|
||||||
|
boatPoly.setLayoutY(y);
|
||||||
|
teamNameObject.setLayoutX(x);
|
||||||
|
teamNameObject.setLayoutY(y);
|
||||||
|
velocityObject.setLayoutX(x);
|
||||||
|
velocityObject.setLayoutY(y);
|
||||||
|
wake.setLayoutX(x);
|
||||||
|
wake.setLayoutY(y);
|
||||||
|
wake.rotate(currentRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the position of all graphics in the BoatGroup based off of the given time interval.
|
||||||
|
* @param timeInterval The interval, in milliseconds, the boat should update it's position based on.
|
||||||
|
*/
|
||||||
|
public void updatePosition (long timeInterval) {
|
||||||
|
//Calculate the movement of the boat.
|
||||||
|
if (isMaximized) {
|
||||||
|
double dx = pixelVelocityX * timeInterval;
|
||||||
|
double dy = pixelVelocityY * timeInterval;
|
||||||
|
double rotation = rotationalVelocity * timeInterval;
|
||||||
|
distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
||||||
|
moveGroupBy(dx, dy, rotation);
|
||||||
|
//Draw a new section of the trail every 20 pixels of movement.
|
||||||
|
if (distanceTravelled > 20) {
|
||||||
|
distanceTravelled = 0;
|
||||||
|
if (lastPoint != null) {
|
||||||
|
Line l = new Line(
|
||||||
|
lastPoint.getX(),
|
||||||
|
lastPoint.getY(),
|
||||||
|
boatPoly.getLayoutX(),
|
||||||
|
boatPoly.getLayoutY()
|
||||||
|
);
|
||||||
|
l.getStrokeDashArray().setAll(3d, 7d);
|
||||||
|
l.setStroke(boatPoly.getFill());
|
||||||
|
lineGroup.getChildren().add(l);
|
||||||
|
}
|
||||||
|
if (destinationSet) { //Only begin drawing after the first destination is set
|
||||||
|
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wake.updatePosition(timeInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the destination of the boat and the headng it should have once it reaches
|
||||||
|
* @param newXValue The X co-ordinate the boat needs to move to.
|
||||||
|
* @param newYValue The Y co-ordinate the boat needs to move to.
|
||||||
|
* @param rotation Rotation to move graphics to.
|
||||||
|
* @param raceIds RaceID of the object to move.
|
||||||
|
*/
|
||||||
|
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) {
|
||||||
|
if (hasRaceId(raceIds)) {
|
||||||
|
if (setToInitialLocation) {
|
||||||
|
destinationSet = true;
|
||||||
|
boat.setVelocity(groundSpeed);
|
||||||
|
if (currentRotation < 0)
|
||||||
|
currentRotation = 360 - currentRotation;
|
||||||
|
double dx = newXValue - boatPoly.getLayoutX();
|
||||||
|
double dy = newYValue - boatPoly.getLayoutY();
|
||||||
|
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
|
||||||
|
if (Math.abs(dx) > 50 || Math.abs(dy) > 50) {
|
||||||
|
dx = 0;
|
||||||
|
dy = 0;
|
||||||
|
moveTo(newXValue, newYValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
pixelVelocityX = dx / expectedUpdateInterval;
|
||||||
|
pixelVelocityY = dy / expectedUpdateInterval;
|
||||||
|
rotationalGoal = rotation;
|
||||||
|
calculateRotationalVelocity();
|
||||||
|
|
||||||
|
if (wakeGenerationDelay > 0) {
|
||||||
|
wake.rotate(rotationalGoal);
|
||||||
|
rotateTo(rotationalGoal); //Need to test with this removed.
|
||||||
|
rotationalVelocity = 0;
|
||||||
|
wakeGenerationDelay--;
|
||||||
|
} else {
|
||||||
|
wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, boat.getVelocity());
|
||||||
|
}
|
||||||
|
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
|
||||||
|
} else {
|
||||||
|
setToInitialLocation = true;
|
||||||
|
rotationalGoal = rotation;
|
||||||
|
moveTo(newXValue, newYValue, rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//If minimized generate lines every 5 calls to set destination.
|
||||||
|
if (!isMaximized) {
|
||||||
|
setToInitialLocation = false;
|
||||||
|
wakeGenerationDelay = 2;
|
||||||
|
if(setCallCount-- == 0) {
|
||||||
|
setCallCount = 5;
|
||||||
|
if (lastPoint != null) {
|
||||||
|
Line l = new Line(
|
||||||
|
lastPoint.getX(),
|
||||||
|
lastPoint.getY(),
|
||||||
|
newXValue,
|
||||||
|
newYValue
|
||||||
|
);
|
||||||
|
l.getStrokeDashArray().setAll(3d, 7d);
|
||||||
|
l.setStroke(boatPoly.getFill());
|
||||||
|
lineStorage.add(l);
|
||||||
|
}
|
||||||
|
if (destinationSet) { //Only begin drawing after the first destination is set
|
||||||
|
lastPoint = new Point2D(newXValue, newYValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination (double newXValue, double newYValue, double groundSpeed, 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, groundSpeed, raceIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotateTo (double rotation) {
|
||||||
|
currentRotation = rotation;
|
||||||
|
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forceRotation () {
|
||||||
|
rotateTo (rotationalGoal);
|
||||||
|
wake.rotate(rotationalGoal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTeamNameObjectVisible(Boolean visible) {
|
||||||
|
teamNameObject.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVelocityObjectVisible(Boolean visible) {
|
||||||
|
velocityObject.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineGroupVisible(Boolean visible) {
|
||||||
|
lineGroup.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWakeVisible(Boolean visible) {
|
||||||
|
wake.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Yacht getBoat() {
|
||||||
|
return boat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this BoatGroup contains at least one of the given IDs.
|
||||||
|
*
|
||||||
|
* @param raceIds The ID's to check the BoatGroup for.
|
||||||
|
* @return True if the BoatGroup contains at east one of the given IDs, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasRaceId (int... raceIds) {
|
||||||
|
for (int id : raceIds) {
|
||||||
|
if (id == boat.getSourceID())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
|
||||||
|
*
|
||||||
|
* @return An array containing all ID's associated with this RaceObject.
|
||||||
|
*/
|
||||||
|
public int[] getRaceIds () {
|
||||||
|
return new int[] {boat.getSourceID()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the
|
||||||
|
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function
|
||||||
|
* returns these annotations as a group.
|
||||||
|
*
|
||||||
|
* @return A group containing low priority annotations.
|
||||||
|
*/
|
||||||
|
public Group getLowPriorityAnnotations () {
|
||||||
|
Group group = new Group();
|
||||||
|
group.getChildren().addAll(wake, lineGroup);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this function to let the BoatGroup know about the stage it is in. If it knows about it's stage then it will
|
||||||
|
* listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the
|
||||||
|
* Stage there is guarantee that the BoatGroup will draw properly when the stage is minimized.
|
||||||
|
*
|
||||||
|
* @param stage The stage that the BoatGroup is added to.
|
||||||
|
*/
|
||||||
|
public void setStage (Stage stage) {
|
||||||
|
/* TODO: 4/05/17 cir27 - Find a way to get the stage to this point. Need to pass it through multiple controllers.
|
||||||
|
App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController
|
||||||
|
*/
|
||||||
|
this.stage = stage;
|
||||||
|
this.stage.iconifiedProperty().addListener(e -> {
|
||||||
|
isMaximized = !stage.isIconified();
|
||||||
|
if (!lineStorage.isEmpty()) {
|
||||||
|
lineGroup.getChildren().addAll(lineStorage);
|
||||||
|
lineStorage.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ryan_ on 16/03/2017.
|
||||||
|
*/
|
||||||
|
public enum Colors {
|
||||||
|
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE;
|
||||||
|
|
||||||
|
static Integer index = 0;
|
||||||
|
|
||||||
|
public static Color getColor() {
|
||||||
|
if (index == 6) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
return Color.valueOf(values()[index++].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event class containing the time of specific event, related team/boat, and
|
||||||
|
* event location such as leg.
|
||||||
|
*/
|
||||||
|
public class Event {
|
||||||
|
private Double time; // Time the event occurs
|
||||||
|
private Yacht boat;
|
||||||
|
private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race
|
||||||
|
private Mark mark1; // This mark
|
||||||
|
private Mark mark2; // Next mark
|
||||||
|
private int markPosInRace; // the position of the current mark in the race course
|
||||||
|
private double heading;
|
||||||
|
private final double ORIGIN_LAT = 32.320504;
|
||||||
|
private final double ORIGIN_LON = -64.857063;
|
||||||
|
private final double SCALE = 16000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event class containing the time of specific event, related team/boat, and
|
||||||
|
* event location such as leg.
|
||||||
|
*
|
||||||
|
* @param eventTime, what time the event happens
|
||||||
|
* @param eventBoat, the boat that the event belongs to
|
||||||
|
*/
|
||||||
|
public Event(Double eventTime, Yacht eventBoat, Mark mark1, Mark mark2, int markPosInRace) {
|
||||||
|
this.time = eventTime;
|
||||||
|
this.boat = eventBoat;
|
||||||
|
this.mark1 = mark1;
|
||||||
|
this.mark2 = mark2;
|
||||||
|
this.markPosInRace = markPosInRace;
|
||||||
|
this.heading = angleFromCoordinate(mark1, mark2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event class containing the time of specific event, related team/boat, and
|
||||||
|
* event location such as leg.
|
||||||
|
*
|
||||||
|
* @param eventTime, what time the event happens
|
||||||
|
* @param eventBoat, the boat that the event belongs to
|
||||||
|
*/
|
||||||
|
public Event(Double eventTime, Yacht eventBoat, Mark mark1, int markPosInRace) {
|
||||||
|
this.time = eventTime;
|
||||||
|
this.boat = eventBoat;
|
||||||
|
this.mark1 = mark1;
|
||||||
|
this.markPosInRace = markPosInRace;
|
||||||
|
this.isFinishingEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTime() {
|
||||||
|
return this.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(double eventTime) {
|
||||||
|
this.time = eventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the time in a formatted string
|
||||||
|
*
|
||||||
|
* @return the string of time
|
||||||
|
*/
|
||||||
|
public String getTimeString() {
|
||||||
|
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time.longValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Yacht getBoat() {
|
||||||
|
return this.boat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoat(Yacht eventBoat) {
|
||||||
|
this.boat = eventBoat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIsFinishingEvent() {
|
||||||
|
return this.isFinishingEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string that contains the timestamp and course information for this event
|
||||||
|
*
|
||||||
|
* @return A string that details what happened in this event
|
||||||
|
*/
|
||||||
|
public String getEventString() {
|
||||||
|
// This event is a boat finishing the race
|
||||||
|
if (this.isFinishingEvent) {
|
||||||
|
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " finished the race");
|
||||||
|
}
|
||||||
|
// System.out.println(this.getDistanceBetweenMarks());
|
||||||
|
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the distance between the two marks
|
||||||
|
*/
|
||||||
|
public double getDistanceBetweenMarks() {
|
||||||
|
double earth_radius = 6378.137;
|
||||||
|
double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180;
|
||||||
|
double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180;
|
||||||
|
|
||||||
|
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||||
|
|
||||||
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
double d = earth_radius * c;
|
||||||
|
|
||||||
|
return d * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates current boat heading direction.
|
||||||
|
* @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise.
|
||||||
|
*/
|
||||||
|
public double getBoatHeading() {
|
||||||
|
if (mark2 == null){
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE;
|
||||||
|
double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE;
|
||||||
|
double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE;
|
||||||
|
double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE;
|
||||||
|
|
||||||
|
double headingRadians = Math.atan2(y2-y1, x2-x1);
|
||||||
|
|
||||||
|
if (headingRadians < 0){
|
||||||
|
headingRadians += 2 * Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert back to degrees, and flip 180 degrees
|
||||||
|
// return ((headingRadians) * 180) / Math.PI;
|
||||||
|
return (Math.toDegrees(headingRadians) + 90) % 360;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the angle between to angular co-ordinates on a sphere.
|
||||||
|
*
|
||||||
|
* @param geoPointOne first geographical location
|
||||||
|
* @param geoPointTwo second geographical location
|
||||||
|
* @return the angle from point one to point two
|
||||||
|
*/
|
||||||
|
private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) {
|
||||||
|
if (geoPointTwo == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
double x1 = geoPointOne.getLatitude();
|
||||||
|
double y1 = -geoPointOne.getLongitude();
|
||||||
|
double x2 = geoPointTwo.getLatitude();
|
||||||
|
double y2 = -geoPointTwo.getLongitude();
|
||||||
|
|
||||||
|
return Math.toDegrees(Math.atan2(x2-x1, y2-y1));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getThisMark() {
|
||||||
|
return this.mark1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMarkPosInRace() {
|
||||||
|
return markPosInRace;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the leg of a race.
|
||||||
|
*/
|
||||||
|
public class Leg {
|
||||||
|
private int heading;
|
||||||
|
private int distance;
|
||||||
|
private boolean isFinishingLeg;
|
||||||
|
private SingleMark startingSingleMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new leg
|
||||||
|
*
|
||||||
|
* @param heading, the magnetic heading of this leg
|
||||||
|
* @param distance, the total distance of this leg in meters
|
||||||
|
* @param singleMark, the singleMark this leg starts on
|
||||||
|
*/
|
||||||
|
public Leg(int heading, int distance, SingleMark singleMark) {
|
||||||
|
this.heading = heading;
|
||||||
|
this.distance = distance;
|
||||||
|
this.startingSingleMark = singleMark;
|
||||||
|
this.isFinishingLeg = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new leg
|
||||||
|
*
|
||||||
|
* @param heading, the magnetic heading of this leg
|
||||||
|
* @param distance, the total distance of this leg in meters
|
||||||
|
* @param markerName, the name of the marker this leg starts on
|
||||||
|
*/
|
||||||
|
public Leg(int heading, int distance, String markerName) {
|
||||||
|
this.heading = heading;
|
||||||
|
this.distance = distance;
|
||||||
|
this.startingSingleMark = new SingleMark(markerName);
|
||||||
|
this.isFinishingLeg = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the heading of this leg
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public int getHeading() {
|
||||||
|
return this.heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the heading for this leg
|
||||||
|
* @param heading
|
||||||
|
*/
|
||||||
|
public void setHeading(int heading) {
|
||||||
|
this.heading = heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total distance of this leg in meters
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public int getDistance() {
|
||||||
|
return this.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the distance of this leg in meters
|
||||||
|
* @param distance
|
||||||
|
*/
|
||||||
|
public void setDistance(int distance) {
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the marker this leg started on
|
||||||
|
* @return SingleMark
|
||||||
|
*/
|
||||||
|
public SingleMark getMarker() {
|
||||||
|
return this.startingSingleMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the singleMark this leg starts on
|
||||||
|
* @param singleMark
|
||||||
|
*/
|
||||||
|
public void setMarker(SingleMark singleMark) {
|
||||||
|
this.startingSingleMark = singleMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the marker this leg started on
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
public String getMarkerLabel() {
|
||||||
|
return this.startingSingleMark.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify whether or not the race finishes on this leg
|
||||||
|
*
|
||||||
|
* @param isFinishingLeg whether or not the race finishes on this leg
|
||||||
|
*/
|
||||||
|
public void setFinishingLeg(boolean isFinishingLeg) {
|
||||||
|
this.isFinishingLeg = isFinishingLeg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the race finishes after this leg
|
||||||
|
* @return true if this the race finishes after this leg
|
||||||
|
*/
|
||||||
|
public boolean getIsFinishingLeg() {
|
||||||
|
return this.isFinishingLeg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Race class containing the boats and legs in the race
|
||||||
|
* Created by mra106 on 8/3/2017.
|
||||||
|
*/
|
||||||
|
public class Race {
|
||||||
|
|
||||||
|
private ArrayList<Yacht> boats; // The boats in the race
|
||||||
|
private ArrayList<Yacht> finishingOrder; // The order in which the boats finish the race
|
||||||
|
private HashMap<Yacht, List> events = new HashMap<>(); // The events that occur in the race
|
||||||
|
private List<Mark> course; // Marks in the race
|
||||||
|
private long startTime = 0;
|
||||||
|
private double timeScale = 1;
|
||||||
|
private boolean raceFinished = false; // Race is finished
|
||||||
|
private int raceTime = -2; // Current time in the race
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Race class containing the boats and legs in the race
|
||||||
|
*/
|
||||||
|
public Race() {
|
||||||
|
this.boats = new ArrayList<>();
|
||||||
|
this.finishingOrder = new ArrayList<>();
|
||||||
|
this.course = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a boat to the race
|
||||||
|
*
|
||||||
|
* @param boat, the boat to add
|
||||||
|
*/
|
||||||
|
public void addBoat(Yacht boat) {
|
||||||
|
boats.add(boat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of boats in a random order
|
||||||
|
*
|
||||||
|
* @return a list of boats
|
||||||
|
*/
|
||||||
|
public Yacht[] getShuffledBoats() {
|
||||||
|
// Shuffle the list of boats
|
||||||
|
long seed = System.nanoTime();
|
||||||
|
Collections.shuffle(this.boats, new Random(seed));
|
||||||
|
|
||||||
|
return boats.toArray(new Yacht[boats.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of boats in the order that they
|
||||||
|
* finished the race (position 0 is first place)
|
||||||
|
*
|
||||||
|
* @return a list of boats
|
||||||
|
*/
|
||||||
|
public Yacht[] getFinishedBoats() {
|
||||||
|
return this.finishingOrder.toArray(new Yacht[this.finishingOrder.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of boats in the race
|
||||||
|
*
|
||||||
|
* @return a list of the boats competing in the race
|
||||||
|
*/
|
||||||
|
public Yacht[] getBoats() {
|
||||||
|
return boats.toArray(new Yacht[boats.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets time scale
|
||||||
|
*
|
||||||
|
* @param timeScale
|
||||||
|
*/
|
||||||
|
public void setTimeScale(double timeScale) {
|
||||||
|
this.timeScale = timeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate all events that will happen during the race.
|
||||||
|
*/
|
||||||
|
private void generateEvents() {
|
||||||
|
|
||||||
|
for (Yacht boat : this.boats) {
|
||||||
|
double totalDistance = 0;
|
||||||
|
int numberOfMarks = this.course.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < numberOfMarks; i++) {
|
||||||
|
Double time = (totalDistance / boat.getVelocity() / timeScale);
|
||||||
|
|
||||||
|
// If there are singleMarks after this event
|
||||||
|
if (i < numberOfMarks - 1) {
|
||||||
|
Event event = new Event(time, boat, course.get(i), course.get(i + 1), i);
|
||||||
|
|
||||||
|
try {
|
||||||
|
events.get(boat).add(event);
|
||||||
|
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
events.put(boat, new ArrayList<>(Arrays.asList(event)));
|
||||||
|
}
|
||||||
|
totalDistance += event.getDistanceBetweenMarks();
|
||||||
|
//System.out.println(totalDistance);
|
||||||
|
//System.out.println(boat.getVelocity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no more marks after this event
|
||||||
|
|
||||||
|
else{
|
||||||
|
Event event = new Event(time, boat, course.get(i), i);
|
||||||
|
events.get(boat).add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a race and generates all events for the race.
|
||||||
|
*/
|
||||||
|
public void startRace() {
|
||||||
|
// record start time.
|
||||||
|
this.startTime = System.currentTimeMillis();
|
||||||
|
generateEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the race course
|
||||||
|
* @param course a list of marks in the course
|
||||||
|
*/
|
||||||
|
public void addCourse(List<Mark> course) {
|
||||||
|
this.course = course;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of marks in the course
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Mark> getCourse() {
|
||||||
|
return course;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map of the events in the race
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HashMap<Yacht, List> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a boat as finished
|
||||||
|
* @param boat The boat that has finished the race/home/cosc/student/wmu16
|
||||||
|
*/
|
||||||
|
public void setBoatFinished(Yacht boat){
|
||||||
|
this.finishingOrder.add(boat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the race as finished
|
||||||
|
*/
|
||||||
|
public void setRaceFinished(){
|
||||||
|
this.raceFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not the race is finished
|
||||||
|
* @return true if the race is finished
|
||||||
|
*/
|
||||||
|
public boolean isRaceFinished(){
|
||||||
|
return this.raceFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the race time
|
||||||
|
* @param raceTime the race time in seconds
|
||||||
|
*/
|
||||||
|
public void setRaceTime(int raceTime){
|
||||||
|
this.raceTime = raceTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the race time
|
||||||
|
* @return the race time in seconds
|
||||||
|
*/
|
||||||
|
public int getRaceTime(){
|
||||||
|
return this.raceTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the race time by one second
|
||||||
|
*/
|
||||||
|
public void incrementRaceTime(){
|
||||||
|
this.raceTime += this.timeScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RaceObject defines the behaviour that animated objects whose position is updated from a yacht race data stream must
|
||||||
|
* adhere to.
|
||||||
|
*/
|
||||||
|
public abstract class RaceObject extends Group {
|
||||||
|
|
||||||
|
//Time between sections of race
|
||||||
|
protected static double expectedUpdateInterval = 200;
|
||||||
|
|
||||||
|
protected double rotationalGoal;
|
||||||
|
protected double currentRotation;
|
||||||
|
protected double rotationalVelocity;
|
||||||
|
protected double pixelVelocityX;
|
||||||
|
protected double pixelVelocityY;
|
||||||
|
|
||||||
|
public Point2D getPosition () {
|
||||||
|
return new Point2D(super.getLayoutX(), getLayoutY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getExpectedUpdateInterval() {
|
||||||
|
return expectedUpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static void setExpectedUpdateInterval(double expectedUpdateInterval) {
|
||||||
|
RaceObject.expectedUpdateInterval = expectedUpdateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation.
|
||||||
|
*/
|
||||||
|
protected void calculateRotationalVelocity () {
|
||||||
|
if (Math.abs(rotationalGoal - currentRotation) > 180) {
|
||||||
|
if (rotationalGoal - currentRotation >= 0) {
|
||||||
|
this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval;
|
||||||
|
} else {
|
||||||
|
this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval;
|
||||||
|
}
|
||||||
|
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
|
||||||
|
if (Math.abs(rotationalVelocity) > 1) {
|
||||||
|
rotationalVelocity = 0;
|
||||||
|
rotateTo(rotationalGoal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) with the given rotation.
|
||||||
|
* @param x X 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 raceIds RaceID of the object to move.
|
||||||
|
*/
|
||||||
|
public abstract void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds);
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
* @param x X 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.
|
||||||
|
*/
|
||||||
|
public abstract void setDestination (double x, double y, double groundSpeed, int... raceIds);
|
||||||
|
|
||||||
|
public abstract void updatePosition (long timeInterval);
|
||||||
|
|
||||||
|
public abstract void moveTo (double x, double y, double rotation);
|
||||||
|
|
||||||
|
public abstract void moveTo (double x, double y);
|
||||||
|
|
||||||
|
public abstract void moveGroupBy(double x, double y, double rotation);
|
||||||
|
|
||||||
|
public abstract void rotateTo (double rotation);
|
||||||
|
|
||||||
|
public abstract boolean hasRaceId (int... raceIds);
|
||||||
|
|
||||||
|
public abstract int[] getRaceIds ();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by zyt10 on 17/03/17.
|
||||||
|
* this class is literally just to associate a timeline with a DoubleProperty x and y
|
||||||
|
*/
|
||||||
|
public class TimelineInfo {
|
||||||
|
private Timeline timeline;
|
||||||
|
private DoubleProperty x;
|
||||||
|
private DoubleProperty y;
|
||||||
|
|
||||||
|
public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) {
|
||||||
|
this.timeline = timeline;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timeline getTimeline() {
|
||||||
|
return timeline;
|
||||||
|
}
|
||||||
|
public DoubleProperty getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
public DoubleProperty getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Arc;
|
||||||
|
import javafx.scene.shape.ArcType;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more
|
||||||
|
* transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity().
|
||||||
|
* The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is
|
||||||
|
* assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs
|
||||||
|
* to be populated for the class to work as expected.
|
||||||
|
*/
|
||||||
|
class Wake extends Group {
|
||||||
|
|
||||||
|
private int numWakes = 5;
|
||||||
|
private double[] velocities = new double[13];
|
||||||
|
private Arc[] arcs = new Arc[numWakes];
|
||||||
|
private double[] rotations = new double[numWakes];
|
||||||
|
private int[] velocityIndices = new int[numWakes];
|
||||||
|
private double sum = 0;
|
||||||
|
private static double max;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wake at the given location.
|
||||||
|
* @param startingX x location where the tip of wake arcs will be.
|
||||||
|
* @param startingY y location where the tip of wake arcs will be.
|
||||||
|
*/
|
||||||
|
Wake(double startingX, double startingY) {
|
||||||
|
super.setLayoutX(startingX);
|
||||||
|
super.setLayoutY(startingY);
|
||||||
|
Arc arc;
|
||||||
|
for (int i = 0; i < numWakes; i++) {
|
||||||
|
//Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg.
|
||||||
|
arc = new Arc(0,0,0,0,-110,40);
|
||||||
|
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs.
|
||||||
|
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i));
|
||||||
|
arc.setType(ArcType.ROUND);
|
||||||
|
arcs[i] = arc;
|
||||||
|
}
|
||||||
|
super.getChildren().addAll(arcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
|
||||||
|
* the latest given velocity.
|
||||||
|
* @param rotationalVelocity The rotationalVelocity the wake should move at.
|
||||||
|
* @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is
|
||||||
|
* used to prevent desynchronisation with the Boat polygon.
|
||||||
|
* @param velocity The real world velocity of the boat in m/s.
|
||||||
|
*/
|
||||||
|
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) {
|
||||||
|
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]);
|
||||||
|
sum += Math.abs(rotationalVelocity);
|
||||||
|
max = Math.max(max, rotationalVelocity);
|
||||||
|
if (sum < (max / 3))
|
||||||
|
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position.
|
||||||
|
//This stops the wake from eventually becoming out of sync with the boat.
|
||||||
|
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough.
|
||||||
|
//Basically just for our internal mock.
|
||||||
|
if (Math.abs(rotationalVelocity) > 0.05) {
|
||||||
|
rotationalVelocity = 0;
|
||||||
|
rotate(rotationGoal);
|
||||||
|
}
|
||||||
|
//Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the
|
||||||
|
//next smallest wake.
|
||||||
|
velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13;
|
||||||
|
velocities[velocityIndices[0]] = rotationalVelocity;
|
||||||
|
for (int i = 1; i < numWakes; i++)
|
||||||
|
velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13;
|
||||||
|
|
||||||
|
//Scale wakes based on velocity.
|
||||||
|
double baseRad = 20;
|
||||||
|
double rad;
|
||||||
|
for (Arc arc :arcs) {
|
||||||
|
rad = baseRad + velocity;
|
||||||
|
arc.setRadiusX(rad);
|
||||||
|
arc.setRadiusY(rad);
|
||||||
|
baseRad += 5 + (velocity / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arcs rotate based on the distance they would have travelled over the supplied time interval.
|
||||||
|
* @param timeInterval the time interval, in microseconds, that the wake should move.
|
||||||
|
*/
|
||||||
|
void updatePosition (long timeInterval) {
|
||||||
|
for (int i = 0; i < numWakes; i++) {
|
||||||
|
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval;
|
||||||
|
arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate all wakes to the given rotation.
|
||||||
|
* @param rotation the from north angle in degrees to rotate to.
|
||||||
|
*/
|
||||||
|
void rotate (double rotation) {
|
||||||
|
for (int i = 0; i < arcs.length; i++) {
|
||||||
|
rotations[i] = rotation;
|
||||||
|
arcs[i].getTransforms().setAll(new Rotate(rotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat.
|
||||||
|
*
|
||||||
|
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
|
||||||
|
* also done outside Boat class because some old variables are not used anymore.
|
||||||
|
*/
|
||||||
|
public class Yacht {
|
||||||
|
private Color colour;
|
||||||
|
private double velocity;
|
||||||
|
private Integer markLastPast;
|
||||||
|
|
||||||
|
private String boatType;
|
||||||
|
private Integer sourceID;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
// Boat status
|
||||||
|
private Integer boatStatus;
|
||||||
|
private Integer legNumber;
|
||||||
|
private Integer penaltiesAwarded;
|
||||||
|
private Integer penaltiesServed;
|
||||||
|
private Long estimateTimeAtNextMark;
|
||||||
|
private Long estimateTimeAtFinish;
|
||||||
|
private String position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in EventTest and RaceTest.
|
||||||
|
*
|
||||||
|
* @param boatName Create a yacht object with name.
|
||||||
|
*/
|
||||||
|
public Yacht (String boatName) {
|
||||||
|
this.boatName = boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in BoatGroupTest.
|
||||||
|
*
|
||||||
|
* @param boatName The name of the team sailing the boat
|
||||||
|
* @param boatVelocity The speed of the boat in meters/second
|
||||||
|
* @param shortName A shorter version of the teams name
|
||||||
|
*/
|
||||||
|
public Yacht(String boatName, double boatVelocity, String shortName, int id) {
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.velocity = boatVelocity;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.sourceID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) {
|
||||||
|
this.boatType = boatType;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.hullID = hullID;
|
||||||
|
this.shortName = shortName;
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.country = country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatType() {
|
||||||
|
return boatType;
|
||||||
|
}
|
||||||
|
public Integer getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
public String getHullID() {
|
||||||
|
return hullID;
|
||||||
|
}
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
public String getCountry() {
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getBoatStatus() {
|
||||||
|
return boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatStatus(Integer boatStatus) {
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegNumber() {
|
||||||
|
return legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLegNumber(Integer legNumber) {
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPenaltiesAwarded() {
|
||||||
|
return penaltiesAwarded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPenaltiesAwarded(Integer penaltiesAwarded) {
|
||||||
|
this.penaltiesAwarded = penaltiesAwarded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPenaltiesServed() {
|
||||||
|
return penaltiesServed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPenaltiesServed(Integer penaltiesServed) {
|
||||||
|
this.penaltiesServed = penaltiesServed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getEstimateTimeAtNextMark() {
|
||||||
|
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
// return format.format(estimateTimeAtNextMark);
|
||||||
|
return estimateTimeAtNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
|
||||||
|
this.estimateTimeAtNextMark = estimateTimeAtNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEstimateTimeAtFinish() {
|
||||||
|
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
return format.format(estimateTimeAtFinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
|
||||||
|
this.estimateTimeAtFinish = estimateTimeAtFinish;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(String position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColour() {
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColour(Color colour) {
|
||||||
|
this.colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getVelocity() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVelocity(double velocity) {
|
||||||
|
this.velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMarkLastPast() {
|
||||||
|
return markLastPast;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkLastPast(Integer markLastPast) {
|
||||||
|
this.markLastPast = markLastPast;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To represent a gate mark which contains two single marks.
|
||||||
|
* Created by ptg19 on 16/03/17.
|
||||||
|
* Modified by Haoming Yin (hyi25) on 17/3/2017.
|
||||||
|
*/
|
||||||
|
public class GateMark extends Mark {
|
||||||
|
|
||||||
|
private SingleMark singleMark1;
|
||||||
|
private SingleMark singleMark2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of Gate Mark which contains two single mark
|
||||||
|
* @param name the name of the gate mark
|
||||||
|
* @param singleMark1 one single mark inside of the gate mark
|
||||||
|
* @param singleMark2 the second mark inside of the gate mark
|
||||||
|
*/
|
||||||
|
public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) {
|
||||||
|
super(name, type, latitude, longitude);
|
||||||
|
this.singleMark1 = singleMark1;
|
||||||
|
this.singleMark2 = singleMark2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleMark getSingleMark1() {
|
||||||
|
return singleMark1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleMark1(SingleMark singleMark1) {
|
||||||
|
this.singleMark1 = singleMark1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleMark getSingleMark2() {
|
||||||
|
return singleMark2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleMark2(SingleMark singleMark2) {
|
||||||
|
this.singleMark2 = singleMark2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLatitude(){
|
||||||
|
//return (this.getSingleMark1().getLatitude() + this.getSingleMark2().getLatitude()) / 2;
|
||||||
|
return (this.getSingleMark1().getLatitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLongitude(){
|
||||||
|
//return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2;
|
||||||
|
return (this.getSingleMark1().getLongitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public abstract class Mark {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private MarkType markType;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mark instance by passing its name and type
|
||||||
|
* @param name the name of the mark
|
||||||
|
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
|
||||||
|
*/
|
||||||
|
public Mark (String name, MarkType markType, int id) {
|
||||||
|
this.name = name;
|
||||||
|
this.markType = markType;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark(String name, MarkType markType, double latitude, double longitude) {
|
||||||
|
this.name = name;
|
||||||
|
this.markType = markType;
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculated the heading in radians from first Mark to the second Mark.
|
||||||
|
*
|
||||||
|
* @param pointOne First Mark
|
||||||
|
* @param pointTwo Second Mark
|
||||||
|
* @return Heading in radians
|
||||||
|
*/
|
||||||
|
public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) {
|
||||||
|
Double longitude1 = pointOne.getLongitude();
|
||||||
|
Double longitude2 = pointTwo.getLongitude();
|
||||||
|
Double latitude1 = pointOne.getLatitude();
|
||||||
|
Double latitude2 = pointTwo.getLatitude();
|
||||||
|
return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical
|
||||||
|
* latitude2, longitude 2
|
||||||
|
* @param longitude1 Longitude of first point in degrees
|
||||||
|
* @param longitude2 Longitude of second point in degrees
|
||||||
|
* @param latitude1 Latitude of first point in degrees
|
||||||
|
* @param latitude2 Latitude of first point in degrees
|
||||||
|
* @return Heading in radians
|
||||||
|
*/
|
||||||
|
public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
|
||||||
|
latitude1 = Math.toRadians(latitude1);
|
||||||
|
latitude2 = Math.toRadians(latitude2);
|
||||||
|
Double longDiff= Math.toRadians(longitude2-longitude1);
|
||||||
|
Double y = Math.sin(longDiff)*Math.cos(latitude2);
|
||||||
|
Double x = Math.cos(latitude1)*Math.sin(latitude2)-Math.sin(latitude1)*Math.cos(latitude2)*Math.cos(longDiff);
|
||||||
|
return Math.atan2(y, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the distance in meters from the first Mark to a second Mark
|
||||||
|
*
|
||||||
|
* @param pointOne First Mark
|
||||||
|
* @param pointTwo Second Mark
|
||||||
|
* @return Distance in meters
|
||||||
|
*/
|
||||||
|
public static Double calculateDistance(Mark pointOne, Mark pointTwo) {
|
||||||
|
Double longitude1 = pointOne.getLongitude();
|
||||||
|
Double longitude2 = pointTwo.getLongitude();
|
||||||
|
Double latitude1 = pointOne.getLatitude();
|
||||||
|
Double latitude2 = pointTwo.getLatitude();
|
||||||
|
return calculateDistance(latitude1, longitude1, latitude2, longitude2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical
|
||||||
|
* latitude2, longitude 2
|
||||||
|
*
|
||||||
|
* @param longitude1 Longitude of first point in degrees
|
||||||
|
* @param longitude2 Longitude of second point in degrees
|
||||||
|
* @param latitude1 Latitude of first point in degrees
|
||||||
|
* @param latitude2 Latitude of first point in degrees
|
||||||
|
* @return Distance in meters
|
||||||
|
*/
|
||||||
|
public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
|
||||||
|
Double theta = longitude1 - longitude2;
|
||||||
|
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
|
||||||
|
Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
|
||||||
|
Math.cos(Math.toRadians(theta));
|
||||||
|
dist = Math.acos(dist);
|
||||||
|
dist = Math.toDegrees(dist);
|
||||||
|
dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
|
||||||
|
dist = dist * 1609.344; //ratio of miles to metres
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkType getMarkType() {
|
||||||
|
return markType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkType(MarkType markType) {
|
||||||
|
this.markType = markType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLatitude() {
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLongitude() {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import javafx.scene.shape.Line;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import seng302.models.RaceObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by CJIRWIN on 26/04/2017.
|
||||||
|
*/
|
||||||
|
public class MarkGroup extends RaceObject {
|
||||||
|
|
||||||
|
private static int MARK_RADIUS = 5;
|
||||||
|
private static int LINE_THICKNESS = 2;
|
||||||
|
private static double DASHED_GAP_LEN = 2d;
|
||||||
|
private static double DASHED_LINE_LEN = 5d;
|
||||||
|
|
||||||
|
private List<Mark> marks = new ArrayList<>();
|
||||||
|
private Mark mainMark;
|
||||||
|
private double[] nodePixelVelocitiesX;
|
||||||
|
private double[] nodePixelVelocitiesY;
|
||||||
|
private Point2D[] nodeDestinations;
|
||||||
|
|
||||||
|
public MarkGroup (Mark mark, Point2D... points) {
|
||||||
|
nodePixelVelocitiesX = new double[points.length];
|
||||||
|
nodePixelVelocitiesY = new double[points.length];
|
||||||
|
nodeDestinations = new Point2D[points.length];
|
||||||
|
marks.add(mark);
|
||||||
|
mainMark = mark;
|
||||||
|
Color color = Color.BLACK;
|
||||||
|
if (mark.getName().equals("Start")){
|
||||||
|
color = Color.GREEN;
|
||||||
|
} else if (mark.getName().equals("Finish")){
|
||||||
|
color = Color.RED;
|
||||||
|
}
|
||||||
|
Circle markCircle;
|
||||||
|
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||||
|
markCircle = new Circle(
|
||||||
|
points[0].getX(),
|
||||||
|
points[0].getY(),
|
||||||
|
MARK_RADIUS,
|
||||||
|
color
|
||||||
|
);
|
||||||
|
nodeDestinations = new Point2D[]{
|
||||||
|
new Point2D(markCircle.getCenterX(), markCircle.getCenterY()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
super.getChildren().add(markCircle);
|
||||||
|
} else {
|
||||||
|
marks.add(((GateMark) mark).getSingleMark1());
|
||||||
|
marks.add(((GateMark) mark).getSingleMark2());
|
||||||
|
nodePixelVelocitiesX = new double[]{0d,0d};
|
||||||
|
nodePixelVelocitiesY = new double[]{0d,0d};
|
||||||
|
nodeDestinations = new Point2D[2];
|
||||||
|
|
||||||
|
markCircle = new Circle(
|
||||||
|
points[0].getX(),
|
||||||
|
points[0].getY(),
|
||||||
|
MARK_RADIUS,
|
||||||
|
color
|
||||||
|
);
|
||||||
|
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
|
||||||
|
super.getChildren().add(markCircle);
|
||||||
|
|
||||||
|
markCircle = new Circle(
|
||||||
|
points[1].getX(),
|
||||||
|
points[1].getY(),
|
||||||
|
MARK_RADIUS,
|
||||||
|
color
|
||||||
|
);
|
||||||
|
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
|
||||||
|
super.getChildren().add(markCircle);
|
||||||
|
Line line = new Line(
|
||||||
|
points[0].getX(),
|
||||||
|
points[0].getY(),
|
||||||
|
points[1].getX(),
|
||||||
|
points[1].getY()
|
||||||
|
);
|
||||||
|
line.setStrokeWidth(LINE_THICKNESS);
|
||||||
|
line.setStroke(color);
|
||||||
|
if (mark.getMarkType() == MarkType.OPEN_GATE) {
|
||||||
|
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
|
||||||
|
}
|
||||||
|
super.getChildren().add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) {
|
||||||
|
setDestination(x, y, 0, raceIds);
|
||||||
|
this.rotationalGoal = rotation;
|
||||||
|
calculateRotationalVelocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination (double x, double y, double groundSpeed, int... raceIds) {
|
||||||
|
for (int i = 0; i < marks.size(); i++)
|
||||||
|
for (int id : raceIds)
|
||||||
|
if (id == marks.get(i).getId())
|
||||||
|
setDestinationChild(x, y, 0, Math.max(0, i-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setDestinationChild (double x, double y, double speed, int childIndex) {
|
||||||
|
//double relativeX = x - super.getLayoutX();
|
||||||
|
//double relativeY = y - super.getLayoutY();
|
||||||
|
Circle markCircle = (Circle) super.getChildren().get(childIndex);
|
||||||
|
this.nodeDestinations[childIndex] = new Point2D(x, y);
|
||||||
|
//if (Math.abs(relativeX - markCircle.getCenterX()) > 30 && Math.abs(relativeY - markCircle.getCenterY()) > 30) {
|
||||||
|
this.nodePixelVelocitiesX[childIndex] = (x - markCircle.getCenterX()) / expectedUpdateInterval;
|
||||||
|
this.nodePixelVelocitiesY[childIndex] = (y - markCircle.getCenterY()) / expectedUpdateInterval;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotateTo (double rotation) {
|
||||||
|
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
|
||||||
|
Line line = (Line) super.getChildren().get(2);
|
||||||
|
double xCenter = Math.abs(line.getEndX() - line.getStartX());
|
||||||
|
double yCenter = Math.abs(line.getEndY() - line.getStartY());
|
||||||
|
super.getTransforms().setAll(new Rotate(rotation, xCenter, yCenter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePosition (long timeInterval) {
|
||||||
|
Circle markCircle = (Circle) super.getChildren().get(0);
|
||||||
|
|
||||||
|
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() ||
|
||||||
|
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY())
|
||||||
|
nodePixelVelocitiesX[0] = 0;
|
||||||
|
else if (nodePixelVelocitiesX[0] != 0)
|
||||||
|
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval);
|
||||||
|
|
||||||
|
if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() ||
|
||||||
|
nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY())
|
||||||
|
nodePixelVelocitiesY[0] = 0;
|
||||||
|
else if (nodePixelVelocitiesY[0] != 0)
|
||||||
|
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval);
|
||||||
|
|
||||||
|
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
|
||||||
|
|
||||||
|
Line line = (Line) super.getChildren().get(2);
|
||||||
|
line.setStartX(markCircle.getCenterX());
|
||||||
|
line.setStartY(markCircle.getCenterY());
|
||||||
|
|
||||||
|
markCircle = (Circle) super.getChildren().get(1);
|
||||||
|
|
||||||
|
if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() ||
|
||||||
|
nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX())
|
||||||
|
nodePixelVelocitiesX[1] = 0;
|
||||||
|
else if (nodePixelVelocitiesX[1] != 0)
|
||||||
|
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval);
|
||||||
|
|
||||||
|
if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() ||
|
||||||
|
nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY())
|
||||||
|
nodePixelVelocitiesY[1] = 0;
|
||||||
|
else if (nodePixelVelocitiesY[1] != 0)
|
||||||
|
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval);
|
||||||
|
line.setEndX(markCircle.getCenterX());
|
||||||
|
line.setEndY(markCircle.getCenterY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveGroupBy (double x, double y, double rotation) {
|
||||||
|
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
|
||||||
|
Line line = (Line) super.getChildren().get(2);
|
||||||
|
for (int childIndex = 0; childIndex < 2; childIndex++){
|
||||||
|
Circle mark = (Circle) super.getChildren().get(childIndex);
|
||||||
|
mark.setCenterY(mark.getCenterY() + y);
|
||||||
|
mark.setCenterX(mark.getCenterX() + x);
|
||||||
|
}
|
||||||
|
line.setStartX(line.getStartX() + x);
|
||||||
|
line.setStartY(line.getStartY() + y);
|
||||||
|
line.setEndX(line.getEndX() + x);
|
||||||
|
line.setEndY(line.getEndY() + y);
|
||||||
|
} else {
|
||||||
|
Circle mark = (Circle) super.getChildren().get(0);
|
||||||
|
mark.setCenterY(mark.getCenterY() + y);
|
||||||
|
mark.setCenterX(mark.getCenterX() + x);
|
||||||
|
}
|
||||||
|
rotateTo(currentRotation + rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveTo (double x, double y, double rotation) {
|
||||||
|
moveTo(x, y);
|
||||||
|
rotateTo(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveTo (double x, double y) {
|
||||||
|
Circle markCircle = (Circle) super.getChildren().get(0);
|
||||||
|
markCircle.setCenterX(x);
|
||||||
|
markCircle.setCenterY(y);
|
||||||
|
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
|
||||||
|
markCircle = (Circle) super.getChildren().get(1);
|
||||||
|
markCircle.setCenterX(x);
|
||||||
|
markCircle.setCenterY(y);
|
||||||
|
Line line = (Line) super.getChildren().get(2);
|
||||||
|
line.setStartX(x);
|
||||||
|
line.setStartY(y);
|
||||||
|
line.setEndX(x);
|
||||||
|
line.setEndY(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRaceId (int... raceIds) {
|
||||||
|
for (int id : raceIds)
|
||||||
|
for (Mark mark : marks)
|
||||||
|
if (id == mark.getId())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMarkRadius() {
|
||||||
|
return MARK_RADIUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMarkRadius(int markRadius) {
|
||||||
|
MARK_RADIUS = markRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getRaceIds () {
|
||||||
|
int[] idArray = new int[marks.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Mark mark : marks)
|
||||||
|
idArray[i++] = mark.getId();
|
||||||
|
return idArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To represent two types of mark
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public enum MarkType {
|
||||||
|
SINGLE_MARK, OPEN_GATE, CLOSED_GATE
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the marker as a single mark
|
||||||
|
*
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/2017
|
||||||
|
*/
|
||||||
|
public class SingleMark extends Mark {
|
||||||
|
private double lat;
|
||||||
|
private double lon;
|
||||||
|
private String name;
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a marker
|
||||||
|
*
|
||||||
|
* @param name, the name of the marker*
|
||||||
|
* @param lat, the latitude of the marker
|
||||||
|
* @param lon, the longitude of the marker
|
||||||
|
*/
|
||||||
|
public SingleMark(String name, double lat, double lon, int id) {
|
||||||
|
super(name, MarkType.SINGLE_MARK, id);
|
||||||
|
this.lat = lat;
|
||||||
|
this.lon = lon;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the marker at the beginning of a leg
|
||||||
|
*
|
||||||
|
* @param name, the name of the marker
|
||||||
|
*/
|
||||||
|
public SingleMark(String name) {
|
||||||
|
super(name, MarkType.SINGLE_MARK, 0);
|
||||||
|
this.lat = 0;
|
||||||
|
this.lon = 0;
|
||||||
|
this.id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLatitude() {
|
||||||
|
return this.lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLongitude() {
|
||||||
|
return this.lon;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import java.util.DoubleSummaryStatistics;
|
||||||
|
|
||||||
|
public class ConfigParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
|
||||||
|
public ConfigParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets wind direction from config file.
|
||||||
|
*
|
||||||
|
* @return a double type degree, or 0 if no value or invalid value is found
|
||||||
|
*/
|
||||||
|
public double getWindDirection() {
|
||||||
|
return getDoubleByTagName("wind-direction", 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a non negative time scale for the race
|
||||||
|
*
|
||||||
|
* @return a double type scale, or 0 if no scale or invalid scale is found
|
||||||
|
*/
|
||||||
|
public double getTimeScale() {
|
||||||
|
return getDoubleByTagName("time-scale", 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a double type number by given tag name found in xml file
|
||||||
|
*
|
||||||
|
* @param tagName a string of tag name
|
||||||
|
* @param defaultVal value returned if no value or invalid value is found
|
||||||
|
* @return value found
|
||||||
|
*/
|
||||||
|
public double getDoubleByTagName(String tagName, double defaultVal) {
|
||||||
|
double val = defaultVal;
|
||||||
|
try {
|
||||||
|
Node node = this.doc.getElementsByTagName(tagName).item(0);
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
val = Double.valueOf(element.getTextContent());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
} finally {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a string by given tag name found in xml file
|
||||||
|
*
|
||||||
|
* @param tagName a string of tag name
|
||||||
|
* @param defaultVal a string returned if no value or invalid value is found
|
||||||
|
* @return string found
|
||||||
|
*/
|
||||||
|
public String getStringByTagName(String tagName, String defaultVal) {
|
||||||
|
String string = defaultVal;
|
||||||
|
try {
|
||||||
|
Node node = this.doc.getElementsByTagName(tagName).item(0);
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
string = element.getTextContent();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
} finally {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.*;
|
||||||
|
import seng302.models.mark.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse a course xml file
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class CourseParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private HashMap<String, Mark> marks = new HashMap<>();
|
||||||
|
|
||||||
|
public CourseParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a mark by given node
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @return a mark, or null if fails to create a mark
|
||||||
|
*/
|
||||||
|
private SingleMark generateSingleMark(Node node) {
|
||||||
|
try {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
String name = element.getElementsByTagName("name").item(0).getTextContent();
|
||||||
|
double lat = Double.valueOf(element.getElementsByTagName("latitude").item(0).getTextContent());
|
||||||
|
double lon = Double.valueOf(element.getElementsByTagName("longitude").item(0).getTextContent());
|
||||||
|
int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
|
||||||
|
SingleMark singleMark = new SingleMark(name, lat, lon, id);
|
||||||
|
return singleMark;
|
||||||
|
} else {
|
||||||
|
throw new NoSuchElementException("Cannot generate a mark by given node.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate an arrayList of gates
|
||||||
|
*
|
||||||
|
* @return an arrayList of gates, or null if no gate has been found.
|
||||||
|
*/
|
||||||
|
private void generateGateMarks() {
|
||||||
|
ArrayList<GateMark> gateMarks = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
NodeList nodes = doc.getElementsByTagName("gate");
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
Node node = nodes.item(i);
|
||||||
|
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
String name = element.getElementsByTagName("name").item(0).getTextContent();
|
||||||
|
SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0));
|
||||||
|
SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1));
|
||||||
|
GateMark gateMark;
|
||||||
|
if (name.equals("Start") || name.equals("Finish"))
|
||||||
|
gateMark = new GateMark(name, MarkType.CLOSED_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
|
||||||
|
else
|
||||||
|
gateMark = new GateMark(name, MarkType.OPEN_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
|
||||||
|
marks.put(name, gateMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate an arrayList of marks
|
||||||
|
*
|
||||||
|
* @return an arrayList of marks, or null if no gate has been found.
|
||||||
|
*/
|
||||||
|
private void generateSingleMarks() {
|
||||||
|
ArrayList<SingleMark> singleMarks = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// find the "marks" tag
|
||||||
|
Node node = doc.getElementsByTagName("marks").item(0);
|
||||||
|
// iterate all "marks"'s children
|
||||||
|
for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
|
||||||
|
// if node's tag name is "mark"
|
||||||
|
if (n.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) n;
|
||||||
|
if (element.getNodeName() == "mark") {
|
||||||
|
Mark mark = generateSingleMark(n);
|
||||||
|
marks.put(mark.getName(), mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the order of all the marks along a course
|
||||||
|
*
|
||||||
|
* @return an arrayList of the names of ordered course marks
|
||||||
|
*/
|
||||||
|
private ArrayList<String> getOrder() {
|
||||||
|
ArrayList<String> markOrder = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Node orderNode = doc.getElementsByTagName("order").item(0);
|
||||||
|
for (Node node = orderNode.getFirstChild(); node != null; node = node.getNextSibling()) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
String name = element.getTextContent();
|
||||||
|
markOrder.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return markOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Mark> getCourse() {
|
||||||
|
generateSingleMarks();
|
||||||
|
generateGateMarks();
|
||||||
|
ArrayList<Mark> course = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
for (String mark : getOrder()) {
|
||||||
|
course.add(marks.get(mark));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return course;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
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,53 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Kusal on 4/24/2017.
|
||||||
|
*/
|
||||||
|
public enum PacketType {
|
||||||
|
HEARTBEAT,
|
||||||
|
RACE_STATUS,
|
||||||
|
DISPLAY_TEXT_MESSAGE,
|
||||||
|
XML_MESSAGE,
|
||||||
|
RACE_START_STATUS,
|
||||||
|
YACHT_EVENT_CODE,
|
||||||
|
YACHT_ACTION_CODE,
|
||||||
|
CHATTER_TEXT,
|
||||||
|
BOAT_LOCATION,
|
||||||
|
MARK_ROUNDING,
|
||||||
|
COURSE_WIND,
|
||||||
|
AVG_WIND,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
static PacketType assignPacketType(int packetType){
|
||||||
|
switch(packetType){
|
||||||
|
case 1:
|
||||||
|
return HEARTBEAT;
|
||||||
|
case 12:
|
||||||
|
return RACE_STATUS;
|
||||||
|
case 20:
|
||||||
|
return DISPLAY_TEXT_MESSAGE;
|
||||||
|
case 26:
|
||||||
|
return XML_MESSAGE;
|
||||||
|
case 27:
|
||||||
|
return RACE_START_STATUS;
|
||||||
|
case 29:
|
||||||
|
return YACHT_EVENT_CODE;
|
||||||
|
case 31:
|
||||||
|
return YACHT_ACTION_CODE;
|
||||||
|
case 36:
|
||||||
|
return CHATTER_TEXT;
|
||||||
|
case 37:
|
||||||
|
return BOAT_LOCATION;
|
||||||
|
case 38:
|
||||||
|
return MARK_ROUNDING;
|
||||||
|
case 44:
|
||||||
|
return COURSE_WIND;
|
||||||
|
case 47:
|
||||||
|
return AVG_WIND;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 23/04/17.
|
||||||
|
*/
|
||||||
|
public class StreamPacket {
|
||||||
|
|
||||||
|
//Change int to an ENUM for the type
|
||||||
|
private PacketType type;
|
||||||
|
|
||||||
|
private long messageLength;
|
||||||
|
private long timeStamp;
|
||||||
|
private byte[] payload;
|
||||||
|
|
||||||
|
StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
||||||
|
this.type = PacketType.assignPacketType(type);
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
this.payload = payload;
|
||||||
|
// System.out.println("type = " + this.type.toString());
|
||||||
|
//switch the packet type to deal with what ever specific packet you want to deal with
|
||||||
|
// if (this.type == PacketType.XML_MESSAGE){
|
||||||
|
// //System.out.println("--------");
|
||||||
|
// System.out.println(new String(payload));
|
||||||
|
// //StreamParser.parsePacket(this);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMessageLength() {
|
||||||
|
return messageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,583 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.parsers.packets.BoatPositionPacket;
|
||||||
|
import seng302.models.parsers.packets.StreamPacket;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of this class is to take in the stream of divided packets so they can be read
|
||||||
|
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
|
||||||
|
* that are threadsafe so the visualiser can always access the latest speed and position available
|
||||||
|
* Created by kre39 on 23/04/17.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class StreamParser extends Thread{
|
||||||
|
|
||||||
|
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatPositions = new ConcurrentHashMap<>();
|
||||||
|
private String threadName;
|
||||||
|
private Thread t;
|
||||||
|
private static boolean raceStarted = false;
|
||||||
|
private static XMLParser xmlObject;
|
||||||
|
private static boolean raceFinished = false;
|
||||||
|
private static boolean streamStatus = false;
|
||||||
|
private static long timeSinceStart = -1;
|
||||||
|
private static Map<Integer, Yacht> boats = new HashMap<>();
|
||||||
|
private static Map<Long, Yacht> boatsPos = new TreeMap<>();
|
||||||
|
private static double windDirection = 0;
|
||||||
|
private static String currentTimeString;
|
||||||
|
private static boolean appRunning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to initialise the thread name and stream parser object so a thread can be executed
|
||||||
|
*
|
||||||
|
* @param threadName name of the thread
|
||||||
|
*/
|
||||||
|
public StreamParser(String threadName){
|
||||||
|
this.threadName = threadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to within threading so when the stream parser thread runs, it will keep looking for a packet to
|
||||||
|
* process until it is unable to find anymore packets
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
appRunning = true;
|
||||||
|
try {
|
||||||
|
System.out.println("[CLIENT] Start of stream");
|
||||||
|
streamStatus = true;
|
||||||
|
xmlObject = new XMLParser();
|
||||||
|
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
while (appRunning){
|
||||||
|
StreamPacket packet = StreamReceiver.packetBuffer.peek();
|
||||||
|
//this code adds a delay to reading from the packetBuffer so
|
||||||
|
//out of order packets have time to order themselves in the queue
|
||||||
|
int delayTime = 1000;
|
||||||
|
int loopTime = delayTime * 10;
|
||||||
|
long transitTime = (System.currentTimeMillis()%loopTime - packet.getTimeStamp()%loopTime);
|
||||||
|
if (transitTime < 0){
|
||||||
|
transitTime = loopTime + transitTime;
|
||||||
|
}
|
||||||
|
if (transitTime < delayTime) {
|
||||||
|
long sleepTime = delayTime - (transitTime);
|
||||||
|
Thread.sleep(sleepTime);
|
||||||
|
}
|
||||||
|
packet = StreamReceiver.packetBuffer.take();
|
||||||
|
parsePacket(packet);
|
||||||
|
Thread.sleep(1);
|
||||||
|
while (StreamReceiver.packetBuffer.peek() == null) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to start the stream parser thread when multithreading
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void start () {
|
||||||
|
System.out.println("[CLIENT] Starting " + threadName );
|
||||||
|
if (t == null) {
|
||||||
|
t = new Thread (this, threadName);
|
||||||
|
t.start ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks at the type of the packet then sends it to the appropriate parser to extract the
|
||||||
|
* specific data associated with that packet type
|
||||||
|
*
|
||||||
|
* @param packet the packet to be looked at and processed
|
||||||
|
*/
|
||||||
|
private static void parsePacket(StreamPacket packet) {
|
||||||
|
try{
|
||||||
|
switch (packet.getType()){
|
||||||
|
case HEARTBEAT:
|
||||||
|
extractHeartBeat(packet);
|
||||||
|
break;
|
||||||
|
case RACE_STATUS:
|
||||||
|
extractRaceStatus(packet);
|
||||||
|
break;
|
||||||
|
case DISPLAY_TEXT_MESSAGE:
|
||||||
|
extractDisplayMessage(packet);
|
||||||
|
break;
|
||||||
|
case XML_MESSAGE:
|
||||||
|
extractXmlMessage(packet);
|
||||||
|
break;
|
||||||
|
case RACE_START_STATUS:
|
||||||
|
extractRaceStartStatus(packet);
|
||||||
|
break;
|
||||||
|
case YACHT_EVENT_CODE:
|
||||||
|
extractYachtEventCode(packet);
|
||||||
|
break;
|
||||||
|
case YACHT_ACTION_CODE:
|
||||||
|
extractYachtActionCode(packet);
|
||||||
|
break;
|
||||||
|
case CHATTER_TEXT:
|
||||||
|
extractChatterText(packet);
|
||||||
|
break;
|
||||||
|
case BOAT_LOCATION:
|
||||||
|
extractBoatLocation(packet);
|
||||||
|
break;
|
||||||
|
case MARK_ROUNDING:
|
||||||
|
extractMarkRounding(packet);
|
||||||
|
break;
|
||||||
|
case COURSE_WIND:
|
||||||
|
extractCourseWind(packet);
|
||||||
|
break;
|
||||||
|
case AVG_WIND:
|
||||||
|
extractAvgWind(packet);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
//System.out.println(packet.getType().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NullPointerException e){
|
||||||
|
System.out.println("Error parsing packet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the seq num used in the heartbeat packet
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractHeartBeat(StreamPacket packet) {
|
||||||
|
long heartbeat = bytesToLong(packet.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTimeZoneString() {
|
||||||
|
|
||||||
|
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
|
||||||
|
StringBuilder utcOffset = new StringBuilder();
|
||||||
|
utcOffset.append("GMT");
|
||||||
|
if (offset > 0) {
|
||||||
|
utcOffset.append("+");
|
||||||
|
utcOffset.append(offset);
|
||||||
|
} else if (offset < 0) {
|
||||||
|
utcOffset.append("-");
|
||||||
|
utcOffset.append(offset);
|
||||||
|
}
|
||||||
|
return utcOffset.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the useful race status data from race status type packets. This method will also print to the
|
||||||
|
* console the current state of the race (if it has started/finished or is about to start), along side
|
||||||
|
* this it'll also display the amount of time since the race has started or time till it starts
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractRaceStatus(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long currentTime = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11));
|
||||||
|
int raceStatus = payload[11];
|
||||||
|
// System.out.println("raceStatus = " + raceStatus);
|
||||||
|
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
|
||||||
|
|
||||||
|
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
if (xmlObject.getRegattaXML() != null) {
|
||||||
|
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
|
||||||
|
currentTimeString = format.format((new Date (currentTime)).getTime());
|
||||||
|
}
|
||||||
|
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
|
||||||
|
|
||||||
|
|
||||||
|
if (timeTillStart > 0) {
|
||||||
|
timeSinceStart = timeTillStart;
|
||||||
|
//System.out.println("Time till start: " + timeTillStart + " Seconds");
|
||||||
|
} else {
|
||||||
|
if (raceStatus == 4 || raceStatus == 8){
|
||||||
|
raceFinished = true;
|
||||||
|
raceStarted = false;
|
||||||
|
System.out.println("[CLIENT] Race has finished");
|
||||||
|
} else if (!raceStarted){
|
||||||
|
raceStarted = true;
|
||||||
|
raceFinished = false;
|
||||||
|
System.out.println("[CLIENT] Race has started");
|
||||||
|
}
|
||||||
|
//System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
|
||||||
|
timeSinceStart = timeTillStart;
|
||||||
|
}
|
||||||
|
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
||||||
|
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
|
||||||
|
windDirection = windDir / windDirFactor;
|
||||||
|
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
||||||
|
int noBoats = payload[22];
|
||||||
|
int raceType = payload[23];
|
||||||
|
// ArrayList<String> boatStatuses = new ArrayList<>();
|
||||||
|
boatsPos = new TreeMap<>();
|
||||||
|
for (int i = 0; i < noBoats; i++){
|
||||||
|
Long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20)));
|
||||||
|
Yacht boat = boats.get((int)(long) boatStatusSourceID);
|
||||||
|
boat.setBoatStatus((int)payload[28 + (i * 20)]);
|
||||||
|
boat.setLegNumber((int)payload[29 + (i * 20)]);
|
||||||
|
boat.setPenaltiesAwarded((int)payload[29 + (i * 20)]);
|
||||||
|
boat.setPenaltiesServed((int)payload[30 + (i * 20)]);
|
||||||
|
Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
|
||||||
|
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||||
|
Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
|
||||||
|
boat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||||
|
boatsPos.put(estTimeAtFinish, boat);
|
||||||
|
// String boatStatus = "SourceID: " + boatStatusSourceID;
|
||||||
|
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
|
||||||
|
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
|
||||||
|
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
|
||||||
|
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
|
||||||
|
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
|
||||||
|
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
|
||||||
|
// boatStatuses.add(boatStatus);
|
||||||
|
}
|
||||||
|
if (isRaceStarted()) {
|
||||||
|
int pos = 1;
|
||||||
|
for (Yacht yacht : boatsPos.values()) {
|
||||||
|
yacht.setPosition(String.valueOf(pos));
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Yacht yacht : boatsPos.values()) {
|
||||||
|
yacht.setPosition("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to extract the messages passed through with the display message packet
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractDisplayMessage(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
int numOfLines = payload[3];
|
||||||
|
int totalLen = 0;
|
||||||
|
for (int i = 0; i < numOfLines; i++){
|
||||||
|
int lineNum = payload[4 + totalLen];
|
||||||
|
int textLength = payload[5 + totalLen];
|
||||||
|
byte[] messageTextBytes = Arrays.copyOfRange(payload,6 + totalLen,6 + textLength + totalLen);
|
||||||
|
String messageText = new String(messageTextBytes);
|
||||||
|
totalLen += 2 + textLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to read in the xml data. Will call the specific methods to create the course and boats
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractXmlMessage(StreamPacket packet){
|
||||||
|
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
|
||||||
|
int messageType = payload[9];
|
||||||
|
long messagelength = bytesToLong(Arrays.copyOfRange(payload,12,14));
|
||||||
|
String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messagelength)))).trim();
|
||||||
|
//System.out.println("xmlMessage2 = " + xmlMessage);
|
||||||
|
|
||||||
|
//Create XML document Object
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder db = null;
|
||||||
|
Document doc = null;
|
||||||
|
try {
|
||||||
|
db = dbf.newDocumentBuilder();
|
||||||
|
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
|
||||||
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlObject.constructXML(doc, messageType);
|
||||||
|
if (messageType == 7) { //7 is the boat XML
|
||||||
|
boats = xmlObject.getBoatXML().getCompetingBoats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the race start status from the packet, currently is unused within the app but
|
||||||
|
* is here for potential future use
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractRaceStartStatus(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload,9,15));
|
||||||
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,15,19));
|
||||||
|
int notificationType = payload[19];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
|
||||||
|
* currently unused
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractYachtEventCode(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
||||||
|
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
||||||
|
long incidentId = bytesToLong(Arrays.copyOfRange(payload,17,21));
|
||||||
|
int eventId = payload[21];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary info,
|
||||||
|
* currently unused
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractYachtActionCode(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long subjectId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
||||||
|
long incidentId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
||||||
|
int eventId = payload[17];
|
||||||
|
// System.out.println("eventId = " + eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips the message from the chatter text type packets, currently the message is unused
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractChatterText(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
int messageType = payload[1];
|
||||||
|
int length = payload[2];
|
||||||
|
String message = new String(Arrays.copyOfRange(payload,3,3 + length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are all used
|
||||||
|
* All the other extra data is still being read and translated however is unused.
|
||||||
|
*
|
||||||
|
* @param packet Packet parsed in to use the payload
|
||||||
|
*/
|
||||||
|
private static void extractBoatLocation(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
|
||||||
|
int deviceType = (int)payload[15];
|
||||||
|
long timeValid = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long seq = bytesToLong(Arrays.copyOfRange(payload,11,15));
|
||||||
|
long boatId = bytesToLong(Arrays.copyOfRange(payload,7,11));
|
||||||
|
long rawLat = bytesToLong(Arrays.copyOfRange(payload,16,20));
|
||||||
|
long rawLon = bytesToLong(Arrays.copyOfRange(payload,20,24));
|
||||||
|
//Converts the double to a usable lat/lon
|
||||||
|
double lat = ((180d * (double)rawLat)/Math.pow(2,31));
|
||||||
|
double lon = ((180d *(double)rawLon)/Math.pow(2,31));
|
||||||
|
long heading = bytesToLong(Arrays.copyOfRange(payload,28,30));
|
||||||
|
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload,38,40))/1000.0;
|
||||||
|
|
||||||
|
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
|
||||||
|
if (deviceType == 1 || deviceType == 3){
|
||||||
|
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
|
||||||
|
|
||||||
|
//add a new priority que to the boatPositions HashMap
|
||||||
|
if (!boatPositions.containsKey(boatId)){
|
||||||
|
boatPositions.put(boatId, new PriorityBlockingQueue<BoatPositionPacket>(256, new Comparator<BoatPositionPacket>() {
|
||||||
|
@Override
|
||||||
|
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
||||||
|
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
//Adding the boatPacket to the priority que
|
||||||
|
boatPositions.get(boatId).put(boatPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This packet type is received when a mark or gate is rounded by a boat
|
||||||
|
*
|
||||||
|
* @param packet The packet containing the payload
|
||||||
|
*/
|
||||||
|
private static void extractMarkRounding(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13));
|
||||||
|
long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17));
|
||||||
|
int boatStatus = payload[17];
|
||||||
|
int roundingSide = payload[18];
|
||||||
|
int markType = payload[19];
|
||||||
|
int markId = payload[20];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This packet type contains periodic data on the state of the wind
|
||||||
|
*
|
||||||
|
* @param packet The packet containing the payload
|
||||||
|
*/
|
||||||
|
private static void extractCourseWind(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
int selectedWindId = payload[1];
|
||||||
|
int loopCount = payload[2];
|
||||||
|
ArrayList<String> windInfo = new ArrayList<>();
|
||||||
|
for (int i = 0; i < loopCount; i++){
|
||||||
|
String wind = "WindId: " + payload[3 + (20 * i)];
|
||||||
|
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 += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i)));
|
||||||
|
wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i)));
|
||||||
|
wind += "\nBestUpWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,18 + (20 * i),20 + (20 * i)));
|
||||||
|
wind += "\nBestDownWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,20 + (20 * i),22 + (20 * i)));
|
||||||
|
wind += "\nFlags: " + String.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)).replace(' ', '0');
|
||||||
|
windInfo.add(wind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This packet conatins the average wind to ground speed
|
||||||
|
*
|
||||||
|
* @param packet The packet containing the paylaod
|
||||||
|
*/
|
||||||
|
private static void extractAvgWind(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
|
||||||
|
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload,7,9));
|
||||||
|
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload,9,11));
|
||||||
|
long period2 = bytesToLong(Arrays.copyOfRange(payload,11,13));
|
||||||
|
long speed2 = bytesToLong(Arrays.copyOfRange(payload,13,15));
|
||||||
|
long period3 = bytesToLong(Arrays.copyOfRange(payload,15,17));
|
||||||
|
long speed3 = bytesToLong(Arrays.copyOfRange(payload,17,19));
|
||||||
|
long period4 = bytesToLong(Arrays.copyOfRange(payload,19,21));
|
||||||
|
long speed4 = bytesToLong(Arrays.copyOfRange(payload,21,23));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes an array of up to 7 bytes and returns a positive
|
||||||
|
* long constructed from the input bytes
|
||||||
|
*
|
||||||
|
* @return a positive long if there is less than 7 bytes -1 otherwise
|
||||||
|
*/
|
||||||
|
private static long bytesToLong(byte[] bytes){
|
||||||
|
long partialLong = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (byte b: bytes){
|
||||||
|
if (index > 6){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return partialLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns false if race not started, true otherwise
|
||||||
|
*
|
||||||
|
* @return race started status
|
||||||
|
*/
|
||||||
|
public static boolean isRaceStarted() {
|
||||||
|
return raceStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns false if stream not connected, true otherwise
|
||||||
|
*
|
||||||
|
* @return stream started status
|
||||||
|
*/
|
||||||
|
public static boolean isStreamStatus() {
|
||||||
|
return streamStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns race timer
|
||||||
|
*
|
||||||
|
* @return race timer in long
|
||||||
|
*/
|
||||||
|
public static long getTimeSinceStart() {
|
||||||
|
return timeSinceStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return false if race not finished, true otherwise
|
||||||
|
*
|
||||||
|
* @return race finished status
|
||||||
|
*/
|
||||||
|
public static boolean isRaceFinished() {
|
||||||
|
return raceFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return a map of boats with sourceID and the boat
|
||||||
|
*
|
||||||
|
* @return map of boats
|
||||||
|
*/
|
||||||
|
public static Map<Integer, Yacht> getBoats() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the latest updated object from xml parser
|
||||||
|
*
|
||||||
|
* @return the latest xml object
|
||||||
|
*/
|
||||||
|
public static XMLParser getXmlObject() {
|
||||||
|
return xmlObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the wind direction in degrees
|
||||||
|
*
|
||||||
|
* @return a double wind direction value
|
||||||
|
*/
|
||||||
|
public static double getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns stream time in formatted string format
|
||||||
|
*
|
||||||
|
* @return String of stream time
|
||||||
|
*/
|
||||||
|
public static String getCurrentTimeString() {
|
||||||
|
return currentTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used in boat position since tree map can sort position efficiently.
|
||||||
|
*
|
||||||
|
* @return a map of time to finish and boat.
|
||||||
|
*/
|
||||||
|
public static Map<Long, Yacht> getBoatsPos() {
|
||||||
|
return boatsPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void appClose(){
|
||||||
|
appRunning = false;
|
||||||
|
System.out.println("[CLIENT] Shutting down stream parser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
import seng302.models.parsers.packets.StreamPacket;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
|
||||||
|
public class StreamReceiver extends Thread {
|
||||||
|
private InputStream stream;
|
||||||
|
private Socket host;
|
||||||
|
private ByteArrayOutputStream crcBuffer;
|
||||||
|
private Thread t;
|
||||||
|
private String threadName;
|
||||||
|
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
|
||||||
|
private static boolean moreBytes;
|
||||||
|
|
||||||
|
public StreamReceiver(String hostAddress, int hostPort, String threadName) {
|
||||||
|
this.threadName = threadName;
|
||||||
|
this.setDaemon(true);
|
||||||
|
try {
|
||||||
|
host = new Socket(hostAddress, hostPort);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(){
|
||||||
|
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
|
||||||
|
@Override
|
||||||
|
public int compare(StreamPacket s1, StreamPacket s2) {
|
||||||
|
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
packetBuffer = pq;
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start () {
|
||||||
|
System.out.println("[CLIENT] Starting " + threadName );
|
||||||
|
if (t == null) {
|
||||||
|
t = new Thread (this, threadName);
|
||||||
|
t.start ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
|
||||||
|
this.host=host;
|
||||||
|
this.packetBuffer = packetBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void connect(){
|
||||||
|
try {
|
||||||
|
stream = host.getInputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sync1;
|
||||||
|
int sync2;
|
||||||
|
moreBytes = true;
|
||||||
|
while(moreBytes) {
|
||||||
|
try {
|
||||||
|
crcBuffer = new ByteArrayOutputStream();
|
||||||
|
sync1 = readByte();
|
||||||
|
sync2 = readByte();
|
||||||
|
//checking if it is the start of the packet
|
||||||
|
if(sync1 == 0x47 && sync2 == 0x83) {
|
||||||
|
int type = readByte();
|
||||||
|
//No. of milliseconds since Jan 1st 1970
|
||||||
|
long timeStamp = bytesToLong(getBytes(6));
|
||||||
|
skipBytes(4);
|
||||||
|
long payloadLength = bytesToLong(getBytes(2));
|
||||||
|
byte[] payload = getBytes((int) payloadLength);
|
||||||
|
Checksum checksum = new CRC32();
|
||||||
|
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
|
||||||
|
long computedCrc = checksum.getValue();
|
||||||
|
long packetCrc = bytesToLong(getBytes(4));
|
||||||
|
if (computedCrc == packetCrc) {
|
||||||
|
packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
} else {
|
||||||
|
System.err.println("Packet has been dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
moreBytes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readByte() throws Exception {
|
||||||
|
int currentByte = -1;
|
||||||
|
try {
|
||||||
|
currentByte = stream.read();
|
||||||
|
crcBuffer.write(currentByte);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (currentByte == -1){
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getBytes(int n) throws Exception{
|
||||||
|
byte[] bytes = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++){
|
||||||
|
bytes[i] = (byte) readByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipBytes(long n) throws Exception{
|
||||||
|
for (int i=0; i < n; i++){
|
||||||
|
readByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes an array of up to 7 bytes in little endian format and
|
||||||
|
* returns a positive long constructed from the input bytes
|
||||||
|
*
|
||||||
|
* @return a positive long if there is less than 8 bytes -1 otherwise
|
||||||
|
*/
|
||||||
|
private long bytesToLong(byte[] bytes){
|
||||||
|
long partialLong = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (byte b: bytes){
|
||||||
|
if (index > 6){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return partialLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
|
||||||
|
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
|
||||||
|
sr.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void noMoreBytes(){
|
||||||
|
moreBytes = false;
|
||||||
|
System.out.println("[CLIENT] Shutting down stream receiver");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//package seng302.models.parsers;
|
||||||
|
//
|
||||||
|
//import org.w3c.dom.*;
|
||||||
|
//import seng302.models.Yacht;
|
||||||
|
//
|
||||||
|
//import java.util.ArrayList;
|
||||||
|
//import java.util.NoSuchElementException;
|
||||||
|
//
|
||||||
|
//public class TeamsParser extends FileParser {
|
||||||
|
//
|
||||||
|
// private Document doc;
|
||||||
|
//
|
||||||
|
// public TeamsParser(String path) {
|
||||||
|
// super(path);
|
||||||
|
// this.doc = this.parseFile();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Create a boat instance by a given team node
|
||||||
|
// * @param node a boat node containing name, alias and velocity
|
||||||
|
// * @return an instance of Boat
|
||||||
|
// */
|
||||||
|
// private Yacht parseBoat(Node node) {
|
||||||
|
// try {
|
||||||
|
// if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
// Element element = (Element) node;
|
||||||
|
// String name = element.getElementsByTagName("name").item(0).getTextContent();
|
||||||
|
// String alias = element.getElementsByTagName("alias").item(0).getTextContent();
|
||||||
|
// double velocity = Double.valueOf(element.getElementsByTagName("velocity").item(0).getTextContent());
|
||||||
|
// int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
|
||||||
|
// Yacht boat = new Yacht(name, velocity, alias, id);
|
||||||
|
// return boat;
|
||||||
|
// } else {
|
||||||
|
// throw new NoSuchElementException("Cannot generate a boat by given node");
|
||||||
|
// }
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Create an arraylist of boats instance.
|
||||||
|
// * @return an arraylist of boats in teams file
|
||||||
|
// */
|
||||||
|
// public ArrayList<Yacht> getBoats() {
|
||||||
|
// ArrayList<Yacht> boats = new ArrayList<>();
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// NodeList nodes = this.doc.getElementsByTagName("team");
|
||||||
|
// for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
// Node node = nodes.item(i);
|
||||||
|
// boats.add(parseBoat(node));
|
||||||
|
// }
|
||||||
|
// return boats;
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
@@ -0,0 +1,492 @@
|
|||||||
|
package seng302.models.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class XMLParser {
|
||||||
|
|
||||||
|
private Document xmlDoc;
|
||||||
|
|
||||||
|
private RaceXMLObject raceXML;
|
||||||
|
private RegattaXMLObject regattaXML;
|
||||||
|
private BoatXMLObject boatXML;
|
||||||
|
|
||||||
|
public XMLParser() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for XMLParser
|
||||||
|
* @param doc Document to create XML object.
|
||||||
|
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
|
||||||
|
*/
|
||||||
|
public void constructXML(Document doc, Integer messageType) {
|
||||||
|
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; }
|
||||||
|
public RegattaXMLObject getRegattaXML() { return regattaXML; }
|
||||||
|
public BoatXMLObject getBoatXML() { return boatXML; }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
||||||
|
* @param ele Document Element with child elements.
|
||||||
|
* @param tag Tag to find in document elements child elements.
|
||||||
|
* @return Text content from tag if found, null otherwise.
|
||||||
|
*/
|
||||||
|
private static Integer getElementInt(Element ele, String tag) {
|
||||||
|
NodeList tagList = ele.getElementsByTagName(tag);
|
||||||
|
if (tagList.getLength() > 0) {
|
||||||
|
return Integer.parseInt(tagList.item(0).getTextContent());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of a given child element tag, assuming it exists, as an String.
|
||||||
|
* @param ele Document Element with child elements.
|
||||||
|
* @param tag Tag to find in document elements child elements.
|
||||||
|
* @return Text content from tag if found, null otherwise.
|
||||||
|
*/
|
||||||
|
private static String getElementString(Element ele, String tag) {
|
||||||
|
NodeList tagList = ele.getElementsByTagName(tag);
|
||||||
|
if (tagList.getLength() > 0) {
|
||||||
|
return tagList.item(0).getTextContent();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of a given child element tag, assuming it exists, as a Double.
|
||||||
|
* @param ele Document Element with child elements.
|
||||||
|
* @param tag Tag to find in document elements child elements.
|
||||||
|
* @return Text content from tag if found, null otherwise.
|
||||||
|
*/
|
||||||
|
private static Double getElementDouble(Element ele, String tag) {
|
||||||
|
NodeList tagList = ele.getElementsByTagName(tag);
|
||||||
|
if (tagList.getLength() > 0) {
|
||||||
|
return Double.parseDouble(tagList.item(0).getTextContent());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
|
||||||
|
* @param n A node object that should have some attributes
|
||||||
|
* @param attr The attribute you want to get from the given node.
|
||||||
|
* @return The String representation of the text content of an attribute in the given node, else returns null.
|
||||||
|
*/
|
||||||
|
private static String getNodeAttributeString(Node n, String attr) {
|
||||||
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
|
if (attrItem != null) {
|
||||||
|
return attrItem.getTextContent();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
|
||||||
|
* @param n A node object that should have some attributes
|
||||||
|
* @param attr The attribute you want to get from the given node.
|
||||||
|
* @return The Integer representation of the text content of an attribute in the given node, else returns null.
|
||||||
|
*/
|
||||||
|
private static Integer getNodeAttributeInt(Node n, String attr) {
|
||||||
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
|
if (attrItem != null) {
|
||||||
|
return Integer.parseInt(attrItem.getTextContent());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
|
||||||
|
* @param n A node object that should have some attributes
|
||||||
|
* @param attr The attribute you want to get from the given node.
|
||||||
|
* @return The Double representation of the text content of an attribute in the given node, else returns null.
|
||||||
|
*/
|
||||||
|
private static Double getNodeAttributeDouble(Node n, String attr) {
|
||||||
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
|
if (attrItem != null) {
|
||||||
|
return Double.parseDouble(attrItem.getTextContent());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegattaXMLObject {
|
||||||
|
//Regatta Info
|
||||||
|
private Integer regattaID;
|
||||||
|
private String regattaName;
|
||||||
|
private String courseName;
|
||||||
|
private Double centralLat;
|
||||||
|
private Double centralLng;
|
||||||
|
private Integer utcOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for a RegattaXMLObject.
|
||||||
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
* @param doc XML Document Object
|
||||||
|
*/
|
||||||
|
RegattaXMLObject(Document doc) {
|
||||||
|
Element docEle = doc.getDocumentElement();
|
||||||
|
|
||||||
|
this.regattaID = getElementInt(docEle, "RegattaID");
|
||||||
|
this.regattaName = getElementString(docEle, "RegattaName");
|
||||||
|
this.courseName = getElementString(docEle, "CourseName");
|
||||||
|
this.centralLat = getElementDouble(docEle, "CentralLatitude");
|
||||||
|
this.centralLng = getElementDouble(docEle, "CentralLongitude");
|
||||||
|
this.utcOffset = getElementInt(docEle, "UtcOffset");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRegattaID() { return regattaID; }
|
||||||
|
public String getRegattaName() { return regattaName; }
|
||||||
|
public String getCourseName() { return courseName; }
|
||||||
|
public Double getCentralLat() { return centralLat; }
|
||||||
|
public Double getCentralLng() { return centralLng; }
|
||||||
|
public Integer getUtcOffset() { return utcOffset; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RaceXMLObject {
|
||||||
|
|
||||||
|
// Race Info
|
||||||
|
private Integer raceID;
|
||||||
|
private String raceType;
|
||||||
|
private String creationTimeDate; // XML Creation Time
|
||||||
|
|
||||||
|
//Race Start Details
|
||||||
|
private String raceStartTime;
|
||||||
|
private Boolean postponeStatus;
|
||||||
|
|
||||||
|
//Non atomic race attributes
|
||||||
|
private ArrayList<Participant> participants;
|
||||||
|
private ArrayList<CompoundMark> course;
|
||||||
|
private ArrayList<Corner> compoundMarkSequence;
|
||||||
|
private ArrayList<Limit> courseLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for a RaceXMLObject.
|
||||||
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
* @param doc XML Document Object
|
||||||
|
*/
|
||||||
|
RaceXMLObject(Document doc) {
|
||||||
|
Element docEle = doc.getDocumentElement();
|
||||||
|
|
||||||
|
//Atomic and Semi-Atomic Elements
|
||||||
|
this.raceID = getElementInt(docEle, "RaceID");
|
||||||
|
this.raceType = getElementString(docEle, "RaceType");
|
||||||
|
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
|
||||||
|
|
||||||
|
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
|
||||||
|
this.raceStartTime = getNodeAttributeString(raceStart, "Start") ;
|
||||||
|
this.postponeStatus = Boolean.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
|
||||||
|
|
||||||
|
//Participants
|
||||||
|
participants = new ArrayList<>();
|
||||||
|
|
||||||
|
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
|
||||||
|
for (int i = 0; i < pList.getLength(); i++) {
|
||||||
|
Node pNode = pList.item(i);
|
||||||
|
String entry;
|
||||||
|
if (pNode.getNodeName().equals("Yacht")) {
|
||||||
|
Integer sourceID = getNodeAttributeInt(pNode, "SourceID");
|
||||||
|
|
||||||
|
if (pNode.getAttributes().getLength() == 2) {
|
||||||
|
entry = getNodeAttributeString(pNode, "Entry");
|
||||||
|
} else {
|
||||||
|
entry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Participant pa = new Participant(sourceID, entry);
|
||||||
|
participants.add(pa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Course
|
||||||
|
course = new ArrayList<>();
|
||||||
|
|
||||||
|
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
||||||
|
for (int i = 0; i < cMarkList.getLength(); i++) {
|
||||||
|
Node cMarkNode = cMarkList.item(i);
|
||||||
|
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
||||||
|
CompoundMark cMark = new CompoundMark(cMarkNode);
|
||||||
|
course.add(cMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Course Mark Sequence
|
||||||
|
compoundMarkSequence = new ArrayList<>();
|
||||||
|
|
||||||
|
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0).getChildNodes();
|
||||||
|
for (int i = 0; i < cornerList.getLength(); i++) {
|
||||||
|
Node cornerNode = cornerList.item(i);
|
||||||
|
if (cornerNode.getNodeName().equals("Corner")) {
|
||||||
|
Corner corner = new Corner(cornerNode);
|
||||||
|
compoundMarkSequence.add(corner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Course Limits
|
||||||
|
courseLimit = new ArrayList<>();
|
||||||
|
|
||||||
|
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
|
||||||
|
for (int i = 0; i < limitList.getLength(); i++) {
|
||||||
|
Node limitNode = limitList.item(i);
|
||||||
|
if (limitNode.getNodeName().equals("Limit")) {
|
||||||
|
Limit limit = new Limit(limitNode);
|
||||||
|
courseLimit.add(limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRaceID() { return raceID; }
|
||||||
|
public String getRaceType() { return raceType; }
|
||||||
|
public String getCreationTimeDate() { return creationTimeDate; }
|
||||||
|
public String getRaceStartTime() { return raceStartTime; }
|
||||||
|
public Boolean getPostponeStatus() { return postponeStatus; }
|
||||||
|
|
||||||
|
public ArrayList<Participant> getParticipants() { return participants; }
|
||||||
|
public ArrayList<CompoundMark> getCompoundMarks() { return course; }
|
||||||
|
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
|
||||||
|
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
|
||||||
|
|
||||||
|
public class Participant {
|
||||||
|
Integer sourceID;
|
||||||
|
String entry;
|
||||||
|
|
||||||
|
Participant(Integer sourceID, String entry) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getsourceID() { return sourceID; }
|
||||||
|
public String getEntry() { return entry; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompoundMark {
|
||||||
|
private Integer markID;
|
||||||
|
private String cMarkName;
|
||||||
|
private ArrayList<Mark> marks;
|
||||||
|
|
||||||
|
CompoundMark(Node compoundMark) {
|
||||||
|
marks = new ArrayList<>();
|
||||||
|
this.markID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
|
||||||
|
this.cMarkName = getNodeAttributeString(compoundMark, "Name");
|
||||||
|
NodeList childMarks = compoundMark.getChildNodes();
|
||||||
|
for (int i = 0; i < childMarks.getLength(); i++) {
|
||||||
|
Node markNode = childMarks.item(i);
|
||||||
|
if (markNode.getNodeName().equals("Mark")) {
|
||||||
|
Mark mark = new Mark(markNode);
|
||||||
|
marks.add(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMarkID() { return markID; }
|
||||||
|
public String getcMarkName() { return cMarkName; }
|
||||||
|
public ArrayList<Mark> getMarks() { return marks; }
|
||||||
|
|
||||||
|
public class Mark {
|
||||||
|
private Integer seqID;
|
||||||
|
private Integer sourceID;
|
||||||
|
private String markName;
|
||||||
|
private Double targetLat;
|
||||||
|
private Double targetLng;
|
||||||
|
|
||||||
|
Mark(Node markNode) {
|
||||||
|
this.seqID = getNodeAttributeInt(markNode, "SeqID");
|
||||||
|
this.sourceID = getNodeAttributeInt(markNode, "SourceID");
|
||||||
|
this.markName = getNodeAttributeString(markNode, "Name");
|
||||||
|
this.targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
||||||
|
this.targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() { return seqID; }
|
||||||
|
public Integer getSourceID() { return sourceID; }
|
||||||
|
public String getMarkName() { return markName; }
|
||||||
|
public Double getTargetLat() { return targetLat; }
|
||||||
|
public Double getTargetLng() { return targetLng; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Corner {
|
||||||
|
private Integer seqID;
|
||||||
|
private Integer compoundMarkID;
|
||||||
|
private String rounding;
|
||||||
|
private Integer zoneSize;
|
||||||
|
|
||||||
|
Corner(Node cornerNode) {
|
||||||
|
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
|
||||||
|
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
|
||||||
|
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
|
||||||
|
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() { return seqID; }
|
||||||
|
public Integer getCompoundMarkID() { return compoundMarkID; }
|
||||||
|
public String getRounding() { return rounding; }
|
||||||
|
public Integer getZoneSize() { return zoneSize; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Limit {
|
||||||
|
private Integer seqID;
|
||||||
|
private Double lat;
|
||||||
|
private Double lng;
|
||||||
|
|
||||||
|
Limit(Node limitNode) {
|
||||||
|
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
|
||||||
|
this.lat = getNodeAttributeDouble(limitNode, "Lat");
|
||||||
|
this.lng = getNodeAttributeDouble(limitNode, "Lon");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() { return seqID; }
|
||||||
|
public Double getLat() { return lat; }
|
||||||
|
public Double getLng() { return lng; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BoatXMLObject {
|
||||||
|
|
||||||
|
private String lastModified;
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
|
||||||
|
private String boatType;
|
||||||
|
private Double boatLength;
|
||||||
|
private Double hullLength;
|
||||||
|
private Double markZoneSize;
|
||||||
|
private Double courseZoneSize;
|
||||||
|
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
|
||||||
|
|
||||||
|
//Boats
|
||||||
|
ArrayList<Yacht> boats;
|
||||||
|
//Competing boats
|
||||||
|
Map<Integer, Yacht> competingBoats = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for a BoatXMLObject.
|
||||||
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
* @param doc XML Document Object
|
||||||
|
*/
|
||||||
|
BoatXMLObject(Document doc) {
|
||||||
|
|
||||||
|
Element docEle = doc.getDocumentElement();
|
||||||
|
|
||||||
|
this.lastModified = getElementString(docEle, "Modified");
|
||||||
|
this.version = getElementInt(docEle, "Version");
|
||||||
|
|
||||||
|
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
|
||||||
|
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
|
||||||
|
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
|
||||||
|
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
|
||||||
|
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
|
||||||
|
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
|
||||||
|
|
||||||
|
Node zoneLimitsList = settingsList.item(7);
|
||||||
|
this.zoneLimits = new ArrayList<>();
|
||||||
|
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
|
||||||
|
String tag = String.format("Limit%d", i+1);
|
||||||
|
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.boats = new ArrayList<>();
|
||||||
|
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
|
||||||
|
for (int i = 0; i < boatsList.getLength(); i++) {
|
||||||
|
Node currentBoat = boatsList.item(i);
|
||||||
|
if (currentBoat.getNodeName().equals("Boat")) {
|
||||||
|
// Boat boat = new Boat(currentBoat);
|
||||||
|
Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
|
||||||
|
getNodeAttributeInt(currentBoat, "SourceID"),
|
||||||
|
getNodeAttributeString(currentBoat, "HullNum"),
|
||||||
|
getNodeAttributeString(currentBoat, "ShortName"),
|
||||||
|
getNodeAttributeString(currentBoat, "BoatName"),
|
||||||
|
getNodeAttributeString(currentBoat, "Country"));
|
||||||
|
this.boats.add(boat);
|
||||||
|
if (boat.getBoatType().equals("Yacht")) {
|
||||||
|
competingBoats.put(boat.getSourceID(), boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//System.out.println(this.getBoats());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastModified() { return lastModified; }
|
||||||
|
public Integer getVersion() { return version; }
|
||||||
|
public String getBoatType() { return boatType; }
|
||||||
|
public Double getBoatLength() { return boatLength; }
|
||||||
|
public Double getHullLength() { return hullLength; }
|
||||||
|
public Double getMarkZoneSize() { return markZoneSize; }
|
||||||
|
public Double getCourseZoneSize() { return courseZoneSize; }
|
||||||
|
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
|
||||||
|
public ArrayList<Yacht> getBoats() { return boats; }
|
||||||
|
public Map<Integer, Yacht> getCompetingBoats() {
|
||||||
|
return competingBoats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public class Boat {
|
||||||
|
//
|
||||||
|
// private String boatType;
|
||||||
|
// private Integer sourceID;
|
||||||
|
// private String hullID; //matches HullNum in the XML spec.
|
||||||
|
// private String shortName;
|
||||||
|
// private String boatName;
|
||||||
|
// private String country;
|
||||||
|
//
|
||||||
|
// Boat(Node boatNode) {
|
||||||
|
// this.boatType = getNodeAttributeString(boatNode, "Type");
|
||||||
|
// this.sourceID = getNodeAttributeInt(boatNode, "SourceID");
|
||||||
|
// this.hullID = getNodeAttributeString(boatNode, "HullNum");
|
||||||
|
// this.shortName = getNodeAttributeString(boatNode, "ShortName");
|
||||||
|
// this.boatName = getNodeAttributeString(boatNode, "BoatName");
|
||||||
|
// this.country = getNodeAttributeString(boatNode, "Country");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public String getBoatType() { return boatType; }
|
||||||
|
// public Integer getSourceID() { return sourceID; }
|
||||||
|
// public String getHullID() { return hullID; }
|
||||||
|
// public String getShortName() { return shortName; }
|
||||||
|
// public String getBoatName() { return boatName; }
|
||||||
|
// public String getCountry() { return country; }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package seng302.models.parsers.packets;
|
||||||
|
|
||||||
|
public class BoatPositionPacket {
|
||||||
|
private long boatId;
|
||||||
|
private long timeValid;
|
||||||
|
private double lat;
|
||||||
|
private double lon;
|
||||||
|
private double heading;
|
||||||
|
private double groundSpeed;
|
||||||
|
|
||||||
|
public BoatPositionPacket(long boatId, long timeValid, double lat, double lon, double heading, double groundSpeed) {
|
||||||
|
this.boatId = boatId;
|
||||||
|
this.timeValid = timeValid;
|
||||||
|
this.lat = lat;
|
||||||
|
this.lon = lon;
|
||||||
|
this.heading = heading;
|
||||||
|
this.groundSpeed = groundSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeValid() {
|
||||||
|
return timeValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLon() {
|
||||||
|
return lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getHeading() {
|
||||||
|
return heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getGroundSpeed() {
|
||||||
|
return groundSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.models.parsers.packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Kusal on 4/24/2017.
|
||||||
|
*/
|
||||||
|
public enum PacketType {
|
||||||
|
HEARTBEAT,
|
||||||
|
RACE_STATUS,
|
||||||
|
DISPLAY_TEXT_MESSAGE,
|
||||||
|
XML_MESSAGE,
|
||||||
|
RACE_START_STATUS,
|
||||||
|
YACHT_EVENT_CODE,
|
||||||
|
YACHT_ACTION_CODE,
|
||||||
|
CHATTER_TEXT,
|
||||||
|
BOAT_LOCATION,
|
||||||
|
MARK_ROUNDING,
|
||||||
|
COURSE_WIND,
|
||||||
|
AVG_WIND,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
public static PacketType assignPacketType(int packetType){
|
||||||
|
switch(packetType){
|
||||||
|
case 1:
|
||||||
|
return HEARTBEAT;
|
||||||
|
case 12:
|
||||||
|
return RACE_STATUS;
|
||||||
|
case 20:
|
||||||
|
return DISPLAY_TEXT_MESSAGE;
|
||||||
|
case 26:
|
||||||
|
return XML_MESSAGE;
|
||||||
|
case 27:
|
||||||
|
return RACE_START_STATUS;
|
||||||
|
case 29:
|
||||||
|
return YACHT_EVENT_CODE;
|
||||||
|
case 31:
|
||||||
|
return YACHT_ACTION_CODE;
|
||||||
|
case 36:
|
||||||
|
return CHATTER_TEXT;
|
||||||
|
case 37:
|
||||||
|
return BOAT_LOCATION;
|
||||||
|
case 38:
|
||||||
|
return MARK_ROUNDING;
|
||||||
|
case 44:
|
||||||
|
return COURSE_WIND;
|
||||||
|
case 47:
|
||||||
|
return AVG_WIND;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package seng302.models.parsers.packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 23/04/17.
|
||||||
|
*/
|
||||||
|
public class StreamPacket {
|
||||||
|
|
||||||
|
//Change int to an ENUM for the type
|
||||||
|
private PacketType type;
|
||||||
|
|
||||||
|
private long messageLength;
|
||||||
|
private long timeStamp;
|
||||||
|
private byte[] payload;
|
||||||
|
|
||||||
|
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
||||||
|
this.type = PacketType.assignPacketType(type);
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
this.timeStamp = timeStamp;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMessageLength() {
|
||||||
|
return messageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeStamp() {
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.Simulator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerThread implements Runnable, Observer {
|
||||||
|
private StreamingServerSocket server;
|
||||||
|
private long startTime;
|
||||||
|
private boolean raceStarted = false;
|
||||||
|
private Map<Integer,Boolean> boatsFinished = new HashMap<>();
|
||||||
|
private List<Boat> boats;
|
||||||
|
private Simulator raceSimulator;
|
||||||
|
private boolean sendingRaceFinishedLocationMessages = true;
|
||||||
|
|
||||||
|
private final int HEARTBEAT_PERIOD = 5000;
|
||||||
|
private final int RACE_STATUS_PERIOD = 1000/2;
|
||||||
|
private final int RACE_START_STATUS_PERIOD = 1000;
|
||||||
|
private final int BOAT_LOCATION_PERIOD = 1000/5;
|
||||||
|
private final int PORT_NUMBER = 4949;
|
||||||
|
private final int TIME_TILL_RACE_START = 20*1000;
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
|
||||||
|
public ServerThread(String threadName){
|
||||||
|
Thread runner = new Thread(this, threadName);
|
||||||
|
runner.setDaemon(true);
|
||||||
|
|
||||||
|
serverLog("Spawning Server", 0);
|
||||||
|
|
||||||
|
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
||||||
|
raceSimulator.addObserver(this);
|
||||||
|
// run race simulator, so it can send boats' static location.
|
||||||
|
Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator");
|
||||||
|
|
||||||
|
boats = raceSimulator.getBoats();
|
||||||
|
|
||||||
|
for (Boat b : boats){
|
||||||
|
boatsFinished.put(b.getSourceID(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.start();
|
||||||
|
raceSimulatorThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
private 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
|
||||||
|
*/
|
||||||
|
private Message getRaceStatusMessage(){
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
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, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish());
|
||||||
|
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.SOUTH,
|
||||||
|
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an instance of the race simulator
|
||||||
|
*/
|
||||||
|
private void startRaceSim(){
|
||||||
|
serverLog("Starting Running Race Simulator", 0);
|
||||||
|
// set race started to true, so the simulator will start moving boats
|
||||||
|
raceSimulator.setRaceStarted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending static boat position updates when race has finished
|
||||||
|
*/
|
||||||
|
private void startSendingRaceFinishedBoatPostions(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
for (Boat b : raceSimulator.getBoats()){
|
||||||
|
Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(),
|
||||||
|
b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
|
||||||
|
((long) 0));
|
||||||
|
|
||||||
|
server.send(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, BOAT_LOCATION_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a boat location message when they are updated by the simulator
|
||||||
|
* @param o .
|
||||||
|
* @param arg .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
// Only send if server started
|
||||||
|
// TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17
|
||||||
|
if(server == null || !server.isStarted()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numOfBoatsFinished = 0;
|
||||||
|
for (Boat boat : (List<Boat>) arg){
|
||||||
|
try {
|
||||||
|
if (boat.isFinished()) {
|
||||||
|
numOfBoatsFinished ++;
|
||||||
|
if (!boatsFinished.get(boat.getSourceID())) {
|
||||||
|
boatsFinished.put(boat.getSourceID(), true);
|
||||||
|
serverLog("Boat " + boat.getSourceID() + " finished the race", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
|
||||||
|
boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(),
|
||||||
|
((long) boat.getSpeed()));
|
||||||
|
server.send(m);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Couldn't send a boat status message", 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (NullPointerException e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
|
||||||
|
startSendingRaceFinishedBoatPostions();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,167 @@
|
|||||||
|
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){
|
||||||
|
boatSpeed /= 10;
|
||||||
|
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 = 2;
|
||||||
|
this.SOG = boatSpeed ;
|
||||||
|
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);
|
||||||
|
putInt((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,85 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
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;
|
||||||
|
private ByteBuffer buff = ByteBuffer.allocate(getSize());
|
||||||
|
private int buffPos = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 this boat status message
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
// Source ID, 4 bytes
|
||||||
|
putInBuffer(Message.intToByteArray(sourceId, 4), 4);
|
||||||
|
|
||||||
|
// Boat Status, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Leg number, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Number of penalties awarded, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Number of penalties served, 1 byte
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array(), 1);
|
||||||
|
|
||||||
|
// Estimated time at next mark, 6 bytes
|
||||||
|
putInBuffer(Message.intToByteArray((int) estimatedTimeAtNextMark, 6),6);
|
||||||
|
|
||||||
|
// Estimated time at finish, 6 bytes
|
||||||
|
putInBuffer(Message.intToByteArray((int) estimatedTimeAtFinish, 6), 6);
|
||||||
|
|
||||||
|
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,207 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
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.array());
|
||||||
|
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){
|
||||||
|
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,125 @@
|
|||||||
|
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 boolean isFinished;
|
||||||
|
private long estimatedTimeTillFinish;
|
||||||
|
|
||||||
|
private Corner lastPassedCorner, headingCorner;
|
||||||
|
|
||||||
|
public Boat(int sourceID, String boatName) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.boatName = boatName;
|
||||||
|
this.isFinished = false;
|
||||||
|
estimatedTimeTillFinish = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
return isFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinished(boolean finished) {
|
||||||
|
isFinished = finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEstimatedTimeTillFinish(){
|
||||||
|
return (long) (-getSpeed()) + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,141 @@
|
|||||||
|
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;
|
||||||
|
private boolean isRaceStarted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
isRaceStarted = false;
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
|
||||||
|
// if race has started, then boat should start to move.
|
||||||
|
if (isRaceStarted) {
|
||||||
|
for (Boat boat : boats) {
|
||||||
|
numOfFinishedBoats += moveBoat(boat, lapse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//System.out.println(boats.get(0));
|
||||||
|
|
||||||
|
setChanged();
|
||||||
|
notifyObservers(boats);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(lapse);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("[SERVER] Race simulator has been terminated");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
boat.setFinished(true);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRaceStarted(boolean raceStarted) {
|
||||||
|
isRaceStarted = raceStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<configurations>
|
||||||
|
<race-name>AC35</race-name>
|
||||||
|
<race-size>6</race-size>
|
||||||
|
<time-scale>10.0</time-scale>
|
||||||
|
<wind-direction>135</wind-direction>
|
||||||
|
</configurations>
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<markers>
|
||||||
|
<marks>
|
||||||
|
<gate>
|
||||||
|
<name type="start-line">Start</name>
|
||||||
|
<mark>
|
||||||
|
<name>Start1</name>
|
||||||
|
<latitude>57.6703330</latitude>
|
||||||
|
<longitude>11.8278330</longitude>
|
||||||
|
<id>122</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Start2</name>
|
||||||
|
<latitude>57.6706330</latitude>
|
||||||
|
<longitude>11.8281330</longitude>
|
||||||
|
<id>123</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<mark>
|
||||||
|
<name>Mid Mark</name>
|
||||||
|
<latitude>57.6675700</latitude>
|
||||||
|
<longitude>11.8359880</longitude>
|
||||||
|
<id>131</id>
|
||||||
|
</mark>
|
||||||
|
<gate>
|
||||||
|
<name>Leeward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate1</name>
|
||||||
|
<latitude>57.6708220</latitude>
|
||||||
|
<longitude>11.8433900</longitude>
|
||||||
|
<id>124</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate2</name>
|
||||||
|
<latitude>57.6711220</latitude>
|
||||||
|
<longitude>11.8436900</longitude>
|
||||||
|
<id>125</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate>
|
||||||
|
<name>Windward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate1</name>
|
||||||
|
<latitude>57.6650170</latitude>
|
||||||
|
<longitude>11.8279170</longitude>
|
||||||
|
<id>126</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate2</name>
|
||||||
|
<latitude>57.6653170</latitude>
|
||||||
|
<longitude>11.8282170</longitude>
|
||||||
|
<id>127</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate type="finish-line">
|
||||||
|
<name>Finish</name>
|
||||||
|
<mark>
|
||||||
|
<name>Finish1</name>
|
||||||
|
<latitude>57.6715240</latitude>
|
||||||
|
<longitude>11.8444950</longitude>
|
||||||
|
<id>128</id>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Finish2</name>
|
||||||
|
<latitude>57.6718240</latitude>
|
||||||
|
<longitude>11.8447950</longitude>
|
||||||
|
<id>129</id>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
</marks>
|
||||||
|
<order>
|
||||||
|
<one>Start</one>
|
||||||
|
<two>Mid Mark</two>
|
||||||
|
<three>Leeward Gate</three>
|
||||||
|
<four>Windward Gate</four>
|
||||||
|
<five>Leeward Gate</five>
|
||||||
|
<six>Finish</six>
|
||||||
|
</order>
|
||||||
|
</markers>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<course>
|
||||||
|
<marks>
|
||||||
|
<gate>
|
||||||
|
<name type="start-line">Start</name>
|
||||||
|
<mark>
|
||||||
|
<name>Start1</name>
|
||||||
|
<latitude>32.296577</latitude>
|
||||||
|
<longitude>-64.854304</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Start2</name>
|
||||||
|
<latitude>32.293771</latitude>
|
||||||
|
<longitude>-64.855242</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<mark>
|
||||||
|
<name>Mid Mark</name>
|
||||||
|
<latitude>32.293039</latitude>
|
||||||
|
<longitude>-64.843983</longitude>
|
||||||
|
</mark>
|
||||||
|
<gate>
|
||||||
|
<name>Leeward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate1</name>
|
||||||
|
<latitude>32.284680</latitude>
|
||||||
|
<longitude>-64.850045</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Leeward Gate2</name>
|
||||||
|
<latitude>32.280164</latitude>
|
||||||
|
<longitude>-64.847591</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate>
|
||||||
|
<name>Windward Gate</name>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate1</name>
|
||||||
|
<latitude>32.309693</latitude>
|
||||||
|
<longitude>-64.835249</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Windward Gate2</name>
|
||||||
|
<latitude>32.308046</latitude>
|
||||||
|
<longitude>-64.831785</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
<gate type="finish-line">
|
||||||
|
<name>Finish</name>
|
||||||
|
<mark>
|
||||||
|
<name>Finish1</name>
|
||||||
|
<latitude>32.317379</latitude>
|
||||||
|
<longitude>-64.839291</longitude>
|
||||||
|
</mark>
|
||||||
|
<mark>
|
||||||
|
<name>Finish2</name>
|
||||||
|
<latitude>32.317257</latitude>
|
||||||
|
<longitude>-64.836260</longitude>
|
||||||
|
</mark>
|
||||||
|
</gate>
|
||||||
|
</marks>
|
||||||
|
<order>
|
||||||
|
<one>Start</one>
|
||||||
|
<two>Mid Mark</two>
|
||||||
|
<three>Leeward Gate</three>
|
||||||
|
<four>Windward Gate</four>
|
||||||
|
<five>Leeward Gate</five>
|
||||||
|
<six>Finish</six>
|
||||||
|
</order>
|
||||||
|
</course>
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<teams>
|
||||||
|
<team>
|
||||||
|
<name>Oracle Team USA</name>
|
||||||
|
<alias>USA</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>102</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Artemis Racing</name>
|
||||||
|
<alias>ART</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>101</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Emirates Team New Zealand</name>
|
||||||
|
<alias>NZL</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>103</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Land Rover BAR</name>
|
||||||
|
<alias>BAR</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>104</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>SoftBank Team Japan</name>
|
||||||
|
<alias>JAP</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>105</id>
|
||||||
|
</team>
|
||||||
|
<team>
|
||||||
|
<name>Groupama Team France</name>
|
||||||
|
<alias>FRC</alias>
|
||||||
|
<velocity>0.0</velocity>
|
||||||
|
<id>106</id>
|
||||||
|
</team>
|
||||||
|
</teams>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
|
||||||
|
<AnchorPane fx:id="canvasPane" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<AnchorPane fx:id="raceResults" maxHeight="1080.0" maxWidth="1920.0" minHeight="1080.0" minWidth="1920.0" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<children>
|
||||||
|
<GridPane layoutX="444.0" layoutY="256.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="620.1734008789062" minWidth="10.0" prefWidth="493.2829895019531" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="862.0581665039062" minWidth="10.0" prefWidth="786.7170104980469" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="112.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="99.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="348.0" minHeight="7.0" prefHeight="88.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="348.0" minHeight="0.0" prefHeight="278.3376770019531" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="653.0" minHeight="0.0" prefHeight="98.66232299804688" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="812.0" minHeight="10.0" prefHeight="418.4577941894531" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Race Results:" wrappingWidth="616.5260620117188" GridPane.rowIndex="2">
|
||||||
|
<font>
|
||||||
|
<Font size="45.0" />
|
||||||
|
</font>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="50.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Text>
|
||||||
|
<VBox fx:id="resultsVBox" prefHeight="44.0" prefWidth="571.0" GridPane.rowIndex="3">
|
||||||
|
<opaqueInsets>
|
||||||
|
<Insets />
|
||||||
|
</opaqueInsets>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="50.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets top="60.0" />
|
||||||
|
</padding></VBox>
|
||||||
|
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Finish!" wrappingWidth="623.9530334472656" GridPane.rowIndex="1">
|
||||||
|
<font>
|
||||||
|
<Font size="75.0" />
|
||||||
|
</font>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="50.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Text>
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
<?import javafx.scene.canvas.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
|
||||||
|
<children>
|
||||||
|
<GridPane nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="5.0" prefHeight="55.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="23.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Label alignment="CENTER" text="Welcome to Race Vision" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
||||||
|
<font>
|
||||||
|
<Font size="40.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label text="Your live AC35 livestream" GridPane.halignment="CENTER" GridPane.rowIndex="1">
|
||||||
|
<font>
|
||||||
|
<Font size="20.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label text="Livestream Status:" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
|
||||||
|
<font>
|
||||||
|
<Font size="28.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4">
|
||||||
|
<font>
|
||||||
|
<Font size="27.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
|
||||||
|
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
|
||||||
|
<TableView fx:id="teamList" maxWidth="500.0" prefHeight="200.0" prefWidth="210.0" GridPane.halignment="CENTER" GridPane.rowIndex="5">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" />
|
||||||
|
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" />
|
||||||
|
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="107.0" minWidth="107.0" prefWidth="107.0" resizable="false" sortable="false" text="Short Name" />
|
||||||
|
<TableColumn fx:id="countryCol" editable="false" maxWidth="147.0" minWidth="147.0" prefWidth="147.0" resizable="false" sortable="false" text="Country" />
|
||||||
|
</columns>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</GridPane.margin>
|
||||||
|
</TableView>
|
||||||
|
<Label fx:id="realTime" text="Local time" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.shape.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
|
||||||
|
<GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="500.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints />
|
||||||
|
<RowConstraints />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3">
|
||||||
|
<children>
|
||||||
|
<Label layoutX="11.0" layoutY="259.0" text="Team Position" />
|
||||||
|
<Label layoutX="13.0" layoutY="432.0" text="Settings" />
|
||||||
|
<Label layoutX="11.0" layoutY="14.0" text="Timer" />
|
||||||
|
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" />
|
||||||
|
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" />
|
||||||
|
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
|
||||||
|
<font>
|
||||||
|
<Font name="AdobeArabic-Regular" size="55.0" />
|
||||||
|
</font>
|
||||||
|
</Text>
|
||||||
|
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="13.0" />
|
||||||
|
</font>
|
||||||
|
</Text>
|
||||||
|
<CheckBox fx:id="toggleFps" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" />
|
||||||
|
<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">
|
||||||
|
<children>
|
||||||
|
<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 size="25.0" />
|
||||||
|
</font>
|
||||||
|
</Text>
|
||||||
|
</children>
|
||||||
|
</Pane>
|
||||||
|
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="3.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" />
|
||||||
|
<Label layoutX="10.0" layoutY="499.0" text="Annotations" />
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
|
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
|
||||||
|
<children>
|
||||||
|
<fx:include fx:id="includedCanvas" source="CanvasView.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
|
</children></AnchorPane>
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for simple App.
|
|
||||||
*/
|
|
||||||
public class AppTest
|
|
||||||
{
|
|
||||||
@Test
|
|
||||||
public void testApp()
|
|
||||||
{
|
|
||||||
assertTrue( true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,35 @@
|
|||||||
package seng302;
|
//package seng302;
|
||||||
|
//
|
||||||
import org.junit.Test;
|
//import org.junit.Test;
|
||||||
import static org.junit.Assert.assertTrue;
|
//import seng302.models.Boat;
|
||||||
import static org.junit.Assert.assertEquals;
|
//
|
||||||
|
//import static org.junit.Assert.assertEquals;
|
||||||
/**
|
//
|
||||||
* Unit test for the Team class.
|
///**
|
||||||
*/
|
// * Unit test for the Team class.
|
||||||
public class BoatTest
|
// */
|
||||||
{
|
//public class BoatTest {
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
public void testBoatCreation()
|
// public void testBoatCreation() {
|
||||||
{
|
// Boat boat1 = new Boat("Team 1");
|
||||||
Boat boat1 = new Boat("Team 1");
|
// assertEquals(boat1.getTeamName(), "Team 1");
|
||||||
assertEquals(boat1.getTeamName(), "Team 1");
|
// assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
public void testChangeTeamName()
|
// public void testChangeTeamName() {
|
||||||
{
|
// Boat boat1 = new Boat("Team 1");
|
||||||
Boat boat1 = new Boat("Team 1");
|
// boat1.setTeamName("Team 2");
|
||||||
boat1.setTeamName("Team 2");
|
// assertEquals(boat1.getTeamName(), "Team 2");
|
||||||
assertEquals(boat1.getTeamName(), "Team 2");
|
// }
|
||||||
}
|
//
|
||||||
}
|
// @Test
|
||||||
|
// public void testSetVelocity() {
|
||||||
|
// Boat boat1 = new Boat("Team 1", 29.0, "", 100);
|
||||||
|
// assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15);
|
||||||
|
//
|
||||||
|
// boat1.setVelocity(12.0);
|
||||||
|
// assertEquals(boat1.getVelocity(), (double)12.0, 1e-15);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package seng302;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.models.Colors;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ryan_ on 16/03/2017.
|
||||||
|
*/
|
||||||
|
public class ColorsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNextColor() {
|
||||||
|
Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE};
|
||||||
|
for (int i = 0; i<6; i++)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(expectedColors[i], Colors.getColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import seng302.models.Event;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for Event class
|
* Test for Event class
|
||||||
@@ -11,11 +13,26 @@ import static org.junit.Assert.*;
|
|||||||
*/
|
*/
|
||||||
public class EventTest {
|
public class EventTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getTimeString() throws Exception {
|
public void getTimeString() throws Exception {
|
||||||
Leg leg = new Leg(035, 100, "Start");
|
Yacht boat = new Yacht("testBoat");
|
||||||
Boat boat = new Boat("testBoat");
|
Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0);
|
||||||
Event event = new Event(1231242, boat, leg);
|
assertEquals("20:31:242", event.getTimeString());
|
||||||
assertEquals("20:31:242", event.getTimeString());
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
public void testBoatHeading() throws Exception {
|
||||||
|
Yacht boat = new Yacht("testBoat");
|
||||||
|
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1, 1), new SingleMark("mark2", 121.9,99.2, 2), 0);
|
||||||
|
|
||||||
|
assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDistanceBetweenMarks() throws Exception {
|
||||||
|
Yacht boat = new Yacht("testBoat");
|
||||||
|
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1, 1), new SingleMark("mark2", 121.9,99.2, 2), 0);
|
||||||
|
|
||||||
|
assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/** Unit test for FileParser class
|
|
||||||
* Created by Haoming on 5/03/17.
|
|
||||||
*/
|
|
||||||
public class FileParserTest {
|
|
||||||
|
|
||||||
/*
|
|
||||||
test if it fails from reading non existed file
|
|
||||||
*/
|
|
||||||
@Test (expected = FileNotFoundException.class)
|
|
||||||
public void readNonExistedFile() throws Exception {
|
|
||||||
FileParser fileParser = new FileParser("test/java/seng302/non-existed.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
test a valid json file with valid content.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void readValidFile() throws Exception{
|
|
||||||
FileParser fileParser = new FileParser("src/test/java/seng302/valid.json");
|
|
||||||
|
|
||||||
assertEquals("AC35", fileParser.getRaceName());
|
|
||||||
|
|
||||||
assertEquals("Oracle Team USA", fileParser.getTeams().get(0).get("team-name"));
|
|
||||||
assertEquals(20.9, fileParser.getTeams().get(0).get("velocity"));
|
|
||||||
assertEquals(2, fileParser.getRaceSize());
|
|
||||||
assertEquals(6, fileParser.getTotalNumberOfTeams());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
test an invalid json file within wrong type value and misnamed
|
|
||||||
variable name.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void readInvalidFile() throws Exception {
|
|
||||||
FileParser fileParser = new FileParser("src/test/java/seng302/invalid.json");
|
|
||||||
|
|
||||||
assertEquals(null, fileParser.getRaceName());
|
|
||||||
assertEquals(null, fileParser.getTeams());
|
|
||||||
//assertEquals(-1, fileParser.getTimeScale());
|
|
||||||
assertEquals(null,fileParser.getTeams());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,52 +1,51 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.assertTrue;
|
import seng302.models.Leg;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for the Leg class.
|
* Unit test for the Leg class.
|
||||||
*/
|
*/
|
||||||
public class LegTest{
|
public class LegTest {
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Test creation of the leg by specifying a string
|
* Test creation of the leg by specifying a string
|
||||||
for the marker label
|
* for the marker label
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testLegCreationUsingMarkerLabel()
|
public void testLegCreationUsingMarkerLabel() {
|
||||||
{
|
Leg leg = new Leg(010, 100, "SingleMark");
|
||||||
Leg leg = new Leg(010, 100, "Marker");
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
assertEquals(leg.getHeading(), 010);
|
||||||
assertEquals(leg.getDistance(), 100);
|
assertEquals(leg.getDistance(), 100);
|
||||||
assertEquals(leg.getMarkerLabel(), "Marker");
|
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
assertEquals(leg.getIsFinishingLeg(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Test creation of the leg by providing a
|
* Test creation of the leg by providing a
|
||||||
Marker object
|
* SingleMark object
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testLegCreation()
|
public void testLegCreation() {
|
||||||
{
|
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
|
||||||
Leg leg = new Leg(010, 100, new Marker("Marker"));
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
assertEquals(leg.getHeading(), 010);
|
||||||
assertEquals(leg.getDistance(), 100);
|
assertEquals(leg.getDistance(), 100);
|
||||||
assertEquals(leg.getMarkerLabel(), "Marker");
|
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
assertEquals(leg.getIsFinishingLeg(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Test changing whether or not a
|
* Test changing whether or not a
|
||||||
leg is the finishing leg
|
* leg is the finishing leg
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSetFinishLeg()
|
public void testSetFinishLeg() {
|
||||||
{
|
Leg leg = new Leg(010, 100, "SingleMark");
|
||||||
Leg leg = new Leg(010, 100, "Marker");
|
|
||||||
|
|
||||||
leg.setFinishingLeg(true);
|
leg.setFinishingLeg(true);
|
||||||
assertEquals(leg.getIsFinishingLeg(), true);
|
assertEquals(leg.getIsFinishingLeg(), true);
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.assertTrue;
|
import seng302.models.Race;
|
||||||
import static org.junit.Assert.assertEquals;
|
import seng302.models.Yacht;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for the Race class.
|
* Unit test for the Race class.
|
||||||
*/
|
*/
|
||||||
public class RaceTest
|
public class RaceTest {
|
||||||
{
|
/**
|
||||||
/*
|
* Test that all boats were added to the race
|
||||||
Test that all boats were added to the race
|
*/
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddingBoatsToRace(){
|
public void testAddingBoatsToRace() {
|
||||||
Boat boat1 = new Boat("Team 1");
|
Yacht boat1 = new Yacht("Team 1");
|
||||||
Boat boat2 = new Boat("Team 2");
|
Yacht boat2 = new Yacht("Team 2");
|
||||||
|
|
||||||
Race race = new Race();
|
Race race = new Race();
|
||||||
race.addBoat(boat1);
|
race.addBoat(boat1);
|
||||||
@@ -24,4 +26,16 @@ public class RaceTest
|
|||||||
|
|
||||||
assertEquals(Array.getLength(race.getBoats()), 2);
|
assertEquals(Array.getLength(race.getBoats()), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetShuffledBoats(){
|
||||||
|
Yacht boat1 = new Yacht("Team 1");
|
||||||
|
Yacht boat2 = new Yacht("Team 2");
|
||||||
|
|
||||||
|
Race race = new Race();
|
||||||
|
race.addBoat(boat1);
|
||||||
|
race.addBoat(boat2);
|
||||||
|
|
||||||
|
assertEquals(Array.getLength(race.getShuffledBoats()), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package seng302;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.controllers.RaceViewController;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
public class TestRaceTimer {
|
||||||
|
@Test
|
||||||
|
public void testPositiveTimeString(){
|
||||||
|
RaceViewController controller = new RaceViewController();
|
||||||
|
String result = controller.convertTimeToMinutesSeconds(61);
|
||||||
|
|
||||||
|
assertTrue(result.equals("01:01"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNegativeTimeString(){
|
||||||
|
RaceViewController controller = new RaceViewController();
|
||||||
|
String result = controller.convertTimeToMinutesSeconds(-61);
|
||||||
|
|
||||||
|
assertTrue(result.equals("-01:01"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"time-scale": "abc",
|
|
||||||
"race-name": 123,
|
|
||||||
"teams-with-wrong-name":["team1","team2","team3"]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work.
|
||||||
|
|
||||||
|
//package seng302.models;
|
||||||
|
//
|
||||||
|
//public class BoatGroupTest {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
package seng302.models;
|
||||||
|
/*
|
||||||
|
* Created by cir27 on 4/05/17.
|
||||||
|
|
||||||
|
public class BoatGroupTest {
|
||||||
|
BoatGroup boatGroup;
|
||||||
|
@Before
|
||||||
|
public void setUp () {
|
||||||
|
Yacht b = new Yacht("TEST", 0.0, "T" ,0);
|
||||||
|
boatGroup = new BoatGroup(b, Color.BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDestinationFirstUseForcesLocationUpdate () {
|
||||||
|
boatGroup.setDestination(10, 10, 90, 0);
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
Assert.assertTrue(10 == bp.getLayoutX());
|
||||||
|
Assert.assertTrue(10 == bp.getLayoutY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDestinationFutureUseDoesntForce () {
|
||||||
|
for (int i = 0; i < 60; i++) {
|
||||||
|
boatGroup.setDestination(200, 200, 90, 0);
|
||||||
|
}
|
||||||
|
boatGroup.setDestination(210, 210, 90, 0);
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
Assert.assertTrue(200 == bp.getLayoutX());
|
||||||
|
Assert.assertTrue(200 == bp.getLayoutY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDestinationUnrealisticMovementForceUpdate () {
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
double xLocation = bp.getLayoutX();
|
||||||
|
double yLocation = bp.getLayoutY();
|
||||||
|
boatGroup.setDestination(xLocation + 500, yLocation + 500, 90, 0);
|
||||||
|
Assert.assertTrue(xLocation + 500 == bp.getLayoutX());
|
||||||
|
Assert.assertTrue(yLocation + 500 == bp.getLayoutY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDestinationUnrealisticNegativeForceUpdate () {
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
double xLocation = bp.getLayoutX();
|
||||||
|
double yLocation = bp.getLayoutY();
|
||||||
|
boatGroup.setDestination(xLocation - 500, yLocation - 500, 90, 0);
|
||||||
|
Assert.assertTrue(xLocation - 500 == bp.getLayoutX());
|
||||||
|
Assert.assertTrue(yLocation - 500 == bp.getLayoutY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updatePositionGeneratesExpectedMovement () {
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
double xLocation = bp.getLayoutX();
|
||||||
|
double yLocation = bp.getLayoutY();
|
||||||
|
int movement = 10;
|
||||||
|
double delay = RaceObject.getExpectedUpdateInterval();
|
||||||
|
double defaultTimePeriod = 1000 / 60;
|
||||||
|
double expectedMovement = movement / delay * defaultTimePeriod;
|
||||||
|
for (int i = 0; i < 60; i++) {
|
||||||
|
boatGroup.setDestination(xLocation, yLocation, 90, 0);
|
||||||
|
}
|
||||||
|
boatGroup.setDestination(xLocation + 10, yLocation + 10, 90, 0);
|
||||||
|
boatGroup.updatePosition(1000/60);
|
||||||
|
Assert.assertEquals(expectedMovement, bp.getLayoutX() - xLocation, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void correctRaceID () {
|
||||||
|
Assert.assertTrue(boatGroup.hasRaceId(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void incorrectRaceID () {
|
||||||
|
Assert.assertTrue(!boatGroup.hasRaceId(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nothingOnWrongId () {
|
||||||
|
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
|
||||||
|
double originalX = bp.getLayoutX();
|
||||||
|
double originalY = bp.getLayoutY();
|
||||||
|
boatGroup.setDestination(10, 10, 90, 12);
|
||||||
|
Assert.assertTrue(originalX == bp.getLayoutX());
|
||||||
|
Assert.assertTrue(originalY == bp.getLayoutY());
|
||||||
|
}
|
||||||
|
}*/
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work.
|
||||||
|
//
|
||||||
|
//package seng302.models;
|
||||||
|
//
|
||||||
|
//public class MarkGroupTest {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
package seng302.models;
|
||||||
|
/*
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import seng302.*;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.models.mark.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cir27 on 4/05/17.
|
||||||
|
*/
|
||||||
|
//public class MarkGroupTest {
|
||||||
|
// private MarkGroup gateMG;
|
||||||
|
// private MarkGroup singleMG;
|
||||||
|
//
|
||||||
|
// @Before
|
||||||
|
// public void setUp () {
|
||||||
|
// Mark single = new SingleMark("SM", 0, 0 , 0);
|
||||||
|
// Mark gate = new GateMark(
|
||||||
|
// "GM",
|
||||||
|
// MarkType.OPEN_GATE,
|
||||||
|
// new SingleMark("GM1", 0, 0, 1),
|
||||||
|
// new SingleMark("GM2", 0, 0, 2),
|
||||||
|
// 0,
|
||||||
|
// 0);
|
||||||
|
// gateMG = new MarkGroup(gate, new Point2D(10, 10), new Point2D(20, 20));
|
||||||
|
// singleMG = new MarkGroup(single, new Point2D(0, 0));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void hasIDSingle () {
|
||||||
|
// Assert.assertTrue(singleMG.hasRaceId(0));
|
||||||
|
// Assert.assertTrue(!singleMG.hasRaceId(100,12));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void hasIdGate () {
|
||||||
|
// Assert.assertTrue(gateMG.hasRaceId(1));
|
||||||
|
// Assert.assertTrue(gateMG.hasRaceId(2));
|
||||||
|
// Assert.assertTrue(!gateMG.hasRaceId(100,12));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void nothingOnWrongId () {
|
||||||
|
// double originalX = singleMG.getChildren().get(0).getLayoutX();
|
||||||
|
// double originalY = singleMG.getChildren().get(0).getLayoutY();
|
||||||
|
// singleMG.setDestination(10, 10, 0, 4);
|
||||||
|
// singleMG.updatePosition(400);
|
||||||
|
// Assert.assertTrue(originalX == singleMG.getChildren().get(0).getLayoutX());
|
||||||
|
// Assert.assertTrue(originalY == singleMG.getChildren().get(0).getLayoutY());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void correctMovementCorrectIdSingle () {
|
||||||
|
// double originalX = singleMG.getChildren().get(0).getLayoutX();
|
||||||
|
// double originalY = singleMG.getChildren().get(0).getLayoutY();
|
||||||
|
// long timeinterval = 1000/60;
|
||||||
|
// double expectedChange = 10 / 200 * timeinterval;
|
||||||
|
// singleMG.setDestination(originalX + 10, originalY + 10, 0, 0);
|
||||||
|
// singleMG.updatePosition(timeinterval);
|
||||||
|
// Assert.assertTrue(originalX + expectedChange == singleMG.getChildren().get(0).getLayoutX());
|
||||||
|
// Assert.assertTrue(originalY + expectedChange == singleMG.getChildren().get(0).getLayoutY());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void correctMovementCorrectIDGate () {
|
||||||
|
// double originalX1 = gateMG.getChildren().get(0).getLayoutX();
|
||||||
|
// double originalY1 = gateMG.getChildren().get(0).getLayoutY();
|
||||||
|
// double originalX2 = gateMG.getChildren().get(1).getLayoutX();
|
||||||
|
// double originalY2 = gateMG.getChildren().get(1).getLayoutY();
|
||||||
|
// long timeinterval = 1000/60;
|
||||||
|
// double expectedChange = 10 / 200 * timeinterval;
|
||||||
|
// gateMG.setDestination(originalX1 + 10, originalY1 + 10, 0, 1);
|
||||||
|
// gateMG.setDestination(originalX2 + 10, originalY2 + 10, 0, 2);
|
||||||
|
// gateMG.updatePosition(timeinterval);
|
||||||
|
// Assert.assertTrue(originalX1 + expectedChange == gateMG.getChildren().get(0).getLayoutX());
|
||||||
|
// Assert.assertTrue(originalY1 + expectedChange == gateMG.getChildren().get(0).getLayoutY());
|
||||||
|
// Assert.assertTrue(originalX2 + expectedChange == gateMG.getChildren().get(1).getLayoutX());
|
||||||
|
// Assert.assertTrue(originalY2 + expectedChange == gateMG.getChildren().get(1).getLayoutY());
|
||||||
|
// }
|
||||||
|
//}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user