mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen
# Conflicts: # src/main/java/seng302/controllers/CanvasController.java # src/main/java/seng302/models/stream/StreamParser.java
This commit is contained in:
@@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import seng302.models.PolarTable;
|
||||
import seng302.models.stream.StreamParser;
|
||||
import seng302.models.stream.StreamReceiver;
|
||||
import seng302.server.ServerThread;
|
||||
@@ -13,6 +14,8 @@ public class App extends Application {
|
||||
|
||||
@Override
|
||||
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"));
|
||||
primaryStage.setTitle("RaceVision");
|
||||
primaryStage.setScene(new Scene(root, 1530, 960));
|
||||
@@ -27,6 +30,8 @@ public class App extends Application {
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -63,9 +68,7 @@ public class App extends Application {
|
||||
}
|
||||
//Change the StreamReceiver in this else block to change the default data source.
|
||||
else{
|
||||
// sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
|
||||
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||
}
|
||||
|
||||
sr.start();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -225,11 +225,9 @@ public class CanvasController {
|
||||
double[] yBoundaryPoints = new double[courseLimits.size()];
|
||||
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
||||
Limit thisPoint1 = courseLimits.get(i);
|
||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(),
|
||||
thisPoint1.getSeqID());
|
||||
Limit thisPoint2 = courseLimits.get(i + 1);
|
||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(),
|
||||
thisPoint2.getSeqID());
|
||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
||||
Limit thisPoint2 = courseLimits.get(i+1);
|
||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||
@@ -237,12 +235,10 @@ public class CanvasController {
|
||||
xBoundaryPoints[i] = borderPoint1.getX();
|
||||
yBoundaryPoints[i] = borderPoint1.getY();
|
||||
}
|
||||
Limit thisPoint1 = courseLimits.get(courseLimits.size() - 1);
|
||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(),
|
||||
thisPoint1.getSeqID());
|
||||
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
||||
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 borderPoint2 = findScaledXY(thisMark2);
|
||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
||||
@@ -257,7 +253,7 @@ public class CanvasController {
|
||||
for (BoatGroup boatGroup : boatGroups) {
|
||||
// some raceObjects will have multiple ID's (for instance gate marks)
|
||||
//checking if the current "ID" has any updates associated with it
|
||||
if (StreamParser.boatPositions.containsKey(boatGroup.getRaceId())) {
|
||||
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
||||
if (boatGroup.isStopped()) {
|
||||
updateBoatGroup(boatGroup);
|
||||
}
|
||||
@@ -266,7 +262,7 @@ public class CanvasController {
|
||||
}
|
||||
for (MarkGroup markGroup : markGroups) {
|
||||
for (Long id : markGroup.getRaceIds()) {
|
||||
if (StreamParser.markPositions.containsKey(id)) {
|
||||
if (StreamParser.markLocations.containsKey(id)) {
|
||||
updateMarkGroup(id, markGroup);
|
||||
}
|
||||
}
|
||||
@@ -283,8 +279,7 @@ public class CanvasController {
|
||||
}
|
||||
|
||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions
|
||||
.get(boatGroup.getRaceId());
|
||||
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) {
|
||||
try {
|
||||
@@ -301,10 +296,9 @@ public class CanvasController {
|
||||
}
|
||||
}
|
||||
|
||||
void updateMarkGroup(long raceId, MarkGroup markGroup) {
|
||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markPositions
|
||||
.get(raceId);
|
||||
if (movementQueue.size() > 0) {
|
||||
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());
|
||||
@@ -342,7 +336,7 @@ public class CanvasController {
|
||||
}
|
||||
|
||||
private void initializeMarks() {
|
||||
ArrayList<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getCompoundMarks();
|
||||
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
|
||||
for (Mark mark : allMarks) {
|
||||
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||
SingleMark sMark = (SingleMark) mark;
|
||||
@@ -433,20 +427,16 @@ public class CanvasController {
|
||||
}
|
||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
||||
Limit minLatMark = sortedPoints.get(0);
|
||||
Limit maxLatMark = sortedPoints.get(sortedPoints.size() - 1);
|
||||
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(),
|
||||
minLatMark.getLng(), minLatMark.getSeqID());
|
||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(),
|
||||
maxLatMark.getLng(), maxLatMark.getSeqID());
|
||||
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
||||
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
|
||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
|
||||
|
||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
||||
//If the course is on a point on the earth where longitudes wrap around.
|
||||
Limit minLonMark = sortedPoints.get(0);
|
||||
Limit maxLonMark = sortedPoints.get(sortedPoints.size() - 1);
|
||||
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(),
|
||||
minLonMark.getLng(), minLonMark.getSeqID());
|
||||
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(),
|
||||
maxLonMark.getLng(), maxLonMark.getSeqID());
|
||||
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
||||
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
|
||||
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
|
||||
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
|
||||
horizontalInversion = true;
|
||||
}
|
||||
@@ -522,7 +512,7 @@ public class CanvasController {
|
||||
return findScaledXY(unscaled.getLatitude(), unscaled.getLongitude());
|
||||
}
|
||||
|
||||
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
|
||||
public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
|
||||
double distanceFromReference;
|
||||
double angleFromReference;
|
||||
int xAxisLocation = (int) referencePointX;
|
||||
@@ -571,8 +561,8 @@ public class CanvasController {
|
||||
Point2D p1, p2;
|
||||
Mark m1, m2;
|
||||
double theta, distance, dx, dy, dHorizontal, dVertical;
|
||||
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1);
|
||||
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2);
|
||||
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
|
||||
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
|
||||
p1 = findScaledXY(m1);
|
||||
p2 = findScaledXY(m2);
|
||||
theta = Mark.calculateHeadingRad(m1, m2);
|
||||
@@ -588,4 +578,8 @@ public class CanvasController {
|
||||
List<BoatGroup> getBoatGroups() {
|
||||
return boatGroups;
|
||||
}
|
||||
|
||||
List<MarkGroup> getMarkGroups() {
|
||||
return markGroups;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,6 @@ public class Controller implements Initializable {
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
setContentPane("/views/StartScreenView.fxml");
|
||||
StreamParser.boatPositions.clear();
|
||||
StreamParser.boatLocations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.LineChart;
|
||||
@@ -21,17 +22,24 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
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.mark.GateMark;
|
||||
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.util.*;
|
||||
@@ -39,7 +47,6 @@ import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* Created by ptg19 on 29/03/17.
|
||||
*/
|
||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||
@@ -67,7 +74,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
@FXML
|
||||
private CanvasController includedCanvasController;
|
||||
|
||||
private ArrayList<Yacht> startingBoats = new ArrayList<>();
|
||||
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
|
||||
private boolean displayFps;
|
||||
private Timeline timerTimeline;
|
||||
private Stage stage;
|
||||
@@ -83,9 +90,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
//Formatting the y axis of the sparkline
|
||||
raceSparkLine.getYAxis().setRotate(180);
|
||||
raceSparkLine.getYAxis().setTickLabelRotation(180);
|
||||
raceSparkLine.getYAxis().setTranslateX(15);
|
||||
raceSparkLine.getYAxis().setTranslateX(-5);
|
||||
raceSparkLine.getYAxis().setAutoRanging(false);
|
||||
|
||||
sparklineYAxis.setTickMarkVisible(false);
|
||||
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
|
||||
|
||||
includedCanvasController.setup(this);
|
||||
@@ -147,7 +154,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
(observable, oldValue, newValue) -> displayFps = !displayFps);
|
||||
}
|
||||
|
||||
|
||||
private void initialiseAnnotationSlider() {
|
||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||
@Override
|
||||
@@ -221,7 +227,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 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 -> {
|
||||
@@ -254,6 +259,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
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 ),
|
||||
@@ -285,6 +293,40 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 Mark getNextMark(BoatGroup bg) {
|
||||
Integer legNumber = bg.getBoat().getLegNumber();
|
||||
|
||||
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence();
|
||||
|
||||
if (legNumber == 0) {
|
||||
return null;
|
||||
} else if (legNumber == markSequence.size() - 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
|
||||
if (legNumber + 2 == corner.getSeqID()) {
|
||||
Integer thisCompoundMarkID = corner.getCompoundMarkID();
|
||||
|
||||
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
|
||||
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
||||
return mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the wind direction arrow and text as from info from the StreamParser
|
||||
*/
|
||||
@@ -367,6 +409,94 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -443,7 +573,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
|
||||
/**
|
||||
* Display the important annotations for a specific BoatGroup
|
||||
*
|
||||
* @param bg The boat group to set the annotations for
|
||||
*/
|
||||
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
|
||||
@@ -528,6 +657,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
//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 {
|
||||
|
||||
@@ -141,7 +141,7 @@ public class StartScreenController implements Initializable {
|
||||
}
|
||||
|
||||
public void switchToRaceView() {
|
||||
StreamParser.boatPositions.clear();
|
||||
StreamParser.boatLocations.clear();
|
||||
switchedToRaceView = true;
|
||||
setContentPane("/views/RaceView.fxml");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package seng302.models;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
@@ -8,6 +10,11 @@ import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.transform.Rotate;
|
||||
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.text.DateFormat;
|
||||
@@ -50,6 +57,8 @@ public class BoatGroup extends Group {
|
||||
private Text estTimeToNextMarkObject;
|
||||
private Text legTimeObject;
|
||||
private Wake wake;
|
||||
private Line leftLayLine;
|
||||
private Line rightLayline;
|
||||
private Double distanceTravelled = 0.0;
|
||||
private Point2D lastPoint;
|
||||
private boolean destinationSet;
|
||||
@@ -149,10 +158,13 @@ public class BoatGroup extends Group {
|
||||
|
||||
}
|
||||
|
||||
leftLayLine = new Line();
|
||||
rightLayline = new Line();
|
||||
|
||||
wake = new Wake(0, -BOAT_HEIGHT);
|
||||
super.getChildren()
|
||||
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
|
||||
legTimeObject);
|
||||
legTimeObject, leftLayLine, rightLayline);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,6 +371,44 @@ public class BoatGroup extends Group {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -367,6 +417,7 @@ public class BoatGroup extends Group {
|
||||
setWakeVisible(isSelected);
|
||||
setEstTimeToNextMarkObjectVisible(isSelected);
|
||||
setLegTimeObjectVisible(isSelected);
|
||||
setLayLinesVisible(isSelected);
|
||||
}
|
||||
|
||||
|
||||
@@ -394,6 +445,23 @@ public class BoatGroup extends Group {
|
||||
wake.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLayLinesVisible(Boolean visible) {
|
||||
leftLayLine.setVisible(visible);
|
||||
rightLayline.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLaylines(Line line1, Line line2) {
|
||||
this.leftLayLine = line1;
|
||||
this.rightLayline = line2;
|
||||
}
|
||||
|
||||
public ArrayList<Line> getLaylines() {
|
||||
ArrayList<Line> laylines = new ArrayList<>();
|
||||
laylines.add(leftLayLine);
|
||||
laylines.add(rightLayline);
|
||||
return laylines;
|
||||
}
|
||||
|
||||
public Yacht getBoat() {
|
||||
return boat;
|
||||
}
|
||||
@@ -420,6 +488,16 @@ public class BoatGroup extends Group {
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
public Double getBoatLayoutX() {
|
||||
return boatPoly.getLayoutX();
|
||||
}
|
||||
|
||||
|
||||
public Double getBoatLayoutY() {
|
||||
return boatPoly.getLayoutY();
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return isStopped;
|
||||
}
|
||||
|
||||
@@ -1,171 +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");
|
||||
}
|
||||
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the distance between the two marks
|
||||
*/
|
||||
public double getDistanceBetweenMarks() {
|
||||
double earth_radius = 6378.137;
|
||||
double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180;
|
||||
double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180;
|
||||
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
double d = earth_radius * c;
|
||||
|
||||
return d * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates current boat heading direction.
|
||||
* @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise.
|
||||
*/
|
||||
public double getBoatHeading() {
|
||||
if (mark2 == null){
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE;
|
||||
double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE;
|
||||
double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE;
|
||||
double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE;
|
||||
|
||||
double headingRadians = Math.atan2(y2-y1, x2-x1);
|
||||
|
||||
if (headingRadians < 0){
|
||||
headingRadians += 2 * Math.PI;
|
||||
}
|
||||
|
||||
// Convert back to degrees, and flip 180 degrees
|
||||
// return ((headingRadians) * 180) / Math.PI;
|
||||
return (Math.toDegrees(headingRadians) + 90) % 360;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the angle between to angular co-ordinates on a sphere.
|
||||
*
|
||||
* @param geoPointOne first geographical location
|
||||
* @param geoPointTwo second geographical location
|
||||
* @return the angle from point one to point two
|
||||
*/
|
||||
private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) {
|
||||
if (geoPointTwo == null)
|
||||
return null;
|
||||
|
||||
double x1 = geoPointOne.getLatitude();
|
||||
double y1 = -geoPointOne.getLongitude();
|
||||
double x2 = geoPointTwo.getLatitude();
|
||||
double y2 = -geoPointTwo.getLongitude();
|
||||
|
||||
return Math.toDegrees(Math.atan2(x2-x1, y2-y1));
|
||||
|
||||
}
|
||||
|
||||
public double getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public Mark getThisMark() {
|
||||
return this.mark1;
|
||||
}
|
||||
|
||||
public int getMarkPosInRace() {
|
||||
return markPosInRace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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,11 +1,13 @@
|
||||
package seng302.models;
|
||||
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.controllers.RaceViewController;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import seng302.models.stream.StreamParser;
|
||||
import seng302.models.stream.XMLParser.RaceXMLObject.Corner;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat.
|
||||
@@ -35,6 +37,8 @@ public class Yacht {
|
||||
private String position;
|
||||
// Mark rounding
|
||||
private Long markRoundingTime;
|
||||
private Mark lastMarkRounded;
|
||||
private Mark nextMark;
|
||||
|
||||
|
||||
/**
|
||||
@@ -181,8 +185,24 @@ public class Yacht {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ public class GateMark extends Mark {
|
||||
* @param singleMark1 one single mark inside of the gate mark
|
||||
* @param singleMark2 the second mark inside of the gate mark
|
||||
*/
|
||||
public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) {
|
||||
super(name, type, latitude, longitude);
|
||||
public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude, int compoundMarkID) {
|
||||
super(name, type, latitude, longitude, compoundMarkID);
|
||||
this.singleMark1 = singleMark1;
|
||||
this.singleMark2 = singleMark2;
|
||||
}
|
||||
|
||||
@@ -11,25 +11,27 @@ public abstract class Mark {
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private long id;
|
||||
private int compoundMarkID;
|
||||
|
||||
/**
|
||||
* Create a mark instance by passing its name and type
|
||||
*
|
||||
* @param name the name of the mark
|
||||
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
|
||||
*/
|
||||
public Mark(String name, MarkType markType, int id) {
|
||||
public Mark (String name, MarkType markType, int sourceID, int compoundMarkID) {
|
||||
this.name = name;
|
||||
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.markType = markType;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
id = 0;
|
||||
this.id = 0;
|
||||
this.compoundMarkID = compoundMarkID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,4 +141,8 @@ public abstract class Mark {
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getCompoundMarkID() {
|
||||
return compoundMarkID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Line;
|
||||
import seng302.GeometryUtils;
|
||||
|
||||
/**
|
||||
* Grouping of javaFX objects needed to represent a Mark on screen.
|
||||
@@ -45,6 +47,29 @@ public class MarkGroup extends Group {
|
||||
super.getChildren().add(markCircle);
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -84,6 +109,11 @@ public class MarkGroup extends Group {
|
||||
}
|
||||
super.getChildren().add(line);
|
||||
|
||||
//Laylines
|
||||
// if (mark.)
|
||||
|
||||
// addLayLine(points1, 12.0, 90.0);
|
||||
// addLayLine(points2, 12.0, 90.0);
|
||||
}
|
||||
|
||||
public void moveMarkTo (double x, double y, long raceId)
|
||||
@@ -126,4 +156,8 @@ public class MarkGroup extends Group {
|
||||
idArray[i++] = mark.getId();
|
||||
return idArray;
|
||||
}
|
||||
|
||||
public Mark getMainMark() {
|
||||
return mainMark;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ public class SingleMark extends Mark {
|
||||
private double lat;
|
||||
private double lon;
|
||||
private String name;
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* Represents a marker
|
||||
@@ -18,24 +17,12 @@ public class SingleMark extends Mark {
|
||||
* @param lat, the latitude of the marker
|
||||
* @param lon, the longitude of the marker
|
||||
*/
|
||||
public SingleMark(String name, double lat, double lon, int id) {
|
||||
super(name, MarkType.SINGLE_MARK, id);
|
||||
public SingleMark(String name, double lat, double lon, int sourceID, int compoundMarkID) {
|
||||
super(name, MarkType.SINGLE_MARK, sourceID, compoundMarkID);
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the marker at the beginning of a leg
|
||||
*
|
||||
* @param name, the name of the marker
|
||||
*/
|
||||
public SingleMark(String name) {
|
||||
super(name, MarkType.SINGLE_MARK, 0);
|
||||
this.lat = 0;
|
||||
this.lon = 0;
|
||||
this.id = 0;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return this.lat;
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeMap;
|
||||
@@ -23,6 +22,7 @@ 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;
|
||||
|
||||
@@ -34,8 +34,8 @@ import seng302.models.stream.packets.StreamPacket;
|
||||
*/
|
||||
public class StreamParser extends Thread {
|
||||
|
||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markPositions = new ConcurrentHashMap<>();
|
||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatPositions = new ConcurrentHashMap<>();
|
||||
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;
|
||||
@@ -45,12 +45,17 @@ public class StreamParser extends Thread {
|
||||
private static boolean streamStatus = false;
|
||||
private static long timeSinceStart = -1;
|
||||
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
||||
private static Map<Long, Yacht> boatsPos = new ConcurrentSkipListMap<>();
|
||||
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
|
||||
private static double windDirection = 0;
|
||||
private static Long currentTimeLong;
|
||||
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
|
||||
*
|
||||
@@ -145,6 +150,7 @@ public class StreamParser extends Thread {
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
System.out.println("Error parsing packet");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +193,9 @@ public class StreamParser extends Thread {
|
||||
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 windSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
|
||||
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");
|
||||
@@ -215,16 +221,16 @@ public class StreamParser extends Thread {
|
||||
|
||||
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];
|
||||
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) boatStatusSourceID);
|
||||
boat.setBoatStatus((int) payload[28 + (i * 20)]);
|
||||
boat.setLegNumber((int) payload[29 + (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(
|
||||
@@ -233,7 +239,7 @@ public class StreamParser extends Thread {
|
||||
Long estTimeAtFinish = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
|
||||
boat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||
boatsPos.put(estTimeAtFinish, boat);
|
||||
// boatsPos.put(estTimeAtFinish, boat);
|
||||
// String boatStatus = "SourceID: " + boatStatusSourceID;
|
||||
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
|
||||
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
|
||||
@@ -243,12 +249,34 @@ public class StreamParser extends Thread {
|
||||
// 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++;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,9 +420,9 @@ public class StreamParser extends Thread {
|
||||
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,
|
||||
//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) {
|
||||
@@ -402,14 +430,14 @@ public class StreamParser extends Thread {
|
||||
}
|
||||
}));
|
||||
}
|
||||
boatPositions.get(boatId).put(boatPacket);
|
||||
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 boatPositions HashMap
|
||||
if (!markPositions.containsKey(boatId)) {
|
||||
markPositions.put(boatId,
|
||||
//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) {
|
||||
@@ -417,7 +445,7 @@ public class StreamParser extends Thread {
|
||||
}
|
||||
}));
|
||||
}
|
||||
markPositions.get(boatId).put(markPacket);
|
||||
markLocations.get(boatId).put(markPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +466,13 @@ public class StreamParser extends Thread {
|
||||
int markId = payload[20];
|
||||
|
||||
// assign mark rounding time to boat
|
||||
boats.get((int) subjectId).setMarkRoundingTime(timeStamp);
|
||||
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
|
||||
|
||||
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
|
||||
if (mark.getCompoundMarkID() == markId) {
|
||||
boats.get((int)subjectId).setLastMarkRounded(mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -575,6 +609,15 @@ public class StreamParser extends Thread {
|
||||
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
|
||||
*
|
||||
@@ -589,7 +632,8 @@ public class StreamParser extends Thread {
|
||||
*
|
||||
* @return a map of time to finish and boat.
|
||||
*/
|
||||
public static Map<Long, Yacht> getBoatsPos() {
|
||||
public static Map<Integer, Yacht> getBoatsPos() {
|
||||
|
||||
return boatsPos;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,8 @@ public class XMLParser {
|
||||
|
||||
//Non atomic race attributes
|
||||
private ArrayList<Participant> participants;
|
||||
private ArrayList<Mark> course;
|
||||
private ArrayList<Mark> allMarks;
|
||||
private ArrayList<Mark> nonDuplicateMarks;
|
||||
private ArrayList<Corner> compoundMarkSequence;
|
||||
private ArrayList<Limit> courseLimit;
|
||||
|
||||
@@ -283,7 +284,9 @@ public class XMLParser {
|
||||
}
|
||||
|
||||
//Course
|
||||
course = createCompoundMarks(docEle);
|
||||
allMarks = new ArrayList<>();
|
||||
nonDuplicateMarks = new ArrayList<>();
|
||||
createCompoundMarks(docEle);
|
||||
|
||||
//Course Mark Sequence
|
||||
compoundMarkSequence = new ArrayList<>();
|
||||
@@ -312,27 +315,23 @@ public class XMLParser {
|
||||
}
|
||||
|
||||
|
||||
private ArrayList<Mark> createCompoundMarks(Element docEle) {
|
||||
ArrayList<Mark> cMarks = new ArrayList<>();
|
||||
private void createCompoundMarks(Element docEle) {
|
||||
|
||||
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
||||
for (int i = 0; i < cMarkList.getLength(); i++) {
|
||||
Node cMarkNode = cMarkList.item(i);
|
||||
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
||||
Mark mark = createMark(cMarkNode);
|
||||
if (mark != null) {
|
||||
cMarks.add(mark);
|
||||
}
|
||||
createAndAddMark(cMarkNode);
|
||||
}
|
||||
}
|
||||
|
||||
return cMarks;
|
||||
}
|
||||
|
||||
|
||||
private Mark createMark(Node compoundMark) {
|
||||
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();
|
||||
@@ -346,27 +345,33 @@ public class XMLParser {
|
||||
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
|
||||
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
|
||||
|
||||
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID);
|
||||
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
|
||||
marksList.add(mark);
|
||||
}
|
||||
}
|
||||
|
||||
for (SingleMark mark : marksList) {
|
||||
if (seenSourceIDs.contains(mark.getId())) {
|
||||
return null;
|
||||
markSeen = true;
|
||||
} else {
|
||||
seenSourceIDs.add(mark.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (marksList.size() == 1) {
|
||||
return marksList.get(0);
|
||||
if (!markSeen) {
|
||||
nonDuplicateMarks.add(marksList.get(0));
|
||||
}
|
||||
allMarks.add(marksList.get(0));
|
||||
} else if (marksList.size() == 2) {
|
||||
return new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
|
||||
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
|
||||
marksList.get(1), marksList.get(0).getLatitude(),
|
||||
marksList.get(0).getLongitude());
|
||||
} else {
|
||||
return null;
|
||||
marksList.get(0).getLongitude(), compoundMarkID);
|
||||
if(!markSeen) {
|
||||
nonDuplicateMarks.add(thisGateMark);
|
||||
}
|
||||
allMarks.add(thisGateMark);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -395,8 +400,18 @@ public class XMLParser {
|
||||
return participants;
|
||||
}
|
||||
|
||||
public ArrayList<Mark> getCompoundMarks() {
|
||||
return course;
|
||||
/**
|
||||
* @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() {
|
||||
|
||||
@@ -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
|
||||
|
@@ -184,5 +184,17 @@ Remove scroll bars
|
||||
}
|
||||
|
||||
.chart{
|
||||
-fx-background-color: #ffffff;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 1.6em;
|
||||
}
|
||||
|
||||
.axis-label {
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.axis {
|
||||
-fx-tick-label-fill: #ffffff;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" />
|
||||
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" textFill="WHITE" />
|
||||
<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="#a8a8a8" 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 name="AdobeArabic-Regular" size="55.0" />
|
||||
</font>
|
||||
@@ -51,7 +51,7 @@
|
||||
<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="-15.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="260.0" title="Boat Positions">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user