Merge remote-tracking branch 'origin/develop' into remove_observers

# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
#	src/main/java/seng302/fxObjects/MarkGroup.java
#	src/main/java/seng302/models/Yacht.java
This commit is contained in:
Calum
2017-05-25 14:52:54 +12:00
21 changed files with 710 additions and 302 deletions
+6
View File
@@ -5,6 +5,7 @@ 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.PolarTable;
import seng302.models.stream.StreamParser; import seng302.models.stream.StreamParser;
import seng302.models.stream.StreamReceiver; import seng302.models.stream.StreamReceiver;
import seng302.server.ServerThread; import seng302.server.ServerThread;
@@ -13,6 +14,8 @@ 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, 1530, 960)); primaryStage.setScene(new Scene(root, 1530, 960));
@@ -27,6 +30,8 @@ public class App extends Application {
System.exit(0); System.exit(0);
}); });
} }
public static void main(String[] args) { public static void main(String[] args) {
@@ -65,6 +70,7 @@ public class App extends Application {
else{ else{
// sr = new StreamReceiver("localhost", 4949, "RaceStream"); // sr = new StreamReceiver("localhost", 4949, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); // sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
} }
+63
View File
@@ -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);
}
}
@@ -212,7 +212,7 @@ public class CanvasController {
for (BoatGroup boatGroup : boatGroups) { for (BoatGroup boatGroup : boatGroups) {
// some raceObjects will have multiple ID's (for instance gate marks) // some raceObjects will have multiple ID's (for instance gate marks)
//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(boatGroup.getRaceId())) { if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
if (boatGroup.isStopped()) { if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup); updateBoatGroup(boatGroup);
} }
@@ -221,7 +221,7 @@ public class CanvasController {
} }
for (MarkGroup markGroup : markGroups) { for (MarkGroup markGroup : markGroups) {
for (Long id : markGroup.getRaceIds()) { for (Long id : markGroup.getRaceIds()) {
if (StreamParser.markPositions.containsKey(id)) { if (StreamParser.markLocations.containsKey(id)) {
updateMarkGroup(id, markGroup); updateMarkGroup(id, markGroup);
} }
} }
@@ -236,7 +236,7 @@ public class CanvasController {
} }
private void updateBoatGroup(BoatGroup boatGroup) { 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 // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0){ if (movementQueue.size() > 0){
try { try {
@@ -252,7 +252,7 @@ public class CanvasController {
} }
void updateMarkGroup (long raceId, MarkGroup markGroup) { void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markPositions.get(raceId); PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId);
if (movementQueue.size() > 0){ if (movementQueue.size() > 0){
try { try {
BoatPositionPacket positionPacket = movementQueue.take(); BoatPositionPacket positionPacket = movementQueue.take();
@@ -290,7 +290,7 @@ public class CanvasController {
} }
private void initializeMarks() { private void initializeMarks() {
ArrayList<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getCompoundMarks(); List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
for (Mark mark : allMarks) { for (Mark mark : allMarks) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) { if (mark.getMarkType() == MarkType.SINGLE_MARK) {
SingleMark sMark = (SingleMark) mark; SingleMark sMark = (SingleMark) mark;
@@ -374,15 +374,15 @@ 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);
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID()); minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID()); maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) { if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
horizontalInversion = true; horizontalInversion = true;
} }
@@ -454,7 +454,7 @@ public class CanvasController {
return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude()); return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude());
} }
private Point2D findScaledXY (double unscaledLat, double unscaledLon) { public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
double distanceFromReference; double distanceFromReference;
double angleFromReference; double angleFromReference;
int xAxisLocation = (int) referencePointX; int xAxisLocation = (int) referencePointX;
@@ -491,8 +491,8 @@ public class CanvasController {
Point2D p1, p2; Point2D p1, p2;
Mark m1, m2; Mark m1, m2;
double theta, distance, dx, dy, dHorizontal, dVertical; double theta, distance, dx, dy, dHorizontal, dVertical;
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1); m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2); m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
p1 = findScaledXY(m1); p1 = findScaledXY(m1);
p2 = findScaledXY(m2); p2 = findScaledXY(m2);
theta = Mark.calculateHeadingRad(m1, m2); theta = Mark.calculateHeadingRad(m1, m2);
@@ -508,4 +508,8 @@ public class CanvasController {
List<BoatGroup> getBoatGroups() { List<BoatGroup> getBoatGroups() {
return boatGroups; return boatGroups;
} }
List<MarkGroup> getMarkGroups() {
return markGroups;
}
} }
@@ -34,6 +34,6 @@ public class Controller implements Initializable {
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml"); setContentPane("/views/StartScreenView.fxml");
StreamParser.boatPositions.clear(); StreamParser.boatLocations.clear();
} }
} }
@@ -6,6 +6,8 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
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.Scene;
import javafx.scene.chart.LineChart; import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis; import javafx.scene.chart.NumberAxis;
@@ -20,18 +22,25 @@ 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.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.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.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate; import seng302.controllers.annotations.ImportantAnnotationDelegate;
import seng302.controllers.annotations.ImportantAnnotationsState; import seng302.controllers.annotations.ImportantAnnotationsState;
import seng302.fxObjects.BoatGroup; import seng302.fxObjects.BoatGroup;
import seng302.models.*; 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.StreamParser;
import seng302.models.stream.XMLParser;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -39,7 +48,6 @@ import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
*
* Created by ptg19 on 29/03/17. * Created by ptg19 on 29/03/17.
*/ */
public class RaceViewController extends Thread implements ImportantAnnotationDelegate { public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@@ -67,7 +75,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML @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 Stage stage; private Stage stage;
@@ -147,7 +155,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
(observable, oldValue, newValue) -> displayFps = !displayFps); (observable, oldValue, newValue) -> displayFps = !displayFps);
} }
private void initialiseAnnotationSlider() { private void initialiseAnnotationSlider() {
annotationSlider.setLabelFormatter(new StringConverter<Double>() { annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override @Override
@@ -221,7 +228,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
}); });
// Adds the new data series to the sparkline (and set the colour of the series) // Adds the new data series to the sparkline (and set the colour of the series)
raceSparkLine.setCreateSymbols(false); raceSparkLine.setCreateSymbols(false);
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> { positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
@@ -254,6 +260,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
color = yacht.getColour(); color = yacht.getColour();
} }
} }
if (color == null){
return String.format( "#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X", return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ), (int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ), (int)( color.getGreen() * 255 ),
@@ -285,6 +294,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 * Updates the wind direction arrow and text as from info from the StreamParser
*/ */
@@ -367,6 +410,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 * Initialised the combo box with any boats currently in the race and adds the required listener
* for the combobox to take action upon selection * for the combobox to take action upon selection
@@ -482,6 +613,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 //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. //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) { if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
updateLaylines(bg);
bg.setIsSelected(true); bg.setIsSelected(true);
selectedBoat = yacht; selectedBoat = yacht;
} else { } else {
@@ -138,7 +138,7 @@ public class StartScreenController implements Initializable {
} }
public void switchToRaceView() { public void switchToRaceView() {
StreamParser.boatPositions.clear(); StreamParser.boatLocations.clear();
switchedToRaceView = true; switchedToRaceView = true;
setContentPane("/views/RaceView.fxml"); setContentPane("/views/RaceView.fxml");
} }
@@ -1,5 +1,7 @@
package seng302.fxObjects; package seng302.fxObjects;
import java.util.ArrayList;
import javafx.event.EventHandler;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.CacheHint; import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
@@ -9,6 +11,11 @@ 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 seng302.models.Yacht; import seng302.models.Yacht;
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 seng302.models.stream.StreamParser;
import java.text.DateFormat; import java.text.DateFormat;
@@ -39,6 +46,8 @@ public class BoatGroup extends Group {
private Group lineGroup = new Group(); private Group lineGroup = new Group();
private Polygon boatPoly; private Polygon boatPoly;
private Wake wake; private Wake wake;
private Line leftLayLine;
private Line rightLayline;
private Double distanceTravelled = 0.0; private Double distanceTravelled = 0.0;
private Point2D lastPoint; private Point2D lastPoint;
private boolean destinationSet; private boolean destinationSet;
@@ -239,11 +248,50 @@ 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) { public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected; this.isSelected = isSelected;
setLineGroupVisible(isSelected); setLineGroupVisible(isSelected);
setWakeVisible(isSelected); setWakeVisible(isSelected);
boatAnnotations.setVisible(isSelected); boatAnnotations.setVisible(isSelected);
setLayLinesVisible(isSelected);
} }
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) { public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) {
@@ -260,6 +308,23 @@ public class BoatGroup extends Group {
wake.setVisible(visible); 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() { public Yacht getBoat() {
return boat; return boat;
} }
@@ -286,6 +351,16 @@ public class BoatGroup extends Group {
return group; return group;
} }
public Double getBoatLayoutX() {
return boatPoly.getLayoutX();
}
public Double getBoatLayoutY() {
return boatPoly.getLayoutY();
}
public boolean isStopped() { public boolean isStopped() {
return isStopped; return isStopped;
} }
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Group; import javafx.scene.Group;
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;
@@ -11,6 +12,7 @@ import seng302.models.mark.GateMark;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.models.mark.MarkType; import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark; import seng302.models.mark.SingleMark;
import seng302.GeometryUtils;
/** /**
* Grouping of javaFX objects needed to represent a Mark on screen. * Grouping of javaFX objects needed to represent a Mark on screen.
@@ -49,6 +51,29 @@ public class MarkGroup extends Group {
super.getChildren().add(markCircle); 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) { public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
marks.add(mark.getSingleMark1()); marks.add(mark.getSingleMark1());
marks.add(mark.getSingleMark2()); marks.add(mark.getSingleMark2());
@@ -136,4 +161,8 @@ public class MarkGroup extends Group {
idArray[i++] = mark.getId(); idArray[i++] = mark.getId();
return idArray; return idArray;
} }
public Mark getMainMark() {
return mainMark;
}
} }
-171
View File
@@ -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;
}
}
+23
View File
@@ -1,10 +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 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.
@@ -34,6 +37,10 @@ public class Yacht {
private Long timeTillNext; private Long timeTillNext;
private Long markRoundTime; private Long markRoundTime;
// Mark rounding
private Long markRoundingTime;
private Mark lastMarkRounded;
private Mark nextMark;
/** /**
@@ -179,9 +186,25 @@ public class Yacht {
return markRoundTime; return markRoundTime;
} }
public Mark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(Mark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
@Override @Override
public String toString() { public String toString() {
return boatName; 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 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;
} }
+11 -5
View File
@@ -11,25 +11,27 @@ public abstract class Mark {
private double latitude; private double latitude;
private double longitude; private double longitude;
private long 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;
} }
/** /**
@@ -139,4 +141,8 @@ public abstract class Mark {
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
public int getCompoundMarkID() {
return compoundMarkID;
}
} }
@@ -9,7 +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
@@ -18,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;
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeMap; import java.util.TreeMap;
@@ -23,6 +22,7 @@ import org.w3c.dom.Document;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import seng302.models.Yacht; import seng302.models.Yacht;
import seng302.models.mark.Mark;
import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.stream.packets.StreamPacket; import seng302.models.stream.packets.StreamPacket;
@@ -34,8 +34,8 @@ import seng302.models.stream.packets.StreamPacket;
*/ */
public class StreamParser extends Thread { public class StreamParser extends Thread {
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markPositions = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatPositions = new ConcurrentHashMap<>(); public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
private String threadName; private String threadName;
private Thread t; private Thread t;
private static boolean newRaceXmlReceived = false; private static boolean newRaceXmlReceived = false;
@@ -45,12 +45,17 @@ public class StreamParser extends Thread {
private static boolean streamStatus = false; private static boolean streamStatus = false;
private static long timeSinceStart = -1; private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>(); 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 double windDirection = 0;
private static Long currentTimeLong; private static Double windSpeed = 0d;
private static Long currentTimeLong;
private static String currentTimeString; private static String currentTimeString;
private static boolean appRunning; 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 * 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) { } catch (NullPointerException e) {
System.out.println("Error parsing packet"); 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 currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
int raceStatus = payload[11]; int raceStatus = payload[11];
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload, 12, 18)); long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20)); long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
long windSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22)); long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
currentTimeLong = currentTime; currentTimeLong = currentTime;
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); 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... double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor; windDirection = windDir / windDirFactor;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
int noBoats = payload[22]; int noBoats = payload[22];
int raceType = payload[23]; int raceType = payload[23];
boatsPos = new TreeMap<>();
for (int i = 0; i < noBoats; i++) { for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong( long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20))); Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID); Yacht boat = boats.get((int) boatStatusSourceID);
boat.setBoatStatus((int) payload[28 + (i * 20)]); 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.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
boat.setPenaltiesServed((int) payload[31 + (i * 20)]); boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong( Long estTimeAtNextMark = bytesToLong(
@@ -233,6 +239,7 @@ public class StreamParser extends Thread {
Long estTimeAtFinish = bytesToLong( Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20))); Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
boat.setEstimateTimeAtFinish(estTimeAtFinish); boat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID; // String boatStatus = "SourceID: " + boatStatusSourceID;
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)]; // boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)]; // boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
@@ -242,16 +249,34 @@ public class StreamParser extends Thread {
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20))); // boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
// boatStatuses.add(boatStatus); // boatStatuses.add(boatStatus);
} }
if (isRaceStarted()) { // if (isRaceStarted()) {
int pos = 1; // int pos = 1;
for (Yacht yacht : boatsPos.values()) { // for (Yacht yacht : boatsPos.values()) {
yacht.setPosition(String.valueOf(pos)); // yacht.setPosition(String.valueOf(pos));
pos++; // pos++;
} // }
} else { // } else {
for (Yacht yacht : boatsPos.values()) { // for (Yacht yacht : boatsPos.values()) {
yacht.setPosition("-"); // 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);
} }
} }
@@ -396,9 +421,9 @@ public class StreamParser extends Thread {
boat.setVelocity(groundSpeed); boat.setVelocity(groundSpeed);
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed); BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
//add a new priority que to the boatPositions HashMap //add a new priority que to the boatLocations HashMap
if (!boatPositions.containsKey(boatId)) { if (!boatLocations.containsKey(boatId)) {
boatPositions.put(boatId, boatLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() { new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override @Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) { public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
@@ -406,14 +431,14 @@ public class StreamParser extends Thread {
} }
})); }));
} }
boatPositions.get(boatId).put(boatPacket); boatLocations.get(boatId).put(boatPacket);
} else if (deviceType == 3) { } else if (deviceType == 3) {
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed); heading, groundSpeed);
//add a new priority que to the boatPositions HashMap //add a new priority que to the boatLocations HashMap
if (!markPositions.containsKey(boatId)) { if (!markLocations.containsKey(boatId)) {
markPositions.put(boatId, markLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() { new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override @Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) { public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
@@ -421,7 +446,7 @@ public class StreamParser extends Thread {
} }
})); }));
} }
markPositions.get(boatId).put(markPacket); markLocations.get(boatId).put(markPacket);
} }
} }
@@ -442,7 +467,13 @@ public class StreamParser extends Thread {
int markId = payload[20]; int markId = payload[20];
// assign mark rounding time to boat // 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);
}
}
} }
/** /**
@@ -579,6 +610,15 @@ public class StreamParser extends Thread {
return windDirection; 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 * returns stream time in formatted string format
* *
@@ -593,7 +633,8 @@ public class StreamParser extends Thread {
* *
* @return a map of time to finish and boat. * @return a map of time to finish and boat.
*/ */
public static Map<Long, Yacht> getBoatsPos() { public static Map<Integer, Yacht> getBoatsPos() {
return boatsPos; return boatsPos;
} }
@@ -235,7 +235,8 @@ public class XMLParser {
//Non atomic race attributes //Non atomic race attributes
private ArrayList<Participant> participants; private ArrayList<Participant> participants;
private ArrayList<Mark> 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;
@@ -283,7 +284,9 @@ public class XMLParser {
} }
//Course //Course
course = createCompoundMarks(docEle); allMarks = new ArrayList<>();
nonDuplicateMarks = new ArrayList<>();
createCompoundMarks(docEle);
//Course Mark Sequence //Course Mark Sequence
compoundMarkSequence = new ArrayList<>(); compoundMarkSequence = new ArrayList<>();
@@ -312,27 +315,23 @@ public class XMLParser {
} }
private ArrayList<Mark> createCompoundMarks(Element docEle) { private void createCompoundMarks(Element docEle) {
ArrayList<Mark> cMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes(); NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
for (int i = 0; i < cMarkList.getLength(); i++) { for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i); Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) { if (cMarkNode.getNodeName().equals("CompoundMark")) {
Mark mark = createMark(cMarkNode); createAndAddMark(cMarkNode);
if (mark != null) {
cMarks.add(mark);
}
} }
} }
return cMarks;
} }
private Mark createMark(Node compoundMark) { private void createAndAddMark(Node compoundMark) {
Boolean markSeen = false;
List<SingleMark> marksList = new ArrayList<>(); List<SingleMark> marksList = new ArrayList<>();
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = getNodeAttributeString(compoundMark, "Name"); String cMarkName = getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes(); NodeList childMarks = compoundMark.getChildNodes();
@@ -346,27 +345,33 @@ public class XMLParser {
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat"); Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng"); 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); marksList.add(mark);
} }
} }
for (SingleMark mark : marksList) { for (SingleMark mark : marksList) {
if (seenSourceIDs.contains(mark.getId())) { if (seenSourceIDs.contains(mark.getId())) {
return null; markSeen = true;
} else { } else {
seenSourceIDs.add(mark.getId()); seenSourceIDs.add(mark.getId());
} }
} }
if (marksList.size() == 1) { 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) { } 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(1), marksList.get(0).getLatitude(),
marksList.get(0).getLongitude()); marksList.get(0).getLongitude(), compoundMarkID);
} else { if(!markSeen) {
return null; nonDuplicateMarks.add(thisGateMark);
}
allMarks.add(thisGateMark);
} }
} }
@@ -395,8 +400,18 @@ public class XMLParser {
return participants; 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() { public ArrayList<Corner> getCompoundMarkSequence() {
+8
View File
@@ -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
1 Tws Twa0 Bsp0 Twa1 Bsp1 UpTwa UpBsp Twa2 Bsp2 Twa3 Bsp3 Twa4 Bsp4 Twa5 Bsp5 Twa6 Bsp6 DnTwa DnBsp Twa7 Bsp7
2 4 0 0 30 4 45 8 60 9 75 10 90 10 115 10 145 10 155 10 175 4
3 8 0 0 30 7 43 10 60 11 75 11 90 11 115 12 145 12 153 12 175 10
4 12 0 0 30 11 43 14.4 60 16 75 20 90 23 115 24 145 23 153 21.6 175 14
5 16 0 0 30 12 42 19.2 60 25 75 27 90 31 115 32 145 30 153 28.8 175 20
6 20 0 0 30 13 41 24 60 29 75 37 90 39 115 40 145 38 153 36 175 24
7 25 0 0 30 15 40 30 60 38 75 44 90 49 115 50 145 49 151 47 175 30
8 30 0 0 30 15 42 30 60 37 75 42 90 48 115 49 145 48 150 46 175 32
+1 -1
View File
@@ -25,7 +25,7 @@
<Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" /> <Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" />
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" 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" /> <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>
<Font name="AdobeArabic-Regular" size="55.0" /> <Font name="AdobeArabic-Regular" size="55.0" />
</font> </font>
-38
View File
@@ -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);
}
}
+65
View File
@@ -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);
}
}
@@ -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