diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index a3d5f49f..33ce16ee 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -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(); diff --git a/src/main/java/seng302/GeometryUtils.java b/src/main/java/seng302/GeometryUtils.java new file mode 100644 index 00000000..ce8b9fe4 --- /dev/null +++ b/src/main/java/seng302/GeometryUtils.java @@ -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); + + } + +} diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index d3b3722f..733fa1f1 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -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 movementQueue = StreamParser.boatPositions - .get(boatGroup.getRaceId()); + PriorityBlockingQueue 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 movementQueue = StreamParser.markPositions - .get(raceId); - if (movementQueue.size() > 0) { + void updateMarkGroup (long raceId, MarkGroup markGroup) { + PriorityBlockingQueue 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 allMarks = StreamParser.getXmlObject().getRaceXML().getCompoundMarks(); + List 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 getBoatGroups() { return boatGroups; } + + List getMarkGroups() { + return markGroups; + } } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index a222cfc3..73b3766b 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -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(); } } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 03b51cfa..3360fa05 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -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 startingBoats = new ArrayList<>(); + private static ArrayList 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() { @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 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 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 { diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java index 3e9d27bd..931874d5 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -141,7 +141,7 @@ public class StartScreenController implements Initializable { } public void switchToRaceView() { - StreamParser.boatPositions.clear(); + StreamParser.boatLocations.clear(); switchedToRaceView = true; setContentPane("/views/RaceView.fxml"); } diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index b50cd155..0bb8a81b 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -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 getLaylines() { + ArrayList 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; } diff --git a/src/main/java/seng302/models/Event.java b/src/main/java/seng302/models/Event.java deleted file mode 100644 index 325afdf0..00000000 --- a/src/main/java/seng302/models/Event.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java new file mode 100644 index 00000000..168d2291 --- /dev/null +++ b/src/main/java/seng302/models/PolarTable.java @@ -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> polarTable; + private static HashMap> upwindOptimal; + private static HashMap> 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 thisPolar = new HashMap<>(); + HashMap thisUpWindPolar = new HashMap<>(); + HashMap 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> getPolarTable() { + return polarTable; + } + + + /** + * @return The polar table just containing the optimal upwind values + */ + public static HashMap> getUpwindOptimal() { + return upwindOptimal; + } + + + /** + * @return The polar table just containing the optimal downwind values + */ + public static HashMap> 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 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 getOptimalDownwindVMG(Double thisWindSpeed) { + + Double polarWindSpeed = getClosestMatch(thisWindSpeed); + return downwindOptimal.get(polarWindSpeed); + } + + + private static Double getClosestMatch(Double thisWindSpeed) { + + ArrayList 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; + } + +} \ No newline at end of file diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index c5bdcbb7..0cfa17f9 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -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; + } } diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java index 79943114..8459b882 100644 --- a/src/main/java/seng302/models/mark/GateMark.java +++ b/src/main/java/seng302/models/mark/GateMark.java @@ -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; } diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index acb61ea1..027bf6d3 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -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; + } } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 661f45de..db55656d 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -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 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; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/mark/SingleMark.java b/src/main/java/seng302/models/mark/SingleMark.java index 9239552a..56ba9dc6 100644 --- a/src/main/java/seng302/models/mark/SingleMark.java +++ b/src/main/java/seng302/models/mark/SingleMark.java @@ -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; diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/models/stream/StreamParser.java index 583443be..6cec3185 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/models/stream/StreamParser.java @@ -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> markPositions = new ConcurrentHashMap<>(); - public static ConcurrentHashMap> boatPositions = new ConcurrentHashMap<>(); + public static ConcurrentHashMap> markLocations = new ConcurrentHashMap<>(); + public static ConcurrentHashMap> 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 boats = new ConcurrentHashMap<>(); - private static Map boatsPos = new ConcurrentSkipListMap<>(); + private static Map 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() { @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() { @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 getBoatsPos() { + public static Map getBoatsPos() { + return boatsPos; } diff --git a/src/main/java/seng302/models/stream/XMLParser.java b/src/main/java/seng302/models/stream/XMLParser.java index 674a2611..99ce72c8 100644 --- a/src/main/java/seng302/models/stream/XMLParser.java +++ b/src/main/java/seng302/models/stream/XMLParser.java @@ -235,7 +235,8 @@ public class XMLParser { //Non atomic race attributes private ArrayList participants; - private ArrayList course; + private ArrayList allMarks; + private ArrayList nonDuplicateMarks; private ArrayList compoundMarkSequence; private ArrayList 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 createCompoundMarks(Element docEle) { - ArrayList 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 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 getCompoundMarks() { - return course; + /** + * @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS) + */ + public List getAllCompoundMarks() { + return allMarks; + } + + /** + * @return Returns Marks from the race XML without any duplicates + */ + public List getNonDupCompoundMarks() { + return nonDuplicateMarks; } public ArrayList getCompoundMarkSequence() { diff --git a/src/main/resources/config/acc_polars.csv b/src/main/resources/config/acc_polars.csv new file mode 100644 index 00000000..ee7ea80e --- /dev/null +++ b/src/main/resources/config/acc_polars.csv @@ -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 diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index bef93923..42fc44c7 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -184,5 +184,17 @@ Remove scroll bars } .chart{ - -fx-background-color: #ffffff; +} + +.chart-title { + -fx-text-fill: #ffffff; + -fx-font-size: 1.6em; +} + +.axis-label { + -fx-text-fill: #ffffff; +} + +.axis { + -fx-tick-label-fill: #ffffff; } diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index b225ec98..d777cece 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -25,7 +25,7 @@