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:
Zhi You Tan
2017-05-25 16:12:44 +12:00
22 changed files with 742 additions and 329 deletions
+6 -3
View File
@@ -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();
+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);
}
}
@@ -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");
}
+79 -1
View File
@@ -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;
}
-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;
}
}
+21 -1
View File
@@ -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 -5
View File
@@ -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() {