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]
This commit is contained in:
William Muir
2017-05-24 20:31:07 +12:00
parent 1cac7cc189
commit 3cbbdb070f
9 changed files with 192 additions and 85 deletions
-6
View File
@@ -25,12 +25,6 @@
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.opencsv/opencsv -->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
+2 -2
View File
@@ -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();
@@ -264,7 +264,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;
@@ -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<XMLParser.RaceXMLObject.Corner> 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<Double, Double> 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
+1 -4
View File
@@ -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 {
@@ -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<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;
}
}
@@ -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)
@@ -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<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Long, Yacht> 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,24 +449,9 @@ 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<Corner> 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
*
@@ -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,26 +315,21 @@ 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 void createAndAddMark(Node compoundMark) {
private Mark createMark(Node compoundMark) {
Boolean markSeen = false;
List<SingleMark> 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<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() {