mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
171 Commits
sprint_3.0
...
sprint_4.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 762829e5ff | |||
| 835f79b113 | |||
| e0854bc68c | |||
| cec7014856 | |||
| f1a9da83fc | |||
| 6e903bfbed | |||
| 23d62f552e | |||
| 53f6a6b8c5 | |||
| af81bf5891 | |||
| e72ac1def8 | |||
| adbb9ffe3b | |||
| a6d9c66fc9 | |||
| 99588c7ff8 | |||
| 68c3e3e999 | |||
| 67b5650288 | |||
| ffe70a8313 | |||
| 331e0fc6ab | |||
| b3fd735f5c | |||
| 3cbbdb070f | |||
| d2e55bf964 | |||
| 05cdadac79 | |||
| e6aed88188 | |||
| d032314ddb | |||
| 6301fd2fb7 | |||
| 34c3899ec4 | |||
| 46f5fc5172 | |||
| 81c021b59a | |||
| 8ab57e4e61 | |||
| 13ff179840 | |||
| 76c0d34760 | |||
| 71637d7286 | |||
| 1cac7cc189 | |||
| c42942430f | |||
| 7abb36c362 | |||
| 3fd0c0a2dd | |||
| 397f7d003a | |||
| f85d3bf5fe | |||
| 8f93956ff1 | |||
| 4fe4ac1079 | |||
| 8a2f0a9f45 | |||
| 5cc865f0af | |||
| 189ba93e64 | |||
| 89464e033e | |||
| 6d7c36e31f | |||
| ca8ea03870 | |||
| 3f57adb9cf | |||
| 2686dac62e | |||
| ffd40fef6d | |||
| e1b8e19966 | |||
| dec742cf54 | |||
| 6f1b0b06c3 | |||
| 408d70c420 | |||
| e51c966969 | |||
| 08eacacfd4 | |||
| 87150b3c72 | |||
| e385ac5c09 | |||
| c30629542b | |||
| 3f9fa24c69 | |||
| 78573fa837 | |||
| d4837cacda | |||
| 0367805f0f | |||
| e0750f5341 | |||
| 80528c9c42 | |||
| be633c0e60 | |||
| 97f1ccb6c1 | |||
| 68a243725b | |||
| 8f81060a18 | |||
| 07c76f12e1 | |||
| c6ab96a86f | |||
| 059c0de1fa | |||
| 8e147bd1bd | |||
| 4d3cfe71f7 | |||
| 5adb7c3762 | |||
| 937b309b07 | |||
| 48d58ea660 | |||
| aaf2e6a3f0 | |||
| 422dcd4501 | |||
| 951a726309 | |||
| b692ddcbe6 | |||
| 5d6b356602 | |||
| 08057edb28 | |||
| 390aabc78f | |||
| a2123df0c5 | |||
| b87008f590 | |||
| ecc0e722b5 | |||
| 6f9a8e5581 | |||
| b17bba3629 | |||
| cf4d7e03f5 | |||
| 73eeeb0ef9 | |||
| 0f79353936 | |||
| 38b44fa92b | |||
| 3fd13ddc0a | |||
| 2e375978bd | |||
| 45db731a60 | |||
| 95e353c14e | |||
| 8a3a41294a | |||
| f41858e2c7 | |||
| aaa3dc93f1 | |||
| e5eab0a6c8 | |||
| 7c39368126 | |||
| ade926e2f2 | |||
| c63c8e4d73 | |||
| 3c418b2aa4 | |||
| d34a158c34 | |||
| 6e3d037021 | |||
| 7d160eaf54 | |||
| 4fc99edbd6 | |||
| 4da8c1645e | |||
| 1c01aab1e7 | |||
| 6a85b0800f | |||
| 1acb0fbac4 | |||
| 2b294702a9 | |||
| afd97d6e05 | |||
| 2d5a7a8a49 | |||
| 9e3036e134 | |||
| 8dec458ba9 | |||
| da07d885da | |||
| a9de005e1a | |||
| e03e121da4 | |||
| fc3ca70e5d | |||
| 9c7144c918 | |||
| ed8d70c3b3 | |||
| fa501460cb | |||
| 110143ae6e | |||
| eda3d76077 | |||
| 51f090324a | |||
| 335540ff4a | |||
| 5fa47ff65b | |||
| 3a1c1a5e43 | |||
| 4c7f530458 | |||
| 2e914a7704 | |||
| 8fbb9d6d4e | |||
| 23bc643c91 | |||
| c4fe116267 | |||
| 081d7e3dcb | |||
| 764ae37ce4 | |||
| e62a609b6b | |||
| 4b1a4aae87 | |||
| ccda5f2a2e | |||
| 3fd8b1b855 | |||
| 94d1982670 | |||
| 39efafc75f | |||
| afe0c9f1a6 | |||
| 04b105d74b | |||
| 1ab6351d48 | |||
| 9c348df5a5 | |||
| 256ec046fc | |||
| 85d4d63287 | |||
| 213d36ed56 | |||
| ff6bfc9516 | |||
| ae5678482b | |||
| 63514cfafb | |||
| 03ca60f2e1 | |||
| 89ef6e5277 | |||
| 47880d09bc | |||
| 5472765b95 | |||
| 14d975dce4 | |||
| a23bdd0c53 | |||
| b0d8c3db0a | |||
| 4a75c062ce | |||
| fe90a3bf13 | |||
| 711f6f4c45 | |||
| 8fa7829a3c | |||
| 6d7697a0eb | |||
| cdd80af27b | |||
| 2cb09b81f8 | |||
| b529d621e9 | |||
| ac3f3bfd55 | |||
| 3a72409fb8 | |||
| fd092bb7e1 | |||
| 9c60521d00 |
@@ -0,0 +1,17 @@
|
|||||||
|
engines:
|
||||||
|
pmd:
|
||||||
|
enabled: true
|
||||||
|
channel: "beta"
|
||||||
|
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
strings:
|
||||||
|
- FIXME
|
||||||
|
- TODO
|
||||||
|
- BUG
|
||||||
|
- FIX
|
||||||
|
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- "**.java"
|
||||||
@@ -16,10 +16,12 @@
|
|||||||
# 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@michaelrausch.net>
|
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
||||||
|
Michael Rausch <mra106@uclive.ac.nz> <michael@michaelrausch.net>
|
||||||
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
||||||
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
||||||
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
||||||
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
||||||
|
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
|
||||||
|
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com>
|
||||||
|
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||||
@@ -5,27 +5,33 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import seng302.models.parsers.StreamParser;
|
import seng302.models.PolarTable;
|
||||||
import seng302.models.parsers.StreamReceiver;
|
import seng302.models.stream.StreamParser;
|
||||||
|
import seng302.models.stream.StreamReceiver;
|
||||||
import seng302.server.ServerThread;
|
import seng302.server.ServerThread;
|
||||||
|
|
||||||
public class App extends Application
|
public class App extends Application {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
|
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile());
|
||||||
|
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
||||||
primaryStage.setTitle("RaceVision");
|
primaryStage.setTitle("RaceVision");
|
||||||
primaryStage.setScene(new Scene(root));
|
primaryStage.setScene(new Scene(root, 1530, 960));
|
||||||
primaryStage.setMaximized(true);
|
primaryStage.setMaxWidth(1530);
|
||||||
|
primaryStage.setMaxHeight(960);
|
||||||
|
// primaryStage.setMaximized(true);
|
||||||
|
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
primaryStage.setOnCloseRequest(e -> {
|
||||||
StreamParser.appClose();
|
StreamParser.appClose();
|
||||||
StreamReceiver.noMoreBytes();
|
StreamReceiver.noMoreBytes();
|
||||||
System.out.println("[CLIENT] Exiting program");
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -39,15 +45,15 @@ public class App extends Application
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length == 1 && args[0].equals("-standalone")){
|
if (args.length == 1 && args[0].equals("-standalone")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length == 3 && args[0].equals("-server")){
|
if (args.length == 3 && args[0].equals("-server")) {
|
||||||
|
|
||||||
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
|
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
|
||||||
|
|
||||||
} else if(args.length == 2 && args[0].equals("-server")){
|
} else if (args.length == 2 && args[0].equals("-server")) {
|
||||||
switch (args[1]) {
|
switch (args[1]) {
|
||||||
case "internal":
|
case "internal":
|
||||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||||
@@ -71,8 +77,6 @@ public class App extends Application
|
|||||||
|
|
||||||
launch(args);
|
launch(args);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package seng302;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class for performing geometric calculations on the canvas
|
||||||
|
* Created by wmu16 on 24/05/17.
|
||||||
|
*/
|
||||||
|
public final class GeometryUtils {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the line function on two points of a line and a test point to test which side of the line that point is
|
||||||
|
* on. If the return value is
|
||||||
|
* return 1, then the point is on one side of the line,
|
||||||
|
* return -1 then the point is on the other side of the line
|
||||||
|
* return 0 then the point is exactly on the line.
|
||||||
|
* @param linePoint1 One point of the line
|
||||||
|
* @param linePoint2 Second point of the line
|
||||||
|
* @param testPoint The point to test with this line
|
||||||
|
* @return A return value indicating which side of the line the point is on
|
||||||
|
*/
|
||||||
|
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
|
||||||
|
|
||||||
|
Double x = testPoint.getX();
|
||||||
|
Double y = testPoint.getY();
|
||||||
|
Double x1 = linePoint1.getX();
|
||||||
|
Double y1 = linePoint1.getY();
|
||||||
|
Double x2 = linePoint2.getX();
|
||||||
|
Double y2 = linePoint2.getY();
|
||||||
|
|
||||||
|
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
|
||||||
|
|
||||||
|
if (result > 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (result < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
|
||||||
|
* point
|
||||||
|
* @param originPoint The point with which to use as the base for our vector addition
|
||||||
|
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
|
||||||
|
* @param vectorLength The length out on this angle from the origin point to create the new point
|
||||||
|
* @return a Point2D
|
||||||
|
*/
|
||||||
|
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
|
||||||
|
|
||||||
|
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
|
||||||
|
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
|
||||||
|
|
||||||
|
return new Point2D(endPointX, endPointY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
package seng302.controllers;
|
package seng302.controllers;
|
||||||
|
|
||||||
import javafx.animation.*;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Polygon;
|
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.Stage;
|
import seng302.models.BoatGroup;
|
||||||
import seng302.models.*;
|
import seng302.models.Colors;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.map.Boundary;
|
||||||
|
import seng302.models.map.CanvasMap;
|
||||||
import seng302.models.mark.*;
|
import seng302.models.mark.*;
|
||||||
import seng302.models.parsers.StreamParser;
|
import seng302.models.stream.StreamParser;
|
||||||
import seng302.models.parsers.StreamReceiver;
|
import seng302.models.stream.XMLParser;
|
||||||
import seng302.models.parsers.packets.BoatPositionPacket;
|
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
|
||||||
import seng302.models.parsers.XMLParser;
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
|
import seng302.models.stream.packets.BoatPositionPacket;
|
||||||
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
|
import seng302.server.simulator.GeoUtility;
|
||||||
import seng302.models.mark.Mark;
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.util.ArrayList;
|
||||||
import java.util.*;
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,15 +44,14 @@ public class CanvasController {
|
|||||||
private ResizableCanvas canvas;
|
private ResizableCanvas canvas;
|
||||||
private Group group;
|
private Group group;
|
||||||
private GraphicsContext gc;
|
private GraphicsContext gc;
|
||||||
|
private ImageView mapImage;
|
||||||
|
|
||||||
private final int MARK_SIZE = 10;
|
|
||||||
private final int BUFFER_SIZE = 50;
|
private final int BUFFER_SIZE = 50;
|
||||||
private final int CANVAS_WIDTH = 1000;
|
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
||||||
private final int CANVAS_HEIGHT = 1000;
|
private final int PANEL_HEIGHT = 960;
|
||||||
private final int LHS_BUFFER = BUFFER_SIZE;
|
private final int CANVAS_WIDTH = 720;
|
||||||
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
|
private final int CANVAS_HEIGHT = 720;
|
||||||
private final int TOP_BUFFER = BUFFER_SIZE;
|
private boolean horizontalInversion = false;
|
||||||
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
|
|
||||||
|
|
||||||
private double distanceScaleFactor;
|
private double distanceScaleFactor;
|
||||||
private ScaleDirection scaleDirection;
|
private ScaleDirection scaleDirection;
|
||||||
@@ -59,16 +61,17 @@ public class CanvasController {
|
|||||||
private Mark maxLonPoint;
|
private Mark maxLonPoint;
|
||||||
private double referencePointX;
|
private double referencePointX;
|
||||||
private double referencePointY;
|
private double referencePointY;
|
||||||
private double metersToPixels;
|
private double metersPerPixelX;
|
||||||
private List<RaceObject> raceObjects = new ArrayList<>();
|
private double metersPerPixelY;
|
||||||
private List<Mark> raceMarks = new ArrayList<>();
|
|
||||||
|
private List<MarkGroup> markGroups = new ArrayList<>();
|
||||||
|
private List<BoatGroup> boatGroups = new ArrayList<>();
|
||||||
|
|
||||||
//FRAME RATE
|
//FRAME RATE
|
||||||
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
|
private Double frameRate = 60.0;
|
||||||
private final long[] frameTimes = new long[30];
|
private final long[] frameTimes = new long[30];
|
||||||
private int frameTimeIndex = 0;
|
private int frameTimeIndex = 0;
|
||||||
private boolean arrayFilled = false;
|
private boolean arrayFilled = false;
|
||||||
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
|
|
||||||
|
|
||||||
public AnimationTimer timer;
|
public AnimationTimer timer;
|
||||||
|
|
||||||
@@ -86,29 +89,33 @@ public class CanvasController {
|
|||||||
canvas = new ResizableCanvas();
|
canvas = new ResizableCanvas();
|
||||||
group = new Group();
|
group = new Group();
|
||||||
|
|
||||||
|
// create image view for map, bind panel size to image
|
||||||
|
mapImage = new ImageView();
|
||||||
|
canvasPane.getChildren().add(mapImage);
|
||||||
|
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
|
||||||
|
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
|
||||||
|
|
||||||
canvasPane.getChildren().add(canvas);
|
canvasPane.getChildren().add(canvas);
|
||||||
canvasPane.getChildren().add(group);
|
canvasPane.getChildren().add(group);
|
||||||
// Bind canvas size to stack pane size.
|
// Bind canvas size to stack pane size.
|
||||||
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
|
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
|
||||||
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
|
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
|
||||||
//group.minWidth(CANVAS_WIDTH);
|
|
||||||
//group.minHeight(CANVAS_HEIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeCanvas (){
|
public void initializeCanvas (){
|
||||||
|
|
||||||
gc = canvas.getGraphicsContext2D();
|
gc = canvas.getGraphicsContext2D();
|
||||||
gc.save();
|
gc.setGlobalAlpha(0.5);
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
||||||
gc.restore();
|
|
||||||
fitMarksToCanvas();
|
fitMarksToCanvas();
|
||||||
|
drawGoogleMap();
|
||||||
|
|
||||||
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
||||||
drawBoats();
|
initializeBoats();
|
||||||
|
initializeMarks();
|
||||||
timer = new AnimationTimer() {
|
timer = new AnimationTimer() {
|
||||||
|
|
||||||
|
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
|
||||||
|
private int updateFPMCounter = 100;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(long now) {
|
public void handle(long now) {
|
||||||
|
|
||||||
@@ -123,13 +130,14 @@ public class CanvasController {
|
|||||||
if (arrayFilled) {
|
if (arrayFilled) {
|
||||||
elapsedNanos = now - oldFrameTime ;
|
elapsedNanos = now - oldFrameTime ;
|
||||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
||||||
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
||||||
|
if (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
|
||||||
|
updateFPMCounter = 0;
|
||||||
drawFps(frameRate.intValue());
|
drawFps(frameRate.intValue());
|
||||||
}
|
}
|
||||||
|
raceViewController.updateSparkLine();
|
||||||
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
|
}
|
||||||
elapsedNanos = 1000 / 60;
|
updateGroups();
|
||||||
updateRaceObjects();
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
if (StreamParser.isRaceFinished()) {
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
@@ -137,6 +145,30 @@ public class CanvasController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First find the top right and bottom left points' geo locations, then retrieve
|
||||||
|
* map from google to display on image view. - Haoming 22/5/2017
|
||||||
|
*/
|
||||||
|
private void drawGoogleMap() {
|
||||||
|
findMetersPerPixel();
|
||||||
|
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
||||||
|
// distance from top left extreme to panel origin (top left corner)
|
||||||
|
double distanceFromTopLeftToOrigin = Math.sqrt(Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math.pow(topLeftPoint.getY() * metersPerPixelY, 2));
|
||||||
|
// angle from top left extreme to panel origin
|
||||||
|
double bearingFromTopLeftToOrigin = Math.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
|
||||||
|
// the top left extreme
|
||||||
|
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
||||||
|
Position originPos = GeoUtility.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
||||||
|
|
||||||
|
// distance from origin corner to bottom right corner of the panel
|
||||||
|
double distanceFromOriginToBottomRight = Math.sqrt(Math.pow(PANEL_HEIGHT* metersPerPixelY, 2) + Math.pow(PANEL_WIDTH * metersPerPixelX, 2));
|
||||||
|
double bearingFromOriginToBottomRight = Math.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
|
||||||
|
Position bottomRightPos = GeoUtility.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight);
|
||||||
|
|
||||||
|
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), bottomRightPos.getLat(), originPos.getLng());
|
||||||
|
CanvasMap canvasMap = new CanvasMap(boundary);
|
||||||
|
mapImage.setImage(canvasMap.getMapImage());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds border marks to the canvas, taken from the XML file
|
* Adds border marks to the canvas, taken from the XML file
|
||||||
@@ -154,9 +186,9 @@ public class CanvasController {
|
|||||||
double[] yBoundaryPoints = new double[courseLimits.size()];
|
double[] yBoundaryPoints = new double[courseLimits.size()];
|
||||||
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
||||||
Limit thisPoint1 = courseLimits.get(i);
|
Limit thisPoint1 = courseLimits.get(i);
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
|
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
||||||
Limit thisPoint2 = courseLimits.get(i+1);
|
Limit thisPoint2 = courseLimits.get(i+1);
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
|
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
Point2D borderPoint1 = findScaledXY(thisMark1);
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
Point2D borderPoint2 = findScaledXY(thisMark2);
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||||
@@ -165,108 +197,118 @@ public class CanvasController {
|
|||||||
yBoundaryPoints[i] = borderPoint1.getY();
|
yBoundaryPoints[i] = borderPoint1.getY();
|
||||||
}
|
}
|
||||||
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
|
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
||||||
Limit thisPoint2 = courseLimits.get(0);
|
Limit thisPoint2 = courseLimits.get(0);
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
|
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
Point2D borderPoint1 = findScaledXY(thisMark1);
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
Point2D borderPoint2 = findScaledXY(thisMark2);
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||||
borderPoint2.getX(), borderPoint2.getY());
|
borderPoint2.getX(), borderPoint2.getY());
|
||||||
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
||||||
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
||||||
gc.setFill(Color.LIGHTBLUE);
|
// gc.setFill(Color.LIGHTBLUE);
|
||||||
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
// gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateGroups(){
|
||||||
/**
|
for (BoatGroup boatGroup : boatGroups) {
|
||||||
* Adds the course marks to the canvas, taken from the XMl file
|
// some raceObjects will have multiple ID's (for instance gate marks)
|
||||||
*
|
|
||||||
* 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
|
//checking if the current "ID" has any updates associated with it
|
||||||
if (StreamParser.boatPositions.containsKey(id)) {
|
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
||||||
move(id, raceObject);
|
if (boatGroup.isStopped()) {
|
||||||
|
updateBoatGroup(boatGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boatGroup.move();
|
||||||
|
}
|
||||||
|
for (MarkGroup markGroup : markGroups) {
|
||||||
|
for (Long id : markGroup.getRaceIds()) {
|
||||||
|
if (StreamParser.markLocations.containsKey(id)) {
|
||||||
|
updateMarkGroup(id, markGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkForCourseChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForCourseChanges() {
|
||||||
|
if (StreamParser.isNewRaceXmlReceived()){
|
||||||
|
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||||
|
drawGoogleMap();
|
||||||
|
addRaceBorder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void move(long id, RaceObject raceObject){
|
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(id);
|
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
|
||||||
|
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
|
||||||
if (movementQueue.size() > 0){
|
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 {
|
try {
|
||||||
positionPacket = movementQueue.take();
|
BoatPositionPacket positionPacket = movementQueue.take();
|
||||||
Point2D p2d = latLonToXY(positionPacket.getLat(), positionPacket.getLon());
|
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||||
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
||||||
raceObject.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), (int) id);
|
boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate, boatGroup.getRaceId());
|
||||||
|
} catch (InterruptedException e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateMarkGroup (long raceId, MarkGroup markGroup) {
|
||||||
|
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId);
|
||||||
|
if (movementQueue.size() > 0){
|
||||||
|
try {
|
||||||
|
BoatPositionPacket positionPacket = movementQueue.take();
|
||||||
|
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||||
|
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
|
||||||
} catch (InterruptedException e){
|
} catch (InterruptedException e){
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws all the boats.
|
||||||
|
*/
|
||||||
|
private void initializeBoats() {
|
||||||
|
Map<Integer, Yacht> boats = StreamParser.getBoats();
|
||||||
|
Group boatAnnotations = new Group();
|
||||||
|
|
||||||
|
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML().getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Yacht boat : boats.values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) {
|
||||||
|
boat.setColour(Colors.getColor());
|
||||||
|
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
||||||
|
boatGroups.add(boatGroup);
|
||||||
|
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.getChildren().add(boatAnnotations);
|
||||||
|
group.getChildren().addAll(boatGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMarks() {
|
||||||
|
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
|
||||||
|
for (Mark mark : allMarks) {
|
||||||
|
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||||
|
SingleMark sMark = (SingleMark) mark;
|
||||||
|
|
||||||
|
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
|
||||||
|
markGroups.add(markGroup);
|
||||||
|
} else {
|
||||||
|
GateMark gMark = (GateMark) mark;
|
||||||
|
|
||||||
|
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
|
||||||
|
markGroups.add(markGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.getChildren().addAll(markGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResizableCanvas extends Canvas {
|
class ResizableCanvas extends Canvas {
|
||||||
@@ -294,63 +336,37 @@ public class CanvasController {
|
|||||||
public double prefWidth(double height) {
|
public double prefWidth(double height) {
|
||||||
return getWidth();
|
return getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double prefHeight(double width) {
|
public double prefHeight(double width) {
|
||||||
return getHeight();
|
return getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawFps(int fps){
|
private void drawFps(int fps){
|
||||||
if (raceViewController.isDisplayFps()){
|
if (raceViewController.isDisplayFps()){
|
||||||
gc.clearRect(5,5,50,20);
|
gc.clearRect(5, 5, 60, 30);
|
||||||
gc.setFill(Color.SKYBLUE);
|
gc.setFont(new Font(16));
|
||||||
gc.fillRect(4,4,51,21);
|
gc.setLineWidth(4);
|
||||||
gc.setFill(Color.BLACK);
|
gc.setGlobalAlpha(0.75);
|
||||||
gc.setFont(new Font(14));
|
|
||||||
gc.setLineWidth(3);
|
|
||||||
gc.fillText(fps + " FPS", 5, 20);
|
gc.fillText(fps + " FPS", 5, 20);
|
||||||
|
gc.setGlobalAlpha(0.5);
|
||||||
} else {
|
} else {
|
||||||
gc.clearRect(5,5,50,20);
|
gc.clearRect(5,5,60,30);
|
||||||
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.
|
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
|
||||||
*/
|
*/
|
||||||
private void fitMarksToCanvas() {
|
private void fitMarksToCanvas() {
|
||||||
|
//Check is called once to avoid unnecessarily change the course limits once the race is running
|
||||||
|
StreamParser.isNewRaceXmlReceived();
|
||||||
findMinMaxPoint();
|
findMinMaxPoint();
|
||||||
double minLonToMaxLon = scaleRaceExtremities();
|
double minLonToMaxLon = scaleRaceExtremities();
|
||||||
calculateReferencePointLocation(minLonToMaxLon);
|
calculateReferencePointLocation(minLonToMaxLon);
|
||||||
givePointsXY();
|
//givePointsXY();
|
||||||
addRaceBorder();
|
addRaceBorder();
|
||||||
findMetersToPixels();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -366,23 +382,18 @@ public class CanvasController {
|
|||||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
||||||
Limit minLatMark = sortedPoints.get(0);
|
Limit minLatMark = sortedPoints.get(0);
|
||||||
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
||||||
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID());
|
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
|
||||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID());
|
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
|
||||||
|
|
||||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
||||||
//If the course is on a point on the earth where longitudes wrap around.
|
//If the course is on a point on the earth where longitudes wrap around.
|
||||||
Limit minLonMark = sortedPoints.get(0);
|
Limit minLonMark = sortedPoints.get(0);
|
||||||
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
||||||
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
|
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
|
||||||
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
|
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
|
||||||
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around.
|
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
|
||||||
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) {
|
horizontalInversion = true;
|
||||||
SingleMark temp = thisMinLon;
|
|
||||||
thisMinLon = thisMaxLon;
|
|
||||||
thisMaxLon = temp;
|
|
||||||
}
|
}
|
||||||
minLonPoint = thisMinLon;
|
|
||||||
maxLonPoint = thisMaxLon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,24 +408,28 @@ public class CanvasController {
|
|||||||
|
|
||||||
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||||
referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
||||||
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER);
|
referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
|
||||||
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||||
referencePointY = referencePointY / 2;
|
referencePointY = referencePointY / 2;
|
||||||
referencePointY += TOP_BUFFER;
|
referencePointY += BUFFER_SIZE;
|
||||||
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||||
} else {
|
} else {
|
||||||
referencePointY = CANVAS_HEIGHT - BOT_BUFFER;
|
referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||||
referencePointX = LHS_BUFFER;
|
referencePointX = BUFFER_SIZE;
|
||||||
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||||
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
||||||
|
}
|
||||||
|
if(horizontalInversion) {
|
||||||
|
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor
|
* 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.
|
* Returns the max horizontal distance of the map.
|
||||||
@@ -431,10 +446,10 @@ public class CanvasController {
|
|||||||
horiAngle = horiAngle - (Math.PI / 2);
|
horiAngle = horiAngle - (Math.PI / 2);
|
||||||
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
|
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
|
||||||
|
|
||||||
double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance;
|
double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
|
||||||
|
|
||||||
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) {
|
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
|
||||||
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance;
|
distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
|
||||||
scaleDirection = ScaleDirection.HORIZONTAL;
|
scaleDirection = ScaleDirection.HORIZONTAL;
|
||||||
} else {
|
} else {
|
||||||
distanceScaleFactor = vertScale;
|
distanceScaleFactor = vertScale;
|
||||||
@@ -443,43 +458,18 @@ public class CanvasController {
|
|||||||
return horiDistance;
|
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) {
|
private Point2D findScaledXY (Mark unscaled) {
|
||||||
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(),
|
return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude());
|
||||||
unscaled.getLatitude(), unscaled.getLongitude());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) {
|
public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
|
||||||
double distanceFromReference;
|
double distanceFromReference;
|
||||||
double angleFromReference;
|
double angleFromReference;
|
||||||
int xAxisLocation = (int) referencePointX;
|
int xAxisLocation = (int) referencePointX;
|
||||||
int yAxisLocation = (int) referencePointY;
|
int yAxisLocation = (int) referencePointY;
|
||||||
|
|
||||||
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB);
|
angleFromReference = Mark.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
|
||||||
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB);
|
distanceFromReference = Mark.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
|
||||||
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
||||||
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
@@ -496,44 +486,38 @@ public class CanvasController {
|
|||||||
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||||
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||||
}
|
}
|
||||||
|
if(horizontalInversion) {
|
||||||
|
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
|
||||||
|
}
|
||||||
return new Point2D(xAxisLocation, yAxisLocation);
|
return new Point2D(xAxisLocation, yAxisLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the number of meters per pixel.
|
* Find the number of meters per pixel.
|
||||||
*/
|
*/
|
||||||
private void findMetersToPixels () {
|
private void findMetersPerPixel () {
|
||||||
Double angularDistance;
|
Point2D p1, p2;
|
||||||
Double angle;
|
Mark m1, m2;
|
||||||
Double straightLineDistance;
|
double theta, distance, dx, dy, dHorizontal, dVertical;
|
||||||
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
|
||||||
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint);
|
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
|
||||||
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
p1 = findScaledXY(m1);
|
||||||
if (angle > Math.PI / 2) {
|
p2 = findScaledXY(m2);
|
||||||
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance;
|
theta = Mark.calculateHeadingRad(m1, m2);
|
||||||
} else {
|
distance = Mark.calculateDistance(m1, m2);
|
||||||
straightLineDistance = Math.cos(angle) * angularDistance;
|
dHorizontal = Math.abs(Math.sin(theta) * distance);
|
||||||
}
|
dVertical = Math.abs(Math.cos(theta) * distance);
|
||||||
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance;
|
dx = Math.abs(p1.getX() - p2.getX());
|
||||||
} else {
|
dy = Math.abs(p1.getY() - p2.getY());
|
||||||
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint);
|
metersPerPixelX = dHorizontal / dx;
|
||||||
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint);
|
metersPerPixelY = dVertical / dy;
|
||||||
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) {
|
List<BoatGroup> getBoatGroups() {
|
||||||
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude);
|
return boatGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RaceObject> getRaceObjects() {
|
List<MarkGroup> getMarkGroups() {
|
||||||
return raceObjects;
|
return markGroups;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,164 +1,39 @@
|
|||||||
package seng302.controllers;
|
package seng302.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
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.AnchorPane;
|
||||||
import javafx.scene.layout.Pane;
|
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.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Timer;
|
import seng302.models.stream.StreamParser;
|
||||||
import java.util.TimerTask;
|
|
||||||
|
|
||||||
public class Controller implements Initializable {
|
public class Controller implements Initializable {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private AnchorPane contentPane;
|
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 {
|
||||||
private void setContentPane(String jfxUrl){
|
|
||||||
try{
|
|
||||||
contentPane.getChildren().removeAll();
|
contentPane.getChildren().removeAll();
|
||||||
contentPane.getChildren().clear();
|
contentPane.getChildren().clear();
|
||||||
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
}
|
contentPane.getChildren()
|
||||||
catch(javafx.fxml.LoadException e){
|
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
System.err.println(e.getCause());
|
System.err.println(e.getCause());
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch(IOException e){
|
|
||||||
System.err.println(e);
|
System.err.println(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
//format.setTimeZone(TimeZone.getTimeZone("GMT-8"));
|
setContentPane("/views/StartScreenView.fxml");
|
||||||
//realTime.setText(format.format(new Date()));
|
StreamParser.boatLocations.clear();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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--;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,60 @@
|
|||||||
package seng302.controllers;
|
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.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.geometry.Side;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.chart.LineChart;
|
||||||
|
import javafx.scene.chart.NumberAxis;
|
||||||
|
import javafx.scene.chart.XYChart;
|
||||||
|
import javafx.scene.chart.XYChart.Series;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.paint.Paint;
|
||||||
|
import javafx.scene.shape.Line;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import seng302.GeometryUtils;
|
||||||
|
import seng302.controllers.annotations.Annotation;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationController;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationDelegate;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationsState;
|
||||||
import seng302.models.*;
|
import seng302.models.*;
|
||||||
import seng302.models.parsers.ConfigParser;
|
import seng302.models.mark.GateMark;
|
||||||
import seng302.models.parsers.StreamParser;
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.MarkGroup;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
import seng302.models.stream.StreamParser;
|
||||||
|
import seng302.models.stream.XMLParser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by ptg19 on 29/03/17.
|
* Created by ptg19 on 29/03/17.
|
||||||
*/
|
*/
|
||||||
public class RaceViewController extends Thread{
|
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private LineChart raceSparkLine;
|
||||||
|
@FXML
|
||||||
|
private NumberAxis sparklineYAxis;
|
||||||
@FXML
|
@FXML
|
||||||
private VBox positionVbox;
|
private VBox positionVbox;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -43,64 +68,105 @@ public class RaceViewController extends Thread{
|
|||||||
@FXML
|
@FXML
|
||||||
private Slider annotationSlider;
|
private Slider annotationSlider;
|
||||||
@FXML
|
@FXML
|
||||||
|
private Button selectAnnotationBtn;
|
||||||
|
@FXML
|
||||||
|
private ComboBox boatSelectionComboBox;
|
||||||
|
@FXML
|
||||||
private CanvasController includedCanvasController;
|
private CanvasController includedCanvasController;
|
||||||
|
|
||||||
private ArrayList<Yacht> startingBoats = new ArrayList<>();
|
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
|
||||||
private boolean displayFps;
|
private boolean displayFps;
|
||||||
private Timeline timerTimeline;
|
private Timeline timerTimeline;
|
||||||
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
|
|
||||||
private ArrayList<Yacht> boatOrder = new ArrayList<>();
|
|
||||||
private Race race;
|
|
||||||
private Stage stage;
|
private Stage stage;
|
||||||
|
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
|
||||||
|
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
|
||||||
|
private ImportantAnnotationsState importantAnnotations;
|
||||||
|
private Yacht selectedBoat;
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
// Load a default important annotation state
|
||||||
|
importantAnnotations = new ImportantAnnotationsState();
|
||||||
|
|
||||||
RaceController raceController = new RaceController();
|
//Formatting the y axis of the sparkline
|
||||||
raceController.initializeRace();
|
raceSparkLine.getYAxis().setRotate(180);
|
||||||
race = raceController.getRace();
|
raceSparkLine.getYAxis().setTickLabelRotation(180);
|
||||||
for (Yacht boat : race.getBoats()) {
|
raceSparkLine.getYAxis().setTranslateX(-5);
|
||||||
startingBoats.add(boat);
|
raceSparkLine.getYAxis().setAutoRanging(false);
|
||||||
}
|
sparklineYAxis.setTickMarkVisible(false);
|
||||||
// try{
|
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
|
||||||
// initializeTimelines();
|
|
||||||
// }
|
|
||||||
// catch (Exception e){
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
|
|
||||||
includedCanvasController.setup(this);
|
includedCanvasController.setup(this);
|
||||||
includedCanvasController.initializeCanvas();
|
includedCanvasController.initializeCanvas();
|
||||||
initializeTimer();
|
initializeUpdateTimer();
|
||||||
initializeSettings();
|
initialiseFPSCheckBox();
|
||||||
initialiseWindDirection();
|
initialiseAnnotationSlider();
|
||||||
initialisePositionVBox();
|
initialiseBoatSelectionComboBox();
|
||||||
//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();
|
includedCanvasController.timer.start();
|
||||||
|
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The important annotations have been changed, update this view
|
||||||
|
*
|
||||||
|
* @param importantAnnotationsState The current state of the selected annotations
|
||||||
|
*/
|
||||||
|
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
||||||
|
this.importantAnnotations = importantAnnotationsState;
|
||||||
|
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeSettings() {
|
|
||||||
|
/**
|
||||||
|
* Loads the "select annotations" view in a new window
|
||||||
|
*/
|
||||||
|
private void loadSelectAnnotationView() {
|
||||||
|
try {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader();
|
||||||
|
Stage stage = new Stage();
|
||||||
|
|
||||||
|
// Set controller
|
||||||
|
ImportantAnnotationController controller = new ImportantAnnotationController(this,
|
||||||
|
stage);
|
||||||
|
fxmlLoader.setController(controller);
|
||||||
|
|
||||||
|
// Load FXML and set CSS
|
||||||
|
fxmlLoader
|
||||||
|
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
|
||||||
|
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
|
||||||
|
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
stage.initStyle(StageStyle.UNDECORATED);
|
||||||
|
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.show();
|
||||||
|
|
||||||
|
controller.loadState(importantAnnotations);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void initialiseFPSCheckBox() {
|
||||||
displayFps = true;
|
displayFps = true;
|
||||||
|
toggleFps.selectedProperty().addListener(
|
||||||
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
(observable, oldValue, newValue) -> displayFps = !displayFps);
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
|
||||||
displayFps = !displayFps;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
//SLIFER STUFF BELOW
|
private void initialiseAnnotationSlider() {
|
||||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(Double n) {
|
public String toString(Double n) {
|
||||||
if (n == 0) return "None";
|
if (n == 0) {
|
||||||
if (n == 1) return "Low";
|
return "None";
|
||||||
if (n == 2) return "Medium";
|
}
|
||||||
if (n == 3) return "All";
|
if (n == 1) {
|
||||||
|
return "Important";
|
||||||
|
}
|
||||||
|
if (n == 2) {
|
||||||
|
return "All";
|
||||||
|
}
|
||||||
|
|
||||||
return "All";
|
return "All";
|
||||||
}
|
}
|
||||||
@@ -110,38 +176,115 @@ public class RaceViewController extends Thread{
|
|||||||
switch (s) {
|
switch (s) {
|
||||||
case "None":
|
case "None":
|
||||||
return 0d;
|
return 0d;
|
||||||
case "Low":
|
case "Important":
|
||||||
return 1d;
|
return 1d;
|
||||||
case "Medium":
|
|
||||||
return 2d;
|
|
||||||
case "All":
|
case "All":
|
||||||
return 3d;
|
return 2d;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 3d;
|
return 2d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
|
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
|
||||||
setAnnotations((int)annotationSlider.getValue()));
|
setAnnotations((int) annotationSlider.getValue()));
|
||||||
|
|
||||||
annotationSlider.setValue(3);
|
annotationSlider.setValue(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeTimer(){
|
|
||||||
|
/**
|
||||||
|
* Used to add any new boats into the race that may have started late or not have had data received yet
|
||||||
|
*/
|
||||||
|
void updateSparkLine(){
|
||||||
|
// Collect the racing boats that aren't already in the chart
|
||||||
|
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
|
||||||
|
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
// Obtain the qualifying boats to set the max on the Y axis
|
||||||
|
racingBoats = startingBoats.stream().filter(yacht ->
|
||||||
|
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
|
||||||
|
|
||||||
|
// Create a new data series for new boats
|
||||||
|
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
|
||||||
|
Series<String, Double> yachtData = new Series<>();
|
||||||
|
yachtData.setName(yacht.getBoatName());
|
||||||
|
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||||
|
sparkLineData.put(yacht.getSourceID(), yachtData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lambda function to sort the series in order of leg (later legs shown more to the right)
|
||||||
|
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
|
||||||
|
Collections.sort(positions, (o1, o2) -> {
|
||||||
|
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
|
||||||
|
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
|
||||||
|
if (leg2 < leg1){
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adds the new data series to the sparkline (and set the colour of the series)
|
||||||
|
raceSparkLine.setCreateSymbols(false);
|
||||||
|
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
|
||||||
|
raceSparkLine.getData().add(spark);
|
||||||
|
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the yachts sparkline of the desired boat and using the new leg number
|
||||||
|
* @param yacht The yacht to be updated on the sparkline
|
||||||
|
* @param legNumber the leg number that the position will be assigned to
|
||||||
|
*/
|
||||||
|
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
|
||||||
|
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
|
||||||
|
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the rgb string of the boats colour to use for the chart via css
|
||||||
|
* @param boatName boat passed in to get the boats colour
|
||||||
|
* @return the colour as an rgb string
|
||||||
|
*/
|
||||||
|
private String getBoatColorAsRGB(String boatName){
|
||||||
|
Color color = Color.WHITE;
|
||||||
|
for (Yacht yacht: startingBoats){
|
||||||
|
if (Objects.equals(yacht.getBoatName(), boatName)){
|
||||||
|
color = yacht.getColour();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (color == null){
|
||||||
|
return String.format( "#%02X%02X%02X",255,255,255);
|
||||||
|
}
|
||||||
|
return String.format( "#%02X%02X%02X",
|
||||||
|
(int)( color.getRed() * 255 ),
|
||||||
|
(int)( color.getGreen() * 255 ),
|
||||||
|
(int)( color.getBlue() * 255 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
|
||||||
|
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
||||||
|
* Updates of each of these attributes are called ONCE EACH SECOND
|
||||||
|
*/
|
||||||
|
private void initializeUpdateTimer() {
|
||||||
timerTimeline = new Timeline();
|
timerTimeline = new Timeline();
|
||||||
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
// Run timer update every second
|
// Run timer update every second
|
||||||
timerTimeline.getKeyFrames().add(
|
timerTimeline.getKeyFrames().add(
|
||||||
new KeyFrame(Duration.seconds(1),
|
new KeyFrame(Duration.seconds(1),
|
||||||
event -> {
|
event -> {
|
||||||
if (StreamParser.isRaceFinished()) {
|
updateRaceTime();
|
||||||
timerLabel.setFill(Color.RED);
|
updateWindDirection();
|
||||||
timerLabel.setText("Race Finished!");
|
updateOrder();
|
||||||
} else {
|
updateBoatSelectionComboBox();
|
||||||
timerLabel.setText(currentTimer());
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -149,130 +292,233 @@ public class RaceViewController extends Thread{
|
|||||||
timerTimeline.playFromStart();
|
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
|
* Iterates over all corners until ones SeqID matches with the boats current leg number.
|
||||||
|
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
|
||||||
|
* Returns null if no next mark found.
|
||||||
|
* @param bg The BoatGroup to find the next mark of
|
||||||
|
* @return The next Mark or null if none found
|
||||||
*/
|
*/
|
||||||
private void initializeTimelines() {
|
private Mark getNextMark(BoatGroup bg) {
|
||||||
HashMap<Yacht, List> boat_events = race.getEvents();
|
Integer legNumber = bg.getBoat().getLegNumber();
|
||||||
for (Yacht boat : boat_events.keySet()) {
|
|
||||||
startingBoats.add(boat);
|
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence();
|
||||||
// // x, y are the real time coordinates
|
|
||||||
// DoubleProperty x = new SimpleDoubleProperty();
|
if (legNumber == 0) {
|
||||||
// DoubleProperty y = new SimpleDoubleProperty();
|
return null;
|
||||||
//
|
} else if (legNumber == markSequence.size() - 1) {
|
||||||
// List<KeyFrame> keyFrames = new ArrayList<>();
|
return null;
|
||||||
// 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(){
|
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
|
||||||
Double maxDuration = 0.0;
|
if (legNumber + 2 == corner.getSeqID()) {
|
||||||
Timeline maxTimeline = null;
|
Integer thisCompoundMarkID = corner.getCompoundMarkID();
|
||||||
|
|
||||||
for (TimelineInfo timelineInfo : timelineInfos.values()) {
|
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
|
||||||
|
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
||||||
Timeline timeline = timelineInfo.getTimeline();
|
return mark;
|
||||||
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
|
}
|
||||||
maxDuration = timeline.getTotalDuration().toMillis();
|
}
|
||||||
maxTimeline = timeline;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timelines are paused by default
|
return null;
|
||||||
timeline.play();
|
|
||||||
timeline.pause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTimeline.setOnFinished(event -> {
|
|
||||||
race.setRaceFinished();
|
/**
|
||||||
loadRaceResultView();
|
* Updates the wind direction arrow and text as from info from the StreamParser
|
||||||
|
*/
|
||||||
|
private void updateWindDirection() {
|
||||||
|
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
||||||
|
windArrowText.setRotate(StreamParser.getWindDirection());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the clock for the race
|
||||||
|
*/
|
||||||
|
private void updateRaceTime() {
|
||||||
|
if (StreamParser.isRaceFinished()) {
|
||||||
|
timerLabel.setFill(Color.RED);
|
||||||
|
timerLabel.setText("Race Finished!");
|
||||||
|
} else {
|
||||||
|
timerLabel.setText(getTimeSinceStartOfRace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
|
||||||
|
* in the boat selection combo box
|
||||||
|
*/
|
||||||
|
private void updateBoatSelectionComboBox() {
|
||||||
|
ObservableList<Yacht> observableBoats = FXCollections
|
||||||
|
.observableArrayList(StreamParser.getBoatsPos().values());
|
||||||
|
boatSelectionComboBox.setItems(observableBoats);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the order of the boats as from the StreamParser and sets them in the boat order
|
||||||
|
* section
|
||||||
|
*/
|
||||||
|
private void updateOrder() {
|
||||||
|
positionVbox.getChildren().clear();
|
||||||
|
positionVbox.getChildren().removeAll();
|
||||||
|
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
|
||||||
|
// list of racing boat id
|
||||||
|
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
||||||
|
.getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StreamParser.isRaceStarted()) {
|
||||||
|
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
||||||
|
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
||||||
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " (Finished)");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " ");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
textToAdd.setStyle("");
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Yacht boat : StreamParser.getBoats().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
||||||
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " ");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
textToAdd.setStyle("");
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateLaylines(BoatGroup bg) {
|
||||||
|
|
||||||
|
Mark nextMark = getNextMark(bg);
|
||||||
|
Boolean isUpwind = null;
|
||||||
|
// Can only calc leg direction if there is a next mark and it is a gate mark
|
||||||
|
if (nextMark != null) {
|
||||||
|
if (nextMark instanceof GateMark) {
|
||||||
|
if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
|
||||||
|
isUpwind = true;
|
||||||
|
} else {
|
||||||
|
isUpwind = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
|
||||||
|
|
||||||
|
mg.removeLaylines();
|
||||||
|
|
||||||
|
if (mg.getMainMark().getId() == nextMark.getId()) {
|
||||||
|
|
||||||
|
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
||||||
|
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
||||||
|
Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
||||||
|
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
||||||
|
HashMap<Double, Double> angleAndSpeed;
|
||||||
|
if (isUpwind) {
|
||||||
|
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
|
||||||
|
} else {
|
||||||
|
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||||
|
|
||||||
|
|
||||||
|
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||||
|
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||||
|
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
||||||
|
Line rightLayline = new Line();
|
||||||
|
Line leftLayline = new Line();
|
||||||
|
if (lineFuncResult == 1) {
|
||||||
|
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||||
|
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||||
|
} else if (lineFuncResult == -1) {
|
||||||
|
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||||
|
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||||
|
}
|
||||||
|
|
||||||
|
leftLayline.setStrokeWidth(0.5);
|
||||||
|
leftLayline.setStroke(bg.getBoat().getColour());
|
||||||
|
|
||||||
|
rightLayline.setStrokeWidth(0.5);
|
||||||
|
rightLayline.setStroke(bg.getBoat().getColour());
|
||||||
|
|
||||||
|
bg.setLaylines(leftLayline, rightLayline);
|
||||||
|
mg.addLaylines(leftLayline, rightLayline);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
|
||||||
|
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
|
||||||
|
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
|
||||||
|
|
||||||
|
return new Point2D(newX, newY);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||||
|
|
||||||
|
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
|
||||||
|
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||||
|
return line;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||||
|
|
||||||
|
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
|
||||||
|
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||||
|
return line;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialised the combo box with any boats currently in the race and adds the required listener
|
||||||
|
* for the combobox to take action upon selection
|
||||||
|
*/
|
||||||
|
private void initialiseBoatSelectionComboBox() {
|
||||||
|
updateBoatSelectionComboBox();
|
||||||
|
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
//This listener is fired whenever the combo box changes. This means when the values are updated
|
||||||
|
//We dont want to set the selected value if the values are updated but nothing clicked (null)
|
||||||
|
if (newValue != null && newValue != selectedBoat) {
|
||||||
|
Yacht thisYacht = (Yacht) newValue;
|
||||||
|
setSelectedBoat(thisYacht);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Display the list of boats in the order they finished the race
|
||||||
*/
|
*/
|
||||||
private void loadRaceResultView() {
|
private void loadRaceResultView() {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||||
loader.setController(new RaceResultController(race));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contentAnchorPane.getChildren().removeAll();
|
contentAnchorPane.getChildren().removeAll();
|
||||||
@@ -286,40 +532,6 @@ public class RaceViewController extends Thread{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* Convert seconds to a string of the format mm:ss
|
||||||
@@ -334,7 +546,7 @@ public class RaceViewController extends Thread{
|
|||||||
return String.format("%02d:%02d", time / 60, time % 60);
|
return String.format("%02d:%02d", time / 60, time % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String currentTimer() {
|
private String getTimeSinceStartOfRace() {
|
||||||
String timerString = "0:00";
|
String timerString = "0:00";
|
||||||
if (StreamParser.getTimeSinceStart() > 0) {
|
if (StreamParser.getTimeSinceStart() > 0) {
|
||||||
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
@@ -354,84 +566,120 @@ public class RaceViewController extends Thread{
|
|||||||
return timerString;
|
return timerString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopTimer() {
|
|
||||||
timerTimeline.stop();
|
|
||||||
}
|
|
||||||
public void startTimer() {
|
|
||||||
timerTimeline.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDisplayFps() {
|
boolean isDisplayFps() {
|
||||||
return displayFps;
|
return displayFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Race getRace() {
|
/**
|
||||||
return race;
|
* Display the important annotations for a specific BoatGroup
|
||||||
|
* @param bg The boat group to set the annotations for
|
||||||
|
*/
|
||||||
|
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
|
||||||
|
if (importantAnnotations.getAnnotationState(Annotation.NAME)) {
|
||||||
|
bg.setTeamNameObjectVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setTeamNameObjectVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Yacht, TimelineInfo> getTimelineInfos() {
|
if (importantAnnotations.getAnnotationState(Annotation.SPEED)) {
|
||||||
return timelineInfos;
|
bg.setVelocityObjectVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setVelocityObjectVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<Yacht> getStartingBoats(){
|
if (importantAnnotations.getAnnotationState(Annotation.TRACK)) {
|
||||||
return startingBoats;
|
bg.setLineGroupVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setLineGroupVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (importantAnnotations.getAnnotationState(Annotation.WAKE)) {
|
||||||
|
bg.setWakeVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setWakeVisible(false);
|
||||||
|
}
|
||||||
|
//TODO fix boat annotations with new boatgroup
|
||||||
|
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
|
||||||
|
bg.setEstTimeToNextMarkObjectVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setEstTimeToNextMarkObjectVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) {
|
||||||
|
bg.setLegTimeObjectVisible(true);
|
||||||
|
} else {
|
||||||
|
bg.setLegTimeObjectVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setAnnotations(Integer annotationLevel) {
|
private void setAnnotations(Integer annotationLevel) {
|
||||||
switch (annotationLevel) {
|
switch (annotationLevel) {
|
||||||
|
// No Annotations
|
||||||
case 0:
|
case 0:
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
if(ro instanceof BoatGroup) {
|
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
bg.setTeamNameObjectVisible(false);
|
||||||
bg.setVelocityObjectVisible(false);
|
bg.setVelocityObjectVisible(false);
|
||||||
|
bg.setEstTimeToNextMarkObjectVisible(false);
|
||||||
|
bg.setLegTimeObjectVisible(false);
|
||||||
bg.setLineGroupVisible(false);
|
bg.setLineGroupVisible(false);
|
||||||
bg.setWakeVisible(false);
|
bg.setWakeVisible(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
// Important Annotations
|
||||||
case 1:
|
case 1:
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
if(ro instanceof BoatGroup) {
|
setBoatGroupImportantAnnotations(bg);
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// All Annotations
|
||||||
case 2:
|
case 2:
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
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.setTeamNameObjectVisible(true);
|
||||||
bg.setVelocityObjectVisible(true);
|
bg.setVelocityObjectVisible(true);
|
||||||
|
bg.setEstTimeToNextMarkObjectVisible(true);
|
||||||
|
bg.setLegTimeObjectVisible(true);
|
||||||
bg.setLineGroupVisible(true);
|
bg.setLineGroupVisible(true);
|
||||||
bg.setWakeVisible(true);
|
bg.setWakeVisible(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStage (Stage stage) {
|
|
||||||
|
/**
|
||||||
|
* Sets all the annotations of the selected boat to be visible and all others to be hidden
|
||||||
|
*
|
||||||
|
* @param yacht The yacht for which we want to view all annotations
|
||||||
|
*/
|
||||||
|
private void setSelectedBoat(Yacht yacht) {
|
||||||
|
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||||
|
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
|
||||||
|
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
|
||||||
|
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
|
||||||
|
updateLaylines(bg);
|
||||||
|
bg.setIsSelected(true);
|
||||||
|
selectedBoat = yacht;
|
||||||
|
} else {
|
||||||
|
bg.setIsSelected(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStage(Stage stage) {
|
||||||
this.stage = stage;
|
this.stage = stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stage getStage () {
|
Stage getStage() {
|
||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
|
||||||
|
* @param yachtId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean sparkLineStatus(Integer yachtId) {
|
||||||
|
return sparkLineData.containsKey(yachtId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package seng302.controllers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
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.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.stream.StreamParser;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
|
|
||||||
|
public class StartScreenController implements Initializable {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private GridPane gridPane;
|
||||||
|
@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 boolean switchedToRaceView = false;
|
||||||
|
|
||||||
|
private void setContentPane(String jfxUrl) {
|
||||||
|
try {
|
||||||
|
// get the main controller anchor pane (MainView.fxml)
|
||||||
|
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
|
||||||
|
contentPane.getChildren().removeAll();
|
||||||
|
contentPane.getChildren().clear();
|
||||||
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
contentPane.getChildren()
|
||||||
|
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||||
|
} catch (javafx.fxml.LoadException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Running a timer to update the livestream status on welcome screen. Update interval is 1
|
||||||
|
* second.
|
||||||
|
*/
|
||||||
|
public void startStream() {
|
||||||
|
if (StreamParser.isStreamStatus()) {
|
||||||
|
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.isRaceStarted()) {
|
||||||
|
if (!switchedToRaceView) {
|
||||||
|
switchToRaceView();
|
||||||
|
}
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
StreamParser.boatLocations.clear();
|
||||||
|
switchedToRaceView = true;
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the boat is racing
|
||||||
|
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
||||||
|
.getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add boats to the start screen list
|
||||||
|
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
|
||||||
|
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) {
|
||||||
|
data.add(boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // else use StreamParser.getBoats()
|
||||||
|
for (Yacht boat : StreamParser.getBoats().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) {
|
||||||
|
data.add(boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teamList.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotations the user can select as important
|
||||||
|
*/
|
||||||
|
public enum Annotation {
|
||||||
|
SPEED,
|
||||||
|
WAKE,
|
||||||
|
TRACK,
|
||||||
|
NAME,
|
||||||
|
ESTTIMETONEXTMARK,
|
||||||
|
LEGTIME
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import seng302.controllers.RaceViewController;
|
||||||
|
import seng302.controllers.annotations.Annotation;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class ImportantAnnotationController implements Initializable {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JavaFX Outlets
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatWakeSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatSpeedSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatTrackSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatNameSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatEstTimeToNextMarkSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox boatElapsedTimeSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private AnchorPane annotationSelectWindow;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button closeButton;
|
||||||
|
|
||||||
|
private ImportantAnnotationDelegate delegate;
|
||||||
|
private ImportantAnnotationsState importantAnnotationsState;
|
||||||
|
private Stage stage;
|
||||||
|
|
||||||
|
public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
importantAnnotationsState = new ImportantAnnotationsState();
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not an annotation is considered important, then
|
||||||
|
* sends an update to the delegate
|
||||||
|
*
|
||||||
|
* @param annotation The annotation
|
||||||
|
* @param isSet True if annotation is important
|
||||||
|
*/
|
||||||
|
private void setAnnotation(Annotation annotation, Boolean isSet) {
|
||||||
|
importantAnnotationsState.setAnnotationState(annotation, isSet);
|
||||||
|
sendUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an update to the delegate when the important
|
||||||
|
* annotations have changed
|
||||||
|
*/
|
||||||
|
private void sendUpdate() {
|
||||||
|
this.delegate.importantAnnotationsChanged(importantAnnotationsState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the current state of the 'important annotations'
|
||||||
|
*
|
||||||
|
* @param currentState hashmap containing the states of each annotation
|
||||||
|
*/
|
||||||
|
public void loadState(ImportantAnnotationsState currentState) {
|
||||||
|
this.importantAnnotationsState = currentState;
|
||||||
|
|
||||||
|
// Initialise checkboxes
|
||||||
|
for (Annotation annotation : importantAnnotationsState.getAnnotations()) {
|
||||||
|
switch (annotation) {
|
||||||
|
case WAKE:
|
||||||
|
boatWakeSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPEED:
|
||||||
|
boatSpeedSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TRACK:
|
||||||
|
boatTrackSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAME:
|
||||||
|
boatNameSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESTTIMETONEXTMARK:
|
||||||
|
boatEstTimeToNextMarkSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LEGTIME:
|
||||||
|
boatElapsedTimeSelect
|
||||||
|
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View did load
|
||||||
|
*
|
||||||
|
* @param location .
|
||||||
|
* @param resources .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
boatWakeSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected()));
|
||||||
|
boatSpeedSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected()));
|
||||||
|
boatTrackSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected()));
|
||||||
|
boatNameSelect
|
||||||
|
.setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected()));
|
||||||
|
boatEstTimeToNextMarkSelect.setOnAction(event -> setAnnotation(Annotation.ESTTIMETONEXTMARK,
|
||||||
|
boatEstTimeToNextMarkSelect.isSelected()));
|
||||||
|
boatElapsedTimeSelect.setOnAction(
|
||||||
|
event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected()));
|
||||||
|
|
||||||
|
closeButton.setOnAction(event -> stage.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ImportantAnnotationDelegate handles updating the important annotations
|
||||||
|
* displayed to the user on behalf of the ImportantAnnotationController
|
||||||
|
*/
|
||||||
|
public interface ImportantAnnotationDelegate {
|
||||||
|
/**
|
||||||
|
* The important annotations have been changed, update the
|
||||||
|
* annotations displayed to the user
|
||||||
|
* @param importantAnnotationsState The current state of the selected annotations
|
||||||
|
*/
|
||||||
|
void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.controllers.annotations;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ImportantAnnotationsState {
|
||||||
|
public static final Boolean DEFAULT_ANNOTATION_STATE = true;
|
||||||
|
private Map<Annotation, Boolean> currentState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the users preference for the annotations
|
||||||
|
* they consider to be important
|
||||||
|
*/
|
||||||
|
public ImportantAnnotationsState(){
|
||||||
|
this.currentState = new HashMap<>();
|
||||||
|
initialiseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set each annotation to the default annotation state
|
||||||
|
*/
|
||||||
|
private void initialiseState(){
|
||||||
|
for (Annotation annotation : getAnnotations()){
|
||||||
|
currentState.put(annotation, DEFAULT_ANNOTATION_STATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state (visibility) of an annotation
|
||||||
|
* @param annotation The annotation to set
|
||||||
|
* @param visible Whether or not the annotation should be visible
|
||||||
|
*/
|
||||||
|
public void setAnnotationState(Annotation annotation, Boolean visible){
|
||||||
|
this.currentState.put(annotation, visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state (visibility) of a specific annotation
|
||||||
|
* @param annotation The annotation to check
|
||||||
|
* @return True if visible, else false
|
||||||
|
*/
|
||||||
|
public Boolean getAnnotationState(Annotation annotation){
|
||||||
|
return this.currentState.containsKey(annotation) && this.currentState.get(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Return an array containing all defined annotations
|
||||||
|
*/
|
||||||
|
public Annotation[] getAnnotations(){
|
||||||
|
return Annotation.class.getEnumConstants();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +1,137 @@
|
|||||||
package seng302.models;
|
package seng302.models;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Line;
|
import javafx.scene.shape.Line;
|
||||||
import javafx.scene.shape.Polygon;
|
import javafx.scene.shape.Polygon;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.transform.Rotate;
|
import javafx.scene.transform.Rotate;
|
||||||
import javafx.stage.Stage;
|
import seng302.GeometryUtils;
|
||||||
|
import seng302.controllers.CanvasController;
|
||||||
|
import seng302.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
import seng302.models.stream.StreamParser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.text.DateFormat;
|
||||||
import java.util.List;
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat.
|
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
||||||
* 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
|
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
||||||
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime
|
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
|
||||||
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them
|
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
||||||
* when the window is maximised.
|
* minimized in which case it attempts to store animations and apply them when the window is
|
||||||
|
* maximised.
|
||||||
*/
|
*/
|
||||||
public class BoatGroup extends RaceObject{
|
public class BoatGroup extends Group {
|
||||||
|
|
||||||
//Constants for drawing
|
//Constants for drawing
|
||||||
private static final double TEAMNAME_X_OFFSET = 10d;
|
private static final double TEAMNAME_X_OFFSET = 10d;
|
||||||
private static final double TEAMNAME_Y_OFFSET = -15d;
|
private static final double TEAMNAME_Y_OFFSET = -29d;
|
||||||
private static final double VELOCITY_X_OFFSET = 10d;
|
private static final double VELOCITY_X_OFFSET = 10d;
|
||||||
private static final double VELOCITY_Y_OFFSET = -5d;
|
private static final double VELOCITY_Y_OFFSET = -17d;
|
||||||
|
private static final double ESTTIMETONEXTMARK_X_OFFSET = 10d;
|
||||||
|
private static final double ESTTIMETONEXTMARK_Y_OFFSET = -5d;
|
||||||
|
private static final double LEGTIME_X_OFFSET = 10d;
|
||||||
|
private static final double LEGTIME_Y_OFFSET = 7d;
|
||||||
private static final double BOAT_HEIGHT = 15d;
|
private static final double BOAT_HEIGHT = 15d;
|
||||||
private static final double BOAT_WIDTH = 10d;
|
private static final double BOAT_WIDTH = 10d;
|
||||||
//Variables for boat logic.
|
//Variables for boat logic.
|
||||||
private Point2D lastPoint;
|
private boolean isStopped = true;
|
||||||
private int wakeGenerationDelay = 10;
|
private double xIncrement;
|
||||||
private double distanceTravelled;
|
private double yIncrement;
|
||||||
|
private long lastTimeValid = 0;
|
||||||
|
private Double lastRotation = 0.0;
|
||||||
|
private long framesToMove;
|
||||||
//Graphical objects
|
//Graphical objects
|
||||||
private Yacht boat;
|
private Yacht boat;
|
||||||
private Group lineGroup = new Group();
|
private Group lineGroup = new Group();
|
||||||
private Polygon boatPoly;
|
private Polygon boatPoly;
|
||||||
private Text teamNameObject;
|
private Text teamNameObject;
|
||||||
private Text velocityObject;
|
private Text velocityObject;
|
||||||
|
private Text estTimeToNextMarkObject;
|
||||||
|
private Text legTimeObject;
|
||||||
private Wake wake;
|
private Wake wake;
|
||||||
//Handles boat moving when connecting to a stream
|
private Line leftLayLine;
|
||||||
private boolean setToInitialLocation = false;
|
private Line rightLayline;
|
||||||
|
private Double distanceTravelled = 0.0;
|
||||||
|
private Point2D lastPoint;
|
||||||
private boolean destinationSet;
|
private boolean destinationSet;
|
||||||
//Variables for handling minimization
|
private Color textColor = Color.RED;
|
||||||
private Stage stage;
|
|
||||||
private boolean isMaximized= true;
|
private Boolean isSelected = true; //All boats are initalised as selected
|
||||||
private List<Line> lineStorage = new ArrayList<>();
|
|
||||||
private int setCallCount = 5;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BoatGroup with the default triangular boat polygon.
|
* 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 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 color The colour of the boat polygon and the trailing line.
|
||||||
*/
|
*/
|
||||||
public BoatGroup (Yacht boat, Color color){
|
public BoatGroup(Yacht boat, Color color) {
|
||||||
this.boat = boat;
|
this.boat = boat;
|
||||||
initChildren(color);
|
initChildren(color);
|
||||||
|
this.textColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0).
|
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
||||||
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
|
* at point (0,0).
|
||||||
* BoatGroup to update.
|
*
|
||||||
|
* @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 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.
|
* @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)
|
public BoatGroup(Yacht boat, Color color, double... points) {
|
||||||
{
|
|
||||||
this.boat = boat;
|
this.boat = boat;
|
||||||
initChildren(color, points);
|
initChildren(color, points);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the javafx objects that will be the in the group by default.
|
* Return a text object with caching and a color applied
|
||||||
* @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.
|
* @param defaultText The default text to display
|
||||||
|
* @param fill The text fill color
|
||||||
|
* @return The text object
|
||||||
*/
|
*/
|
||||||
private void initChildren (Color color, double... points) {
|
private Text getTextObject(String defaultText, Color fill) {
|
||||||
|
Text text = new Text(defaultText);
|
||||||
|
|
||||||
|
text.setFill(fill);
|
||||||
|
text.setCacheHint(CacheHint.SPEED);
|
||||||
|
text.setCache(true);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
textColor = color;
|
||||||
|
destinationSet = false;
|
||||||
|
|
||||||
boatPoly = new Polygon(points);
|
boatPoly = new Polygon(points);
|
||||||
boatPoly.setFill(color);
|
boatPoly.setFill(color);
|
||||||
|
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
|
||||||
|
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
|
||||||
|
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||||
|
boatPoly.setCache(true);
|
||||||
|
boatPoly.setCacheHint(CacheHint.SPEED);
|
||||||
|
|
||||||
teamNameObject = new Text(boat.getShortName());
|
teamNameObject = getTextObject(boat.getShortName(), textColor);
|
||||||
velocityObject = new Text(String.valueOf(boat.getVelocity()));
|
velocityObject = getTextObject(boat.getVelocity().toString(), textColor);
|
||||||
|
|
||||||
teamNameObject.setX(TEAMNAME_X_OFFSET);
|
teamNameObject.setX(TEAMNAME_X_OFFSET);
|
||||||
teamNameObject.setY(TEAMNAME_Y_OFFSET);
|
teamNameObject.setY(TEAMNAME_Y_OFFSET);
|
||||||
@@ -91,17 +140,39 @@ public class BoatGroup extends RaceObject{
|
|||||||
velocityObject.setX(VELOCITY_X_OFFSET);
|
velocityObject.setX(VELOCITY_X_OFFSET);
|
||||||
velocityObject.setY(VELOCITY_Y_OFFSET);
|
velocityObject.setY(VELOCITY_Y_OFFSET);
|
||||||
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
|
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
|
||||||
destinationSet = false;
|
|
||||||
|
updateLastMarkRoundingTime();
|
||||||
|
updateTimeTillNextMark();
|
||||||
|
|
||||||
|
if (estTimeToNextMarkObject != null) {
|
||||||
|
estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET);
|
||||||
|
estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET);
|
||||||
|
estTimeToNextMarkObject
|
||||||
|
.relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legTimeObject != null) {
|
||||||
|
legTimeObject.setX(LEGTIME_X_OFFSET);
|
||||||
|
legTimeObject.setY(LEGTIME_Y_OFFSET);
|
||||||
|
legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
leftLayLine = new Line();
|
||||||
|
rightLayline = new Line();
|
||||||
|
|
||||||
wake = new Wake(0, -BOAT_HEIGHT);
|
wake = new Wake(0, -BOAT_HEIGHT);
|
||||||
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
|
super.getChildren()
|
||||||
|
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
|
||||||
|
legTimeObject, leftLayLine, rightLayline);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the javafx objects that will be the in the group by default.
|
* 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 color The colour of the boat polygon and the trailing line.
|
||||||
*/
|
*/
|
||||||
private void initChildren (Color color) {
|
private void initChildren(Color color) {
|
||||||
initChildren(color,
|
initChildren(color,
|
||||||
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||||
0.0, -BOAT_HEIGHT / 2,
|
0.0, -BOAT_HEIGHT / 2,
|
||||||
@@ -109,65 +180,106 @@ public class BoatGroup extends RaceObject{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the boat and its children annotations from its current coordinates by specified amounts.
|
* 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 dx The amount to move the X coordinate by
|
||||||
* @param dy The amount to move the Y coordinate by
|
* @param dy The amount to move the Y coordinate by
|
||||||
*/
|
*/
|
||||||
public void moveGroupBy(double dx, double dy, double rotation) {
|
private void moveGroupBy(double dx, double dy) {
|
||||||
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
||||||
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
||||||
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
|
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
|
||||||
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
|
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
|
||||||
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
|
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
|
||||||
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
|
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
|
||||||
|
estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx);
|
||||||
|
estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy);
|
||||||
|
legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx);
|
||||||
|
legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy);
|
||||||
wake.setLayoutX(wake.getLayoutX() + dx);
|
wake.setLayoutX(wake.getLayoutX() + dx);
|
||||||
wake.setLayoutY(wake.getLayoutY() + dy);
|
wake.setLayoutY(wake.getLayoutY() + dy);
|
||||||
rotateTo(rotation + currentRotation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the boat and its children annotations to coordinates specified
|
* Moves the boat and its children annotations to coordinates specified
|
||||||
|
*
|
||||||
* @param x The X coordinate to move the boat to
|
* @param x The X coordinate to move the boat to
|
||||||
* @param y The Y 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) {
|
private void moveTo(double x, double y, double rotation) {
|
||||||
rotateTo(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.setLayoutX(x);
|
||||||
boatPoly.setLayoutY(y);
|
boatPoly.setLayoutY(y);
|
||||||
teamNameObject.setLayoutX(x);
|
teamNameObject.setLayoutX(x);
|
||||||
teamNameObject.setLayoutY(y);
|
teamNameObject.setLayoutY(y);
|
||||||
velocityObject.setLayoutX(x);
|
velocityObject.setLayoutX(x);
|
||||||
velocityObject.setLayoutY(y);
|
velocityObject.setLayoutY(y);
|
||||||
|
estTimeToNextMarkObject.setLayoutX(x);
|
||||||
|
estTimeToNextMarkObject.setLayoutY(y);
|
||||||
|
legTimeObject.setLayoutX(x);
|
||||||
|
legTimeObject.setLayoutY(y);
|
||||||
wake.setLayoutX(x);
|
wake.setLayoutX(x);
|
||||||
wake.setLayoutY(y);
|
wake.setLayoutY(y);
|
||||||
wake.rotate(currentRotation);
|
wake.rotate(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotateTo(double rotation) {
|
||||||
|
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the position of all graphics in the BoatGroup based off of the given time interval.
|
* Updates the time until next mark label, will create a label if one doesn't exist
|
||||||
* @param timeInterval The interval, in milliseconds, the boat should update it's position based on.
|
|
||||||
*/
|
*/
|
||||||
public void updatePosition (long timeInterval) {
|
private void updateTimeTillNextMark() {
|
||||||
//Calculate the movement of the boat.
|
if (estTimeToNextMarkObject == null) {
|
||||||
if (isMaximized) {
|
estTimeToNextMarkObject = getTextObject("Next mark: -", textColor);
|
||||||
double dx = pixelVelocityX * timeInterval;
|
}
|
||||||
double dy = pixelVelocityY * timeInterval;
|
if (boat.getEstimateTimeAtNextMark() != null) {
|
||||||
double rotation = rotationalVelocity * timeInterval;
|
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||||
|
String timeToNextMark = format
|
||||||
|
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
|
||||||
|
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
|
||||||
|
} else {
|
||||||
|
estTimeToNextMarkObject.setText("Next mark: -");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the time since last mark rounding, will create a label if one doesn't exist
|
||||||
|
*/
|
||||||
|
private void updateLastMarkRoundingTime() {
|
||||||
|
if (legTimeObject == null) {
|
||||||
|
legTimeObject = getTextObject("Last mark: -", textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boat.getMarkRoundingTime() != null) {
|
||||||
|
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||||
|
String elapsedTime = format
|
||||||
|
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
|
||||||
|
legTimeObject.setText("Last mark: " + elapsedTime);
|
||||||
|
} else {
|
||||||
|
legTimeObject.setText("Last mark: -");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move() {
|
||||||
|
double dx = xIncrement * framesToMove;
|
||||||
|
double dy = yIncrement * framesToMove;
|
||||||
|
|
||||||
distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
||||||
moveGroupBy(dx, dy, rotation);
|
moveGroupBy(xIncrement, yIncrement);
|
||||||
//Draw a new section of the trail every 20 pixels of movement.
|
framesToMove = framesToMove - 1;
|
||||||
if (distanceTravelled > 20) {
|
|
||||||
distanceTravelled = 0;
|
if (framesToMove <= 0) {
|
||||||
|
isStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distanceTravelled > 70) {
|
||||||
|
distanceTravelled = 0d;
|
||||||
|
|
||||||
if (lastPoint != null) {
|
if (lastPoint != null) {
|
||||||
Line l = new Line(
|
Line l = new Line(
|
||||||
lastPoint.getX(),
|
lastPoint.getX(),
|
||||||
@@ -176,109 +288,139 @@ public class BoatGroup extends RaceObject{
|
|||||||
boatPoly.getLayoutY()
|
boatPoly.getLayoutY()
|
||||||
);
|
);
|
||||||
l.getStrokeDashArray().setAll(3d, 7d);
|
l.getStrokeDashArray().setAll(3d, 7d);
|
||||||
l.setStroke(boatPoly.getFill());
|
l.setStroke(boat.getColour());
|
||||||
|
l.setCache(true);
|
||||||
|
l.setCacheHint(CacheHint.SPEED);
|
||||||
lineGroup.getChildren().add(l);
|
lineGroup.getChildren().add(l);
|
||||||
}
|
}
|
||||||
if (destinationSet) { //Only begin drawing after the first destination is set
|
|
||||||
|
if (destinationSet) {
|
||||||
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wake.updatePosition(timeInterval);
|
|
||||||
|
wake.updatePosition(1000 / 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the rotational velocity required to reach the rotationalGoal from the
|
||||||
|
* currentRotation.
|
||||||
|
*/
|
||||||
|
protected Double calculateRotationalVelocity(Double rotationalGoal) {
|
||||||
|
Double rotationalVelocity = 0.0;
|
||||||
|
|
||||||
|
if (Math.abs(rotationalGoal - lastRotation) > 180) {
|
||||||
|
if (rotationalGoal - lastRotation >= 0.0) {
|
||||||
|
rotationalVelocity = ((rotationalGoal - lastRotation) - 360) / 200;
|
||||||
|
} else {
|
||||||
|
rotationalVelocity = (360 + (rotationalGoal - lastRotation)) / 200;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rotationalVelocity = (rotationalGoal - lastRotation) / 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
|
||||||
|
if (Math.abs(rotationalVelocity) > 1) {
|
||||||
|
rotationalVelocity = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotationalVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the destination of the boat and the headng it should have once it reaches
|
* 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 newXValue The X co-ordinate the boat needs to move to.
|
||||||
* @param newYValue The Y 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 rotation Rotation to move graphics to.
|
||||||
* @param raceIds RaceID of the object to move.
|
* @param timeValid the time the position values are valid for
|
||||||
*/
|
*/
|
||||||
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) {
|
public void setDestination(double newXValue, double newYValue, double rotation,
|
||||||
if (hasRaceId(raceIds)) {
|
double groundSpeed, long timeValid, double frameRate, long id) {
|
||||||
if (setToInitialLocation) {
|
if (lastTimeValid == 0) {
|
||||||
destinationSet = true;
|
lastTimeValid = timeValid - 200;
|
||||||
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);
|
moveTo(newXValue, newYValue, rotation);
|
||||||
}
|
}
|
||||||
}
|
framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid))));
|
||||||
//If minimized generate lines every 5 calls to set destination.
|
double dx = newXValue - boatPoly.getLayoutX();
|
||||||
if (!isMaximized) {
|
double dy = newYValue - boatPoly.getLayoutY();
|
||||||
setToInitialLocation = false;
|
|
||||||
wakeGenerationDelay = 2;
|
xIncrement = dx / framesToMove;
|
||||||
if(setCallCount-- == 0) {
|
yIncrement = dy / framesToMove;
|
||||||
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;
|
destinationSet = true;
|
||||||
|
|
||||||
if (hasRaceId(raceIDs)) {
|
Double rotationalVelocity = calculateRotationalVelocity(rotation);
|
||||||
double rotation = Math.abs(
|
|
||||||
Math.toDegrees(
|
updateTimeTillNextMark();
|
||||||
Math.atan(
|
updateLastMarkRoundingTime();
|
||||||
(newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
|
|
||||||
)
|
if (Math.abs(rotationalVelocity) > 0.075) {
|
||||||
)
|
rotationalVelocity = 0.0;
|
||||||
);
|
wake.rotate(rotation);
|
||||||
setDestination(newXValue, newYValue, rotation, groundSpeed, raceIDs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rotateTo (double rotation) {
|
rotateTo(rotation);
|
||||||
currentRotation = rotation;
|
wake.setRotationalVelocity(rotationalVelocity, groundSpeed);
|
||||||
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
|
||||||
|
velocityObject.setText(String.format("%.2f m/s", groundSpeed));
|
||||||
|
lastTimeValid = timeValid;
|
||||||
|
isStopped = false;
|
||||||
|
|
||||||
|
lastRotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceRotation () {
|
|
||||||
rotateTo (rotationalGoal);
|
/**
|
||||||
wake.rotate(rotationalGoal);
|
* This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
|
||||||
|
* gates position and the current wind
|
||||||
|
* If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
|
||||||
|
* going up wind, if they are on different sides of the gate, then the boat is going downwind
|
||||||
|
* @param canvasController
|
||||||
|
*/
|
||||||
|
public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) {
|
||||||
|
|
||||||
|
Double windAngle = StreamParser.getWindDirection();
|
||||||
|
GateMark thisGateMark = (GateMark) nextMark;
|
||||||
|
SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
||||||
|
SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
||||||
|
Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
||||||
|
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
||||||
|
|
||||||
|
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||||
|
Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||||
|
|
||||||
|
|
||||||
|
Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||||
|
Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
|
||||||
|
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
||||||
|
with the wind.
|
||||||
|
*/
|
||||||
|
if (boatLineFuncResult == windLineFuncResult) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setIsSelected(Boolean isSelected) {
|
||||||
|
this.isSelected = isSelected;
|
||||||
|
setTeamNameObjectVisible(isSelected);
|
||||||
|
setVelocityObjectVisible(isSelected);
|
||||||
|
setLineGroupVisible(isSelected);
|
||||||
|
setWakeVisible(isSelected);
|
||||||
|
setEstTimeToNextMarkObjectVisible(isSelected);
|
||||||
|
setLegTimeObjectVisible(isSelected);
|
||||||
|
setLayLinesVisible(isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setTeamNameObjectVisible(Boolean visible) {
|
public void setTeamNameObjectVisible(Boolean visible) {
|
||||||
teamNameObject.setVisible(visible);
|
teamNameObject.setVisible(visible);
|
||||||
}
|
}
|
||||||
@@ -287,6 +429,14 @@ public class BoatGroup extends RaceObject{
|
|||||||
velocityObject.setVisible(visible);
|
velocityObject.setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEstTimeToNextMarkObjectVisible(Boolean visible) {
|
||||||
|
estTimeToNextMarkObject.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLegTimeObjectVisible(Boolean visible) {
|
||||||
|
legTimeObject.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
public void setLineGroupVisible(Boolean visible) {
|
public void setLineGroupVisible(Boolean visible) {
|
||||||
lineGroup.setVisible(visible);
|
lineGroup.setVisible(visible);
|
||||||
}
|
}
|
||||||
@@ -295,22 +445,25 @@ public class BoatGroup extends RaceObject{
|
|||||||
wake.setVisible(visible);
|
wake.setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Yacht getBoat() {
|
public void setLayLinesVisible(Boolean visible) {
|
||||||
return boat;
|
leftLayLine.setVisible(visible);
|
||||||
|
rightLayline.setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setLaylines(Line line1, Line line2) {
|
||||||
* Returns true if this BoatGroup contains at least one of the given IDs.
|
this.leftLayLine = line1;
|
||||||
*
|
this.rightLayline = line2;
|
||||||
* @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;
|
|
||||||
|
public ArrayList<Line> getLaylines() {
|
||||||
|
ArrayList<Line> laylines = new ArrayList<>();
|
||||||
|
laylines.add(leftLayLine);
|
||||||
|
laylines.add(rightLayline);
|
||||||
|
return laylines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Yacht getBoat() {
|
||||||
|
return boat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,41 +471,39 @@ public class BoatGroup extends RaceObject{
|
|||||||
*
|
*
|
||||||
* @return An array containing all ID's associated with this RaceObject.
|
* @return An array containing all ID's associated with this RaceObject.
|
||||||
*/
|
*/
|
||||||
public int[] getRaceIds () {
|
public long getRaceId() {
|
||||||
return new int[] {boat.getSourceID()};
|
return boat.getSourceID();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the
|
* Due to javaFX limitations annotations associated with a boat that you want to appear below
|
||||||
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function
|
* all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group
|
||||||
* returns these annotations as a group.
|
* of the BoatGroups. This function returns these annotations as a group.
|
||||||
*
|
*
|
||||||
* @return A group containing low priority annotations.
|
* @return A group containing low priority annotations.
|
||||||
*/
|
*/
|
||||||
public Group getLowPriorityAnnotations () {
|
public Group getLowPriorityAnnotations() {
|
||||||
Group group = new Group();
|
Group group = new Group();
|
||||||
group.getChildren().addAll(wake, lineGroup);
|
group.getChildren().addAll(wake, lineGroup);
|
||||||
return group;
|
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
|
public Double getBoatLayoutX() {
|
||||||
* listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the
|
return boatPoly.getLayoutX();
|
||||||
* 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();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
public Double getBoatLayoutY() {
|
||||||
|
return boatPoly.getLayoutY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStopped() {
|
||||||
|
return isStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boat.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ package seng302.models;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by ryan_ on 16/03/2017.
|
* Enum for randomly generating colours.
|
||||||
*/
|
*/
|
||||||
public enum Colors {
|
public enum Colors {
|
||||||
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE;
|
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
|
||||||
|
|
||||||
static Integer index = 0;
|
static Integer index = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
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,163 @@
|
|||||||
|
package seng302.models;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
||||||
|
* upwind and downwind in separate tables here as well
|
||||||
|
* Created by wmu16 on 22/05/17.
|
||||||
|
*/
|
||||||
|
public final class PolarTable {
|
||||||
|
|
||||||
|
//A Polar table will consist of a wind speed key to a hashmap value of pairs of wind angles and boat speeds
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||||
|
|
||||||
|
private static int upTwaIndex;
|
||||||
|
private static int dnTwaIndex;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
|
||||||
|
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
|
||||||
|
* as a value
|
||||||
|
* @param file containing the polar csv information
|
||||||
|
*/
|
||||||
|
public static void parsePolarFile(String file) {
|
||||||
|
polarTable = new HashMap<>();
|
||||||
|
upwindOptimal = new HashMap<>();
|
||||||
|
downwindOptimal = new HashMap<>();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
|
//Initial line in file
|
||||||
|
if (isHeaderLine) {
|
||||||
|
deduceHeaders(thisLine);
|
||||||
|
isHeaderLine = false;
|
||||||
|
} else {
|
||||||
|
HashMap<Double, Double> thisPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisUpWindPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisDnWindPolar = new HashMap<>();
|
||||||
|
Double thisWindSpeed = Double.parseDouble(thisLine[0]);
|
||||||
|
|
||||||
|
// -3 <== -1 for length -1, and a further -2 as we iterate in pairs of 2 so finish before final 2
|
||||||
|
for (int i = 1; i < thisLine.length; i += 2) {
|
||||||
|
Double thisWindAngle = Double.parseDouble(thisLine[i]);
|
||||||
|
Double thisBoatSpeed = Double.parseDouble(thisLine[i + 1]);
|
||||||
|
thisPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
if (i == upTwaIndex) {
|
||||||
|
thisUpWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
} else if (i == dnTwaIndex) {
|
||||||
|
thisDnWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
polarTable.put(thisWindSpeed, thisPolar);
|
||||||
|
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||||
|
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the header line of a polar file
|
||||||
|
* @param thisLine The line which is the header of a polar file
|
||||||
|
*/
|
||||||
|
private static void deduceHeaders(String[] thisLine) {
|
||||||
|
|
||||||
|
for (int i = 0; i < thisLine.length; i++) {
|
||||||
|
String thisItem = thisLine[i];
|
||||||
|
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||||
|
upTwaIndex = i;
|
||||||
|
}
|
||||||
|
else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||||
|
dnTwaIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The entire polar table
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getPolarTable() {
|
||||||
|
return polarTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal upwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getUpwindOptimal() {
|
||||||
|
return upwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal downwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getDownwindOptimal() {
|
||||||
|
return downwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal upwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
||||||
|
return upwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal downwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
||||||
|
return downwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Double getClosestMatch(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet());
|
||||||
|
|
||||||
|
Double lowerVal = windValues.get(0);
|
||||||
|
Double upperVal = windValues.get(1);
|
||||||
|
|
||||||
|
for(int i = 0; i < windValues.size() - 1; i++) {
|
||||||
|
lowerVal = windValues.get(i);
|
||||||
|
upperVal = windValues.get(i+1);
|
||||||
|
if (thisWindSpeed <= upperVal) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
|
||||||
|
Double upperDiff = Math.abs(upperVal - thisWindSpeed);
|
||||||
|
|
||||||
|
return (lowerDiff <= upperDiff) ? lowerVal : upperVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
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 ();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,34 @@
|
|||||||
package seng302.models;
|
package seng302.models;
|
||||||
|
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Arc;
|
import javafx.scene.shape.Arc;
|
||||||
import javafx.scene.shape.ArcType;
|
import javafx.scene.shape.ArcType;
|
||||||
|
import javafx.scene.shape.StrokeLineCap;
|
||||||
import javafx.scene.transform.Rotate;
|
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
|
* A group containing objects used to represent wakes onscreen. Contains functionality for their animation.
|
||||||
* 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 {
|
class Wake extends Group {
|
||||||
|
|
||||||
private int numWakes = 5;
|
//The number of wakes
|
||||||
private double[] velocities = new double[13];
|
private int numWakes = 8;
|
||||||
|
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
|
||||||
|
private final double MAX_DIFF = 75;
|
||||||
|
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
|
||||||
|
private final int UNIFICATION_SPEED = 750;
|
||||||
|
|
||||||
|
|
||||||
private Arc[] arcs = new Arc[numWakes];
|
private Arc[] arcs = new Arc[numWakes];
|
||||||
|
private double[] rotationalVelocities = new double[numWakes];
|
||||||
private double[] rotations = new double[numWakes];
|
private double[] rotations = new double[numWakes];
|
||||||
private int[] velocityIndices = new int[numWakes];
|
private double baseRad;
|
||||||
private double sum = 0;
|
|
||||||
private static double max;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a wake at the given location.
|
* Create a wake at the given location.
|
||||||
|
*
|
||||||
* @param startingX x location where the tip of wake arcs will be.
|
* @param startingX x location where the tip of wake arcs will be.
|
||||||
* @param startingY y location where the tip of wake arcs will be.
|
* @param startingY y location where the tip of wake arcs will be.
|
||||||
*/
|
*/
|
||||||
@@ -34,74 +38,77 @@ class Wake extends Group {
|
|||||||
Arc arc;
|
Arc arc;
|
||||||
for (int i = 0; i < numWakes; i++) {
|
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.
|
//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);
|
arc = new Arc(0, 0, 0, 0, -110, 40);
|
||||||
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs.
|
arc.setCache(true);
|
||||||
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i));
|
arc.setCacheHint(CacheHint.SPEED);
|
||||||
arc.setType(ArcType.ROUND);
|
arc.setType(ArcType.OPEN);
|
||||||
|
arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i)));
|
||||||
|
arc.setStrokeWidth(3.0);
|
||||||
|
arc.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||||
|
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
|
||||||
|
baseRad = (20 / numWakes);
|
||||||
arcs[i] = arc;
|
arcs[i] = arc;
|
||||||
}
|
}
|
||||||
super.getChildren().addAll(arcs);
|
super.getChildren().addAll(arcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
|
* Sets the rotationalVelocity of each arc.
|
||||||
* the latest given velocity.
|
*
|
||||||
* @param rotationalVelocity The rotationalVelocity the wake should move at.
|
* @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.
|
* @param velocity The real world velocity of the boat in m/s.
|
||||||
*/
|
*/
|
||||||
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) {
|
void setRotationalVelocity(double rotationalVelocity, double velocity) {
|
||||||
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]);
|
rotationalVelocities[0] = rotationalVelocity;
|
||||||
sum += Math.abs(rotationalVelocity);
|
for (int i = 1; i < numWakes; i++) {
|
||||||
max = Math.max(max, rotationalVelocity);
|
double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
|
||||||
if (sum < (max / 3))
|
double shortestDistance = Math.atan2(
|
||||||
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position.
|
Math.sin(wakeSeparationRad),
|
||||||
//This stops the wake from eventually becoming out of sync with the boat.
|
Math.cos(wakeSeparationRad)
|
||||||
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough.
|
);
|
||||||
//Basically just for our internal mock.
|
double distDeg = Math.toDegrees(shortestDistance);
|
||||||
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.
|
if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
|
||||||
double baseRad = 20;
|
rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||||
double rad;
|
|
||||||
for (Arc arc :arcs) {
|
} else {
|
||||||
rad = baseRad + velocity;
|
if (distDeg < (MAX_DIFF / numWakes))
|
||||||
|
rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||||
|
else
|
||||||
|
rotationalVelocities[i] = rotationalVelocities[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double rad = baseRad + velocity;
|
||||||
|
for (Arc arc : arcs) {
|
||||||
arc.setRadiusX(rad);
|
arc.setRadiusX(rad);
|
||||||
arc.setRadiusY(rad);
|
arc.setRadiusY(rad);
|
||||||
baseRad += 5 + (velocity / 2);
|
rad += (20 / numWakes) + (velocity / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arcs rotate based on the distance they would have travelled over the supplied time interval.
|
* 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.
|
* @param timeInterval the time interval, in microseconds, that the wake should move.
|
||||||
*/
|
*/
|
||||||
void updatePosition (long timeInterval) {
|
void updatePosition(long timeInterval) {
|
||||||
for (int i = 0; i < numWakes; i++) {
|
for (int i = 0; i < numWakes; i++) {
|
||||||
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval;
|
rotations[i] = rotations[i] + rotationalVelocities[i] * timeInterval;
|
||||||
arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
|
arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate all wakes to the given rotation.
|
* Rotate all wakes to the given rotation.
|
||||||
|
*
|
||||||
* @param rotation the from north angle in degrees to rotate to.
|
* @param rotation the from north angle in degrees to rotate to.
|
||||||
*/
|
*/
|
||||||
void rotate (double rotation) {
|
void rotate(double rotation) {
|
||||||
for (int i = 0; i < arcs.length; i++) {
|
for (int i = 0; i < arcs.length; i++) {
|
||||||
rotations[i] = rotation;
|
rotations[i] = rotation;
|
||||||
|
rotationalVelocities[i] = 0;
|
||||||
arcs[i].getTransforms().setAll(new Rotate(rotation));
|
arcs[i].getTransforms().setAll(new Rotate(rotation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package seng302.models;
|
package seng302.models;
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.controllers.RaceViewController;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import seng302.models.stream.StreamParser;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Corner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Yacht class for the racing boat.
|
* Yacht class for the racing boat.
|
||||||
@@ -12,9 +16,10 @@ import java.text.SimpleDateFormat;
|
|||||||
* also done outside Boat class because some old variables are not used anymore.
|
* also done outside Boat class because some old variables are not used anymore.
|
||||||
*/
|
*/
|
||||||
public class Yacht {
|
public class Yacht {
|
||||||
|
|
||||||
|
// Used in boat group
|
||||||
private Color colour;
|
private Color colour;
|
||||||
private double velocity;
|
private double velocity;
|
||||||
private Integer markLastPast;
|
|
||||||
|
|
||||||
private String boatType;
|
private String boatType;
|
||||||
private Integer sourceID;
|
private Integer sourceID;
|
||||||
@@ -30,13 +35,18 @@ public class Yacht {
|
|||||||
private Long estimateTimeAtNextMark;
|
private Long estimateTimeAtNextMark;
|
||||||
private Long estimateTimeAtFinish;
|
private Long estimateTimeAtFinish;
|
||||||
private String position;
|
private String position;
|
||||||
|
// Mark rounding
|
||||||
|
private Long markRoundingTime;
|
||||||
|
private Mark lastMarkRounded;
|
||||||
|
private Mark nextMark;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in EventTest and RaceTest.
|
* Used in EventTest and RaceTest.
|
||||||
*
|
*
|
||||||
* @param boatName Create a yacht object with name.
|
* @param boatName Create a yacht object with name.
|
||||||
*/
|
*/
|
||||||
public Yacht (String boatName) {
|
public Yacht(String boatName) {
|
||||||
this.boatName = boatName;
|
this.boatName = boatName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,30 +64,37 @@ public class Yacht {
|
|||||||
this.sourceID = id;
|
this.sourceID = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) {
|
public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
|
||||||
|
String boatName, String country) {
|
||||||
this.boatType = boatType;
|
this.boatType = boatType;
|
||||||
this.sourceID = sourceID;
|
this.sourceID = sourceID;
|
||||||
this.hullID = hullID;
|
this.hullID = hullID;
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
this.boatName = boatName;
|
this.boatName = boatName;
|
||||||
this.country = country;
|
this.country = country;
|
||||||
|
this.position = "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBoatType() {
|
public String getBoatType() {
|
||||||
return boatType;
|
return boatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSourceID() {
|
public Integer getSourceID() {
|
||||||
return sourceID;
|
return sourceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHullID() {
|
public String getHullID() {
|
||||||
return hullID;
|
return hullID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBoatName() {
|
public String getBoatName() {
|
||||||
return boatName;
|
return boatName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCountry() {
|
public String getCountry() {
|
||||||
return country;
|
return country;
|
||||||
}
|
}
|
||||||
@@ -95,6 +112,9 @@ public class Yacht {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLegNumber(Integer legNumber) {
|
public void setLegNumber(Integer legNumber) {
|
||||||
|
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
|
||||||
|
RaceViewController.updateYachtPositionSparkline(this, legNumber);
|
||||||
|
}
|
||||||
this.legNumber = legNumber;
|
this.legNumber = legNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +169,7 @@ public class Yacht {
|
|||||||
this.colour = colour;
|
this.colour = colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getVelocity() {
|
public Double getVelocity() {
|
||||||
return velocity;
|
return velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +177,32 @@ public class Yacht {
|
|||||||
this.velocity = velocity;
|
this.velocity = velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getMarkLastPast() {
|
public Long getMarkRoundingTime() {
|
||||||
return markLastPast;
|
return markRoundingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMarkLastPast(Integer markLastPast) {
|
public void setMarkRoundingTime(Long markRoundingTime) {
|
||||||
this.markLastPast = markLastPast;
|
this.markRoundingTime = markRoundingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getLastMarkRounded() {
|
||||||
|
return lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkRounded(Mark lastMarkRounded) {
|
||||||
|
this.lastMarkRounded = lastMarkRounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextMark(Mark nextMark) {
|
||||||
|
this.nextMark = nextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getNextMark(){
|
||||||
|
return nextMark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Boundary class represents a rectangle territorial boundary on a map. It
|
||||||
|
* contains four extremity double values(N, E, S, W). N and S are represented as
|
||||||
|
* latitudes in radians. E and W are represented as longitudes in radians.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 10/5/17
|
||||||
|
*/
|
||||||
|
public class Boundary {
|
||||||
|
|
||||||
|
private double northLat, eastLng, southLat, westLng;
|
||||||
|
|
||||||
|
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
|
||||||
|
this.northLat = northLat;
|
||||||
|
this.eastLng = eastLng;
|
||||||
|
this.southLat = southLat;
|
||||||
|
this.westLng = westLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLat() {
|
||||||
|
return (northLat + southLat) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLng() {
|
||||||
|
return (eastLng + westLng) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getNorthLat() {
|
||||||
|
return northLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getEastLng() {
|
||||||
|
return eastLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSouthLat() {
|
||||||
|
return southLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getWestLng() {
|
||||||
|
return westLng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import java.lang.Math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
|
||||||
|
* By passing a rectangle like geo boundary, it returns a map image with the
|
||||||
|
* highest resolution. However, due to free quote account usage limit, the maximum
|
||||||
|
* resolution is only 1280 * 1280.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class CanvasMap {
|
||||||
|
|
||||||
|
private Boundary boundary;
|
||||||
|
private long width, height; // desired image size
|
||||||
|
private int zoom;
|
||||||
|
|
||||||
|
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
|
||||||
|
|
||||||
|
public CanvasMap(Boundary boundary) {
|
||||||
|
this.boundary = boundary;
|
||||||
|
calculateOptimalMapSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getMapImage() {
|
||||||
|
try {
|
||||||
|
URL url = new URL(getRequest());
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
return new Image(connection.getInputStream());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequest() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
|
||||||
|
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
|
||||||
|
sb.append(String.format("&zoom=%d", zoom));
|
||||||
|
sb.append(String.format("&size=%dx%d&scale=2", width, height));
|
||||||
|
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
|
||||||
|
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
|
||||||
|
// sb.append(String.format("&key=%s", KEY));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateOptimalMapSize() {
|
||||||
|
for (int z = 20; z > 0; z--) {
|
||||||
|
MapSize mapSize = getMapSize(z, boundary);
|
||||||
|
zoom = z;
|
||||||
|
width = mapSize.width;
|
||||||
|
height = mapSize.height;
|
||||||
|
// if map size is valid, exit the loop as we have the highest resolution
|
||||||
|
if (mapSize.isValid()) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapSize getMapSize(int zoom, Boundary boundary) {
|
||||||
|
double scale = Math.pow(2, zoom);
|
||||||
|
MapGeo geoSW = new MapGeo(boundary.getSouthLat(), boundary.getWestLng());
|
||||||
|
MapGeo geoNE = new MapGeo(boundary.getNorthLat(), boundary.getEastLng());
|
||||||
|
MapPoint pointSW = MercatorProjection.toMapPoint(geoSW);
|
||||||
|
MapPoint pointNE = MercatorProjection.toMapPoint(geoNE);
|
||||||
|
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
|
||||||
|
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapSize {
|
||||||
|
long width, height;
|
||||||
|
|
||||||
|
MapSize(double width, double height) {
|
||||||
|
this.width = Math.round(width);
|
||||||
|
this.height = Math.round(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map size is valid when width and height are both less than 640 pixels
|
||||||
|
* @return true if both dimensions are less than 640px
|
||||||
|
*/
|
||||||
|
boolean isValid() {
|
||||||
|
return Math.max(width, height) <= 640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoom() {
|
||||||
|
return zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class represent Geo location (latitude, longitude).
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
class MapGeo {
|
||||||
|
|
||||||
|
private double lat, lng;
|
||||||
|
|
||||||
|
MapGeo(double lat, double lng) {
|
||||||
|
this.lat = lat;
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class represent euclidean planar point (x, y)
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
class MapPoint {
|
||||||
|
|
||||||
|
private double x, y;
|
||||||
|
|
||||||
|
MapPoint(double x, double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setX(double x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setY(double y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility class useful to convert between Geo locations and Mercator projection
|
||||||
|
* planar coordinates.
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class MercatorProjection {
|
||||||
|
|
||||||
|
private static final double MERCATOR_RANGE = 256;
|
||||||
|
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
|
||||||
|
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A help function keeps the value in bound between -0.9999 and 0.9999.
|
||||||
|
* @param value in bound value
|
||||||
|
* @return the value in bound
|
||||||
|
*/
|
||||||
|
private static double bound(double value) {
|
||||||
|
return Math.min(Math.max(value, -0.9999), 0.9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projects a Geo Location (lat, lng) on a planar
|
||||||
|
* @param geo MapGeo (lat, lng) location to be projected
|
||||||
|
* @return the projection GeoPoint (x, y) on planar
|
||||||
|
*/
|
||||||
|
public static MapPoint toMapPoint(MapGeo geo) {
|
||||||
|
MapPoint point = new MapPoint(0, 0);
|
||||||
|
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
point.setX(origin.getX() + geo.getLng() * pixelsPerLngDegree);
|
||||||
|
|
||||||
|
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
|
||||||
|
// 89.189. This is about a third of a tile past the edge of the world tile.
|
||||||
|
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
|
||||||
|
point.setY(origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian));
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
|
||||||
|
* @param point MapPoint (x, y) to be converted back
|
||||||
|
* @return the original Geo location converted from the given projection point
|
||||||
|
*/
|
||||||
|
public static MapGeo toMapGeo(MapPoint point) {
|
||||||
|
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
|
||||||
|
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
|
||||||
|
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
|
||||||
|
return new MapGeo(lat, lng);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class TestMapController implements Initializable{
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Canvas mapCanvas;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
|
||||||
|
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
|
||||||
|
CanvasMap canvasMap = new CanvasMap(bound);
|
||||||
|
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@ public class GateMark extends Mark {
|
|||||||
* @param singleMark1 one single mark inside of the gate mark
|
* @param singleMark1 one single mark inside of the gate mark
|
||||||
* @param singleMark2 the second 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) {
|
public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude, int compoundMarkID) {
|
||||||
super(name, type, latitude, longitude);
|
super(name, type, latitude, longitude, compoundMarkID);
|
||||||
this.singleMark1 = singleMark1;
|
this.singleMark1 = singleMark1;
|
||||||
this.singleMark2 = singleMark2;
|
this.singleMark2 = singleMark2;
|
||||||
}
|
}
|
||||||
@@ -39,12 +39,10 @@ public class GateMark extends Mark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getLatitude(){
|
public double getLatitude(){
|
||||||
//return (this.getSingleMark1().getLatitude() + this.getSingleMark2().getLatitude()) / 2;
|
|
||||||
return (this.getSingleMark1().getLatitude());
|
return (this.getSingleMark1().getLatitude());
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getLongitude(){
|
public double getLongitude(){
|
||||||
//return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2;
|
|
||||||
return (this.getSingleMark1().getLongitude());
|
return (this.getSingleMark1().getLongitude());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,25 +10,28 @@ public abstract class Mark {
|
|||||||
private MarkType markType;
|
private MarkType markType;
|
||||||
private double latitude;
|
private double latitude;
|
||||||
private double longitude;
|
private double longitude;
|
||||||
private int id;
|
private long id;
|
||||||
|
private int compoundMarkID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a mark instance by passing its name and type
|
* Create a mark instance by passing its name and type
|
||||||
* @param name the name of the mark
|
* @param name the name of the mark
|
||||||
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
|
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
|
||||||
*/
|
*/
|
||||||
public Mark (String name, MarkType markType, int id) {
|
public Mark (String name, MarkType markType, int sourceID, int compoundMarkID) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.markType = markType;
|
this.markType = markType;
|
||||||
this.id = id;
|
this.id = sourceID;
|
||||||
|
this.compoundMarkID = compoundMarkID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mark(String name, MarkType markType, double latitude, double longitude) {
|
public Mark(String name, MarkType markType, double latitude, double longitude, int compoundMarkID) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.markType = markType;
|
this.markType = markType;
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
id = 0;
|
this.id = 0;
|
||||||
|
this.compoundMarkID = compoundMarkID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,20 +50,24 @@ public abstract class Mark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical
|
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to
|
||||||
* latitude2, longitude 2
|
* geographical latitude2, longitude 2
|
||||||
|
*
|
||||||
* @param longitude1 Longitude of first point in degrees
|
* @param longitude1 Longitude of first point in degrees
|
||||||
* @param longitude2 Longitude of second point in degrees
|
* @param longitude2 Longitude of second point in degrees
|
||||||
* @param latitude1 Latitude of first point in degrees
|
* @param latitude1 Latitude of first point in degrees
|
||||||
* @param latitude2 Latitude of first point in degrees
|
* @param latitude2 Latitude of first point in degrees
|
||||||
* @return Heading in radians
|
* @return Heading in radians
|
||||||
*/
|
*/
|
||||||
public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
|
public static double calculateHeadingRad(Double latitude1, Double longitude1, Double latitude2,
|
||||||
|
Double longitude2) {
|
||||||
latitude1 = Math.toRadians(latitude1);
|
latitude1 = Math.toRadians(latitude1);
|
||||||
latitude2 = Math.toRadians(latitude2);
|
latitude2 = Math.toRadians(latitude2);
|
||||||
Double longDiff= Math.toRadians(longitude2-longitude1);
|
Double longDiff = Math.toRadians(longitude2 - longitude1);
|
||||||
Double y = Math.sin(longDiff)*Math.cos(latitude2);
|
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);
|
Double x =
|
||||||
|
Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2)
|
||||||
|
* Math.cos(longDiff);
|
||||||
return Math.atan2(y, x);
|
return Math.atan2(y, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +87,8 @@ public abstract class Mark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical
|
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to
|
||||||
* latitude2, longitude 2
|
* geographical latitude2, longitude 2
|
||||||
*
|
*
|
||||||
* @param longitude1 Longitude of first point in degrees
|
* @param longitude1 Longitude of first point in degrees
|
||||||
* @param longitude2 Longitude of second point in degrees
|
* @param longitude2 Longitude of second point in degrees
|
||||||
@@ -89,14 +96,16 @@ public abstract class Mark {
|
|||||||
* @param latitude2 Latitude of first point in degrees
|
* @param latitude2 Latitude of first point in degrees
|
||||||
* @return Distance in meters
|
* @return Distance in meters
|
||||||
*/
|
*/
|
||||||
public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
|
public static Double calculateDistance(Double latitude1, Double longitude1, Double latitude2,
|
||||||
|
Double longitude2) {
|
||||||
Double theta = longitude1 - longitude2;
|
Double theta = longitude1 - longitude2;
|
||||||
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
|
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(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
|
||||||
Math.cos(Math.toRadians(theta));
|
Math.cos(Math.toRadians(theta));
|
||||||
dist = Math.acos(dist);
|
dist = Math.acos(dist);
|
||||||
dist = Math.toDegrees(dist);
|
dist = Math.toDegrees(dist);
|
||||||
dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
|
dist = dist * 60
|
||||||
|
* 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
|
||||||
dist = dist * 1609.344; //ratio of miles to metres
|
dist = dist * 1609.344; //ratio of miles to metres
|
||||||
return dist;
|
return dist;
|
||||||
}
|
}
|
||||||
@@ -125,7 +134,7 @@ public abstract class Mark {
|
|||||||
return longitude;
|
return longitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,4 +142,7 @@ public abstract class Mark {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCompoundMarkID() {
|
||||||
|
return compoundMarkID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package seng302.models.mark;
|
package seng302.models.mark;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Group;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Circle;
|
import javafx.scene.shape.Circle;
|
||||||
import javafx.scene.shape.Line;
|
import javafx.scene.shape.Line;
|
||||||
import javafx.scene.transform.Rotate;
|
import seng302.GeometryUtils;
|
||||||
import seng302.models.RaceObject;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by CJIRWIN on 26/04/2017.
|
* Grouping of javaFX objects needed to represent a Mark on screen.
|
||||||
*/
|
*/
|
||||||
public class MarkGroup extends RaceObject {
|
public class MarkGroup extends Group {
|
||||||
|
|
||||||
private static int MARK_RADIUS = 5;
|
private static int MARK_RADIUS = 5;
|
||||||
private static int LINE_THICKNESS = 2;
|
private static int LINE_THICKNESS = 2;
|
||||||
@@ -23,14 +22,13 @@ public class MarkGroup extends RaceObject {
|
|||||||
|
|
||||||
private List<Mark> marks = new ArrayList<>();
|
private List<Mark> marks = new ArrayList<>();
|
||||||
private Mark mainMark;
|
private Mark mainMark;
|
||||||
private double[] nodePixelVelocitiesX;
|
|
||||||
private double[] nodePixelVelocitiesY;
|
|
||||||
private Point2D[] nodeDestinations;
|
|
||||||
|
|
||||||
public MarkGroup (Mark mark, Point2D... points) {
|
/**
|
||||||
nodePixelVelocitiesX = new double[points.length];
|
* Constructor for singleMark groups
|
||||||
nodePixelVelocitiesY = new double[points.length];
|
* @param mark
|
||||||
nodeDestinations = new Point2D[points.length];
|
* @param points
|
||||||
|
*/
|
||||||
|
public MarkGroup (SingleMark mark, Point2D points) {
|
||||||
marks.add(mark);
|
marks.add(mark);
|
||||||
mainMark = mark;
|
mainMark = mark;
|
||||||
Color color = Color.BLACK;
|
Color color = Color.BLACK;
|
||||||
@@ -40,47 +38,69 @@ public class MarkGroup extends RaceObject {
|
|||||||
color = Color.RED;
|
color = Color.RED;
|
||||||
}
|
}
|
||||||
Circle markCircle;
|
Circle markCircle;
|
||||||
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
|
||||||
markCircle = new Circle(
|
markCircle = new Circle(
|
||||||
points[0].getX(),
|
points.getX(),
|
||||||
points[0].getY(),
|
points.getY(),
|
||||||
MARK_RADIUS,
|
MARK_RADIUS,
|
||||||
color
|
color
|
||||||
);
|
);
|
||||||
nodeDestinations = new Point2D[]{
|
|
||||||
new Point2D(markCircle.getCenterX(), markCircle.getCenterY()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
super.getChildren().add(markCircle);
|
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];
|
|
||||||
|
|
||||||
|
public void addLaylines(Line line1, Line line2) {
|
||||||
|
|
||||||
|
super.getChildren().addAll(line1, line2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removeLaylines() {
|
||||||
|
ArrayList<Node> toRemove = new ArrayList<>();
|
||||||
|
for(Node node : super.getChildren()) {
|
||||||
|
if (node instanceof Line) {
|
||||||
|
Line layLine = (Line) node;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh
|
||||||
|
*/
|
||||||
|
if (layLine.getStrokeWidth() == 0.5){
|
||||||
|
toRemove.add(layLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.getChildren().removeAll(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
|
||||||
|
marks.add(mark.getSingleMark1());
|
||||||
|
marks.add(mark.getSingleMark2());
|
||||||
|
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;
|
||||||
markCircle = new Circle(
|
markCircle = new Circle(
|
||||||
points[0].getX(),
|
points1.getX(),
|
||||||
points[0].getY(),
|
points1.getY(),
|
||||||
MARK_RADIUS,
|
MARK_RADIUS,
|
||||||
color
|
color
|
||||||
);
|
);
|
||||||
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
|
|
||||||
super.getChildren().add(markCircle);
|
super.getChildren().add(markCircle);
|
||||||
|
|
||||||
markCircle = new Circle(
|
markCircle = new Circle(
|
||||||
points[1].getX(),
|
points2.getX(),
|
||||||
points[1].getY(),
|
points2.getY(),
|
||||||
MARK_RADIUS,
|
MARK_RADIUS,
|
||||||
color
|
color
|
||||||
);
|
);
|
||||||
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
|
|
||||||
super.getChildren().add(markCircle);
|
super.getChildren().add(markCircle);
|
||||||
Line line = new Line(
|
Line line = new Line(
|
||||||
points[0].getX(),
|
points1.getX(),
|
||||||
points[0].getY(),
|
points1.getY(),
|
||||||
points[1].getX(),
|
points2.getX(),
|
||||||
points[1].getY()
|
points2.getY()
|
||||||
);
|
);
|
||||||
line.setStrokeWidth(LINE_THICKNESS);
|
line.setStrokeWidth(LINE_THICKNESS);
|
||||||
line.setStroke(color);
|
line.setStroke(color);
|
||||||
@@ -88,120 +108,36 @@ public class MarkGroup extends RaceObject {
|
|||||||
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
|
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
|
||||||
}
|
}
|
||||||
super.getChildren().add(line);
|
super.getChildren().add(line);
|
||||||
}
|
|
||||||
|
//Laylines
|
||||||
|
// if (mark.)
|
||||||
|
|
||||||
|
// addLayLine(points1, 12.0, 90.0);
|
||||||
|
// addLayLine(points2, 12.0, 90.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) {
|
public void moveMarkTo (double x, double y, long raceId)
|
||||||
setDestination(x, y, 0, raceIds);
|
{
|
||||||
this.rotationalGoal = rotation;
|
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||||
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);
|
Circle markCircle = (Circle) super.getChildren().get(0);
|
||||||
|
|
||||||
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() ||
|
markCircle.setCenterX(x);
|
||||||
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY())
|
markCircle.setCenterY(y);
|
||||||
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 {
|
} else {
|
||||||
Circle mark = (Circle) super.getChildren().get(0);
|
Circle markCircle1 = (Circle) super.getChildren().get(0);
|
||||||
mark.setCenterY(mark.getCenterY() + y);
|
Circle markCircle2 = (Circle) super.getChildren().get(1);
|
||||||
mark.setCenterX(mark.getCenterX() + x);
|
Line connectingLine = (Line) super.getChildren().get(2);
|
||||||
|
if (marks.get(0).getId() == raceId) {
|
||||||
|
markCircle1.setCenterX(x);
|
||||||
|
markCircle1.setCenterY(y);
|
||||||
|
connectingLine.setStartX(markCircle1.getCenterX());
|
||||||
|
connectingLine.setStartY(markCircle1.getCenterY());
|
||||||
|
} else if (marks.get(1).getId() == raceId) {
|
||||||
|
markCircle2.setCenterX(x);
|
||||||
|
markCircle2.setCenterY(y);
|
||||||
|
connectingLine.setEndX(markCircle2.getCenterX());
|
||||||
|
connectingLine.setEndY(markCircle2.getCenterY());
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,19 +149,15 @@ public class MarkGroup extends RaceObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMarkRadius() {
|
public long[] getRaceIds () {
|
||||||
return MARK_RADIUS;
|
long[] idArray = new long[marks.size()];
|
||||||
}
|
|
||||||
|
|
||||||
public static void setMarkRadius(int markRadius) {
|
|
||||||
MARK_RADIUS = markRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getRaceIds () {
|
|
||||||
int[] idArray = new int[marks.size()];
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Mark mark : marks)
|
for (Mark mark : marks)
|
||||||
idArray[i++] = mark.getId();
|
idArray[i++] = mark.getId();
|
||||||
return idArray;
|
return idArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mark getMainMark() {
|
||||||
|
return mainMark;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@ package seng302.models.mark;
|
|||||||
* Created by Haoming Yin (hyi25) on 17/3/17.
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
*/
|
*/
|
||||||
public enum MarkType {
|
public enum MarkType {
|
||||||
SINGLE_MARK, OPEN_GATE, CLOSED_GATE
|
SINGLE_MARK, OPEN_GATE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ public class SingleMark extends Mark {
|
|||||||
private double lat;
|
private double lat;
|
||||||
private double lon;
|
private double lon;
|
||||||
private String name;
|
private String name;
|
||||||
private int id;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a marker
|
* Represents a marker
|
||||||
@@ -19,24 +17,12 @@ public class SingleMark extends Mark {
|
|||||||
* @param lat, the latitude of the marker
|
* @param lat, the latitude of the marker
|
||||||
* @param lon, the longitude of the marker
|
* @param lon, the longitude of the marker
|
||||||
*/
|
*/
|
||||||
public SingleMark(String name, double lat, double lon, int id) {
|
public SingleMark(String name, double lat, double lon, int sourceID, int compoundMarkID) {
|
||||||
super(name, MarkType.SINGLE_MARK, id);
|
super(name, MarkType.SINGLE_MARK, sourceID, compoundMarkID);
|
||||||
this.lat = lat;
|
this.lat = lat;
|
||||||
this.lon = lon;
|
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() {
|
public double getLatitude() {
|
||||||
return this.lat;
|
return this.lat;
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,583 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
//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,668 @@
|
|||||||
|
package seng302.models.stream;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import seng302.models.Yacht;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.stream.packets.BoatPositionPacket;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>> markLocations = new ConcurrentHashMap<>();
|
||||||
|
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
|
||||||
|
private String threadName;
|
||||||
|
private Thread t;
|
||||||
|
private static boolean newRaceXmlReceived = false;
|
||||||
|
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 ConcurrentHashMap<>();
|
||||||
|
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
|
||||||
|
private static double windDirection = 0;
|
||||||
|
private static Double windSpeed = 0d;
|
||||||
|
private static Long currentTimeLong;
|
||||||
|
private static String currentTimeString;
|
||||||
|
private static boolean appRunning;
|
||||||
|
|
||||||
|
|
||||||
|
//CONVERSION CONSTANTS
|
||||||
|
private static final Double MS_TO_KNOTS = 1.94384;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
streamStatus = true;
|
||||||
|
xmlObject = new XMLParser();
|
||||||
|
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
while (appRunning) {
|
||||||
|
StreamPacket packet = StreamReceiver.packetBuffer.take();
|
||||||
|
parsePacket(packet);
|
||||||
|
Thread.sleep(1);
|
||||||
|
while (StreamReceiver.packetBuffer.peek() == null) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to start the stream parser thread when multithreading
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
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:
|
||||||
|
newRaceXmlReceived = true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
System.out.println("Error parsing packet");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
|
||||||
|
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
||||||
|
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
||||||
|
|
||||||
|
currentTimeLong = currentTime;
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
if (raceStatus == 4 || raceStatus == 8) {
|
||||||
|
raceFinished = true;
|
||||||
|
raceStarted = false;
|
||||||
|
} else if (!raceStarted) {
|
||||||
|
raceStarted = true;
|
||||||
|
raceFinished = false;
|
||||||
|
}
|
||||||
|
timeSinceStart = timeTillStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
|
||||||
|
windDirection = windDir / windDirFactor;
|
||||||
|
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
|
||||||
|
|
||||||
|
int noBoats = payload[22];
|
||||||
|
int raceType = payload[23];
|
||||||
|
for (int i = 0; i < noBoats; i++) {
|
||||||
|
long boatStatusSourceID = bytesToLong(
|
||||||
|
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
|
||||||
|
Yacht boat = boats.get((int) boatStatusSourceID);
|
||||||
|
boat.setBoatStatus((int) payload[28 + (i * 20)]);
|
||||||
|
setBoatLegPosition(boat, (int) payload[29 + (i * 20)]);
|
||||||
|
boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
|
||||||
|
boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
|
||||||
|
Long estTimeAtNextMark = bytesToLong(
|
||||||
|
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
|
||||||
|
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||||
|
Long estTimeAtFinish = bytesToLong(
|
||||||
|
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (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("-");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
||||||
|
Integer placing = 1;
|
||||||
|
if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
|
||||||
|
for (Yacht boat : boats.values()) {
|
||||||
|
if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
|
||||||
|
placing += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatingBoat.setPosition(placing.toString());
|
||||||
|
updatingBoat.setLegNumber(leg);
|
||||||
|
boatsPos.putIfAbsent(placing, updatingBoat);
|
||||||
|
boatsPos.replace(placing, updatingBoat);
|
||||||
|
} else if(updatingBoat.getLegNumber() == null){
|
||||||
|
updatingBoat.setPosition("1");
|
||||||
|
updatingBoat.setLegNumber(leg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
//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();
|
||||||
|
}
|
||||||
|
if (messageType == 6) { //6 is race info xml
|
||||||
|
|
||||||
|
newRaceXmlReceived = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
|
||||||
|
heading, groundSpeed);
|
||||||
|
|
||||||
|
//add a new priority que to the boatLocations HashMap
|
||||||
|
if (!boatLocations.containsKey(boatId)) {
|
||||||
|
boatLocations.put(boatId,
|
||||||
|
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
||||||
|
@Override
|
||||||
|
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
||||||
|
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
boatLocations.get(boatId).put(boatPacket);
|
||||||
|
} else if (deviceType == 3) {
|
||||||
|
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
|
||||||
|
heading, groundSpeed);
|
||||||
|
|
||||||
|
//add a new priority que to the boatLocations HashMap
|
||||||
|
if (!markLocations.containsKey(boatId)) {
|
||||||
|
markLocations.put(boatId,
|
||||||
|
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
||||||
|
@Override
|
||||||
|
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
||||||
|
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
markLocations.get(boatId).put(markPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
|
||||||
|
// assign mark rounding time to boat
|
||||||
|
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
|
||||||
|
|
||||||
|
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
|
||||||
|
if (mark.getCompoundMarkID() == markId) {
|
||||||
|
boats.get((int)subjectId).setLastMarkRounded(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 the wind speed in knots
|
||||||
|
* @return A double indicating the wind speed in knots
|
||||||
|
*/
|
||||||
|
public static Double getWindSpeed() {
|
||||||
|
return windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Integer, Yacht> getBoatsPos() {
|
||||||
|
|
||||||
|
return boatsPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns current time in stream in long
|
||||||
|
*
|
||||||
|
* @return a long value of current time
|
||||||
|
*/
|
||||||
|
public static Long getCurrentTimeLong() {
|
||||||
|
return currentTimeLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void appClose() {
|
||||||
|
appRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check if a new un-processed xml has been found, if so will return true before
|
||||||
|
* toggling off so that the next check will return false.
|
||||||
|
*
|
||||||
|
* @return the status of if new xml has been received
|
||||||
|
*/
|
||||||
|
public static boolean isNewRaceXmlReceived() {
|
||||||
|
if (newRaceXmlReceived) {
|
||||||
|
newRaceXmlReceived = false;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+2
-6
@@ -1,13 +1,11 @@
|
|||||||
package seng302.models.parsers;
|
package seng302.models.stream;
|
||||||
|
|
||||||
import seng302.models.parsers.packets.StreamPacket;
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
@@ -46,7 +44,6 @@ public class StreamReceiver extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start () {
|
public void start () {
|
||||||
System.out.println("[CLIENT] Starting " + threadName );
|
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
t = new Thread (this, threadName);
|
t = new Thread (this, threadName);
|
||||||
t.start ();
|
t.start ();
|
||||||
@@ -157,6 +154,5 @@ public class StreamReceiver extends Thread {
|
|||||||
|
|
||||||
public static void noMoreBytes(){
|
public static void noMoreBytes(){
|
||||||
moreBytes = false;
|
moreBytes = false;
|
||||||
System.out.println("[CLIENT] Shutting down stream receiver");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+252
-134
@@ -1,15 +1,18 @@
|
|||||||
package seng302.models.parsers;
|
package seng302.models.stream;
|
||||||
|
|
||||||
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.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
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 seng302.models.mark.GateMark;
|
||||||
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.models.mark.MarkType;
|
||||||
|
import seng302.models.mark.SingleMark;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to create an XML object from the XML Packet Messages.
|
* Class to create an XML object from the XML Packet Messages.
|
||||||
@@ -21,7 +24,6 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* xmlP = new XMLParser(doc, xmlMessageType);
|
* xmlP = new XMLParser(doc, xmlMessageType);
|
||||||
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
|
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class XMLParser {
|
public class XMLParser {
|
||||||
|
|
||||||
@@ -31,10 +33,12 @@ public class XMLParser {
|
|||||||
private RegattaXMLObject regattaXML;
|
private RegattaXMLObject regattaXML;
|
||||||
private BoatXMLObject boatXML;
|
private BoatXMLObject boatXML;
|
||||||
|
|
||||||
public XMLParser() {}
|
public XMLParser() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for XMLParser
|
* Constructor for XMLParser
|
||||||
|
*
|
||||||
* @param doc Document to create XML object.
|
* @param doc Document to create XML object.
|
||||||
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
|
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
|
||||||
*/
|
*/
|
||||||
@@ -53,13 +57,22 @@ public class XMLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RaceXMLObject getRaceXML() { return raceXML; }
|
public RaceXMLObject getRaceXML() {
|
||||||
public RegattaXMLObject getRegattaXML() { return regattaXML; }
|
return raceXML;
|
||||||
public BoatXMLObject getBoatXML() { return boatXML; }
|
}
|
||||||
|
|
||||||
|
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.
|
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
||||||
|
*
|
||||||
* @param ele Document Element with child elements.
|
* @param ele Document Element with child elements.
|
||||||
* @param tag Tag to find in document elements child elements.
|
* @param tag Tag to find in document elements child elements.
|
||||||
* @return Text content from tag if found, null otherwise.
|
* @return Text content from tag if found, null otherwise.
|
||||||
@@ -75,6 +88,7 @@ public class XMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as an String.
|
* Returns the text content of a given child element tag, assuming it exists, as an String.
|
||||||
|
*
|
||||||
* @param ele Document Element with child elements.
|
* @param ele Document Element with child elements.
|
||||||
* @param tag Tag to find in document elements child elements.
|
* @param tag Tag to find in document elements child elements.
|
||||||
* @return Text content from tag if found, null otherwise.
|
* @return Text content from tag if found, null otherwise.
|
||||||
@@ -90,6 +104,7 @@ public class XMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of a given child element tag, assuming it exists, as a Double.
|
* Returns the text content of a given child element tag, assuming it exists, as a Double.
|
||||||
|
*
|
||||||
* @param ele Document Element with child elements.
|
* @param ele Document Element with child elements.
|
||||||
* @param tag Tag to find in document elements child elements.
|
* @param tag Tag to find in document elements child elements.
|
||||||
* @return Text content from tag if found, null otherwise.
|
* @return Text content from tag if found, null otherwise.
|
||||||
@@ -105,9 +120,11 @@ public class XMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
|
* 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 n A node object that should have some attributes
|
||||||
* @param attr The attribute you want to get from the given node.
|
* @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.
|
* @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) {
|
private static String getNodeAttributeString(Node n, String attr) {
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
@@ -120,9 +137,11 @@ public class XMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
|
* 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 n A node object that should have some attributes
|
||||||
* @param attr The attribute you want to get from the given node.
|
* @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.
|
* @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) {
|
private static Integer getNodeAttributeInt(Node n, String attr) {
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
@@ -135,9 +154,11 @@ public class XMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
|
* 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 n A node object that should have some attributes
|
||||||
* @param attr The attribute you want to get from the given node.
|
* @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.
|
* @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) {
|
private static Double getNodeAttributeDouble(Node n, String attr) {
|
||||||
Node attrItem = n.getAttributes().getNamedItem(attr);
|
Node attrItem = n.getAttributes().getNamedItem(attr);
|
||||||
@@ -149,6 +170,7 @@ public class XMLParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class RegattaXMLObject {
|
public class RegattaXMLObject {
|
||||||
|
|
||||||
//Regatta Info
|
//Regatta Info
|
||||||
private Integer regattaID;
|
private Integer regattaID;
|
||||||
private String regattaName;
|
private String regattaName;
|
||||||
@@ -160,6 +182,7 @@ public class XMLParser {
|
|||||||
/**
|
/**
|
||||||
* Constructor for a RegattaXMLObject.
|
* Constructor for a RegattaXMLObject.
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
*
|
||||||
* @param doc XML Document Object
|
* @param doc XML Document Object
|
||||||
*/
|
*/
|
||||||
RegattaXMLObject(Document doc) {
|
RegattaXMLObject(Document doc) {
|
||||||
@@ -173,12 +196,29 @@ public class XMLParser {
|
|||||||
this.utcOffset = getElementInt(docEle, "UtcOffset");
|
this.utcOffset = getElementInt(docEle, "UtcOffset");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getRegattaID() { return regattaID; }
|
public Integer getRegattaID() {
|
||||||
public String getRegattaName() { return regattaName; }
|
return regattaID;
|
||||||
public String getCourseName() { return courseName; }
|
}
|
||||||
public Double getCentralLat() { return centralLat; }
|
|
||||||
public Double getCentralLng() { return centralLng; }
|
public String getRegattaName() {
|
||||||
public Integer getUtcOffset() { return utcOffset; }
|
return regattaName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseName() {
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCentralLat() {
|
||||||
|
return centralLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCentralLng() {
|
||||||
|
return centralLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUtcOffset() {
|
||||||
|
return utcOffset;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +235,18 @@ public class XMLParser {
|
|||||||
|
|
||||||
//Non atomic race attributes
|
//Non atomic race attributes
|
||||||
private ArrayList<Participant> participants;
|
private ArrayList<Participant> participants;
|
||||||
private ArrayList<CompoundMark> course;
|
private ArrayList<Mark> allMarks;
|
||||||
|
private ArrayList<Mark> nonDuplicateMarks;
|
||||||
private ArrayList<Corner> compoundMarkSequence;
|
private ArrayList<Corner> compoundMarkSequence;
|
||||||
private ArrayList<Limit> courseLimit;
|
private ArrayList<Limit> courseLimit;
|
||||||
|
|
||||||
|
// ensures there's no duplicate marks.
|
||||||
|
private List<Long> seenSourceIDs = new ArrayList<Long>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for a RaceXMLObject.
|
* Constructor for a RaceXMLObject.
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
*
|
||||||
* @param doc XML Document Object
|
* @param doc XML Document Object
|
||||||
*/
|
*/
|
||||||
RaceXMLObject(Document doc) {
|
RaceXMLObject(Document doc) {
|
||||||
@@ -213,8 +258,9 @@ public class XMLParser {
|
|||||||
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
|
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
|
||||||
|
|
||||||
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
|
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
|
||||||
this.raceStartTime = getNodeAttributeString(raceStart, "Start") ;
|
this.raceStartTime = getNodeAttributeString(raceStart, "Start");
|
||||||
this.postponeStatus = Boolean.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
|
this.postponeStatus = Boolean
|
||||||
|
.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
|
||||||
|
|
||||||
//Participants
|
//Participants
|
||||||
participants = new ArrayList<>();
|
participants = new ArrayList<>();
|
||||||
@@ -238,21 +284,15 @@ public class XMLParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Course
|
//Course
|
||||||
course = new ArrayList<>();
|
allMarks = new ArrayList<>();
|
||||||
|
nonDuplicateMarks = new ArrayList<>();
|
||||||
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
createCompoundMarks(docEle);
|
||||||
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
|
//Course Mark Sequence
|
||||||
compoundMarkSequence = new ArrayList<>();
|
compoundMarkSequence = new ArrayList<>();
|
||||||
|
|
||||||
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0).getChildNodes();
|
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0)
|
||||||
|
.getChildNodes();
|
||||||
for (int i = 0; i < cornerList.getLength(); i++) {
|
for (int i = 0; i < cornerList.getLength(); i++) {
|
||||||
Node cornerNode = cornerList.item(i);
|
Node cornerNode = cornerList.item(i);
|
||||||
if (cornerNode.getNodeName().equals("Corner")) {
|
if (cornerNode.getNodeName().equals("Corner")) {
|
||||||
@@ -274,18 +314,116 @@ public class XMLParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
private void createCompoundMarks(Element docEle) {
|
||||||
public ArrayList<CompoundMark> getCompoundMarks() { return course; }
|
|
||||||
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
|
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
||||||
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
|
for (int i = 0; i < cMarkList.getLength(); i++) {
|
||||||
|
Node cMarkNode = cMarkList.item(i);
|
||||||
|
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
||||||
|
createAndAddMark(cMarkNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createAndAddMark(Node compoundMark) {
|
||||||
|
|
||||||
|
Boolean markSeen = false;
|
||||||
|
List<SingleMark> marksList = new ArrayList<>();
|
||||||
|
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
|
||||||
|
String 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")) {
|
||||||
|
|
||||||
|
Integer sourceID = getNodeAttributeInt(markNode, "SourceID");
|
||||||
|
String markName = getNodeAttributeString(markNode, "Name");
|
||||||
|
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
||||||
|
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
||||||
|
|
||||||
|
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
|
||||||
|
marksList.add(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SingleMark mark : marksList) {
|
||||||
|
if (seenSourceIDs.contains(mark.getId())) {
|
||||||
|
markSeen = true;
|
||||||
|
} else {
|
||||||
|
seenSourceIDs.add(mark.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (marksList.size() == 1) {
|
||||||
|
if (!markSeen) {
|
||||||
|
nonDuplicateMarks.add(marksList.get(0));
|
||||||
|
}
|
||||||
|
allMarks.add(marksList.get(0));
|
||||||
|
} else if (marksList.size() == 2) {
|
||||||
|
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
|
||||||
|
marksList.get(1), marksList.get(0).getLatitude(),
|
||||||
|
marksList.get(0).getLongitude(), compoundMarkID);
|
||||||
|
if(!markSeen) {
|
||||||
|
nonDuplicateMarks.add(thisGateMark);
|
||||||
|
}
|
||||||
|
allMarks.add(thisGateMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS)
|
||||||
|
*/
|
||||||
|
public List<Mark> getAllCompoundMarks() {
|
||||||
|
return allMarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns Marks from the race XML without any duplicates
|
||||||
|
*/
|
||||||
|
public List<Mark> getNonDupCompoundMarks() {
|
||||||
|
return nonDuplicateMarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Corner> getCompoundMarkSequence() {
|
||||||
|
return compoundMarkSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Limit> getCourseLimit() {
|
||||||
|
return courseLimit;
|
||||||
|
}
|
||||||
|
|
||||||
public class Participant {
|
public class Participant {
|
||||||
|
|
||||||
Integer sourceID;
|
Integer sourceID;
|
||||||
String entry;
|
String entry;
|
||||||
|
|
||||||
@@ -294,57 +432,17 @@ public class XMLParser {
|
|||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getsourceID() { return sourceID; }
|
public Integer getsourceID() {
|
||||||
public String getEntry() { return entry; }
|
return sourceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompoundMark {
|
public String getEntry() {
|
||||||
private Integer markID;
|
return entry;
|
||||||
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 {
|
public class Corner {
|
||||||
|
|
||||||
private Integer seqID;
|
private Integer seqID;
|
||||||
private Integer compoundMarkID;
|
private Integer compoundMarkID;
|
||||||
private String rounding;
|
private String rounding;
|
||||||
@@ -357,13 +455,25 @@ public class XMLParser {
|
|||||||
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
|
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSeqID() { return seqID; }
|
public Integer getSeqID() {
|
||||||
public Integer getCompoundMarkID() { return compoundMarkID; }
|
return seqID;
|
||||||
public String getRounding() { return rounding; }
|
}
|
||||||
public Integer getZoneSize() { return zoneSize; }
|
|
||||||
|
public Integer getCompoundMarkID() {
|
||||||
|
return compoundMarkID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRounding() {
|
||||||
|
return rounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getZoneSize() {
|
||||||
|
return zoneSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Limit {
|
public class Limit {
|
||||||
|
|
||||||
private Integer seqID;
|
private Integer seqID;
|
||||||
private Double lat;
|
private Double lat;
|
||||||
private Double lng;
|
private Double lng;
|
||||||
@@ -374,9 +484,17 @@ public class XMLParser {
|
|||||||
this.lng = getNodeAttributeDouble(limitNode, "Lon");
|
this.lng = getNodeAttributeDouble(limitNode, "Lon");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSeqID() { return seqID; }
|
public Integer getSeqID() {
|
||||||
public Double getLat() { return lat; }
|
return seqID;
|
||||||
public Double getLng() { return lng; }
|
}
|
||||||
|
|
||||||
|
public Double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -402,6 +520,7 @@ public class XMLParser {
|
|||||||
/**
|
/**
|
||||||
* Constructor for a BoatXMLObject.
|
* Constructor for a BoatXMLObject.
|
||||||
* Takes the information from a Document object and creates a more usable format.
|
* Takes the information from a Document object and creates a more usable format.
|
||||||
|
*
|
||||||
* @param doc XML Document Object
|
* @param doc XML Document Object
|
||||||
*/
|
*/
|
||||||
BoatXMLObject(Document doc) {
|
BoatXMLObject(Document doc) {
|
||||||
@@ -421,7 +540,7 @@ public class XMLParser {
|
|||||||
Node zoneLimitsList = settingsList.item(7);
|
Node zoneLimitsList = settingsList.item(7);
|
||||||
this.zoneLimits = new ArrayList<>();
|
this.zoneLimits = new ArrayList<>();
|
||||||
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
|
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
|
||||||
String tag = String.format("Limit%d", i+1);
|
String tag = String.format("Limit%d", i + 1);
|
||||||
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
|
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,51 +561,50 @@ public class XMLParser {
|
|||||||
competingBoats.put(boat.getSourceID(), boat);
|
competingBoats.put(boat.getSourceID(), boat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//System.out.println(this.getBoats());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLastModified() { return lastModified; }
|
public String getLastModified() {
|
||||||
public Integer getVersion() { return version; }
|
return lastModified;
|
||||||
public String getBoatType() { return boatType; }
|
}
|
||||||
public Double getBoatLength() { return boatLength; }
|
|
||||||
public Double getHullLength() { return hullLength; }
|
public Integer getVersion() {
|
||||||
public Double getMarkZoneSize() { return markZoneSize; }
|
return version;
|
||||||
public Double getCourseZoneSize() { return courseZoneSize; }
|
}
|
||||||
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
|
|
||||||
public ArrayList<Yacht> getBoats() { return boats; }
|
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() {
|
public Map<Integer, Yacht> getCompetingBoats() {
|
||||||
return competingBoats;
|
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; }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.models.parsers.packets;
|
package seng302.models.stream.packets;
|
||||||
|
|
||||||
public class BoatPositionPacket {
|
public class BoatPositionPacket {
|
||||||
private long boatId;
|
private long boatId;
|
||||||
+1
-3
@@ -1,4 +1,4 @@
|
|||||||
package seng302.models.parsers.packets;
|
package seng302.models.stream.packets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Kusal on 4/24/2017.
|
* Created by Kusal on 4/24/2017.
|
||||||
@@ -48,6 +48,4 @@ public enum PacketType {
|
|||||||
}
|
}
|
||||||
return OTHER;
|
return OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.models.parsers.packets;
|
package seng302.models.stream.packets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by kre39 on 23/04/17.
|
* Created by kre39 on 23/04/17.
|
||||||
@@ -29,8 +29,6 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
Thread runner = new Thread(this, threadName);
|
Thread runner = new Thread(this, threadName);
|
||||||
runner.setDaemon(true);
|
runner.setDaemon(true);
|
||||||
|
|
||||||
serverLog("Spawning Server", 0);
|
|
||||||
|
|
||||||
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
||||||
raceSimulator.addObserver(this);
|
raceSimulator.addObserver(this);
|
||||||
// run race simulator, so it can send boats' static location.
|
// run race simulator, so it can send boats' static location.
|
||||||
@@ -134,7 +132,6 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
* Starts an instance of the race simulator
|
* Starts an instance of the race simulator
|
||||||
*/
|
*/
|
||||||
private void startRaceSim(){
|
private void startRaceSim(){
|
||||||
serverLog("Starting Running Race Simulator", 0);
|
|
||||||
// set race started to true, so the simulator will start moving boats
|
// set race started to true, so the simulator will start moving boats
|
||||||
raceSimulator.setRaceStarted(true);
|
raceSimulator.setRaceStarted(true);
|
||||||
}
|
}
|
||||||
@@ -142,8 +139,7 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
/**
|
/**
|
||||||
* Starts sending heartbeat messages to the client
|
* Starts sending heartbeat messages to the client
|
||||||
*/
|
*/
|
||||||
private void startSendingHeartbeats(){
|
private void startSendingHeartbeats() {
|
||||||
serverLog("Sending Heartbeats", 0);
|
|
||||||
Timer t = new Timer();
|
Timer t = new Timer();
|
||||||
|
|
||||||
t.schedule(new TimerTask() {
|
t.schedule(new TimerTask() {
|
||||||
@@ -154,7 +150,7 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
try {
|
try {
|
||||||
server.send(heartbeat);
|
server.send(heartbeat);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.print("");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0, HEARTBEAT_PERIOD);
|
}, 0, HEARTBEAT_PERIOD);
|
||||||
@@ -174,14 +170,13 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
if (startTime < System.currentTimeMillis() && !raceStarted){
|
if (startTime < System.currentTimeMillis() && !raceStarted){
|
||||||
startRaceSim();
|
startRaceSim();
|
||||||
raceStarted = true;
|
raceStarted = true;
|
||||||
serverLog("Race Started", 0);
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
server.send(raceStartStatusMessage);
|
server.send(raceStartStatusMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.print("");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0, RACE_START_STATUS_PERIOD);
|
}, 0, RACE_START_STATUS_PERIOD);
|
||||||
@@ -191,7 +186,6 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
* Start sending race start status messages until race starts
|
* Start sending race start status messages until race starts
|
||||||
*/
|
*/
|
||||||
private void startSendingRaceStatusMessages(){
|
private void startSendingRaceStatusMessages(){
|
||||||
serverLog("Sending race status messages", 0);
|
|
||||||
Timer t = new Timer();
|
Timer t = new Timer();
|
||||||
t.schedule(new TimerTask() {
|
t.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
@@ -199,9 +193,8 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
Message raceStatusMessage = getRaceStatusMessage();
|
Message raceStatusMessage = getRaceStatusMessage();
|
||||||
try {
|
try {
|
||||||
server.send(raceStatusMessage);
|
server.send(raceStatusMessage);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.print("");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0, RACE_STATUS_PERIOD);
|
}, 0, RACE_STATUS_PERIOD);
|
||||||
@@ -218,23 +211,39 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
|
|
||||||
if (raceData != null){
|
if (raceData != null){
|
||||||
server.send(raceData);
|
server.send(raceData);
|
||||||
serverLog("Sending race data", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boatData != null){
|
if (boatData != null){
|
||||||
server.send(boatData);
|
server.send(boatData);
|
||||||
serverLog("Sending boat data", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (regatta != null){
|
if (regatta != null){
|
||||||
server.send(regatta);
|
server.send(regatta);
|
||||||
serverLog("Sending regatta data", 0);
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the post-start race course information
|
||||||
|
*/
|
||||||
|
private void sendPostStartCourseXml(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
|
||||||
|
if (raceData != null) {
|
||||||
|
server.send(raceData);
|
||||||
|
}
|
||||||
|
}catch (IOException e) {
|
||||||
|
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},25000);
|
||||||
|
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
|
||||||
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
try{
|
try{
|
||||||
server = new StreamingServerSocket(PORT_NUMBER);
|
server = new StreamingServerSocket(PORT_NUMBER);
|
||||||
@@ -252,12 +261,13 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
sendXml();
|
sendXml();
|
||||||
startSendingRaceStartStatusMessages();
|
startSendingRaceStartStatusMessages();
|
||||||
startSendingRaceStatusMessages();
|
startSendingRaceStatusMessages();
|
||||||
|
sendPostStartCourseXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start sending static boat position updates when race has finished
|
* Start sending static boat position updates when race has finished
|
||||||
*/
|
*/
|
||||||
private void startSendingRaceFinishedBoatPostions(){
|
private void startSendingRaceFinishedBoatPositions(){
|
||||||
Timer t = new Timer();
|
Timer t = new Timer();
|
||||||
t.schedule(new TimerTask() {
|
t.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
@@ -272,7 +282,7 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.print("");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0, BOAT_LOCATION_PERIOD);
|
}, 0, BOAT_LOCATION_PERIOD);
|
||||||
@@ -299,7 +309,6 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
numOfBoatsFinished ++;
|
numOfBoatsFinished ++;
|
||||||
if (!boatsFinished.get(boat.getSourceID())) {
|
if (!boatsFinished.get(boat.getSourceID())) {
|
||||||
boatsFinished.put(boat.getSourceID(), true);
|
boatsFinished.put(boat.getSourceID(), true);
|
||||||
serverLog("Boat " + boat.getSourceID() + " finished the race", 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
|
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
|
||||||
@@ -316,7 +325,7 @@ public class ServerThread implements Runnable, Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
|
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
|
||||||
startSendingRaceFinishedBoatPostions();
|
startSendingRaceFinishedBoatPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class StreamingServerSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void start(){
|
void start(){
|
||||||
ServerThread.serverLog("Listening For Connections",0);
|
|
||||||
try {
|
try {
|
||||||
client = socket.accept();
|
client = socket.accept();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -39,7 +38,6 @@ class StreamingServerSocket {
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
isServerStarted = true;
|
isServerStarted = true;
|
||||||
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package seng302.server.messages;
|
package seng302.server.messages;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
|
|
||||||
public class BoatLocationMessage extends Message {
|
public class BoatLocationMessage extends Message {
|
||||||
private final int MESSAGE_SIZE = 56;
|
private final int MESSAGE_SIZE = 56;
|
||||||
@@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message {
|
|||||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||||
boatSpeed /= 10;
|
boatSpeed /= 10;
|
||||||
messageVersionNumber = 1;
|
messageVersionNumber = 1;
|
||||||
time = System.currentTimeMillis() / 1000L;
|
time = System.currentTimeMillis();
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
this.sequenceNum = sequenceNum;
|
this.sequenceNum = sequenceNum;
|
||||||
this.deviceType = DeviceType.RACING_YACHT;
|
this.deviceType = DeviceType.RACING_YACHT;
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ public class Simulator extends Observable implements Runnable {
|
|||||||
numOfFinishedBoats += moveBoat(boat, lapse);
|
numOfFinishedBoats += moveBoat(boat, lapse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//System.out.println(boats.get(0));
|
|
||||||
|
|
||||||
setChanged();
|
setChanged();
|
||||||
notifyObservers(boats);
|
notifyObservers(boats);
|
||||||
@@ -65,8 +64,6 @@ public class Simulator extends Observable implements Runnable {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("[SERVER] Race simulator has been terminated");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
|
||||||
|
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
|
||||||
|
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
|
||||||
|
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
|
||||||
|
16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20
|
||||||
|
20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24
|
||||||
|
25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30
|
||||||
|
30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32
|
||||||
|
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
Background colours
|
||||||
|
*/
|
||||||
|
.background-blue{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-dark{
|
||||||
|
-fx-background-color: #2C2c36;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Exit button with no background
|
||||||
|
*/
|
||||||
|
.clear-exit-btn{
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Buttons
|
||||||
|
*/
|
||||||
|
.blue-ui-btn{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-text-fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
-fx-text-fill: white !important;
|
||||||
|
-fx-fill:white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sliders
|
||||||
|
*/
|
||||||
|
.ui-slider .thumb {
|
||||||
|
-fx-background-color: rgb(60, 60, 60);
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
-fx-border-color: darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .track{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .axis{
|
||||||
|
-fx-tick-label-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider .axis .axis-label{
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checkbox
|
||||||
|
*/
|
||||||
|
.ui-checkbox .box{
|
||||||
|
-fx-background-color: white;
|
||||||
|
-fx-graphic:none;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox .box .mark{
|
||||||
|
-fx-background-image: none;
|
||||||
|
-fx-image: none;
|
||||||
|
-fx-graphic: none;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox:selected .box{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-shape: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-checkbox:selected .box .mark{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-shape: none;
|
||||||
|
-fx-graphic: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Table
|
||||||
|
*/
|
||||||
|
.ui-table{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table:focused{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header-background{
|
||||||
|
-fx-background-color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header-background .label{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .column-header {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table .table-cell{
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell{
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:odd{
|
||||||
|
-fx-background-color: #0e6d6c;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:selected {
|
||||||
|
-fx-background-color: #005797;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Combo Box
|
||||||
|
*/
|
||||||
|
|
||||||
|
.combo-box-base {
|
||||||
|
-fx-background-color: #119796;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combo-box-popup .list-view .list-cell:hover {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combo-box .cell:selected {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Remove scroll bars
|
||||||
|
*/
|
||||||
|
.ui-table *.scroll-bar:horizontal *.increment-button,
|
||||||
|
.ui-table *.scroll-bar:horizontal *.decrement-button {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:horizontal *.increment-arrow,
|
||||||
|
.ui-table *.scroll-bar:horizontal *.decrement-arrow {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-shape: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:vertical *.increment-arrow,
|
||||||
|
.ui-table *.scroll-bar:vertical *.decrement-arrow {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-shape: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-table *.scroll-bar:vertical *.increment-button,
|
||||||
|
.ui-table *.scroll-bar:vertical *.decrement-button {
|
||||||
|
-fx-background-color: null;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart{
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
-fx-font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis-label {
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis {
|
||||||
|
-fx-tick-label-fill: #ffffff;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?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>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Race>
|
<Race>
|
||||||
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
|
<CreationTimeDate>2015-08-29T11:27:15+02:00</CreationTimeDate>
|
||||||
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
|
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
|
||||||
<RaceID>15082901</RaceID>
|
<RaceID>15082901</RaceID>
|
||||||
<RaceType>Fleet</RaceType>
|
<RaceType>Fleet</RaceType>
|
||||||
@@ -80,6 +80,8 @@
|
|||||||
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
|
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
|
||||||
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
|
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
|
||||||
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
|
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
|
||||||
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" />
|
<Limit SeqID="10" Lat="57.6692230" Lon="11.8231430" />
|
||||||
|
<Limit SeqID="11" Lat="57.6725370" Lon="11.8272480" />
|
||||||
|
<Limit SeqID="12" Lat="57.6708220" Lon="11.8321340" />
|
||||||
</CourseLimit>
|
</CourseLimit>
|
||||||
</Race>
|
</Race>
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
<?import javafx.scene.layout.*?>
|
<?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" />
|
<AnchorPane fx:id="canvasPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
|
||||||
|
|||||||
@@ -7,58 +7,4 @@
|
|||||||
<?import java.lang.*?>
|
<?import java.lang.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?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">
|
<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>
|
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import java.lang.*?>
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.chart.*?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.shape.*?>
|
<?import javafx.scene.shape.*?>
|
||||||
<?import javafx.scene.text.*?>
|
<?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">
|
<GridPane maxHeight="960.0" maxWidth="1530.0" prefHeight="960.0" prefWidth="1530.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>
|
||||||
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
|
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
|
||||||
@@ -28,39 +18,50 @@
|
|||||||
<RowConstraints />
|
<RowConstraints />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3">
|
<AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
|
||||||
<children>
|
<children>
|
||||||
<Label layoutX="11.0" layoutY="259.0" text="Team Position" />
|
<Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" />
|
||||||
<Label layoutX="13.0" layoutY="432.0" text="Settings" />
|
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
|
||||||
<Label layoutX="11.0" layoutY="14.0" text="Timer" />
|
<Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" />
|
||||||
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" />
|
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" textFill="WHITE" />
|
||||||
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" />
|
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#3dcdc8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#d7d7d7" strokeType="INSIDE" strokeWidth="3.0" />
|
||||||
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
|
<Text fx:id="windArrowText" fill="#a8a8a8" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↓">
|
||||||
<font>
|
<font>
|
||||||
<Font name="AdobeArabic-Regular" size="55.0" />
|
<Font name="AdobeArabic-Regular" size="55.0" />
|
||||||
</font>
|
</font>
|
||||||
</Text>
|
</Text>
|
||||||
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
|
<Text fx:id="windDirectionText" fill="#d3d3d3" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="13.0" />
|
<Font name="System Bold" size="13.0" />
|
||||||
</font>
|
</font>
|
||||||
</Text>
|
</Text>
|
||||||
<CheckBox fx:id="toggleFps" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" />
|
<CheckBox fx:id="toggleFps" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
|
||||||
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
|
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" />
|
||||||
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
|
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
|
||||||
<children>
|
<children>
|
||||||
<Text fx:id="timerLabel" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
|
<Text fx:id="timerLabel" fill="#f8f8f8" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
|
||||||
<font>
|
<font>
|
||||||
<Font size="25.0" />
|
<Font size="25.0" />
|
||||||
</font>
|
</font>
|
||||||
</Text>
|
</Text>
|
||||||
</children>
|
</children>
|
||||||
</Pane>
|
</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" />
|
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
|
||||||
<Label layoutX="10.0" layoutY="499.0" text="Annotations" />
|
<Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
|
||||||
|
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
|
||||||
|
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
|
||||||
|
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
|
||||||
|
<LineChart fx:id="raceSparkLine" layoutX="-1.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="246.0" title="Boat Positions">
|
||||||
|
<xAxis>
|
||||||
|
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis fx:id="sparklineYAxis" minorTickCount="1" minorTickLength="1.0" side="LEFT" styleClass="spark-line-yaxis" tickLabelGap="1.0" tickUnit="1.0" upperBound="7.0" />
|
||||||
|
</yAxis>
|
||||||
|
</LineChart>
|
||||||
</children>
|
</children>
|
||||||
</AnchorPane>
|
</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">
|
<AnchorPane fx:id="contentAnchorPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
|
||||||
<children>
|
<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" />
|
<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></AnchorPane>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?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.*?>
|
||||||
|
|
||||||
|
<GridPane fx:id="gridPane" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" 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.StartScreenController">
|
||||||
|
<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="9.0" prefHeight="55.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="29.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" textFill="WHITE" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
||||||
|
<font>
|
||||||
|
<Font size="40.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label text="Your live AC35 livestream" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="1">
|
||||||
|
<font>
|
||||||
|
<Font size="20.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label text="Livestream Status:" textFill="WHITE" 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" styleClass="blue-ui-btn" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
|
||||||
|
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" styleClass="blue-ui-btn" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
|
||||||
|
<TableView fx:id="teamList" maxWidth="661.0" prefHeight="324.0" prefWidth="629.0" styleClass="ui-table" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="5" GridPane.vgrow="NEVER">
|
||||||
|
<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="155.18472290039062" minWidth="107.0" prefWidth="155.18472290039062" resizable="false" sortable="false" text="Short Name" />
|
||||||
|
<TableColumn fx:id="countryCol" editable="false" maxWidth="258.9999694824219" minWidth="147.0" prefWidth="258.9999694824219" resizable="false" sortable="false" text="Country" />
|
||||||
|
</columns>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</TableView>
|
||||||
|
<Label fx:id="realTime" text="Local time" textFill="WHITE" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.canvas.*?>
|
||||||
|
|
||||||
|
|
||||||
|
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: #ddd;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.models.map.TestMapController">
|
||||||
|
<children>
|
||||||
|
<Canvas fx:id="mapCanvas" height="960.0" width="1280.0" />
|
||||||
|
</children>
|
||||||
|
</Pane>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<AnchorPane fx:id="annotationSelectWindow" maxHeight="270.0" maxWidth="469.0" minHeight="270.0" minWidth="469.0" prefHeight="270.0" prefWidth="469.0" styleClass="background-blue" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<children>
|
||||||
|
<Text fill="WHITE" layoutX="26.0" layoutY="52.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Select important annotations">
|
||||||
|
<font>
|
||||||
|
<Font size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Text>
|
||||||
|
<CheckBox fx:id="boatWakeSelect" layoutX="26.0" layoutY="80.0" mnemonicParsing="false" style="-fx-border-width: 0; -fx-background-insets: 0;" text="Boat Wakes" textFill="#e7e7e7" />
|
||||||
|
<CheckBox fx:id="boatSpeedSelect" layoutX="26.0" layoutY="111.0" mnemonicParsing="false" text="Boat Speed" textFill="#e7e7e7" />
|
||||||
|
<CheckBox fx:id="boatTrackSelect" layoutX="26.0" layoutY="142.0" mnemonicParsing="false" text="Boat Tracks" textFill="#e7e7e7" />
|
||||||
|
<CheckBox fx:id="boatNameSelect" layoutX="26.0" layoutY="173.0" mnemonicParsing="false" text="Boat Name" textFill="#e7e7e7" />
|
||||||
|
<CheckBox fx:id="boatEstTimeToNextMarkSelect" layoutX="26.0" layoutY="204.0" mnemonicParsing="false" text="Boat Estimated Time To Next Mark" textFill="#e7e7e7" />
|
||||||
|
<Button fx:id="closeButton" layoutX="424.0" layoutY="-1.0" mnemonicParsing="false" prefHeight="11.0" prefWidth="49.0" style=": 0;" text="X" textFill="#ffffff4e">
|
||||||
|
<font>
|
||||||
|
<Font size="24.0" />
|
||||||
|
</font>
|
||||||
|
<styleClass>
|
||||||
|
<String fx:value="background-blue" />
|
||||||
|
<String fx:value="clearExitButton" />
|
||||||
|
</styleClass>
|
||||||
|
</Button>
|
||||||
|
<CheckBox fx:id="boatElapsedTimeSelect" layoutX="26.0" layoutY="235.0" mnemonicParsing="false" text="Boat Elapsed Time Since Last Mark" textFill="#e7e7e7" />
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@@ -8,14 +8,11 @@ import seng302.models.Colors;
|
|||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ryan_ on 16/03/2017.
|
|
||||||
*/
|
|
||||||
public class ColorsTest {
|
public class ColorsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNextColor() {
|
public void testNextColor() {
|
||||||
Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE};
|
Color expectedColors[] = {Color.RED, Color.PERU, Color.SEAGREEN, Color.GREEN, Color.BLUE, Color.PURPLE};
|
||||||
for (int i = 0; i<6; i++)
|
for (int i = 0; i<6; i++)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(expectedColors[i], Colors.getColor());
|
Assert.assertEquals(expectedColors[i], Colors.getColor());
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Event;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for Event class
|
|
||||||
* Created by Haoming on 7/03/17.
|
|
||||||
*/
|
|
||||||
public class EventTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getTimeString() throws Exception {
|
|
||||||
Yacht boat = new Yacht("testBoat");
|
|
||||||
Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0);
|
|
||||||
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,54 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Leg;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for the Leg class.
|
|
||||||
*/
|
|
||||||
public class LegTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test creation of the leg by specifying a string
|
|
||||||
* for the marker label
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testLegCreationUsingMarkerLabel() {
|
|
||||||
Leg leg = new Leg(010, 100, "SingleMark");
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
|
||||||
assertEquals(leg.getDistance(), 100);
|
|
||||||
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test creation of the leg by providing a
|
|
||||||
* SingleMark object
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testLegCreation() {
|
|
||||||
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
|
||||||
assertEquals(leg.getDistance(), 100);
|
|
||||||
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test changing whether or not a
|
|
||||||
* leg is the finishing leg
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSetFinishLeg() {
|
|
||||||
Leg leg = new Leg(010, 100, "SingleMark");
|
|
||||||
|
|
||||||
leg.setFinishingLeg(true);
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Race;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for the Race class.
|
|
||||||
*/
|
|
||||||
public class RaceTest {
|
|
||||||
/**
|
|
||||||
* Test that all boats were added to the race
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testAddingBoatsToRace() {
|
|
||||||
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.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,65 @@
|
|||||||
|
package seng302;
|
||||||
|
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Class for the GeometryUtils class
|
||||||
|
* Created by wmu16 on 24/05/17.
|
||||||
|
*/
|
||||||
|
public class TestGeoUtils {
|
||||||
|
|
||||||
|
//Line in x = y
|
||||||
|
private Point2D linePoint1 = new Point2D(0, 0);
|
||||||
|
private Point2D linePoint2 = new Point2D(1, 1);
|
||||||
|
|
||||||
|
//Point below x = y
|
||||||
|
private Point2D arbitraryPoint1 = new Point2D(1, 0);
|
||||||
|
|
||||||
|
//Point above x = y
|
||||||
|
private Point2D arbitraryPoint2 = new Point2D(0, 1);
|
||||||
|
|
||||||
|
//Point on x = y
|
||||||
|
private Point2D arbitraryPoint3 = new Point2D(2, 2);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLineFunction() {
|
||||||
|
|
||||||
|
Integer lineFunctionResult1 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint1);
|
||||||
|
Integer lineFunctionResult2 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint2);
|
||||||
|
Integer lineFunctionResult3 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint3);
|
||||||
|
|
||||||
|
//Point1 and Point2 are on opposite sides
|
||||||
|
assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2));
|
||||||
|
assertNotEquals(lineFunctionResult1, lineFunctionResult2);
|
||||||
|
|
||||||
|
//Point3 is on the line
|
||||||
|
assertEquals((long) lineFunctionResult3, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMakeArbitraryVectorPoint() {
|
||||||
|
|
||||||
|
//Make a point (1,0) from point (0,0)
|
||||||
|
Point2D newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 0d, 1d);
|
||||||
|
Point2D expected = new Point2D(1,0);
|
||||||
|
|
||||||
|
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
|
||||||
|
assertEquals(expected.getY(), newPoint.getY(), 1E-6);
|
||||||
|
|
||||||
|
newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 90d, 1d);
|
||||||
|
expected = new Point2D(0, 1);
|
||||||
|
|
||||||
|
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
|
||||||
|
assertEquals(expected.getY(), newPoint.getY(), 1E-6);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for Mercator Project class.
|
||||||
|
* Created by hyi25 on 15/05/17.
|
||||||
|
*/
|
||||||
|
public class MercatorProjectionTest {
|
||||||
|
@Test
|
||||||
|
public void toMapPoint() throws Exception {
|
||||||
|
MapGeo geo1 = new MapGeo(12.485394, 19.38947);
|
||||||
|
MapPoint actualPoint1 = MercatorProjection.toMapPoint(geo1);
|
||||||
|
MapPoint expectedPoint1 = new MapPoint(141.78806755555556, 119.0503853635612);
|
||||||
|
assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001);
|
||||||
|
assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001);
|
||||||
|
|
||||||
|
MapGeo geo2 = new MapGeo(77.456432, -23.456462);
|
||||||
|
MapPoint actualPoint2 = MercatorProjection.toMapPoint(geo2);
|
||||||
|
MapPoint expectedPoint2 = new MapPoint(111.31984924444444, 38.03143323746788);
|
||||||
|
assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001);
|
||||||
|
assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toMapGeo() throws Exception {
|
||||||
|
MapPoint point1 = new MapPoint(123.1234, 25.4565);
|
||||||
|
MapGeo actualGeo1 = MercatorProjection.toMapGeo(point1);
|
||||||
|
MapGeo expectedGeo1 = new MapGeo(80.77043127275441, -6.857718749999995);
|
||||||
|
assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001);
|
||||||
|
assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001);
|
||||||
|
|
||||||
|
MapPoint point2 = new MapPoint(1.235, 255.4565);
|
||||||
|
MapGeo actualGeo2 = MercatorProjection.toMapGeo(point2);
|
||||||
|
MapGeo expectedGeo2 = new MapGeo(-84.98475532898011, -178.26328125);
|
||||||
|
assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001);
|
||||||
|
assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,9 +16,9 @@ public class MarkTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1);
|
this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1, 0);
|
||||||
this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2);
|
this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2, 1);
|
||||||
this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude());
|
this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package seng302.models.parsers;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Haoming on 23/03/17.
|
|
||||||
*/
|
|
||||||
public class ConfigParserTest {
|
|
||||||
|
|
||||||
private ConfigParser cp;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initializeParser() throws Exception {
|
|
||||||
cp = new ConfigParser("/config/config.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getWindDirection() throws Exception {
|
|
||||||
assertEquals(135, cp.getWindDirection(), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getTimeScale() throws Exception {
|
|
||||||
assertEquals(10.0, cp.getTimeScale(), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getDoubleByTagName() throws Exception {
|
|
||||||
assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10);
|
|
||||||
assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getStringByTagName() throws Exception {
|
|
||||||
assertEquals("AC35", cp.getStringByTagName("race-name", "11"));
|
|
||||||
assertEquals("oops", cp.getStringByTagName("noTag", "oops"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package seng302.models.parsers;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.mark.*;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To test if course parser works as expected.
|
|
||||||
* Created by Haoming on 17/03/17.
|
|
||||||
*/
|
|
||||||
public class CourseParserTest {
|
|
||||||
|
|
||||||
private CourseParser cp;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initializeParser() throws Exception {
|
|
||||||
cp = new CourseParser("/config/course.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getGates() throws Exception {
|
|
||||||
ArrayList<Mark> course = cp.getCourse();
|
|
||||||
|
|
||||||
|
|
||||||
GateMark gateMark1 = (GateMark) course.get(0);
|
|
||||||
assertEquals(57.670633, gateMark1.getSingleMark2().getLatitude(), 0.00000001);
|
|
||||||
assertEquals(11.8281330, gateMark1.getSingleMark2().getLongitude(), 0.00000001);
|
|
||||||
|
|
||||||
GateMark gateMark2 = (GateMark) course.get(5);
|
|
||||||
|
|
||||||
assertEquals("Finish1", gateMark2.getSingleMark1().getName());
|
|
||||||
assertEquals("Finish2", gateMark2.getSingleMark2().getName());
|
|
||||||
assertEquals(57.671824, gateMark2.getSingleMark2().getLatitude(), 0.00000001);
|
|
||||||
assertEquals(11.844795, gateMark2.getSingleMark2().getLongitude(), 0.00000001);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getMarks() throws Exception {
|
|
||||||
ArrayList<Mark> course = cp.getCourse();
|
|
||||||
assertEquals("Mid Mark", course.get(1).getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getOrder() {
|
|
||||||
ArrayList<Mark> course = cp.getCourse();
|
|
||||||
assertEquals(6, course.size());
|
|
||||||
assertEquals("Start", course.get(0).getName());
|
|
||||||
assertEquals("Mid Mark", course.get(1).getName());
|
|
||||||
assertEquals("Leeward Gate", course.get(2).getName());
|
|
||||||
assertEquals("Windward Gate", course.get(3).getName());
|
|
||||||
assertEquals("Leeward Gate", course.get(4).getName());
|
|
||||||
assertEquals("Finish", course.get(5).getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
package seng302.models.parsers;
|
package seng302.models.stream;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -8,6 +8,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
import seng302.models.stream.packets.StreamPacket;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -98,7 +99,7 @@ public class StreamReceiverTest {
|
|||||||
byte[] emptyArray = {};
|
byte[] emptyArray = {};
|
||||||
assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L);
|
assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L);
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
System.out.println("");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.visualizer.annotations;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.controllers.annotations.Annotation;
|
||||||
|
import seng302.controllers.annotations.ImportantAnnotationsState;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class TestImportantAnnotationState {
|
||||||
|
private ImportantAnnotationsState importantAnnotationsState;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpForTest(){
|
||||||
|
importantAnnotationsState = new ImportantAnnotationsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDownAfterTest(){
|
||||||
|
importantAnnotationsState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether each annotation has its default value set to the default value when
|
||||||
|
* the class is initialized
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDefaultValueSet(){
|
||||||
|
for (Annotation annotation : importantAnnotationsState.getAnnotations()){
|
||||||
|
assertEquals(ImportantAnnotationsState.DEFAULT_ANNOTATION_STATE,
|
||||||
|
importantAnnotationsState.getAnnotationState(annotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an annotations state can be changed
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAnnotationStateChange(){
|
||||||
|
Annotation[] annotations = importantAnnotationsState.getAnnotations();
|
||||||
|
|
||||||
|
// do not run test if there are no annotations
|
||||||
|
if (annotations.length <= 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean currentAnnotationState = importantAnnotationsState.getAnnotationState(annotations[0]);
|
||||||
|
importantAnnotationsState.setAnnotationState(annotations[0], !currentAnnotationState);
|
||||||
|
|
||||||
|
assertEquals(!currentAnnotationState, importantAnnotationsState.getAnnotationState(annotations[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user