diff --git a/pom.xml b/pom.xml index ea43245e..8d10c6fc 100644 --- a/pom.xml +++ b/pom.xml @@ -25,12 +25,6 @@ commons-io 1.3.2 - - - com.opencsv - opencsv - 3.9 - com.googlecode.json-simple json-simple diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index c53a4300..8806308b 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -56,7 +56,7 @@ public class App extends Application { sr = new StreamReceiver("localhost", 4949, "RaceStream"); break; case "staffserver": - sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); + sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream"); break; case "official": sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); @@ -67,7 +67,7 @@ public class App extends Application { else { // sr = new StreamReceiver("localhost", 4949, "RaceStream"); sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); -// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); +// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream"); } sr.start(); diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 36d56507..6e37b682 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -264,7 +264,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; diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 754da2eb..16e05352 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.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -29,7 +30,9 @@ 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.*; @@ -185,37 +188,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateWindDirection(); updateOrder(); updateBoatSelectionComboBox(); - - for (Yacht yacht : StreamParser.getBoatsPos().values()) { - - if (yacht.getNextMark() != null){ - System.out.println("next Mark: " + yacht.getNextMark().getName()); - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - - Boolean isUpwindLeg = null; - // Can only calc leg direction if there is a next mark and it is a gate mark - Mark nextMark = bg.getBoat().getNextMark(); - if (!(nextMark == null || !(nextMark instanceof GateMark))) { - isUpwindLeg = bg.isUpwindLeg(includedCanvasController); - } - - for (MarkGroup mg : includedCanvasController.getMarkGroups()) { - if (mg.getMainMark().equals(nextMark)) { - - } - } - if (isUpwindLeg != null) { - if (isUpwindLeg) { - - } - } - - - } - - } - } - + updateLaylines(); }) ); @@ -224,6 +197,43 @@ 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(); + + System.out.println("Leg Number: " + legNumber); + List markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); + + if (legNumber == 0) { + System.out.println("PreStart"); + return null; + } else if (legNumber == markSequence.size() - 2) { + System.out.println("Finishing"); + 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 */ @@ -285,6 +295,53 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } + private void updateLaylines() { + + for (BoatGroup bg : includedCanvasController.getBoatGroups()) { + System.out.println("========" + bg.getBoat().getBoatName() + "========="); + 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) { + System.out.println("Next Mark: " + nextMark.getName()); + if (nextMark instanceof GateMark) { + if (bg.isUpwindLeg(includedCanvasController, nextMark)) { + isUpwind = true; + System.out.println(bg.getBoat().getBoatName() + " is on an upwind leg"); + } else { + isUpwind = false; + System.out.println(bg.getBoat().getBoatName() + " is on a downwind leg"); + } + + for(MarkGroup mg : includedCanvasController.getMarkGroups()) { + 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(); + + mg.addLayLine(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); + mg.addLayLine(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); + + } + } + } + } + + System.out.println(); + } + } + + /** * Initialised the combo box with any boats currently in the race and adds the required listener * for the combobox to take action upon selection diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 41d02d08..32df62fa 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -375,8 +375,7 @@ public class BoatGroup extends Group { * 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 = boat.getNextMark(); + public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) { Double windAngle = StreamParser.getWindDirection(); GateMark thisGateMark = (GateMark) nextMark; @@ -398,8 +397,6 @@ public class BoatGroup extends Group { boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going with the wind. */ - System.out.println("Boat Line func result: " + boatLineFuncResult); - System.out.println("Wind Line func result: " + windLineFuncResult); if (boatLineFuncResult == windLineFuncResult) { return true; } else { diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java index 578a3a7b..168d2291 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/models/PolarTable.java @@ -1,6 +1,7 @@ package seng302.models; import java.io.*; +import java.util.ArrayList; import java.util.HashMap; /** @@ -113,4 +114,50 @@ public final class PolarTable { 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/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 8c4e716b..9e04399b 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -58,7 +58,7 @@ public class MarkGroup extends Group { * @param layLineAngle The angle the laylines point * @param baseAngle The reference angle */ - private void addLayLine(Point2D startPoint, Double layLineAngle, Double baseAngle){ + public void addLayLine(Point2D startPoint, Double layLineAngle, Double baseAngle){ Point2D ep1 = getPointRotation(startPoint, 50.0, baseAngle + -layLineAngle); Point2D ep2 = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle); @@ -117,8 +117,8 @@ public class MarkGroup extends Group { //Laylines // if (mark.) - addLayLine(points1, 12.0, 90.0); - addLayLine(points2, 12.0, 90.0); +// addLayLine(points1, 12.0, 90.0); +// addLayLine(points2, 12.0, 90.0); } public void moveMarkTo (double x, double y, long raceId) diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/models/stream/StreamParser.java index 00bfed25..dd82c250 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.List; import java.util.Map; import java.util.TimeZone; import java.util.TreeMap; @@ -24,7 +23,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.models.Yacht; import seng302.models.mark.Mark; -import seng302.models.stream.XMLParser.RaceXMLObject.Corner; import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.packets.StreamPacket; @@ -49,10 +47,15 @@ public class StreamParser extends Thread{ private static Map boats = new ConcurrentHashMap<>(); private static Map boatsPos = new ConcurrentSkipListMap<>(); private static double windDirection = 0; + private static Double windSpeed = 0d; private static Long currentTimeLong; private static String currentTimeString; private static boolean appRunning; + + //CONVERSION CONSTANTS + private static final Double MS_TO_KNOTS = 1.94384; + /** * Used to initialise the thread name and stream parser object so a thread can be executed * @@ -196,7 +199,7 @@ public class StreamParser extends Thread{ 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 rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); currentTimeLong = currentTime; DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); @@ -224,6 +227,7 @@ 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]; @@ -445,25 +449,10 @@ public class StreamParser extends Thread{ // assign mark rounding time to boat boats.get((int)subjectId).setMarkRoundingTime(timeStamp); - for (Mark mark : xmlObject.getRaceXML().getCompoundMarks()) { + for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) { if (mark.getCompoundMarkID() == markId) { boats.get((int)subjectId).setLastMarkRounded(mark); - - List markSequence = xmlObject.getRaceXML().getCompoundMarkSequence(); - - for (int i = 0; i < markSequence.size() - 1; i++){ - Corner corner = markSequence.get(i); - - if (corner.getCompoundMarkID().equals(mark.getCompoundMarkID()) && (i + 1) < markSequence.size()){ - Corner nextCorner = markSequence.get(i+1); - for (Mark m : xmlObject.getRaceXML().getCompoundMarks()){ - if (m.getCompoundMarkID() == nextCorner.getCompoundMarkID()){ - boats.get((int)subjectId).setNextMark(m); - } - } - } - } - } + } } } @@ -593,6 +582,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 * diff --git a/src/main/java/seng302/models/stream/XMLParser.java b/src/main/java/seng302/models/stream/XMLParser.java index 4bda96cf..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,26 +315,21 @@ 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"); @@ -354,20 +352,26 @@ public class XMLParser { 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(), compoundMarkID); - } else { - return null; + if(!markSeen) { + nonDuplicateMarks.add(thisGateMark); + } + allMarks.add(thisGateMark); } } @@ -396,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() {