From 3cbbdb070f54932bbb5451bb8dfb1acd507ca59b Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 24 May 2017 20:31:07 +1200 Subject: [PATCH] Basic implementation for mapping windspeed to draw a polar on a gate complete Created functionality to grab the closest windspeed value to map to VMG values based off the current wind speed in the Polar Table Created new RaceXML mark object which contains ALL marks for purposes of sequencing Displaying correct (?) polars for one point only on a gate Created functionality to receive leg data for each boat and then map that to the next gate. This may only work for the current race due to a slight fudge factor Created functionality to receive wind speed tags: #story[956] #pair[wmu16, mra106] --- pom.xml | 6 - src/main/java/seng302/App.java | 4 +- .../seng302/controllers/CanvasController.java | 2 +- .../controllers/RaceViewController.java | 119 +++++++++++++----- src/main/java/seng302/models/BoatGroup.java | 5 +- src/main/java/seng302/models/PolarTable.java | 47 +++++++ .../java/seng302/models/mark/MarkGroup.java | 6 +- .../seng302/models/stream/StreamParser.java | 38 +++--- .../java/seng302/models/stream/XMLParser.java | 50 +++++--- 9 files changed, 192 insertions(+), 85 deletions(-) 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() {