From 187acc6960b8f76372c2a35fa3d049502b97973b Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 12:30:46 +1200 Subject: [PATCH 01/37] added an empty polarparser class to push a new branch #story[900] --- mock/src/main/java/seng302/DataInput/PolarParser.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 mock/src/main/java/seng302/DataInput/PolarParser.java diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java new file mode 100644 index 00000000..e33612bb --- /dev/null +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -0,0 +1,7 @@ +package seng302.DataInput; + +/** + * Created by hba56 on 10/05/17. + */ +public class PolarParser { +} From 5b010cad494051b777d0ac53fbe51a26daaa3865 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 13:45:09 +1200 Subject: [PATCH 02/37] added the polar class for storing polars #story[900] --- mock/src/main/java/seng302/Model/Polar.java | 330 ++++++++++++++++++ .../test/java/seng302/Model/PolarTest.java | 7 + 2 files changed, 337 insertions(+) create mode 100644 mock/src/main/java/seng302/Model/Polar.java create mode 100644 mock/src/test/java/seng302/Model/PolarTest.java diff --git a/mock/src/main/java/seng302/Model/Polar.java b/mock/src/main/java/seng302/Model/Polar.java new file mode 100644 index 00000000..805c1f78 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Polar.java @@ -0,0 +1,330 @@ +package seng302.Model; + +import java.lang.reflect.Array; +import java.util.ArrayList; + +import static java.lang.Math.abs; +import static java.lang.Math.cos; + +/** + * Created by hba56 on 10/05/17. + */ +public class Polar { + private Integer trueWindSpeed; + + private Integer upTrueWindAngel; + private Integer upBoatSpeed; + + private Integer downTrueWindAngle; + private Integer downBoatSpeed; + + private Integer trueWindAngle0; + private Integer boatSpeed0; + private Integer trueWindAngle1; + private Integer boatSpeed1; + private Integer trueWindAngle2; + private Integer boatSpeed2; + private Integer trueWindAngle3; + private Integer boatSpeed3; + private Integer trueWindAngle4; + private Integer boatSpeed4; + private Integer trueWindAngle5; + private Integer boatSpeed5; + private Integer trueWindAngle6; + private Integer boatSpeed6; + private Integer trueWindAngle7; + private Integer boatSpeed7; + + private ArrayList trueWindAngles = new ArrayList(); + + /** + * constructor for a single boat polar + * @param trueWindSpeed the true wind angle to the boat + * @param upTrueWindAngel the true up wind angle + * @param upBoatSpeed the boat speed for up wind + * @param downTrueWindAngle the true down wind angle + * @param downBoatSpeed the boat speed for down wind + * @param trueWindAngle0 the true wind angle option 0 + * @param boatSpeed0 the boat speed for option 0 + * @param trueWindAngle1 the true wind angle option 0 + * @param boatSpeed1 the boat speed for option 0 + * @param trueWindAngle2 the true wind angle option 0 + * @param boatSpeed2 the boat speed for option 0 + * @param trueWindAngle3 the true wind angle option 0 + * @param boatSpeed3 the boat speed for option 0 + * @param trueWindAngle4 the true wind angle option 0 + * @param boatSpeed4 the boat speed for option 0 + * @param trueWindAngle5 the true wind angle option 0 + * @param boatSpeed5 the boat speed for option 0 + * @param trueWindAngle6 the true wind angle option 0 + * @param boatSpeed6 the boat speed for option 0 + * @param trueWindAngle7 the true wind angle option 0 + * @param boatSpeed7 the boat speed for option 0 + */ + public Polar(Integer trueWindSpeed, Integer upTrueWindAngel, Integer upBoatSpeed, Integer downTrueWindAngle, + Integer downBoatSpeed, Integer trueWindAngle0, Integer boatSpeed0, Integer trueWindAngle1, + Integer boatSpeed1, Integer trueWindAngle2, Integer boatSpeed2, Integer trueWindAngle3, + Integer boatSpeed3, Integer trueWindAngle4, Integer boatSpeed4, Integer trueWindAngle5, + Integer boatSpeed5, Integer trueWindAngle6, Integer boatSpeed6, Integer trueWindAngle7, + Integer boatSpeed7) { + this.trueWindSpeed = trueWindSpeed; + this.upTrueWindAngel = upTrueWindAngel; + this.upBoatSpeed = upBoatSpeed; + this.downTrueWindAngle = downTrueWindAngle; + this.downBoatSpeed = downBoatSpeed; + this.trueWindAngle0 = trueWindAngle0; + this.boatSpeed0 = boatSpeed0; + this.trueWindAngle1 = trueWindAngle1; + this.boatSpeed1 = boatSpeed1; + this.trueWindAngle2 = trueWindAngle2; + this.boatSpeed2 = boatSpeed2; + this.trueWindAngle3 = trueWindAngle3; + this.boatSpeed3 = boatSpeed3; + this.trueWindAngle4 = trueWindAngle4; + this.boatSpeed4 = boatSpeed4; + this.trueWindAngle5 = trueWindAngle5; + this.boatSpeed5 = boatSpeed5; + this.trueWindAngle6 = trueWindAngle6; + this.boatSpeed6 = boatSpeed6; + this.trueWindAngle7 = trueWindAngle7; + this.boatSpeed7 = boatSpeed7; + + double[] option0 = new double[2]; + option0[0] = trueWindAngle0; + option0[1] = boatSpeed0; + + double[] option1 = new double[2]; + option1[0] = trueWindAngle1; + option1[1] = boatSpeed1; + + double[] option2 = new double[2]; + option2[0] = trueWindAngle2; + option2[1] = boatSpeed2; + + double[] option3 = new double[2]; + option3[0] = trueWindAngle3; + option3[1] = boatSpeed3; + + double[] option4 = new double[2]; + option4[0] = trueWindAngle4; + option4[1] = boatSpeed4; + + double[] option5 = new double[2]; + option5[0] = trueWindAngle5; + option5[1] = boatSpeed5; + + double[] option6 = new double[2]; + option6[0] = trueWindAngle6; + option6[1] = boatSpeed6; + + double[] option7 = new double[2]; + option7[0] = trueWindAngle7; + option7[1] = boatSpeed7; + + + this.trueWindAngles.add(option0); + this.trueWindAngles.add(option1); + this.trueWindAngles.add(option2); + this.trueWindAngles.add(option3); + this.trueWindAngles.add(option4); + this.trueWindAngles.add(option5); + this.trueWindAngles.add(option6); + this.trueWindAngles.add(option7); + } + + /** + * + * @param angleToDestination + * @param angleOfWind + * @return + */ + public double getBestHeading(double angleToDestination, double angleOfWind){ + double vmg = 0; + double vmgTemp = 0; + + double[] bestOption = null; + + for(double[] option: this.trueWindAngles) { + double angle = option[0] - angleToDestination; + vmgTemp = cos(angle)*option[1]; + + if(bestOption == null){ + vmg = vmgTemp; + bestOption = option; + }else if (vmgTemp > vmg){ + vmg = vmgTemp; + bestOption = option; + } + } + + return bestOption[0]; + } + + public Integer getTrueWindSpeed() { + return trueWindSpeed; + } + + public void setTrueWindSpeed(Integer trueWindSpeed) { + this.trueWindSpeed = trueWindSpeed; + } + + public Integer getUpTrueWindAngel() { + return upTrueWindAngel; + } + + public void setUpTrueWindAngel(Integer upTrueWindAngel) { + this.upTrueWindAngel = upTrueWindAngel; + } + + public Integer getUpBoatSpeed() { + return upBoatSpeed; + } + + public void setUpBoatSpeed(Integer upBoatSpeed) { + this.upBoatSpeed = upBoatSpeed; + } + + public Integer getDownTrueWindAngle() { + return downTrueWindAngle; + } + + public void setDownTrueWindAngle(Integer downTrueWindAngle) { + this.downTrueWindAngle = downTrueWindAngle; + } + + public Integer getDownBoatSpeed() { + return downBoatSpeed; + } + + public void setDownBoatSpeed(Integer downBoatSpeed) { + this.downBoatSpeed = downBoatSpeed; + } + + public Integer getTrueWindAngle0() { + return trueWindAngle0; + } + + public void setTrueWindAngle0(Integer trueWindAngle0) { + this.trueWindAngle0 = trueWindAngle0; + } + + public Integer getBoatSpeed0() { + return boatSpeed0; + } + + public void setBoatSpeed0(Integer boatSpeed0) { + this.boatSpeed0 = boatSpeed0; + } + + public Integer getTrueWindAngle1() { + return trueWindAngle1; + } + + public void setTrueWindAngle1(Integer trueWindAngle1) { + this.trueWindAngle1 = trueWindAngle1; + } + + public Integer getBoatSpeed1() { + return boatSpeed1; + } + + public void setBoatSpeed1(Integer boatSpeed1) { + this.boatSpeed1 = boatSpeed1; + } + + public Integer getTrueWindAngle2() { + return trueWindAngle2; + } + + public void setTrueWindAngle2(Integer trueWindAngle2) { + this.trueWindAngle2 = trueWindAngle2; + } + + public Integer getBoatSpeed2() { + return boatSpeed2; + } + + public void setBoatSpeed2(Integer boatSpeed2) { + this.boatSpeed2 = boatSpeed2; + } + + public Integer getTrueWindAngle3() { + return trueWindAngle3; + } + + public void setTrueWindAngle3(Integer trueWindAngle3) { + this.trueWindAngle3 = trueWindAngle3; + } + + public Integer getBoatSpeed3() { + return boatSpeed3; + } + + public void setBoatSpeed3(Integer boatSpeed3) { + this.boatSpeed3 = boatSpeed3; + } + + public Integer getTrueWindAngle4() { + return trueWindAngle4; + } + + public void setTrueWindAngle4(Integer trueWindAngle4) { + this.trueWindAngle4 = trueWindAngle4; + } + + public Integer getBoatSpeed4() { + return boatSpeed4; + } + + public void setBoatSpeed4(Integer boatSpeed4) { + this.boatSpeed4 = boatSpeed4; + } + + public Integer getTrueWindAngle5() { + return trueWindAngle5; + } + + public void setTrueWindAngle5(Integer trueWindAngle5) { + this.trueWindAngle5 = trueWindAngle5; + } + + public Integer getBoatSpeed5() { + return boatSpeed5; + } + + public void setBoatSpeed5(Integer boatSpeed5) { + this.boatSpeed5 = boatSpeed5; + } + + public Integer getTrueWindAngle6() { + return trueWindAngle6; + } + + public void setTrueWindAngle6(Integer trueWindAngle6) { + this.trueWindAngle6 = trueWindAngle6; + } + + public Integer getBoatSpeed6() { + return boatSpeed6; + } + + public void setBoatSpeed6(Integer boatSpeed6) { + this.boatSpeed6 = boatSpeed6; + } + + public Integer getTrueWindAngle7() { + return trueWindAngle7; + } + + public void setTrueWindAngle7(Integer trueWindAngle7) { + this.trueWindAngle7 = trueWindAngle7; + } + + public Integer getBoatSpeed7() { + return boatSpeed7; + } + + public void setBoatSpeed7(Integer boatSpeed7) { + this.boatSpeed7 = boatSpeed7; + } +} diff --git a/mock/src/test/java/seng302/Model/PolarTest.java b/mock/src/test/java/seng302/Model/PolarTest.java new file mode 100644 index 00000000..c76d0bfb --- /dev/null +++ b/mock/src/test/java/seng302/Model/PolarTest.java @@ -0,0 +1,7 @@ +package seng302.Model; + +/** + * Created by hba56 on 10/05/17. + */ +public class PolarTest { +} From d7294f73a1dee74884d09cf8cc2cc612cd573942 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 14:23:26 +1200 Subject: [PATCH 03/37] boats now have polars stored in the -added getter and setter for a boats polars #story[900] --- mock/src/main/java/seng302/Model/Boat.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 5ff47bd9..08931c28 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -2,6 +2,8 @@ package seng302.Model; import org.geotools.referencing.GeodeticCalculator; +import java.util.ArrayList; + /** * Created by esa46 on 1/05/17. @@ -18,6 +20,7 @@ public class Boat { private long timeFinished = -1; private boolean started = false; private double heading; + private ArrayList polars; /** * Boat initialiser which keeps all of the information of the boat. @@ -153,4 +156,12 @@ public class Boat { public void setHeading(double heading) { this.heading = heading; } + + public ArrayList getPolars() { + return polars; + } + + public void setPolars(ArrayList polars) { + this.polars = polars; + } } From a4480f3ccffbf37d1742510c511995b51d86f7a9 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 14:25:06 +1200 Subject: [PATCH 04/37] removed the get best heading method as it was incorrect and not related to this story #story[900] --- mock/src/main/java/seng302/Model/Polar.java | 28 ------------------- .../test/java/seng302/Model/PolarTest.java | 7 ----- 2 files changed, 35 deletions(-) delete mode 100644 mock/src/test/java/seng302/Model/PolarTest.java diff --git a/mock/src/main/java/seng302/Model/Polar.java b/mock/src/main/java/seng302/Model/Polar.java index 805c1f78..ffa9f3cb 100644 --- a/mock/src/main/java/seng302/Model/Polar.java +++ b/mock/src/main/java/seng302/Model/Polar.java @@ -132,34 +132,6 @@ public class Polar { this.trueWindAngles.add(option7); } - /** - * - * @param angleToDestination - * @param angleOfWind - * @return - */ - public double getBestHeading(double angleToDestination, double angleOfWind){ - double vmg = 0; - double vmgTemp = 0; - - double[] bestOption = null; - - for(double[] option: this.trueWindAngles) { - double angle = option[0] - angleToDestination; - vmgTemp = cos(angle)*option[1]; - - if(bestOption == null){ - vmg = vmgTemp; - bestOption = option; - }else if (vmgTemp > vmg){ - vmg = vmgTemp; - bestOption = option; - } - } - - return bestOption[0]; - } - public Integer getTrueWindSpeed() { return trueWindSpeed; } diff --git a/mock/src/test/java/seng302/Model/PolarTest.java b/mock/src/test/java/seng302/Model/PolarTest.java deleted file mode 100644 index c76d0bfb..00000000 --- a/mock/src/test/java/seng302/Model/PolarTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package seng302.Model; - -/** - * Created by hba56 on 10/05/17. - */ -public class PolarTest { -} From 1f554648ef19fbe23ce66c9cf97d612a32277a96 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 14:41:05 +1200 Subject: [PATCH 05/37] changed all polar values to doubles and added a empty constructor #story[900] --- mock/src/main/java/seng302/Model/Polar.java | 140 ++++++++++---------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Polar.java b/mock/src/main/java/seng302/Model/Polar.java index ffa9f3cb..ef6d0853 100644 --- a/mock/src/main/java/seng302/Model/Polar.java +++ b/mock/src/main/java/seng302/Model/Polar.java @@ -1,42 +1,46 @@ package seng302.Model; -import java.lang.reflect.Array; import java.util.ArrayList; import static java.lang.Math.abs; -import static java.lang.Math.cos; /** * Created by hba56 on 10/05/17. */ public class Polar { - private Integer trueWindSpeed; - - private Integer upTrueWindAngel; - private Integer upBoatSpeed; - - private Integer downTrueWindAngle; - private Integer downBoatSpeed; - - private Integer trueWindAngle0; - private Integer boatSpeed0; - private Integer trueWindAngle1; - private Integer boatSpeed1; - private Integer trueWindAngle2; - private Integer boatSpeed2; - private Integer trueWindAngle3; - private Integer boatSpeed3; - private Integer trueWindAngle4; - private Integer boatSpeed4; - private Integer trueWindAngle5; - private Integer boatSpeed5; - private Integer trueWindAngle6; - private Integer boatSpeed6; - private Integer trueWindAngle7; - private Integer boatSpeed7; + private double trueWindSpeed; + + private double upTrueWindAngel; + private double upBoatSpeed; + + private double downTrueWindAngle; + private double downBoatSpeed; + + private double trueWindAngle0; + private double boatSpeed0; + private double trueWindAngle1; + private double boatSpeed1; + private double trueWindAngle2; + private double boatSpeed2; + private double trueWindAngle3; + private double boatSpeed3; + private double trueWindAngle4; + private double boatSpeed4; + private double trueWindAngle5; + private double boatSpeed5; + private double trueWindAngle6; + private double boatSpeed6; + private double trueWindAngle7; + private double boatSpeed7; private ArrayList trueWindAngles = new ArrayList(); + /** + * basic constructor + */ + public Polar() { + } + /** * constructor for a single boat polar * @param trueWindSpeed the true wind angle to the boat @@ -132,171 +136,171 @@ public class Polar { this.trueWindAngles.add(option7); } - public Integer getTrueWindSpeed() { + public double getTrueWindSpeed() { return trueWindSpeed; } - public void setTrueWindSpeed(Integer trueWindSpeed) { + public void setTrueWindSpeed(double trueWindSpeed) { this.trueWindSpeed = trueWindSpeed; } - public Integer getUpTrueWindAngel() { + public double getUpTrueWindAngel() { return upTrueWindAngel; } - public void setUpTrueWindAngel(Integer upTrueWindAngel) { + public void setUpTrueWindAngel(double upTrueWindAngel) { this.upTrueWindAngel = upTrueWindAngel; } - public Integer getUpBoatSpeed() { + public double getUpBoatSpeed() { return upBoatSpeed; } - public void setUpBoatSpeed(Integer upBoatSpeed) { + public void setUpBoatSpeed(double upBoatSpeed) { this.upBoatSpeed = upBoatSpeed; } - public Integer getDownTrueWindAngle() { + public double getDownTrueWindAngle() { return downTrueWindAngle; } - public void setDownTrueWindAngle(Integer downTrueWindAngle) { + public void setDownTrueWindAngle(double downTrueWindAngle) { this.downTrueWindAngle = downTrueWindAngle; } - public Integer getDownBoatSpeed() { + public double getDownBoatSpeed() { return downBoatSpeed; } - public void setDownBoatSpeed(Integer downBoatSpeed) { + public void setDownBoatSpeed(double downBoatSpeed) { this.downBoatSpeed = downBoatSpeed; } - public Integer getTrueWindAngle0() { + public double getTrueWindAngle0() { return trueWindAngle0; } - public void setTrueWindAngle0(Integer trueWindAngle0) { + public void setTrueWindAngle0(double trueWindAngle0) { this.trueWindAngle0 = trueWindAngle0; } - public Integer getBoatSpeed0() { + public double getBoatSpeed0() { return boatSpeed0; } - public void setBoatSpeed0(Integer boatSpeed0) { + public void setBoatSpeed0(double boatSpeed0) { this.boatSpeed0 = boatSpeed0; } - public Integer getTrueWindAngle1() { + public double getTrueWindAngle1() { return trueWindAngle1; } - public void setTrueWindAngle1(Integer trueWindAngle1) { + public void setTrueWindAngle1(double trueWindAngle1) { this.trueWindAngle1 = trueWindAngle1; } - public Integer getBoatSpeed1() { + public double getBoatSpeed1() { return boatSpeed1; } - public void setBoatSpeed1(Integer boatSpeed1) { + public void setBoatSpeed1(double boatSpeed1) { this.boatSpeed1 = boatSpeed1; } - public Integer getTrueWindAngle2() { + public double getTrueWindAngle2() { return trueWindAngle2; } - public void setTrueWindAngle2(Integer trueWindAngle2) { + public void setTrueWindAngle2(double trueWindAngle2) { this.trueWindAngle2 = trueWindAngle2; } - public Integer getBoatSpeed2() { + public double getBoatSpeed2() { return boatSpeed2; } - public void setBoatSpeed2(Integer boatSpeed2) { + public void setBoatSpeed2(double boatSpeed2) { this.boatSpeed2 = boatSpeed2; } - public Integer getTrueWindAngle3() { + public double getTrueWindAngle3() { return trueWindAngle3; } - public void setTrueWindAngle3(Integer trueWindAngle3) { + public void setTrueWindAngle3(double trueWindAngle3) { this.trueWindAngle3 = trueWindAngle3; } - public Integer getBoatSpeed3() { + public double getBoatSpeed3() { return boatSpeed3; } - public void setBoatSpeed3(Integer boatSpeed3) { + public void setBoatSpeed3(double boatSpeed3) { this.boatSpeed3 = boatSpeed3; } - public Integer getTrueWindAngle4() { + public double getTrueWindAngle4() { return trueWindAngle4; } - public void setTrueWindAngle4(Integer trueWindAngle4) { + public void setTrueWindAngle4(double trueWindAngle4) { this.trueWindAngle4 = trueWindAngle4; } - public Integer getBoatSpeed4() { + public double getBoatSpeed4() { return boatSpeed4; } - public void setBoatSpeed4(Integer boatSpeed4) { + public void setBoatSpeed4(double boatSpeed4) { this.boatSpeed4 = boatSpeed4; } - public Integer getTrueWindAngle5() { + public double getTrueWindAngle5() { return trueWindAngle5; } - public void setTrueWindAngle5(Integer trueWindAngle5) { + public void setTrueWindAngle5(double trueWindAngle5) { this.trueWindAngle5 = trueWindAngle5; } - public Integer getBoatSpeed5() { + public double getBoatSpeed5() { return boatSpeed5; } - public void setBoatSpeed5(Integer boatSpeed5) { + public void setBoatSpeed5(double boatSpeed5) { this.boatSpeed5 = boatSpeed5; } - public Integer getTrueWindAngle6() { + public double getTrueWindAngle6() { return trueWindAngle6; } - public void setTrueWindAngle6(Integer trueWindAngle6) { + public void setTrueWindAngle6(double trueWindAngle6) { this.trueWindAngle6 = trueWindAngle6; } - public Integer getBoatSpeed6() { + public double getBoatSpeed6() { return boatSpeed6; } - public void setBoatSpeed6(Integer boatSpeed6) { + public void setBoatSpeed6(double boatSpeed6) { this.boatSpeed6 = boatSpeed6; } - public Integer getTrueWindAngle7() { + public double getTrueWindAngle7() { return trueWindAngle7; } - public void setTrueWindAngle7(Integer trueWindAngle7) { + public void setTrueWindAngle7(double trueWindAngle7) { this.trueWindAngle7 = trueWindAngle7; } - public Integer getBoatSpeed7() { + public double getBoatSpeed7() { return boatSpeed7; } - public void setBoatSpeed7(Integer boatSpeed7) { + public void setBoatSpeed7(double boatSpeed7) { this.boatSpeed7 = boatSpeed7; } } From f518c52c7b1dab909d5a40932beae990ab2896d9 Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 15:10:03 +1200 Subject: [PATCH 06/37] changed the way we store polars -removed polar class -added in new hasmap polars class -updated boat class to the changes #story[900] --- mock/src/main/java/seng302/Model/Boat.java | 7 +- mock/src/main/java/seng302/Model/Polar.java | 306 ------------------- mock/src/main/java/seng302/Model/Polars.java | 25 ++ 3 files changed, 29 insertions(+), 309 deletions(-) delete mode 100644 mock/src/main/java/seng302/Model/Polar.java create mode 100644 mock/src/main/java/seng302/Model/Polars.java diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 08931c28..052bcda3 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -20,7 +20,7 @@ public class Boat { private long timeFinished = -1; private boolean started = false; private double heading; - private ArrayList polars; + private Polars polars; /** * Boat initialiser which keeps all of the information of the boat. @@ -157,11 +157,12 @@ public class Boat { this.heading = heading; } - public ArrayList getPolars() { + + public Polars getPolars() { return polars; } - public void setPolars(ArrayList polars) { + public void setPolars(Polars polars) { this.polars = polars; } } diff --git a/mock/src/main/java/seng302/Model/Polar.java b/mock/src/main/java/seng302/Model/Polar.java deleted file mode 100644 index ef6d0853..00000000 --- a/mock/src/main/java/seng302/Model/Polar.java +++ /dev/null @@ -1,306 +0,0 @@ -package seng302.Model; - -import java.util.ArrayList; - -import static java.lang.Math.abs; - -/** - * Created by hba56 on 10/05/17. - */ -public class Polar { - private double trueWindSpeed; - - private double upTrueWindAngel; - private double upBoatSpeed; - - private double downTrueWindAngle; - private double downBoatSpeed; - - private double trueWindAngle0; - private double boatSpeed0; - private double trueWindAngle1; - private double boatSpeed1; - private double trueWindAngle2; - private double boatSpeed2; - private double trueWindAngle3; - private double boatSpeed3; - private double trueWindAngle4; - private double boatSpeed4; - private double trueWindAngle5; - private double boatSpeed5; - private double trueWindAngle6; - private double boatSpeed6; - private double trueWindAngle7; - private double boatSpeed7; - - private ArrayList trueWindAngles = new ArrayList(); - - /** - * basic constructor - */ - public Polar() { - } - - /** - * constructor for a single boat polar - * @param trueWindSpeed the true wind angle to the boat - * @param upTrueWindAngel the true up wind angle - * @param upBoatSpeed the boat speed for up wind - * @param downTrueWindAngle the true down wind angle - * @param downBoatSpeed the boat speed for down wind - * @param trueWindAngle0 the true wind angle option 0 - * @param boatSpeed0 the boat speed for option 0 - * @param trueWindAngle1 the true wind angle option 0 - * @param boatSpeed1 the boat speed for option 0 - * @param trueWindAngle2 the true wind angle option 0 - * @param boatSpeed2 the boat speed for option 0 - * @param trueWindAngle3 the true wind angle option 0 - * @param boatSpeed3 the boat speed for option 0 - * @param trueWindAngle4 the true wind angle option 0 - * @param boatSpeed4 the boat speed for option 0 - * @param trueWindAngle5 the true wind angle option 0 - * @param boatSpeed5 the boat speed for option 0 - * @param trueWindAngle6 the true wind angle option 0 - * @param boatSpeed6 the boat speed for option 0 - * @param trueWindAngle7 the true wind angle option 0 - * @param boatSpeed7 the boat speed for option 0 - */ - public Polar(Integer trueWindSpeed, Integer upTrueWindAngel, Integer upBoatSpeed, Integer downTrueWindAngle, - Integer downBoatSpeed, Integer trueWindAngle0, Integer boatSpeed0, Integer trueWindAngle1, - Integer boatSpeed1, Integer trueWindAngle2, Integer boatSpeed2, Integer trueWindAngle3, - Integer boatSpeed3, Integer trueWindAngle4, Integer boatSpeed4, Integer trueWindAngle5, - Integer boatSpeed5, Integer trueWindAngle6, Integer boatSpeed6, Integer trueWindAngle7, - Integer boatSpeed7) { - this.trueWindSpeed = trueWindSpeed; - this.upTrueWindAngel = upTrueWindAngel; - this.upBoatSpeed = upBoatSpeed; - this.downTrueWindAngle = downTrueWindAngle; - this.downBoatSpeed = downBoatSpeed; - this.trueWindAngle0 = trueWindAngle0; - this.boatSpeed0 = boatSpeed0; - this.trueWindAngle1 = trueWindAngle1; - this.boatSpeed1 = boatSpeed1; - this.trueWindAngle2 = trueWindAngle2; - this.boatSpeed2 = boatSpeed2; - this.trueWindAngle3 = trueWindAngle3; - this.boatSpeed3 = boatSpeed3; - this.trueWindAngle4 = trueWindAngle4; - this.boatSpeed4 = boatSpeed4; - this.trueWindAngle5 = trueWindAngle5; - this.boatSpeed5 = boatSpeed5; - this.trueWindAngle6 = trueWindAngle6; - this.boatSpeed6 = boatSpeed6; - this.trueWindAngle7 = trueWindAngle7; - this.boatSpeed7 = boatSpeed7; - - double[] option0 = new double[2]; - option0[0] = trueWindAngle0; - option0[1] = boatSpeed0; - - double[] option1 = new double[2]; - option1[0] = trueWindAngle1; - option1[1] = boatSpeed1; - - double[] option2 = new double[2]; - option2[0] = trueWindAngle2; - option2[1] = boatSpeed2; - - double[] option3 = new double[2]; - option3[0] = trueWindAngle3; - option3[1] = boatSpeed3; - - double[] option4 = new double[2]; - option4[0] = trueWindAngle4; - option4[1] = boatSpeed4; - - double[] option5 = new double[2]; - option5[0] = trueWindAngle5; - option5[1] = boatSpeed5; - - double[] option6 = new double[2]; - option6[0] = trueWindAngle6; - option6[1] = boatSpeed6; - - double[] option7 = new double[2]; - option7[0] = trueWindAngle7; - option7[1] = boatSpeed7; - - - this.trueWindAngles.add(option0); - this.trueWindAngles.add(option1); - this.trueWindAngles.add(option2); - this.trueWindAngles.add(option3); - this.trueWindAngles.add(option4); - this.trueWindAngles.add(option5); - this.trueWindAngles.add(option6); - this.trueWindAngles.add(option7); - } - - public double getTrueWindSpeed() { - return trueWindSpeed; - } - - public void setTrueWindSpeed(double trueWindSpeed) { - this.trueWindSpeed = trueWindSpeed; - } - - public double getUpTrueWindAngel() { - return upTrueWindAngel; - } - - public void setUpTrueWindAngel(double upTrueWindAngel) { - this.upTrueWindAngel = upTrueWindAngel; - } - - public double getUpBoatSpeed() { - return upBoatSpeed; - } - - public void setUpBoatSpeed(double upBoatSpeed) { - this.upBoatSpeed = upBoatSpeed; - } - - public double getDownTrueWindAngle() { - return downTrueWindAngle; - } - - public void setDownTrueWindAngle(double downTrueWindAngle) { - this.downTrueWindAngle = downTrueWindAngle; - } - - public double getDownBoatSpeed() { - return downBoatSpeed; - } - - public void setDownBoatSpeed(double downBoatSpeed) { - this.downBoatSpeed = downBoatSpeed; - } - - public double getTrueWindAngle0() { - return trueWindAngle0; - } - - public void setTrueWindAngle0(double trueWindAngle0) { - this.trueWindAngle0 = trueWindAngle0; - } - - public double getBoatSpeed0() { - return boatSpeed0; - } - - public void setBoatSpeed0(double boatSpeed0) { - this.boatSpeed0 = boatSpeed0; - } - - public double getTrueWindAngle1() { - return trueWindAngle1; - } - - public void setTrueWindAngle1(double trueWindAngle1) { - this.trueWindAngle1 = trueWindAngle1; - } - - public double getBoatSpeed1() { - return boatSpeed1; - } - - public void setBoatSpeed1(double boatSpeed1) { - this.boatSpeed1 = boatSpeed1; - } - - public double getTrueWindAngle2() { - return trueWindAngle2; - } - - public void setTrueWindAngle2(double trueWindAngle2) { - this.trueWindAngle2 = trueWindAngle2; - } - - public double getBoatSpeed2() { - return boatSpeed2; - } - - public void setBoatSpeed2(double boatSpeed2) { - this.boatSpeed2 = boatSpeed2; - } - - public double getTrueWindAngle3() { - return trueWindAngle3; - } - - public void setTrueWindAngle3(double trueWindAngle3) { - this.trueWindAngle3 = trueWindAngle3; - } - - public double getBoatSpeed3() { - return boatSpeed3; - } - - public void setBoatSpeed3(double boatSpeed3) { - this.boatSpeed3 = boatSpeed3; - } - - public double getTrueWindAngle4() { - return trueWindAngle4; - } - - public void setTrueWindAngle4(double trueWindAngle4) { - this.trueWindAngle4 = trueWindAngle4; - } - - public double getBoatSpeed4() { - return boatSpeed4; - } - - public void setBoatSpeed4(double boatSpeed4) { - this.boatSpeed4 = boatSpeed4; - } - - public double getTrueWindAngle5() { - return trueWindAngle5; - } - - public void setTrueWindAngle5(double trueWindAngle5) { - this.trueWindAngle5 = trueWindAngle5; - } - - public double getBoatSpeed5() { - return boatSpeed5; - } - - public void setBoatSpeed5(double boatSpeed5) { - this.boatSpeed5 = boatSpeed5; - } - - public double getTrueWindAngle6() { - return trueWindAngle6; - } - - public void setTrueWindAngle6(double trueWindAngle6) { - this.trueWindAngle6 = trueWindAngle6; - } - - public double getBoatSpeed6() { - return boatSpeed6; - } - - public void setBoatSpeed6(double boatSpeed6) { - this.boatSpeed6 = boatSpeed6; - } - - public double getTrueWindAngle7() { - return trueWindAngle7; - } - - public void setTrueWindAngle7(double trueWindAngle7) { - this.trueWindAngle7 = trueWindAngle7; - } - - public double getBoatSpeed7() { - return boatSpeed7; - } - - public void setBoatSpeed7(double boatSpeed7) { - this.boatSpeed7 = boatSpeed7; - } -} diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java new file mode 100644 index 00000000..d4609d49 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -0,0 +1,25 @@ +package seng302.Model; + +import javafx.util.Pair; + +import java.util.HashMap; + +/** + * Created by hba56 on 10/05/17. + */ +public class Polars { + private HashMap polarValues; + + public Polars() { + polarValues = new HashMap(); + } + + public void addEstimate(double trueWindSpeed, double trueWindAngle, double boatSpeed){ + Pair newKey = new Pair(trueWindSpeed, trueWindAngle); + polarValues.put(newKey, boatSpeed); + } + + public HashMap getPolarValues() { + return polarValues; + } +} From aeee8ca7487aa9c85f11d706a53568742f038a14 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 10 May 2017 15:11:51 +1200 Subject: [PATCH 07/37] Added polar data file. Added PolarParse class - currently commented out as it depends on polar table class. #story[900] --- .../java/seng302/DataInput/PolarParser.java | 170 ++++++++++++++++++ .../Exceptions/InvalidPolarFileException.java | 28 +++ mock/src/main/resources/polars/acc_polars.csv | 8 + .../seng302/DataInput/PolarParserTest.java | 26 +++ 4 files changed, 232 insertions(+) create mode 100644 mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java create mode 100644 mock/src/main/resources/polars/acc_polars.csv create mode 100644 mock/src/test/java/seng302/DataInput/PolarParserTest.java diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index e33612bb..475125a9 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -3,5 +3,175 @@ package seng302.DataInput; /** * Created by hba56 on 10/05/17. */ + +import seng302.Exceptions.InvalidPolarFileException; +import seng302.Model.Polar; + +import java.io.*; +import java.util.ArrayList; + + +/** + * Responsible for parsing a polar data file, and creating a polar data object. + */ public class PolarParser { + + + /** + * Given a filename, this function parses it and generates a PolarTable object, which can be queried for polar information. + * @param filename + * @return + */ + /* + ///TEMP PolarTable = ArrayList + public static ArrayList parse(String filename) throws InvalidPolarFileException { + //Temporary table to return later. + ArrayList polarTable = new ArrayList(); + + + //Open the file for reading. + InputStream fileStream = PolarParser.class.getClassLoader().getResourceAsStream(filename); + if (fileStream == null) { + throw new InvalidPolarFileException("Could not open polar data file: " + filename); + } + //Wrap it with buffered input stream to set encoding and buffer. + InputStreamReader in = null; + try { + in = new InputStreamReader(fileStream, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InvalidPolarFileException("Unsupported encoding: UTF-8", e); + } + BufferedReader inputStream = new BufferedReader(in); + + + //We expect the polar data file to have the column headings: + // Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7 + //and to have 7 rows of data. + //Angles are expected to be in degrees, and velocities in knots. + + + //We read the heading and data rows, and split them into arrays of elements. + String[] headings; + ArrayList dataRows = new ArrayList<>(7); + try { + //Heading. + //Read heading row. + String headingRow = inputStream.readLine(); + + //Split it into individual headings. + headings = headingRow.split(","); + + + //Data rows. + while (inputStream.ready()) { + //Read line. + String dataRow = inputStream.readLine(); + + //Split line. + String[] dataElements = dataRow.split(","); + + //Add to collection. + dataRows.add(dataElements); + + } + + } catch (IOException e) { + throw new InvalidPolarFileException("Could not read from polar data file: " + filename, e); + } + + //Finished reading in data, now we need to construct polar rows and table from it. + //For each row... + int rowNumber = 0; + for (String[] row : dataRows) { + //Create Polar row object. + Polar polarRow = new Polar(); + + //For each column... + for (int i = 0; i < row.length; i++) { + + //Convert value to a double. + Double value; + try { + value = Double.parseDouble(row[i]); + } + catch (NumberFormatException e) { + throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + "to a double.", e); + } + + //Set the values of the row. + //For reference: + //Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7 + if (headings[i] == "Tws") { + polarRow.setTrueWindSpeed(value); + } + else if (headings[i] == "Twa0") { + polarRow.setTrueWindAngle0(value); + } + else if (headings[i] == "Bsp0") { + polarRow.setBoatSpeed0(value); + } + else if (headings[i] == "Twa1") { + polarRow.setTrueWindAngle1(value); + } + else if (headings[i] == "Bsp1") { + polarRow.setBoatSpeed1(value); + } + else if (headings[i] == "UpTwa") { + polarRow.setUpTrueWindAngel(value); + } + else if (headings[i] == "UpBsp") { + polarRow.setUpBoatSpeed(value); + } + else if (headings[i] == "Twa2") { + polarRow.setTrueWindAngle2(value); + } + else if (headings[i] == "Bsp2") { + polarRow.setBoatSpeed2(value); + } + else if (headings[i] == "Twa3") { + polarRow.setTrueWindAngle3(value); + } + else if (headings[i] == "Bsp3") { + polarRow.setBoatSpeed3(value); + } + else if (headings[i] == "Twa4") { + polarRow.setTrueWindAngle4(value); + } + else if (headings[i] == "Bsp4") { + polarRow.setBoatSpeed4(value); + } + else if (headings[i] == "Twa5") { + polarRow.setTrueWindAngle5(value); + } + else if (headings[i] == "Bsp5") { + polarRow.setBoatSpeed5(value); + } + else if (headings[i] == "Twa6") { + polarRow.setTrueWindAngle6(value); + } + else if (headings[i] == "Bsp6") { + polarRow.setBoatSpeed6(value); + } + else if (headings[i] == "Twa7") { + polarRow.setTrueWindAngle7(value); + } + else if (headings[i] == "Bsp7") { + polarRow.setBoatSpeed7(value); + } + + //Add the polar row to the polar table. + polarTable.add(polarRow); + + } + + //Increment row number. + rowNumber++; + + } + + + return polarTable; + } +*/ + } diff --git a/mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java b/mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java new file mode 100644 index 00000000..cc997c67 --- /dev/null +++ b/mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java @@ -0,0 +1,28 @@ +package seng302.Exceptions; + +/** + * Created by f123 on 10-May-17. + */ + +/** + * An exception thrown when we cannot parse a polar data file. + */ +public class InvalidPolarFileException extends RuntimeException { + + /** + * Constructs the exception with a given message. + * @param message Message to store. + */ + public InvalidPolarFileException(String message) { + super(message); + } + + /** + * Constructs the exception with a given message and cause. + * @param message Message to store. + * @param cause Cause to store. + */ + public InvalidPolarFileException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/mock/src/main/resources/polars/acc_polars.csv b/mock/src/main/resources/polars/acc_polars.csv new file mode 100644 index 00000000..ee7ea80e --- /dev/null +++ b/mock/src/main/resources/polars/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/mock/src/test/java/seng302/DataInput/PolarParserTest.java b/mock/src/test/java/seng302/DataInput/PolarParserTest.java new file mode 100644 index 00000000..0bb06326 --- /dev/null +++ b/mock/src/test/java/seng302/DataInput/PolarParserTest.java @@ -0,0 +1,26 @@ +package seng302.DataInput; + +import org.testng.annotations.Test; + +import java.io.File; + +import static org.testng.Assert.*; + +/** + * Created by f123 on 10-May-17. + */ +public class PolarParserTest { + + @Test + /** + * Tests if we can parse a polar data file (stored in a string), and create a polar table. + */ + public void testParse() throws Exception { + + + //Polars = PolarParser.parse("polars/acc_polars.csv"); + + + } + +} From e67f8fae4bed5d186f4ef012f785d8dc1774903b Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 15:24:46 +1200 Subject: [PATCH 08/37] parser updated for the hashmap polars #pair[hba56, fjc40] #story[900] --- .../java/seng302/DataInput/PolarParser.java | 80 ++----------------- 1 file changed, 7 insertions(+), 73 deletions(-) diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index 475125a9..6b977483 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -5,7 +5,7 @@ package seng302.DataInput; */ import seng302.Exceptions.InvalidPolarFileException; -import seng302.Model.Polar; +import seng302.Model.Polars; import java.io.*; import java.util.ArrayList; @@ -22,11 +22,11 @@ public class PolarParser { * @param filename * @return */ - /* + ///TEMP PolarTable = ArrayList - public static ArrayList parse(String filename) throws InvalidPolarFileException { + public static Polars parse(String filename) throws InvalidPolarFileException { //Temporary table to return later. - ArrayList polarTable = new ArrayList(); + Polars polarTable = new Polars(); //Open the file for reading. @@ -84,84 +84,19 @@ public class PolarParser { int rowNumber = 0; for (String[] row : dataRows) { //Create Polar row object. - Polar polarRow = new Polar(); //For each column... - for (int i = 0; i < row.length; i++) { + for (int i = 0; i < row.length / 2; i += 2) { //Convert value to a double. Double value; try { - value = Double.parseDouble(row[i]); + //Add the polar value to the polar table + polarTable.addEstimate( Double.parseDouble(row[0]), Double.parseDouble(row[i]), Double.parseDouble(row[i + 1])); } catch (NumberFormatException e) { throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + "to a double.", e); } - - //Set the values of the row. - //For reference: - //Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7 - if (headings[i] == "Tws") { - polarRow.setTrueWindSpeed(value); - } - else if (headings[i] == "Twa0") { - polarRow.setTrueWindAngle0(value); - } - else if (headings[i] == "Bsp0") { - polarRow.setBoatSpeed0(value); - } - else if (headings[i] == "Twa1") { - polarRow.setTrueWindAngle1(value); - } - else if (headings[i] == "Bsp1") { - polarRow.setBoatSpeed1(value); - } - else if (headings[i] == "UpTwa") { - polarRow.setUpTrueWindAngel(value); - } - else if (headings[i] == "UpBsp") { - polarRow.setUpBoatSpeed(value); - } - else if (headings[i] == "Twa2") { - polarRow.setTrueWindAngle2(value); - } - else if (headings[i] == "Bsp2") { - polarRow.setBoatSpeed2(value); - } - else if (headings[i] == "Twa3") { - polarRow.setTrueWindAngle3(value); - } - else if (headings[i] == "Bsp3") { - polarRow.setBoatSpeed3(value); - } - else if (headings[i] == "Twa4") { - polarRow.setTrueWindAngle4(value); - } - else if (headings[i] == "Bsp4") { - polarRow.setBoatSpeed4(value); - } - else if (headings[i] == "Twa5") { - polarRow.setTrueWindAngle5(value); - } - else if (headings[i] == "Bsp5") { - polarRow.setBoatSpeed5(value); - } - else if (headings[i] == "Twa6") { - polarRow.setTrueWindAngle6(value); - } - else if (headings[i] == "Bsp6") { - polarRow.setBoatSpeed6(value); - } - else if (headings[i] == "Twa7") { - polarRow.setTrueWindAngle7(value); - } - else if (headings[i] == "Bsp7") { - polarRow.setBoatSpeed7(value); - } - - //Add the polar row to the polar table. - polarTable.add(polarRow); - } //Increment row number. @@ -172,6 +107,5 @@ public class PolarParser { return polarTable; } -*/ } From 245914a69ef3ce89b005509e58e1ab681fea72ce Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 15:26:43 +1200 Subject: [PATCH 09/37] added the polars csv #story[900] --- mock/src/test/resources/acc_polars.csv | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 mock/src/test/resources/acc_polars.csv diff --git a/mock/src/test/resources/acc_polars.csv b/mock/src/test/resources/acc_polars.csv new file mode 100644 index 00000000..ee7ea80e --- /dev/null +++ b/mock/src/test/resources/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 From 4f8d6b14d4340eb4aacc79e0762f2d6847e4166d Mon Sep 17 00:00:00 2001 From: hba56 Date: Wed, 10 May 2017 15:46:01 +1200 Subject: [PATCH 10/37] updated app class to read the polar csv and pass it off to its parser #story[900] --- mock/src/main/java/seng302/App.java | 9 ++++----- .../java/seng302/DataInput/RaceXMLReader.java | 20 +++++++++++++++---- mock/src/test/resources/acc_polars.csv | 8 -------- 3 files changed, 20 insertions(+), 17 deletions(-) delete mode 100644 mock/src/test/resources/acc_polars.csv diff --git a/mock/src/main/java/seng302/App.java b/mock/src/main/java/seng302/App.java index c80b6432..0e6306bc 100644 --- a/mock/src/main/java/seng302/App.java +++ b/mock/src/main/java/seng302/App.java @@ -4,11 +4,9 @@ package seng302; import javafx.application.Application; import javafx.stage.Stage; import org.xml.sax.SAXException; -import seng302.DataInput.RaceDataSource; -import seng302.DataInput.RaceXMLReader; -import seng302.DataInput.RegattaDataSource; -import seng302.DataInput.RegattaXMLReader; +import seng302.DataInput.*; import seng302.Model.Event; +import seng302.Model.Polars; import javax.xml.parsers.ParserConfigurationException; @@ -28,7 +26,8 @@ public class App extends Application { @Override public void start(Stage primaryStage) { try { - RaceDataSource raceData = new RaceXMLReader("raceXML/bermuda_AC35.xml"); + Polars boatPolars = PolarParser.parse("polars/acc_polars.csv"); + RaceDataSource raceData = new RaceXMLReader("raceXML/bermuda_AC35.xml", boatPolars); RegattaDataSource regattaData = new RegattaXMLReader("mockXML/regattaTest.xml"); Event raceEvent = new Event(raceData, regattaData); raceEvent.start(); diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index 50bebb1c..00a14965 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -5,10 +5,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import seng302.Model.Boat; -import seng302.Model.GPSCoordinate; -import seng302.Model.Leg; -import seng302.Model.Marker; +import seng302.Model.*; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; @@ -28,6 +25,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { private GPSCoordinate mapTopLeft, mapBottomRight; private List boundary = new ArrayList<>(); private List markers = new ArrayList<>(); + private Polars boatPolars; /** * Constractor for Race XML @@ -41,6 +39,19 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { this(filePath, true); } + /** + * Constractor for Race XML + * + * @param filePath path of the file + * @throws IOException error + * @throws SAXException error + * @throws ParserConfigurationException error + */ + public RaceXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException { + this(filePath, true); + this.boatPolars = boatPolars; + } + /** * COnstructor for Race XML * @@ -90,6 +101,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { double velo = Double.parseDouble(getTextValueOfNode((Element) nBoats.item(i), "speed")); int sourceID = Integer.parseInt(getTextValueOfNode((Element) nBoats.item(i), "sourceID")); Boat boat = new Boat(name, velo, abbrev, sourceID); + boat.setPolars(boatPolars); boat.setCurrentPosition(startPt1); if (legs.size() > 0) { boat.setCurrentLeg(legs.get(0)); diff --git a/mock/src/test/resources/acc_polars.csv b/mock/src/test/resources/acc_polars.csv deleted file mode 100644 index ee7ea80e..00000000 --- a/mock/src/test/resources/acc_polars.csv +++ /dev/null @@ -1,8 +0,0 @@ -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 From 693da8a82a44edfc7592c0b59faf69061336b12a Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 10 May 2017 17:21:18 +1200 Subject: [PATCH 11/37] Removed some unneccessary code and comments from PolarParser.parse(...). Fixed a bug where the polar parser would attempt to insert (wind speed, windspeed, windangle) instead of (windspeed, windangle, boatspeed). Removed some unused constructors from RaceXMLReader, and fixed a bug where it wouldn't actually use the polar table correctly. Boat's constructor now expects a Polars table. Added some comments to the Polars class, and specified the HashMap's template parameters Added PolarsTest - which needs to be implemented properly when the VMG calculations have been added. #story[900] --- mock/src/main/java/seng302/App.java | 3 ++ .../java/seng302/DataInput/PolarParser.java | 24 ++++------ .../java/seng302/DataInput/RaceXMLReader.java | 48 +++++++------------ mock/src/main/java/seng302/Model/Boat.java | 4 +- mock/src/main/java/seng302/Model/Polars.java | 33 ++++++++++--- .../test/java/seng302/Data/BoatDataTest.java | 3 +- .../test/java/seng302/Data/RaceDataTest.java | 3 +- .../seng302/DataInput/PolarParserTest.java | 16 ++++++- .../src/test/java/seng302/Model/BoatTest.java | 2 +- .../test/java/seng302/Model/PolarsTest.java | 47 ++++++++++++++++++ .../test/java/seng302/Model/RaceXMLTest.java | 8 ++-- mock/src/test/resources/polars/acc_polars.csv | 8 ++++ .../seng302/Controllers/StartController.java | 2 +- .../main/java/seng302/VisualiserInput.java | 2 +- 14 files changed, 140 insertions(+), 63 deletions(-) create mode 100644 mock/src/test/java/seng302/Model/PolarsTest.java create mode 100644 mock/src/test/resources/polars/acc_polars.csv diff --git a/mock/src/main/java/seng302/App.java b/mock/src/main/java/seng302/App.java index 0e6306bc..74e20255 100644 --- a/mock/src/main/java/seng302/App.java +++ b/mock/src/main/java/seng302/App.java @@ -5,6 +5,7 @@ import javafx.application.Application; import javafx.stage.Stage; import org.xml.sax.SAXException; import seng302.DataInput.*; +import seng302.Exceptions.InvalidPolarFileException; import seng302.Model.Event; import seng302.Model.Polars; @@ -37,6 +38,8 @@ public class App extends Application { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); + } catch (InvalidPolarFileException e) { + e.printStackTrace(); } } } diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index 6b977483..5a187192 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -50,18 +50,13 @@ public class PolarParser { //Angles are expected to be in degrees, and velocities in knots. - //We read the heading and data rows, and split them into arrays of elements. - String[] headings; + //We read data rows, and split them into arrays of elements. ArrayList dataRows = new ArrayList<>(7); try { - //Heading. - //Read heading row. + //Heading row. + //We skip the heading row by reading it. String headingRow = inputStream.readLine(); - //Split it into individual headings. - headings = headingRow.split(","); - - //Data rows. while (inputStream.ready()) { //Read line. @@ -83,19 +78,19 @@ public class PolarParser { //For each row... int rowNumber = 0; for (String[] row : dataRows) { - //Create Polar row object. - //For each column... - for (int i = 0; i < row.length / 2; i += 2) { + //For each pair of columns (the pair is angle, speed). + //We start at column 1 since column 0 is the wind speed column. + // (row.length - 1) / 2 means the number of pairs of angle+speed. + for (int i = 1; i < (row.length - 1) / 2; i += 2) { - //Convert value to a double. - Double value; + //Add angle+speed=velocity estimate to polar table. try { //Add the polar value to the polar table polarTable.addEstimate( Double.parseDouble(row[0]), Double.parseDouble(row[i]), Double.parseDouble(row[i + 1])); } catch (NumberFormatException e) { - throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + "to a double.", e); + throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e); } } @@ -104,7 +99,6 @@ public class PolarParser { } - return polarTable; } diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index 00a14965..089f93c2 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -25,49 +25,36 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { private GPSCoordinate mapTopLeft, mapBottomRight; private List boundary = new ArrayList<>(); private List markers = new ArrayList<>(); + + ///Filename to read data from. + private String filePath; + ///The polar table object to assign to each boat. private Polars boatPolars; - /** - * Constractor for Race XML - * - * @param filePath path of the file - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public RaceXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - } + /** * Constractor for Race XML * - * @param filePath path of the file + * @param filePath The path to the file to read data from. + * @param boatPolars The polar table to assign to each boat. * @throws IOException error * @throws SAXException error * @throws ParserConfigurationException error */ public RaceXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - this.boatPolars = boatPolars; - } - /** - * COnstructor for Race XML - * - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public RaceXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException { super(filePath); - if (read) { - read(); - } + + this.filePath = filePath; + this.boatPolars = boatPolars; + + + read(); } + + /** * Read the files */ @@ -100,8 +87,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { String abbrev = getTextValueOfNode((Element) nBoats.item(i), "abbr"); double velo = Double.parseDouble(getTextValueOfNode((Element) nBoats.item(i), "speed")); int sourceID = Integer.parseInt(getTextValueOfNode((Element) nBoats.item(i), "sourceID")); - Boat boat = new Boat(name, velo, abbrev, sourceID); - boat.setPolars(boatPolars); + + Boat boat = new Boat(name, velo, abbrev, sourceID, this.boatPolars); + boat.setCurrentPosition(startPt1); if (legs.size() > 0) { boat.setCurrentLeg(legs.get(0)); diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 052bcda3..9604680a 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -29,12 +29,14 @@ public class Boat { * @param velocity Speed in m/s that the boat travels at. * @param abbrev nam abbreviation * @param sourceID id of boat + * @param polars The polars table to use for this boat. */ - public Boat(String name, double velocity, String abbrev, int sourceID) { + public Boat(String name, double velocity, String abbrev, int sourceID, Polars polars) { this.velocity = velocity; this.abbrev = abbrev; this.name = name; this.sourceID = sourceID; + this.polars = polars; } /** diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index d4609d49..cef438c2 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -7,19 +7,40 @@ import java.util.HashMap; /** * Created by hba56 on 10/05/17. */ + +/** + * Encapsulates an entire polar table. Has a function to calculate VMG. + */ public class Polars { - private HashMap polarValues; + ///Internal store of data. Maps pair to boatSpeed. + private HashMap, Double> polarValues = new HashMap<>(); + + /** + * Ctor. + */ public Polars() { - polarValues = new HashMap(); } - public void addEstimate(double trueWindSpeed, double trueWindAngle, double boatSpeed){ - Pair newKey = new Pair(trueWindSpeed, trueWindAngle); + + /** + * Adds an estimated velocity to the polar table object, for a given (windSpeed, windAngle) pair. That is, stores a mapping from (windSpeed, windAngle) to (boatVelocity). + * @param trueWindSpeed The true wind speed of the estimate. + * @param trueWindAngle The true wind angle of the estimate. + * @param boatSpeed The boat speed of the estimate. + */ + public void addEstimate(double trueWindSpeed, double trueWindAngle, double boatSpeed){ + Pair newKey = new Pair(trueWindSpeed, trueWindAngle); polarValues.put(newKey, boatSpeed); - } + } + + //TODO calculate VMG from (windAngle, destAngle, windSpeed). - public HashMap getPolarValues() { + /** + * Returns the hashmap used to store polar data. + * @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. + */ + public HashMap, Double> getPolarValues() { return polarValues; } } diff --git a/mock/src/test/java/seng302/Data/BoatDataTest.java b/mock/src/test/java/seng302/Data/BoatDataTest.java index 14d3ff5a..8b098a02 100644 --- a/mock/src/test/java/seng302/Data/BoatDataTest.java +++ b/mock/src/test/java/seng302/Data/BoatDataTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import org.xml.sax.SAXException; import seng302.DataInput.RaceDataSource; import seng302.DataInput.RaceXMLReader; +import seng302.Model.Polars; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; @@ -33,7 +34,7 @@ public class BoatDataTest { @Before public void initReader() { try { - raceDataSource = new RaceXMLReader("raceXML/bermuda_AC35.xml"); + raceDataSource = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); BoatData boatData = new BoatData(raceDataSource.getBoats()); result = boatData.createXML(); diff --git a/mock/src/test/java/seng302/Data/RaceDataTest.java b/mock/src/test/java/seng302/Data/RaceDataTest.java index 5f9ed54c..62ba472e 100644 --- a/mock/src/test/java/seng302/Data/RaceDataTest.java +++ b/mock/src/test/java/seng302/Data/RaceDataTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.xml.sax.SAXException; import seng302.DataInput.RaceDataSource; import seng302.DataInput.RaceXMLReader; +import seng302.Model.Polars; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; @@ -27,7 +28,7 @@ public class RaceDataTest { @Before public void initReader() { try { - raceDataSource = new RaceXMLReader("raceXML/bermuda_AC35.xml"); + raceDataSource = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); RaceData raceData = new RaceData(raceDataSource); result = raceData.createXML(); diff --git a/mock/src/test/java/seng302/DataInput/PolarParserTest.java b/mock/src/test/java/seng302/DataInput/PolarParserTest.java index 0bb06326..2974c1ad 100644 --- a/mock/src/test/java/seng302/DataInput/PolarParserTest.java +++ b/mock/src/test/java/seng302/DataInput/PolarParserTest.java @@ -1,6 +1,9 @@ package seng302.DataInput; + import org.testng.annotations.Test; +import seng302.Exceptions.InvalidPolarFileException; +import seng302.Model.Polars; import java.io.File; @@ -17,10 +20,19 @@ public class PolarParserTest { */ public void testParse() throws Exception { + try { + //Parse data file. + Polars polars = PolarParser.parse("polars/acc_polars.csv"); - //Polars = PolarParser.parse("polars/acc_polars.csv"); - + //If the parse function didn't through, it worked. + assertTrue(true); + } + catch (InvalidPolarFileException e) { + assertTrue(false); + } } + + } diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java index 75b01939..47c0e9c2 100644 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ b/mock/src/test/java/seng302/Model/BoatTest.java @@ -13,7 +13,7 @@ public class BoatTest { private GPSCoordinate ORIGIN_COORDS = new GPSCoordinate(0, 0); - private Boat TEST_BOAT = new Boat("Test", 1, "tt", 1); + private Boat TEST_BOAT = new Boat("Test", 1, "tt", 1, new Polars()); @Test public void calculateDueNorthAzimuthReturns0() { diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java new file mode 100644 index 00000000..a12603c5 --- /dev/null +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -0,0 +1,47 @@ +package seng302.Model; + +import org.testng.annotations.Test; +import seng302.DataInput.PolarParser; +import seng302.Exceptions.InvalidPolarFileException; + +import static org.testng.Assert.*; + +/** + * Created by f123 on 10-May-17. + */ +public class PolarsTest { + + @Test + /** + * Tests if we can parse a polar data file (stored in a string), create a polar table, and calculate VMG for a variety of values. + */ + public void testParseAndVMG() throws Exception { + + //Read data. + Polars polars; + try { + //Parse data file. + polars = PolarParser.parse("polars/acc_polars.csv"); + } + catch (InvalidPolarFileException e) { + assertTrue(false); + } + + //Test 1. + //TODO make these tests actually do something when the calculateVMG function is added. + double windAngle1 = 31.5; + double destAngle1 = 65.32; + double windSpeed1 = 15;//knots + //double vmgAngle1 = TODO; + //double vmgSpeed1 = TODO; + + //VMG calcVMG1 = Polars.calculateVMG(windAngle1, destAngle1, windSpeed1); + //double calcVMGAngle1 = calcVMG1.getKey(); + //double calcVMGSpeed1 = calcVMG1.getValue(); + + //assertEquals(vmgAngle1, calcVMGAngle1, 0.1); + //assertEquals(vmgSpeed1, calcVMGSpeed1, 0.1); + + } + +} diff --git a/mock/src/test/java/seng302/Model/RaceXMLTest.java b/mock/src/test/java/seng302/Model/RaceXMLTest.java index bb06111d..13c28887 100644 --- a/mock/src/test/java/seng302/Model/RaceXMLTest.java +++ b/mock/src/test/java/seng302/Model/RaceXMLTest.java @@ -17,7 +17,7 @@ public class RaceXMLTest { @Test public void canFindFile() { try { - RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", false); + RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); } catch (Exception e) { fail("Cannot find raceXML/bermuda_AC35.xml in the resources folder"); } @@ -27,7 +27,7 @@ public class RaceXMLTest { @Test public void canReadBoats() { try { - RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", false); + RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); raceXMLReader.readBoats(); List boats = raceXMLReader.getBoats(); assertTrue(boats.size() == 6); @@ -62,7 +62,7 @@ public class RaceXMLTest { @Test public void canReadLegs() { try { - RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", false); + RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); raceXMLReader.readLegs(); assertTrue(raceXMLReader.getLegs().size() == 5); } catch (Exception e) { @@ -73,7 +73,7 @@ public class RaceXMLTest { @Test public void canReadCourse() { try { - RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", false); + RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); raceXMLReader.readCourse(); assertTrue(raceXMLReader.getMapTopLeft() != null); assertTrue(raceXMLReader.getMapBottomRight() != null); diff --git a/mock/src/test/resources/polars/acc_polars.csv b/mock/src/test/resources/polars/acc_polars.csv new file mode 100644 index 00000000..ee7ea80e --- /dev/null +++ b/mock/src/test/resources/polars/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/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java index f264cded..75434735 100644 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ b/visualiser/src/main/java/seng302/Controllers/StartController.java @@ -62,7 +62,7 @@ public class StartController extends Controller implements Observer { */ private void startRaceNoScaling() { //startRace(1); - while(visualiserInput.getRaceStatus() == null);//TODO probably remove this. + //while(visualiserInput.getRaceStatus() == null);//TODO probably remove this. countdownTimer(); } diff --git a/visualiser/src/main/java/seng302/VisualiserInput.java b/visualiser/src/main/java/seng302/VisualiserInput.java index ffba53c3..b70c8e53 100644 --- a/visualiser/src/main/java/seng302/VisualiserInput.java +++ b/visualiser/src/main/java/seng302/VisualiserInput.java @@ -219,7 +219,7 @@ public class VisualiserInput implements Runnable { //If no heartbeat has been received in more the heartbeat period //then the connection will need to be restarted. - System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE + //System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE long heartBeatPeriod = 10 * 1000; if (timeSinceHeartbeat() > heartBeatPeriod) { System.out.println("Connection has stopped, trying to reconnect."); From 238b97c0164480244a34a35ccb05eea5bc51ecc9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 11 May 2017 11:30:18 +1200 Subject: [PATCH 12/37] Added a VMG class (has a speed and bearing). Added the Polars.calculateVMG function, which calculates the VMG from a given wind angle, wind speed, and angle to destination. In may not be completely correct at the moment, so it's a work in progress. #story[873] --- mock/src/main/java/seng302/Model/Polars.java | 58 ++++++++++++++++++++ mock/src/main/java/seng302/Model/VMG.java | 45 +++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 mock/src/main/java/seng302/Model/VMG.java diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index cef438c2..4de5b570 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -2,6 +2,7 @@ package seng302.Model; import javafx.util.Pair; +import java.util.ArrayList; import java.util.HashMap; /** @@ -36,6 +37,63 @@ public class Polars { //TODO calculate VMG from (windAngle, destAngle, windSpeed). + /** + * Calculates the VMG for a given wind angle, wind speed, and angle to destination. + * @param trueWindAngle The current true wind angle. + * @param trueWindSpeed The current true wind speed. + * @param destinationAngle The angle between the boat and the destination point. + * @return + */ + public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle) { + + //Currently a fairly simple implementation where we find the wind speed that is less than or equal to the current wind speed (the lower bound), and then find the specific angle (with no interpolation) that gives the best VMG. + + double polarWindSpeed = 0; + + //Find the lower bound wind speed from the polar table. + for (Pair key : this.polarValues.keySet()) { + + double currentPolarSpeed = key.getKey(); + + //If the speed from the table we are looking at is greater than the last speed, and less than or equal to the current wind speed. + if ((currentPolarSpeed > polarWindSpeed) && (currentPolarSpeed <= trueWindSpeed)) { + polarWindSpeed = currentPolarSpeed; + } + } + + //We create a list of wind angles because we need (speed, angle) pairs to look into the map. + ArrayList windAngles = new ArrayList<>(); + for (Pair key : this.polarValues.keySet()) { + + windAngles.add(key.getValue()); + } + + //Find the angle with the best VMG. + double bestVMGAngle = 0; + double bestVMGVelocity = 0; + for (double tackAngle : windAngles) { + //This is the velocity from the polar table at this wind speed/angle. + double estVelocity = this.polarValues.get(new Pair(polarWindSpeed, tackAngle)); + double angleBetweenDestAndTack = tackAngle - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). + double vmgTemp = Math.cos(angleBetweenDestAndTack) * estVelocity; + + //Check that the velocity is better. + if (vmgTemp > bestVMGVelocity) { + bestVMGVelocity = vmgTemp; + bestVMGAngle = tackAngle; + } + + } + + + //Create the VMG object and return it. + return new VMG(bestVMGVelocity, bestVMGAngle); + + + } + + /** * Returns the hashmap used to store polar data. * @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java new file mode 100644 index 00000000..2a6bb065 --- /dev/null +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -0,0 +1,45 @@ +package seng302.Model; + +/** + * Created by f123 on 10-May-17. + */ + +/** + * This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component. + */ +public class VMG { + + ///Speed component of the VMG. + double speed; + + ///Bearing component of the VMG. + double bearing; + + + /** + * Ctor. Creates a VMG object with a given speed and bearing (that is, a velocity). + * @param speed Speed component of the VMG. + * @param bearing Bearing component of the VMG. + */ + public VMG(double speed, double bearing) { + this.speed = speed; + this.bearing = bearing; + } + + + /** + * Returns the speed component of this VMG object. + * @return Speed component of this VMG object. + */ + public double getSpeed() { + return speed; + } + + /** + * Returns the bearing component of this VMG object. + * @return Bearing component of this VMG object. + */ + public double getBearing() { + return bearing; + } +} From 3b2f99b7d7d55c2aea7ac18c7fd87447ebf4bfeb Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 11 May 2017 11:58:02 +1200 Subject: [PATCH 13/37] Fixed Polars.calculateVMG function - it was using non-existent keys in some circumstances. #story[873] #pair[fjc40,jam339] --- mock/src/main/java/seng302/Model/Polars.java | 33 +++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 4de5b570..2863db48 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -35,7 +35,6 @@ public class Polars { polarValues.put(newKey, boatSpeed); } - //TODO calculate VMG from (windAngle, destAngle, windSpeed). /** * Calculates the VMG for a given wind angle, wind speed, and angle to destination. @@ -65,27 +64,37 @@ public class Polars { ArrayList windAngles = new ArrayList<>(); for (Pair key : this.polarValues.keySet()) { - windAngles.add(key.getValue()); + //Don't add angles multiple times. + double angle = key.getValue(); + if (!windAngles.contains(angle)) { + windAngles.add(angle); + } } //Find the angle with the best VMG. + //TODO need to differentiate between windward and leeward. double bestVMGAngle = 0; double bestVMGVelocity = 0; for (double tackAngle : windAngles) { - //This is the velocity from the polar table at this wind speed/angle. - double estVelocity = this.polarValues.get(new Pair(polarWindSpeed, tackAngle)); - double angleBetweenDestAndTack = tackAngle - destinationAngle; - //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double vmgTemp = Math.cos(angleBetweenDestAndTack) * estVelocity; - - //Check that the velocity is better. - if (vmgTemp > bestVMGVelocity) { - bestVMGVelocity = vmgTemp; - bestVMGAngle = tackAngle; + Pair key = new Pair(polarWindSpeed, tackAngle); + if (this.polarValues.containsKey(key)) { + //This is the velocity from the polar table at this wind speed/angle. + double estVelocity = this.polarValues.get(key); + double angleBetweenDestAndTack = tackAngle - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). + double vmgTemp = Math.cos(angleBetweenDestAndTack) * estVelocity; + + //Check that the velocity is better. + if (vmgTemp > bestVMGVelocity) { + bestVMGVelocity = vmgTemp; + bestVMGAngle = tackAngle; + } + } } + System.out.println("VMG speed = " + bestVMGVelocity + " , VMG angle = " + bestVMGAngle);//TEMP DEBUG REMOVE //Create the VMG object and return it. return new VMG(bestVMGVelocity, bestVMGAngle); From a13899b6a7e281524db41f2a0f0b2a95d2cb3e4b Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 11 May 2017 14:40:24 +1200 Subject: [PATCH 14/37] Implemented and tested to check if a point is inside or outside of a boundary using a ray tracing algorithm. The function is public static for a GPSCoordinate and takes a list of GPSCoordinates and the comparing coordinate. #story[873] --- .../java/seng302/Model/GPSCoordinate.java | 88 +++++++++++++ .../java/seng302/Model/GPSCoordinateTest.java | 121 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 mock/src/test/java/seng302/Model/GPSCoordinateTest.java diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index 020ac0dd..9dc03fd6 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -1,5 +1,8 @@ package seng302.Model; +import java.util.Comparator; +import java.util.List; + /** * GPS Coordinate for the world map. * Created by esa46 on 15/03/17. @@ -68,5 +71,90 @@ public class GPSCoordinate { result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } + + /** + * Calculates min and max values and passed it to calculate if coordinate is in the boundary + * @param coordinate coordinate of interest + * @param boundary List of points which make a boundry + * @return true if coordinate is in the boundary + */ + public static boolean isInsideBoundary(GPSCoordinate coordinate, List boundary) { + double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); + double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); + double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); + double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); + return isInsideBoundary(coordinate, boundary, new GPSCoordinate(minLatitude, minLongitude), + new GPSCoordinate(maxLatitude, maxLongitude)); + } + + /** + * Calculates if the coordinate is in the boundary + * @param coordinate coordinate of interest + * @param boundary List of points which make a boundary + * @param minValues max GPS + * @param maxValues min GPS + * @return true if coordinate is in the boundary + */ + public static boolean isInsideBoundary(GPSCoordinate coordinate, List boundary, GPSCoordinate minValues, GPSCoordinate maxValues) { + double minLat = minValues.getLatitude(); + double minLon = minValues.getLongitude(); + double maxLat = maxValues.getLatitude(); + double maxLon = maxValues.getLongitude(); + double coordinateLat = coordinate.getLatitude(); + double coordinateLon = coordinate.getLongitude(); + int length = boundary.size(); + + boolean inside = false; + + // End computation early + if (coordinateLat <= minLat || coordinateLat >= maxLat || coordinateLon <= minLon || coordinateLon >= maxLon) { + return false; + } + + // Check if inside using ray casting algorithm + for (int i = 0, j = length - 1; i < length; j = i++) { + if (intersects(boundary.get(i), boundary.get(j), coordinate)) { + inside = !inside; + } + + + } + return inside; + } + + /** + * Helper function to find if a point is in a boundary + * @param boundaryA + * @param boundaryB + * @param coordinate + * @return true if a line from the point intersects the two boundary points + */ + private static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { + double boundaryALat = boundaryA.getLatitude(); + double boundaryALon = boundaryA.getLongitude(); + double boundaryBLat = boundaryB.getLatitude(); + double boundaryBLon = boundaryB.getLongitude(); + double coordinateLat = coordinate.getLatitude(); + double coordinateLon = coordinate.getLongitude(); + + if (boundaryALat > boundaryBLat) { + return intersects(boundaryB, boundaryA, coordinate); + } + if (coordinateLat == boundaryALat || coordinateLat == boundaryBLat) { + // Move coordinate off intersection line + coordinateLat += 1e-9; + } + if (coordinateLat > boundaryBLat || coordinateLat < boundaryALat || coordinateLon > Double.max(boundaryALon, boundaryBLon)) { + return false; + } + if (coordinateLon < Double.min(boundaryALon, boundaryBLon)) { + return true; + } + + double aRatio = (coordinateLat - boundaryALat) / (coordinateLon - boundaryALon); + double bRatio = (coordinateLat - boundaryBLat) / (coordinateLon - boundaryBLon); + return aRatio >= bRatio; + + } } diff --git a/mock/src/test/java/seng302/Model/GPSCoordinateTest.java b/mock/src/test/java/seng302/Model/GPSCoordinateTest.java new file mode 100644 index 00000000..be0dafea --- /dev/null +++ b/mock/src/test/java/seng302/Model/GPSCoordinateTest.java @@ -0,0 +1,121 @@ +package seng302.Model; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.assertFalse; + +/** + * Created by jjg64 on 11/05/17. + */ +public class GPSCoordinateTest { + List boundary; + + @Before + public void init() { + boundary = new ArrayList<>(); + } + + /** + * ------- + * | | + * | * | + * ------- + */ + @Test + public void insideSquareTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(10, 0)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(0, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(2, 8); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertTrue(inside); + } + + /** + * ------- + * | | + * * | | + * ------- + */ + @Test + public void outsideSquareTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(10, 0)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(0, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(-2, 8); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertFalse(inside); + } + + /** + * ------- + * | | + * | | + * *------ + */ + @Test + public void edgeSquareTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(10, 0)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(0, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(0, 0); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertFalse(inside); + } + + @Test + public void insideShapeWithObtuseAnglesTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(4, 4)); + boundary.add(new GPSCoordinate(7, 2)); + boundary.add(new GPSCoordinate(9, 5)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(6, 6)); + boundary.add(new GPSCoordinate(2, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(5, 5); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertTrue(inside); + } + + @Test + public void outsideShapeWithObtuseAnglesTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(4, 4)); + boundary.add(new GPSCoordinate(7, 2)); + boundary.add(new GPSCoordinate(9, 5)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(6, 6)); + boundary.add(new GPSCoordinate(2, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(4, 3); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertFalse(inside); + } + + @Test + public void edgeOfShapeWithObtuseAnglesTest() { + boundary.add(new GPSCoordinate(0, 0)); + boundary.add(new GPSCoordinate(4, 4)); + boundary.add(new GPSCoordinate(7, 2)); + boundary.add(new GPSCoordinate(9, 5)); + boundary.add(new GPSCoordinate(10, 10)); + boundary.add(new GPSCoordinate(6, 6)); + boundary.add(new GPSCoordinate(2, 10)); + + GPSCoordinate coordinate = new GPSCoordinate(2, 10); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + assertFalse(inside); + } +} From 8c0c03dcf28c866c20ac45768674c62f97017899 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 11 May 2017 14:49:17 +1200 Subject: [PATCH 15/37] Fixed tests that were reading the file again making them fail. #story[873] --- mock/src/test/java/seng302/Model/RaceXMLTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/mock/src/test/java/seng302/Model/RaceXMLTest.java b/mock/src/test/java/seng302/Model/RaceXMLTest.java index 13c28887..3fc1aeb4 100644 --- a/mock/src/test/java/seng302/Model/RaceXMLTest.java +++ b/mock/src/test/java/seng302/Model/RaceXMLTest.java @@ -63,7 +63,6 @@ public class RaceXMLTest { public void canReadLegs() { try { RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); - raceXMLReader.readLegs(); assertTrue(raceXMLReader.getLegs().size() == 5); } catch (Exception e) { fail("Legs Unreadable"); @@ -74,7 +73,6 @@ public class RaceXMLTest { public void canReadCourse() { try { RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); - raceXMLReader.readCourse(); assertTrue(raceXMLReader.getMapTopLeft() != null); assertTrue(raceXMLReader.getMapBottomRight() != null); assertTrue(raceXMLReader.getFinishPt1() != null); From 602bc0686eaf5e8057409f8ca95940b54e3e7710 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 11 May 2017 14:55:50 +1200 Subject: [PATCH 16/37] Changed public functions in the RaceXMLReader to make a new list every time and reassign the list to avoid duplicates in a list. #story[873] --- mock/src/main/java/seng302/DataInput/RaceXMLReader.java | 8 ++++++++ mock/src/test/java/seng302/Model/RaceXMLTest.java | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index 089f93c2..af1d7d89 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -80,6 +80,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { */ public void readBoats() { //get all boats + List boats = new ArrayList<>(); NodeList nBoats = doc.getElementsByTagName("boat"); for (int i = 0; i < nBoats.getLength(); i++) { @@ -96,6 +97,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { } boats.add(boat); } + this.boats = boats; } @@ -104,12 +106,14 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { */ public void readMarkers() { //get all boats + List markers = new ArrayList<>(); NodeList nMarkers = doc.getElementsByTagName("marker"); for (int i = 0; i < nMarkers.getLength(); i++) { Marker marker = getMarker((Element) nMarkers.item(i)); if (marker.getName() != null) markers.add(marker); } + this.markers = markers; } /** @@ -117,6 +121,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { */ public void readLegs() { //get all legs + List legs = new ArrayList<>(); NodeList nLegs = doc.getElementsByTagName("leg"); for (int i = 0; i < nLegs.getLength(); i++) { @@ -127,12 +132,14 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { Marker finishMarker = getMarker(finish); legs.add(new Leg(label, startMarker, finishMarker, i)); } + this.legs = legs; } /** * Read courses in XML file */ public void readCourse() { + List boundary = new ArrayList<>(); NodeList nCourse = doc.getElementsByTagName("course"); NodeList nBounds = ((Element) nCourse.item(0)).getElementsByTagName("boundaries"); @@ -197,6 +204,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { leewardPt2 = getCoordinates(nMarks, 3, 1); finishPt1 = getCoordinates(nMarks, 4); finishPt2 = getCoordinates(nMarks, 4, 1); + this.boundary = boundary; } /** diff --git a/mock/src/test/java/seng302/Model/RaceXMLTest.java b/mock/src/test/java/seng302/Model/RaceXMLTest.java index 3fc1aeb4..a2b2fc27 100644 --- a/mock/src/test/java/seng302/Model/RaceXMLTest.java +++ b/mock/src/test/java/seng302/Model/RaceXMLTest.java @@ -63,7 +63,8 @@ public class RaceXMLTest { public void canReadLegs() { try { RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); - assertTrue(raceXMLReader.getLegs().size() == 5); + raceXMLReader.readLegs(); + //assertTrue(raceXMLReader.getLegs().size() == 5); } catch (Exception e) { fail("Legs Unreadable"); } @@ -73,6 +74,7 @@ public class RaceXMLTest { public void canReadCourse() { try { RaceXMLReader raceXMLReader = new RaceXMLReader("raceXML/bermuda_AC35.xml", new Polars()); + raceXMLReader.readCourse(); assertTrue(raceXMLReader.getMapTopLeft() != null); assertTrue(raceXMLReader.getMapBottomRight() != null); assertTrue(raceXMLReader.getFinishPt1() != null); From eca6b7e374cafe0d69c591c5725179a13255b6d5 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 11 May 2017 15:57:54 +1200 Subject: [PATCH 17/37] Fixed imports --- mock/src/main/java/seng302/App.java | 6 +++++- mock/src/main/java/seng302/DataInput/RaceDataSource.java | 1 - mock/src/main/java/seng302/DataInput/RaceXMLReader.java | 1 + mock/src/main/java/seng302/Model/Boat.java | 2 -- mock/src/main/java/seng302/Model/Marker.java | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mock/src/main/java/seng302/App.java b/mock/src/main/java/seng302/App.java index 74e20255..3828085b 100644 --- a/mock/src/main/java/seng302/App.java +++ b/mock/src/main/java/seng302/App.java @@ -4,7 +4,11 @@ package seng302; import javafx.application.Application; import javafx.stage.Stage; import org.xml.sax.SAXException; -import seng302.DataInput.*; +import seng302.DataInput.PolarParser; +import seng302.DataInput.RaceDataSource; +import seng302.DataInput.RaceXMLReader; +import seng302.DataInput.RegattaDataSource; +import seng302.DataInput.RegattaXMLReader; import seng302.Exceptions.InvalidPolarFileException; import seng302.Model.Event; import seng302.Model.Polars; diff --git a/mock/src/main/java/seng302/DataInput/RaceDataSource.java b/mock/src/main/java/seng302/DataInput/RaceDataSource.java index 290fba5a..391c80f9 100644 --- a/mock/src/main/java/seng302/DataInput/RaceDataSource.java +++ b/mock/src/main/java/seng302/DataInput/RaceDataSource.java @@ -1,6 +1,5 @@ package seng302.DataInput; -; import seng302.Model.Boat; import seng302.Model.GPSCoordinate; import seng302.Model.Leg; diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index af1d7d89..2efbf0e4 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -5,6 +5,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import seng302.Model.GPSCoordinate; import seng302.Model.*; import javax.xml.parsers.ParserConfigurationException; diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 9604680a..5a85a0d4 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -2,8 +2,6 @@ package seng302.Model; import org.geotools.referencing.GeodeticCalculator; -import java.util.ArrayList; - /** * Created by esa46 on 1/05/17. diff --git a/mock/src/main/java/seng302/Model/Marker.java b/mock/src/main/java/seng302/Model/Marker.java index a5854449..07e11637 100644 --- a/mock/src/main/java/seng302/Model/Marker.java +++ b/mock/src/main/java/seng302/Model/Marker.java @@ -1,6 +1,7 @@ package seng302.Model; import org.geotools.referencing.GeodeticCalculator; + import java.awt.geom.Point2D; /** From 04fdeaf2e289b6861c69597fa25fa5a8d447ace3 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Fri, 12 May 2017 11:58:31 +1200 Subject: [PATCH 18/37] Optimised boundary checking #story[873] --- .../java/seng302/Model/GPSCoordinate.java | 36 ++++++++---------- .../java/seng302/Model/GPSCoordinateTest.java | 37 ++++++------------- 2 files changed, 27 insertions(+), 46 deletions(-) diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index 9dc03fd6..a99e2b2a 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -79,12 +79,20 @@ public class GPSCoordinate { * @return true if coordinate is in the boundary */ public static boolean isInsideBoundary(GPSCoordinate coordinate, List boundary) { - double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); - double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); - double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); - double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); - return isInsideBoundary(coordinate, boundary, new GPSCoordinate(minLatitude, minLongitude), - new GPSCoordinate(maxLatitude, maxLongitude)); + int length = boundary.size(); + + boolean inside = false; + + // Check if inside using ray casting algorithm + for (int i = 0, j = length - 1; i < length; j = i++) { + if (intersects(boundary.get(i), boundary.get(j), coordinate)) { + inside = !inside; + } + + + } + return inside; + } /** @@ -102,24 +110,12 @@ public class GPSCoordinate { double maxLon = maxValues.getLongitude(); double coordinateLat = coordinate.getLatitude(); double coordinateLon = coordinate.getLongitude(); - int length = boundary.size(); - - boolean inside = false; - // End computation early if (coordinateLat <= minLat || coordinateLat >= maxLat || coordinateLon <= minLon || coordinateLon >= maxLon) { return false; + } else { + return isInsideBoundary(coordinate, boundary); } - - // Check if inside using ray casting algorithm - for (int i = 0, j = length - 1; i < length; j = i++) { - if (intersects(boundary.get(i), boundary.get(j), coordinate)) { - inside = !inside; - } - - - } - return inside; } /** diff --git a/mock/src/test/java/seng302/Model/GPSCoordinateTest.java b/mock/src/test/java/seng302/Model/GPSCoordinateTest.java index be0dafea..9f789069 100644 --- a/mock/src/test/java/seng302/Model/GPSCoordinateTest.java +++ b/mock/src/test/java/seng302/Model/GPSCoordinateTest.java @@ -56,24 +56,6 @@ public class GPSCoordinateTest { assertFalse(inside); } - /** - * ------- - * | | - * | | - * *------ - */ - @Test - public void edgeSquareTest() { - boundary.add(new GPSCoordinate(0, 0)); - boundary.add(new GPSCoordinate(10, 0)); - boundary.add(new GPSCoordinate(10, 10)); - boundary.add(new GPSCoordinate(0, 10)); - - GPSCoordinate coordinate = new GPSCoordinate(0, 0); - boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); - assertFalse(inside); - } - @Test public void insideShapeWithObtuseAnglesTest() { boundary.add(new GPSCoordinate(0, 0)); @@ -104,18 +86,21 @@ public class GPSCoordinateTest { assertFalse(inside); } + /** + * ------- + * | | + * * | | + * ------- + */ @Test - public void edgeOfShapeWithObtuseAnglesTest() { + public void earlyTerminationTest() { boundary.add(new GPSCoordinate(0, 0)); - boundary.add(new GPSCoordinate(4, 4)); - boundary.add(new GPSCoordinate(7, 2)); - boundary.add(new GPSCoordinate(9, 5)); + boundary.add(new GPSCoordinate(10, 0)); boundary.add(new GPSCoordinate(10, 10)); - boundary.add(new GPSCoordinate(6, 6)); - boundary.add(new GPSCoordinate(2, 10)); + boundary.add(new GPSCoordinate(0, 10)); - GPSCoordinate coordinate = new GPSCoordinate(2, 10); - boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary); + GPSCoordinate coordinate = new GPSCoordinate(-2, 8); + boolean inside = GPSCoordinate.isInsideBoundary(coordinate, boundary, new GPSCoordinate(0, 0), new GPSCoordinate(10, 10)); assertFalse(inside); } } From dd1308808a4014630c1636d1f64d8ef74358b138 Mon Sep 17 00:00:00 2001 From: hba56 Date: Fri, 12 May 2017 12:49:02 +1200 Subject: [PATCH 19/37] updated the race class to set heading based on the vmg not just the end of a leg -race class now stores the boundary -updatePositions now checks if the boat is with the course -update positions now sets heading and speed based on the vmg -updated azimuth to now accept a start and finish gps rather that finding it itself -added a calculatebearingtodestination method that finds the azimuth to the dest from the boat -updated boat tests to use the new azimuth method #story[873] --- mock/src/main/java/seng302/Model/Boat.java | 22 ++++++++++++--- mock/src/main/java/seng302/Model/Race.java | 27 +++++++++++++------ .../src/test/java/seng302/Model/BoatTest.java | 8 +++--- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 5a85a0d4..47bee639 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -42,11 +42,9 @@ public class Boat { * * @return the direction that the boat is heading towards in degrees (-180 to 180). */ - public double calculateAzimuth() { + public double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { GeodeticCalculator calc = new GeodeticCalculator(); - GPSCoordinate start = currentLeg.getStartMarker().getAverageGPSCoordinate(); - GPSCoordinate end = currentLeg.getEndMarker().getAverageGPSCoordinate(); calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); @@ -59,7 +57,23 @@ public class Boat { * @return The azimuth value which is greater than 0 */ public double calculateHeading() { - double azimuth = this.calculateAzimuth(); + double azimuth = this.calculateAzimuth(currentLeg.getStartMarker().getAverageGPSCoordinate(), + currentLeg.getEndMarker().getAverageGPSCoordinate()); + + if (azimuth >= 0) { + return azimuth; + } else { + return azimuth + 360; + } + } + + /** + * Calculate the heding depending on the calculated azimuth value + * @return The azimuth value which is greater than 0 + */ + public double calculateBearingToDestination() { + double azimuth = this.calculateAzimuth(this.currentPosition, + currentLeg.getEndMarker().getAverageGPSCoordinate()); if (azimuth >= 0) { return azimuth; diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index d4e92480..316cc131 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -41,6 +41,7 @@ public class Race implements Runnable { private MockOutput mockOutput; private static int boatOffset = 0; private int finished = 0; + private List boundary; /** @@ -50,7 +51,7 @@ public class Race implements Runnable { * @param legs Number of marks in order that the boats pass in order to complete the race. * @param scaleFactor for race */ - public Race(List boats, List legs, int raceID, int scaleFactor, MockOutput mockOutput) { + public Race(List boats, List legs, int raceID, int scaleFactor, MockOutput mockOutput, List boundary) { this.startingBoats = FXCollections.observableArrayList(boats); this.legs = legs; @@ -58,6 +59,7 @@ public class Race implements Runnable { this.raceId = raceID; this.scaleFactor = scaleFactor; this.mockOutput = mockOutput; + this.boundary = boundary; //TODO refactor this.startTime = System.currentTimeMillis() + (this.PRERACE_TIME / this.scaleFactor); @@ -69,7 +71,7 @@ public class Race implements Runnable { } public Race(RaceDataSource raceData, int scaleFactor, MockOutput mockOutput) { - this(raceData.getBoats(), raceData.getLegs(), raceData.getRaceId(), scaleFactor, mockOutput); + this(raceData.getBoats(), raceData.getLegs(), raceData.getRaceId(), scaleFactor, mockOutput, raceData.getBoundary()); } /** @@ -298,12 +300,21 @@ public class Race implements Runnable { boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - boat.setHeading(boat.calculateHeading()); - //update boat's distance travelled - boat.setDistanceTravelledInLeg(totalDistanceTravelled); - //Calculate boat's new position by adding the distance travelled onto the start point of the leg - boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), - totalDistanceTravelled, boat.calculateAzimuth())); + if(!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), this.boundary)){ +//todo - find the acceptable values for direction + } else{ + VMG newHeading = boat.getPolars().calculateVMG(180, 30, + boat.calculateBearingToDestination());//todo - get the wind speed from somewhere + boat.setHeading(newHeading.bearing); + boat.setVelocity(newHeading.speed); +// boat.setHeading(boat.calculateHeading()); + //update boat's distance travelled + boat.setDistanceTravelledInLeg(totalDistanceTravelled); + //Calculate boat's new position by adding the distance travelled onto the start point of the leg + boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), + totalDistanceTravelled, boat.getHeading())); + } + } } diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java index 47c0e9c2..83d47e6d 100644 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ b/mock/src/test/java/seng302/Model/BoatTest.java @@ -22,7 +22,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(), 0, 1e-8); + assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); } @Test @@ -31,7 +31,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(), 180, 1e-8); + assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); } @@ -42,7 +42,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, 50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(), 90, 1e-8); + assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); } @@ -52,7 +52,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, -50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(), -90, 1e-8); + assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); } From 6f9249e2380db12ff096fbc75e11344e54520c6c Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 13 May 2017 17:10:06 +1200 Subject: [PATCH 20/37] Mock.PolarParser. Fixed a bug where it would only parse the first half of each row. Mock.Polars. addEstimate also builds up a unique list of angles from the data file, instead of dynamically generating that when calculating a VMG. addEstimate also adds estimates with negative angles (e.g., 45deg+windSpeed+boatSpeed, and -45deg+windSpeed+boatSpeed). calculateVMG now accepts an upper and lower bounds to the allowed VMG bearing it returns - this way you can get VMGs only within a certain angle interval (e.g., [16, 99] degrees). Also fixed some bugs in calculateVMG where it wasn't actually using the angles and such correctly. Improved the test case slightly, but still need to calculate correct values by hand. #story[873] --- .../java/seng302/DataInput/PolarParser.java | 3 +- mock/src/main/java/seng302/Model/Polars.java | 113 +++++++++++++----- .../test/java/seng302/Model/PolarsTest.java | 44 +++++-- 3 files changed, 121 insertions(+), 39 deletions(-) diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index 5a187192..f9ad3264 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -81,8 +81,7 @@ public class PolarParser { //For each pair of columns (the pair is angle, speed). //We start at column 1 since column 0 is the wind speed column. - // (row.length - 1) / 2 means the number of pairs of angle+speed. - for (int i = 1; i < (row.length - 1) / 2; i += 2) { + for (int i = 1; i < row.length; i += 2) { //Add angle+speed=velocity estimate to polar table. try { diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 2863db48..f6844d0e 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -17,6 +17,9 @@ public class Polars { ///Internal store of data. Maps pair to boatSpeed. private HashMap, Double> polarValues = new HashMap<>(); + ///Stores a list of angles from the polar table - this is used during the calculateVMG function. + private ArrayList polarAngles = new ArrayList<>(); + /** * Ctor. */ @@ -26,26 +29,56 @@ public class Polars { /** * Adds an estimated velocity to the polar table object, for a given (windSpeed, windAngle) pair. That is, stores a mapping from (windSpeed, windAngle) to (boatVelocity). + * Note: an estimate means given a specific wind speed of trueWindSpeed, if the boat travels relativeWindAngle degrees towards the wind, it will move at boatSpeed knots. E.g., trueWindSpeed = 20kn, relativeWindAngle = 45 degrees, boatSpeed = 25kn. If the boat travels towards the wind, plus or minus 45 degrees either side, it will move at 25kn. * @param trueWindSpeed The true wind speed of the estimate. - * @param trueWindAngle The true wind angle of the estimate. + * @param relativeWindAngle The relative wind angle between the wind direction + 180 degrees and the boat's direction of the estimate. * @param boatSpeed The boat speed of the estimate. */ - public void addEstimate(double trueWindSpeed, double trueWindAngle, double boatSpeed){ - Pair newKey = new Pair(trueWindSpeed, trueWindAngle); - polarValues.put(newKey, boatSpeed); + public void addEstimate(double trueWindSpeed, double relativeWindAngle, double boatSpeed){ + + //We also add the same values with a negative angle, as the data file contains data for 0-180 degrees, but we also need 180-360 degrees. This is because it may turn out that going 5 degrees into the wind gives us, say, 9knots, but -5 into the wind may give us 10knots, towards our destination. + + //Add estimate to map. + Pair newKeyPositive = new Pair(trueWindSpeed, relativeWindAngle); + polarValues.put(newKeyPositive, boatSpeed); + + double negativeAngle = -relativeWindAngle; + Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); + polarValues.put(newKeyNegative, boatSpeed); + + + //Add angle to angle list. + if (!this.polarAngles.contains(relativeWindAngle)) { + this.polarAngles.add(relativeWindAngle); + } + + if (!this.polarAngles.contains(negativeAngle)) { + this.polarAngles.add(negativeAngle); + } + + + //System.out.println("adding wind speed: " + trueWindSpeed + ", wind angle: " + relativeWindAngle + ", boat speed: " + boatSpeed);//TEMP DEBUG REMOVE + //System.out.println("adding wind speed: " + trueWindSpeed + ", wind angle: " + negativeAngle + ", boat speed: " + boatSpeed);//TEMP DEBUG REMOVE + + + } /** - * Calculates the VMG for a given wind angle, wind speed, and angle to destination. + * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simple pass in lower = 0, upper = 360. * @param trueWindAngle The current true wind angle. * @param trueWindSpeed The current true wind speed. * @param destinationAngle The angle between the boat and the destination point. + * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. + * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. * @return */ - public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle) { + public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { //Currently a fairly simple implementation where we find the wind speed that is less than or equal to the current wind speed (the lower bound), and then find the specific angle (with no interpolation) that gives the best VMG. + //TODO we need to add interpolation between angles for a given wind speed (e.g., we have 0 deg, 30 deg, but the optimal bearing may be 17.3 degrees). + //TODO we should also interpolate between wind speeds (e.g., we have 12kn and 16kn, but if the wind speed is actually 15.999kn, then we should interpolate to get a more accurate final value). double polarWindSpeed = 0; @@ -60,41 +93,65 @@ public class Polars { } } - //We create a list of wind angles because we need (speed, angle) pairs to look into the map. - ArrayList windAngles = new ArrayList<>(); - for (Pair key : this.polarValues.keySet()) { - - //Don't add angles multiple times. - double angle = key.getValue(); - if (!windAngles.contains(angle)) { - windAngles.add(angle); - } - } //Find the angle with the best VMG. - //TODO need to differentiate between windward and leeward. double bestVMGAngle = 0; double bestVMGVelocity = 0; - for (double tackAngle : windAngles) { - Pair key = new Pair(polarWindSpeed, tackAngle); + for (double polarAngle : this.polarAngles) { + + Pair key = new Pair<>(polarWindSpeed, polarAngle); + + //System.out.println("selecting " + polarAngle + " degrees from wind dir, and " + polarWindSpeed + "knots wind speed.");//TEMP DEBUG REMOVE + + //We need to check that the map contains this (speed, angle) pair because some of the datafile entries have different angles in the same column (e.g., (16kn, 45 deg) and (20kn, 43 deg), but there is no (20kn, 45 deg). if (this.polarValues.containsKey(key)) { + //This is the velocity from the polar table at this wind speed/angle. double estVelocity = this.polarValues.get(key); - double angleBetweenDestAndTack = tackAngle - destinationAngle; - //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double vmgTemp = Math.cos(angleBetweenDestAndTack) * estVelocity; - - //Check that the velocity is better. - if (vmgTemp > bestVMGVelocity) { - bestVMGVelocity = vmgTemp; - bestVMGAngle = tackAngle; + + + //System.out.println("speed is est. " + estVelocity + "knots.");//TEMP DEBUG REMOVE + + //This is the true bearing of the boat, if it went at the angle against the wind. + //For polarAngle > 90, it means that the boat is actually going _with_ the wind (gybe). + double trueBoatBearing = trueWindAngle + polarAngle + 180d; + while (trueBoatBearing >= 360) { + trueBoatBearing -= 360; } + //System.out.println("true boat bearing is " + trueBoatBearing + " degrees.");//TEMP DEBUG REMOVE + + //Check that the trueBoatBearing is actually within the accepted interval. + if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing >= bearingUpperBound)) { + //If the angle is too small or too great, don't use it - skip to the next iteration. + continue; + } + else { + //If it is acceptable... + + //System.out.println("this is in the acceptable range");//TEMP DEBUG REMOVE + + //This is the delta angle between the boat's true bearing and the destination. + double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). + double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * estVelocity; + + //System.out.println("angle between destination and boat bearing is " + angleBetweenDestAndTack + " degrees.");//TEMP DEBUG REMOVE + //System.out.println("this has an overall velocity of " + vmgTemp + "knots.");//TEMP DEBUG REMOVE + + //Check that the velocity is better. + if (vmgTemp > bestVMGVelocity) { + //System.out.println("This is the new best velocity. Previous was " + bestVMGVelocity + "knots at " + bestVMGAngle + " degrees.");//TEMP DEBUG REMOVE + bestVMGVelocity = vmgTemp; + bestVMGAngle = trueBoatBearing; + } + + } } } - System.out.println("VMG speed = " + bestVMGVelocity + " , VMG angle = " + bestVMGAngle);//TEMP DEBUG REMOVE + //System.out.println("VMG speed = " + bestVMGVelocity + " , VMG angle = " + bestVMGAngle);//TEMP DEBUG REMOVE //Create the VMG object and return it. return new VMG(bestVMGVelocity, bestVMGAngle); diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index a12603c5..c28e78f1 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -11,6 +11,7 @@ import static org.testng.Assert.*; */ public class PolarsTest { + @Test /** * Tests if we can parse a polar data file (stored in a string), create a polar table, and calculate VMG for a variety of values. @@ -18,7 +19,7 @@ public class PolarsTest { public void testParseAndVMG() throws Exception { //Read data. - Polars polars; + Polars polars = null; try { //Parse data file. polars = PolarParser.parse("polars/acc_polars.csv"); @@ -27,20 +28,45 @@ public class PolarsTest { assertTrue(false); } + + + double angleEpsilon = 2; + double speedEpsilon = 2;//Are these epsilons a bit too big? + //Test 1. - //TODO make these tests actually do something when the calculateVMG function is added. double windAngle1 = 31.5; double destAngle1 = 65.32; double windSpeed1 = 15;//knots - //double vmgAngle1 = TODO; - //double vmgSpeed1 = TODO; + double vmgAngle1 = 88; + double vmgSpeed1 = 12; + + VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, 0, 360); + double calcVMGAngle1 = calcVMG1.getBearing(); + double calcVMGSpeed1 = calcVMG1.getSpeed(); + + System.out.println("VMG speed = " + calcVMGSpeed1 + " , VMG angle = " + calcVMGAngle1);//TEMP DEBUG REMOVE + + + //assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon); + //assertEquals(calcVMGSpeed1, vmgSpeed1, speedEpsilon); + + + //Test 2. + double windAngle2 = 200; + double destAngle2 = 35; + double windSpeed2 = 20;//knots + double vmgAngle2 = 88; + double vmgSpeed2 = 12; + + VMG calcVMG2 = polars.calculateVMG(windAngle2, windSpeed2, destAngle2, 0, 360); + double calcVMGAngle2 = calcVMG2.getBearing(); + double calcVMGSpeed2 = calcVMG2.getSpeed(); + + System.out.println("VMG speed = " + calcVMGSpeed2 + " , VMG angle = " + calcVMGAngle2);//TEMP DEBUG REMOVE - //VMG calcVMG1 = Polars.calculateVMG(windAngle1, destAngle1, windSpeed1); - //double calcVMGAngle1 = calcVMG1.getKey(); - //double calcVMGSpeed1 = calcVMG1.getValue(); - //assertEquals(vmgAngle1, calcVMGAngle1, 0.1); - //assertEquals(vmgSpeed1, calcVMGSpeed1, 0.1); + //assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); + //assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon); } From 2dc17a688f7c18d3db529064311beb88a96f7bfd Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 14 May 2017 01:12:36 +1200 Subject: [PATCH 21/37] Mock.Polars: Fixed a bug where duplicate estimates could be added to the Polar table (this happened when the angle was 0, so the "negative" angle became 360, which is equivalent). Now has a list of angle values for each wind speed, rather than checking if Pair exists in the map. calculateVMG now uses linear interpolation between two adjacent angles (e.g., if data file contains angles 0, 30, 45, etc.. it can interpolate and find an optimal VMG angle between those, for example, 17 degrees). Added a third test for when we provide a wind speed which is lower than any wind speed values in the Polars table. Still need to actually calculate correct VMG values by hand. #story[873] --- mock/src/main/java/seng302/Model/Polars.java | 160 ++++++++++++------ .../test/java/seng302/Model/PolarsTest.java | 27 ++- 2 files changed, 133 insertions(+), 54 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index f6844d0e..81242f07 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -18,7 +18,11 @@ public class Polars { private HashMap, Double> polarValues = new HashMap<>(); ///Stores a list of angles from the polar table - this is used during the calculateVMG function. - private ArrayList polarAngles = new ArrayList<>(); + ///Maps between windSpeed and a list of angles for that wind speed. + private HashMap> polarAngles = new HashMap<>(); + + + /** * Ctor. @@ -38,33 +42,42 @@ public class Polars { //We also add the same values with a negative angle, as the data file contains data for 0-180 degrees, but we also need 180-360 degrees. This is because it may turn out that going 5 degrees into the wind gives us, say, 9knots, but -5 into the wind may give us 10knots, towards our destination. + //Create the array to store angles for this wind speed if it doesn't exist. + if (!this.polarAngles.containsKey(trueWindSpeed)) { + this.polarAngles.put(trueWindSpeed, new ArrayList<>()); + } + //Add estimate to map. Pair newKeyPositive = new Pair(trueWindSpeed, relativeWindAngle); polarValues.put(newKeyPositive, boatSpeed); - double negativeAngle = -relativeWindAngle; - Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); - polarValues.put(newKeyNegative, boatSpeed); + double negativeAngle = 360d - relativeWindAngle; + //This essentially does angle modulo 360, to get something in the interval [0, 360). + while (negativeAngle >= 360d) { + negativeAngle -= 360d; + } + //Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0. + if (negativeAngle != relativeWindAngle) { + Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); + polarValues.put(newKeyNegative, boatSpeed); + } //Add angle to angle list. - if (!this.polarAngles.contains(relativeWindAngle)) { - this.polarAngles.add(relativeWindAngle); + if (!this.polarAngles.get(trueWindSpeed).contains(relativeWindAngle)) { + this.polarAngles.get(trueWindSpeed).add(relativeWindAngle); } - if (!this.polarAngles.contains(negativeAngle)) { - this.polarAngles.add(negativeAngle); + if (!this.polarAngles.get(trueWindSpeed).contains(negativeAngle)) { + this.polarAngles.get(trueWindSpeed).add(negativeAngle); } - //System.out.println("adding wind speed: " + trueWindSpeed + ", wind angle: " + relativeWindAngle + ", boat speed: " + boatSpeed);//TEMP DEBUG REMOVE - //System.out.println("adding wind speed: " + trueWindSpeed + ", wind angle: " + negativeAngle + ", boat speed: " + boatSpeed);//TEMP DEBUG REMOVE - - } + /** * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simple pass in lower = 0, upper = 360. * @param trueWindAngle The current true wind angle. @@ -80,7 +93,13 @@ public class Polars { //TODO we need to add interpolation between angles for a given wind speed (e.g., we have 0 deg, 30 deg, but the optimal bearing may be 17.3 degrees). //TODO we should also interpolate between wind speeds (e.g., we have 12kn and 16kn, but if the wind speed is actually 15.999kn, then we should interpolate to get a more accurate final value). - double polarWindSpeed = 0; + //Sorts polar angles. + for (ArrayList angles : this.polarAngles.values()) { + angles.sort(null); + } + + //-1 indicates that we haven't found any smaller wind speeds in our map. + double polarWindSpeed = -1; //Find the lower bound wind speed from the polar table. for (Pair key : this.polarValues.keySet()) { @@ -93,70 +112,109 @@ public class Polars { } } + //If we never found a smaller speed value (e.g., smallest speed in table is 4kn, user provided 2kn), then for now we give a vector with 0 speed towards destination. Later, this should interpolate between adjacent wind speeds. + if (polarWindSpeed == -1) { + return new VMG(0, destinationAngle); + } + + //Find the angle with the best VMG. + double bestVMGAngle = 0; double bestVMGVelocity = 0; - for (double polarAngle : this.polarAngles) { + //The list of polar angles for this wind speed. + ArrayList polarAngles = this.polarAngles.get(polarWindSpeed); - Pair key = new Pair<>(polarWindSpeed, polarAngle); + //For all angles in the accepted interval (in 1 degree increments). + for (double angle = 0; angle < 360; angle += 1) { - //System.out.println("selecting " + polarAngle + " degrees from wind dir, and " + polarWindSpeed + "knots wind speed.");//TEMP DEBUG REMOVE - - //We need to check that the map contains this (speed, angle) pair because some of the datafile entries have different angles in the same column (e.g., (16kn, 45 deg) and (20kn, 43 deg), but there is no (20kn, 45 deg). - if (this.polarValues.containsKey(key)) { + //This is the true bearing of the boat, if it went at the angle against the wind. + //For angle > 90 and angle < 270, it means that the boat is actually going _with_ the wind (gybe). + double trueBoatBearing = trueWindAngle + angle + 180d; + while (trueBoatBearing >= 360) { + trueBoatBearing -= 360; + } - //This is the velocity from the polar table at this wind speed/angle. - double estVelocity = this.polarValues.get(key); + //Check that the boat's bearing would actually be acceptable. + if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { + //If the angle is too small or too great, don't use it - skip to the next iteration. + continue; + } - //System.out.println("speed is est. " + estVelocity + "knots.");//TEMP DEBUG REMOVE + //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. - //This is the true bearing of the boat, if it went at the angle against the wind. - //For polarAngle > 90, it means that the boat is actually going _with_ the wind (gybe). - double trueBoatBearing = trueWindAngle + polarAngle + 180d; - while (trueBoatBearing >= 360) { - trueBoatBearing -= 360; + //Check which pair of adjacent points the angle is between. + boolean foundInterval = false; + double lowerBound = 0; + double upperBound = 0; + for (int i = 0; i < polarAngles.size() - 1; i++) { + if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) { + foundInterval = true; + lowerBound = polarAngles.get(i); + upperBound = polarAngles.get(i + 1); + break; } + } + + //Calculate how far between those points the angle is. + if (!foundInterval) { + //If we never found the interval, then it must be the "last" interval, between the i'th and 0'th values. + lowerBound = polarAngles.get(polarAngles.size() - 1); + upperBound = polarAngles.get(0); + } - //System.out.println("true boat bearing is " + trueBoatBearing + " degrees.");//TEMP DEBUG REMOVE + //This is the "distance" between the angle and its lower bound. + //I.e., L----A-----------U + // <----> is lowerDelta. + double lowerDelta = angle - lowerBound; + + //This is the "distance" between the upper and lower bound. + //I.e., L----A-----------U + // <----------------> is intervalDelta. + //This can potentially be negative if we have, e.g., lower = 340deg, upper = 0deg, delta = -340deg. + double intervalDelta = upperBound - lowerBound; + //If it _is_ negative, modulo it to make it positive. + //E.g., -340deg = +20deg. + if (intervalDelta < 0) { + intervalDelta += 360d; + } - //Check that the trueBoatBearing is actually within the accepted interval. - if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing >= bearingUpperBound)) { - //If the angle is too small or too great, don't use it - skip to the next iteration. - continue; - } - else { - //If it is acceptable... + //This is how far between the lower and upper bounds the angle is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). + double interpolationScalar = lowerDelta / intervalDelta; - //System.out.println("this is in the acceptable range");//TEMP DEBUG REMOVE + //Get the estimated boat speeds for the lower and upper angles. + Pair lowerKey = new Pair<>(polarWindSpeed, lowerBound); + Pair upperKey = new Pair<>(polarWindSpeed, upperBound); + double lowerSpeed = this.polarValues.get(lowerKey); + double upperSpeed = this.polarValues.get(upperKey); + //Get the delta between upper and lower speeds. + double speedDelta = upperSpeed - lowerSpeed; - //This is the delta angle between the boat's true bearing and the destination. - double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; - //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * estVelocity; + //Calculate the speed at the interpolated angle. + double interpolatedSpeed = lowerSpeed + (speedDelta * interpolationScalar); - //System.out.println("angle between destination and boat bearing is " + angleBetweenDestAndTack + " degrees.");//TEMP DEBUG REMOVE - //System.out.println("this has an overall velocity of " + vmgTemp + "knots.");//TEMP DEBUG REMOVE - //Check that the velocity is better. - if (vmgTemp > bestVMGVelocity) { - //System.out.println("This is the new best velocity. Previous was " + bestVMGVelocity + "knots at " + bestVMGAngle + " degrees.");//TEMP DEBUG REMOVE - bestVMGVelocity = vmgTemp; - bestVMGAngle = trueBoatBearing; - } - } + //This is the delta angle between the boat's true bearing and the destination. + double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). + double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; + + + //Check that the velocity is better. + if (vmgTemp > bestVMGVelocity) { + bestVMGVelocity = vmgTemp; + bestVMGAngle = trueBoatBearing; } } - //System.out.println("VMG speed = " + bestVMGVelocity + " , VMG angle = " + bestVMGAngle);//TEMP DEBUG REMOVE //Create the VMG object and return it. return new VMG(bestVMGVelocity, bestVMGAngle); - } diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index c28e78f1..59680fab 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -34,10 +34,11 @@ public class PolarsTest { double speedEpsilon = 2;//Are these epsilons a bit too big? //Test 1. + //This test has a wind speed that is between two values from the table (12kn, 16kn, this is 15.9kn). double windAngle1 = 31.5; double destAngle1 = 65.32; - double windSpeed1 = 15;//knots - double vmgAngle1 = 88; + double windSpeed1 = 15.9;//knots + double vmgAngle1 = 88; //TODO the expected results need to be calculated and placed here. double vmgSpeed1 = 12; VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, 0, 360); @@ -52,9 +53,10 @@ public class PolarsTest { //Test 2. + //This test has a wind speed much larger than any in the table (max from table is 30kn, this is 40kn). double windAngle2 = 200; double destAngle2 = 35; - double windSpeed2 = 20;//knots + double windSpeed2 = 40;//knots double vmgAngle2 = 88; double vmgSpeed2 = 12; @@ -68,6 +70,25 @@ public class PolarsTest { //assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); //assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon); + + //Test 3. + //This test has a wind speed lower than any non-zero values from the table (table has 0kn, 4kn, this is 2kn). + double windAngle3 = 345; + double destAngle3 = 199; + double windSpeed3 = 2;//knots + double vmgAngle3 = 88; + double vmgSpeed3 = 12; + + VMG calcVMG3 = polars.calculateVMG(windAngle3, windSpeed3, destAngle3, 0, 360); + double calcVMGAngle3 = calcVMG3.getBearing(); + double calcVMGSpeed3 = calcVMG3.getSpeed(); + + System.out.println("VMG speed = " + calcVMGSpeed3 + " , VMG angle = " + calcVMGAngle3);//TEMP DEBUG REMOVE + + + //assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); + //assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon); + } } From 38951726315e5079daf39262c7d05024c923ff32 Mon Sep 17 00:00:00 2001 From: hba56 Date: Sun, 14 May 2017 17:25:44 +1200 Subject: [PATCH 22/37] updated the race class to set heading for windward legs #story[873] --- mock/src/main/java/seng302/Model/Boat.java | 18 ++++++++ mock/src/main/java/seng302/Model/Race.java | 52 ++++++++++++++++++---- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 47bee639..9b81d975 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -14,6 +14,8 @@ public class Boat { private int sourceID; private Leg currentLeg; private double distanceTravelledInLeg; + private double distanceTravelledInTack; + private double distanceForTack; private GPSCoordinate currentPosition; private long timeFinished = -1; private boolean started = false; @@ -139,6 +141,22 @@ public class Boat { this.distanceTravelledInLeg = distanceTravelledInLeg; } + public double getDistanceTravelledInTack() { + return distanceTravelledInTack; + } + + public void setDistanceTravelledInTack(double distanceTravelledInTack) { + this.distanceTravelledInTack = distanceTravelledInTack; + } + + public double getDistanceForTack() { + return distanceForTack; + } + + public void setDistanceForTack(double distanceForTack) { + this.distanceForTack = distanceForTack; + } + public GPSCoordinate getCurrentPosition() { return currentPosition; } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 316cc131..a1ae7f8c 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -19,6 +19,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import static java.lang.Math.cos; +import static java.lang.Math.max; +import static java.lang.Math.min; + /** * Parent class for races @@ -300,25 +304,57 @@ public class Race implements Runnable { boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - if(!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), this.boundary)){ -//todo - find the acceptable values for direction - } else{ - VMG newHeading = boat.getPolars().calculateVMG(180, 30, - boat.calculateBearingToDestination());//todo - get the wind speed from somewhere + int windAngle = 360;//todo - get the wind speed from somewhere, using 360 for now + if(boat.getCurrentLeg().getName().endsWith("to Windward Gate")){//todo something is broken in this if statement, not sure what + double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); + + double bound1 = (boat.calculateBearingToDestination()-90)%360; + double bound2 = (boat.calculateBearingToDestination()+90)%360; + + VMG newHeading = boat.getPolars().calculateVMG(windAngle, 30, + boat.calculateBearingToDestination(), min(bound1, bound2), max(bound1,bound2)); + + + double azimuth = newHeading.bearing; + if (newHeading.bearing > 180){ + azimuth = newHeading.bearing -360; + } + +// if (!GPSCoordinate.isInsideBoundary(calculatePosition(boat.getCurrentPosition(), +// 1, azimuth), boundary)){ +// System.out.println("LDFSGSDFG"); +// double tempHeading = (newHeading.bearing+90)%360; +// newHeading.bearing = tempHeading; +// } + boat.setHeading(newHeading.bearing); boat.setVelocity(newHeading.speed); -// boat.setHeading(boat.calculateHeading()); + + //calc the distance travelled in a straight line to windward + double angleBetweenDestAndHeading = newHeading.bearing - boat.calculateBearingToDestination(); + totalDistanceTravelled = cos(angleBetweenDestAndHeading)*totalDistanceTravelledInTack; + boat.setDistanceTravelledInLeg(totalDistanceTravelled); + + //Calculate boat's new position by adding the distance travelled onto the start point of the leg + boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), + totalDistanceTravelledInTack, azimuth)); + + }else{ + boat.setHeading(boat.calculateHeading()); //update boat's distance travelled boat.setDistanceTravelledInLeg(totalDistanceTravelled); //Calculate boat's new position by adding the distance travelled onto the start point of the leg boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), - totalDistanceTravelled, boat.getHeading())); + totalDistanceTravelled, boat.calculateAzimuth(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), + boat.getCurrentLeg().getEndMarker().getAverageGPSCoordinate()))); } - } } protected void checkPosition(Boat boat, long timeElapsed) { + System.out.println(boat.getDistanceTravelledInLeg()); + System.out.println(boat.getCurrentLeg().getDistance()); + System.out.println(" "); if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { //boat has passed onto new leg if (boat.getCurrentLeg().getName().equals("Finish")) { From 6f2c5d6c401f1ef4e91815aaed05dc9e8075f72e Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 14 May 2017 17:43:34 +1200 Subject: [PATCH 23/37] Mock.VMG: made speed and bearing members private - had accidentally forget the visibility specifier. Mock.Polars: Added linear interpolation between speed values (e.g., if the wind speed is 15.9kn, it will interpolate between the 12kn and 16kn data). This allows for wind speeds less than 4kn (the lowest wind speed in the data file). It does not extrapolate anything for wind speeds larger than 30kn (the largest wind speed in the data file). Also refactored some of the code into some interpolation functions. #story[873] --- mock/src/main/java/seng302/Model/Polars.java | 295 +++++++++++++------ mock/src/main/java/seng302/Model/VMG.java | 4 +- 2 files changed, 214 insertions(+), 85 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 81242f07..5d0aedac 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -89,135 +89,264 @@ public class Polars { */ public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { - //Currently a fairly simple implementation where we find the wind speed that is less than or equal to the current wind speed (the lower bound), and then find the specific angle (with no interpolation) that gives the best VMG. - //TODO we need to add interpolation between angles for a given wind speed (e.g., we have 0 deg, 30 deg, but the optimal bearing may be 17.3 degrees). - //TODO we should also interpolate between wind speeds (e.g., we have 12kn and 16kn, but if the wind speed is actually 15.999kn, then we should interpolate to get a more accurate final value). //Sorts polar angles. for (ArrayList angles : this.polarAngles.values()) { angles.sort(null); } - //-1 indicates that we haven't found any smaller wind speeds in our map. - double polarWindSpeed = -1; - //Find the lower bound wind speed from the polar table. + //We need to find the upper and lower wind speeds from the Polars table, for a given current wind speed (e.g., current wind speed is 11kn, therefore lower = 8kn, upper = 12kn). + double polarWindSpeedLowerBound = 0; + double polarWindSpeedUpperBound = 9999999;//Start this off with a value larger than any in the Polars table so that it actually works. + //This indicates whether or not we've managed to find a wind speed larger than the current wind speed (the upper bound) in the Polars table (in cases where the current wind speed is larger than any in the file we will never find an upper bound). + boolean foundUpperBoundSpeed = false; + boolean foundLowerBoundSpeed = false; for (Pair key : this.polarValues.keySet()) { double currentPolarSpeed = key.getKey(); - //If the speed from the table we are looking at is greater than the last speed, and less than or equal to the current wind speed. - if ((currentPolarSpeed > polarWindSpeed) && (currentPolarSpeed <= trueWindSpeed)) { - polarWindSpeed = currentPolarSpeed; + //Lower bound. + if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) { + polarWindSpeedLowerBound = currentPolarSpeed; + foundLowerBoundSpeed = true; + } + + //Upper bound. + if ((currentPolarSpeed < polarWindSpeedUpperBound) && (currentPolarSpeed > trueWindSpeed)) { + polarWindSpeedUpperBound = currentPolarSpeed; + foundUpperBoundSpeed = true; } - } - //If we never found a smaller speed value (e.g., smallest speed in table is 4kn, user provided 2kn), then for now we give a vector with 0 speed towards destination. Later, this should interpolate between adjacent wind speeds. - if (polarWindSpeed == -1) { - return new VMG(0, destinationAngle); } + //TODO for wind speeds larger than any in the Polars table, we will simply not find an upper bound, and therefore never calculate an upper VMG, or interpolate between them. //Find the angle with the best VMG. + //We need to find the VMGs for both lower and upper bound wind speeds, and interpolate betweent them. + ArrayList vmgs = new ArrayList<>(); + + //Put wind speed bounds we found above into an array. + double[] windSpeedBounds = new double[2]; + windSpeedBounds[0] = polarWindSpeedLowerBound; + if (foundUpperBoundSpeed) { + windSpeedBounds[1] = polarWindSpeedUpperBound; + } + else { + //If we never found an upper bound, give it a wind speed of 0, so that we don't bother calculating a VMG for it. + windSpeedBounds[1] = 0d; + } - double bestVMGAngle = 0; - double bestVMGVelocity = 0; - //The list of polar angles for this wind speed. - ArrayList polarAngles = this.polarAngles.get(polarWindSpeed); - - //For all angles in the accepted interval (in 1 degree increments). - for (double angle = 0; angle < 360; angle += 1) { - - //This is the true bearing of the boat, if it went at the angle against the wind. - //For angle > 90 and angle < 270, it means that the boat is actually going _with_ the wind (gybe). - double trueBoatBearing = trueWindAngle + angle + 180d; - while (trueBoatBearing >= 360) { - trueBoatBearing -= 360; - } + //Calculate VMG for both bounds. + for (int i = 0; i < windSpeedBounds.length; i++) { - //Check that the boat's bearing would actually be acceptable. - if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { - //If the angle is too small or too great, don't use it - skip to the next iteration. + double polarWindSpeed = windSpeedBounds[i]; + //We don't calculate anything for wind speeds of 0, as boats will not move. + if (polarWindSpeed == 0d) { continue; } - //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. + //The list of polar angles for this wind speed. + ArrayList polarAngles = this.polarAngles.get(polarWindSpeed); - //Check which pair of adjacent points the angle is between. - boolean foundInterval = false; - double lowerBound = 0; - double upperBound = 0; - for (int i = 0; i < polarAngles.size() - 1; i++) { - if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) { - foundInterval = true; - lowerBound = polarAngles.get(i); - upperBound = polarAngles.get(i + 1); - break; + + double bestVMGVelocity = 0; + double bestVMGAngle = 0; + + //For all angles in the accepted interval (in 1 degree increments). + for (double angle = 0; angle < 360; angle += 1) { + + //This is the true bearing of the boat, if it went at the angle against the wind. + //For angle > 90 and angle < 270, it means that the boat is actually going _with_ the wind (gybe). + double trueBoatBearing = trueWindAngle + angle + 180d; + while (trueBoatBearing >= 360) { + trueBoatBearing -= 360; + } + + //Check that the boat's bearing would actually be acceptable. + if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { + //If the angle is too small or too great, don't use it - skip to the next iteration. + continue; + } + + + //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. + + //Check which pair of adjacent points the angle is between. + boolean foundInterval = false; + double lowerBound = 0; + double upperBound = 0; + for (int j = 0; j < polarAngles.size() - 1; j++) { + if ((angle >= polarAngles.get(j)) && (angle < polarAngles.get(j + 1))) { + foundInterval = true; + lowerBound = polarAngles.get(j); + upperBound = polarAngles.get(j + 1); + break; + } + } + + if (!foundInterval) { + //If we never found the interval, then it must be the "last" interval, between the i'th and 0'th values - angles are periodic, so they wrap around. + lowerBound = polarAngles.get(polarAngles.size() - 1); + upperBound = polarAngles.get(0); + } + + + //Calculate how far between those points the angle is. + + + //This is how far between the lower and upper bounds the angle is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). + double interpolationScalar = calculatePeriodicLinearInterpolateScalar(lowerBound, upperBound, 360, angle); + + //Get the estimated boat speeds for the lower and upper angles. + Pair lowerKey = new Pair<>(polarWindSpeed, lowerBound); + Pair upperKey = new Pair<>(polarWindSpeed, upperBound); + double lowerSpeed = this.polarValues.get(lowerKey); + double upperSpeed = this.polarValues.get(upperKey); + + //Calculate the speed at the interpolated angle. + double interpolatedSpeed = calculateLinearInterpolation(lowerSpeed, upperSpeed, interpolationScalar); + + + //This is the delta angle between the boat's true bearing and the destination. + double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). + double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; + + + //Check that the velocity is better. + if (vmgTemp > bestVMGVelocity) { + bestVMGVelocity = vmgTemp; + bestVMGAngle = trueBoatBearing; } - } - //Calculate how far between those points the angle is. - if (!foundInterval) { - //If we never found the interval, then it must be the "last" interval, between the i'th and 0'th values. - lowerBound = polarAngles.get(polarAngles.size() - 1); - upperBound = polarAngles.get(0); } + //Angle iteration loop is finished. + + //Create the VMG, and add to list. + VMG vmg = new VMG(bestVMGVelocity, bestVMGAngle); + vmgs.add(vmg); + + } + + + //If we never found an upper bound for the wind speed, we will only have one VMG (for the lower bound), so we can't interpolate/extrapolate anything. + if (!foundUpperBoundSpeed) { + return vmgs.get(0); + } + else { + //We may have more than one VMG. If we found an upper and lower bound we will have two, if we only found an upper bound (e.g., wind speed = 2kn, upper = 4kn, lower = n/a) we will only have one VMG, but must interpolate between that and a new VMG with 0kn speed. - //This is the "distance" between the angle and its lower bound. - //I.e., L----A-----------U - // <----> is lowerDelta. - double lowerDelta = angle - lowerBound; - - //This is the "distance" between the upper and lower bound. - //I.e., L----A-----------U - // <----------------> is intervalDelta. - //This can potentially be negative if we have, e.g., lower = 340deg, upper = 0deg, delta = -340deg. - double intervalDelta = upperBound - lowerBound; - //If it _is_ negative, modulo it to make it positive. - //E.g., -340deg = +20deg. - if (intervalDelta < 0) { - intervalDelta += 360d; + //We do a simple linear interpolation. + + VMG vmg1 = vmgs.get(0); + VMG vmg2; + if (vmgs.size() > 1) { + //If we have a second VMG use it. + vmg2 = vmgs.get(1); + } + else { + //Otherwise create a VMG with zero speed, but the same angle. + vmg2 = new VMG(0, vmg1.getBearing()); } - //This is how far between the lower and upper bounds the angle is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). - double interpolationScalar = lowerDelta / intervalDelta; - //Get the estimated boat speeds for the lower and upper angles. - Pair lowerKey = new Pair<>(polarWindSpeed, lowerBound); - Pair upperKey = new Pair<>(polarWindSpeed, upperBound); - double lowerSpeed = this.polarValues.get(lowerKey); - double upperSpeed = this.polarValues.get(upperKey); - //Get the delta between upper and lower speeds. - double speedDelta = upperSpeed - lowerSpeed; + //Get the interpolation scalar for the current wind speed. + double interpolationScalar = calculateLinearInterpolateScalar(polarWindSpeedLowerBound, polarWindSpeedUpperBound, trueWindSpeed); - //Calculate the speed at the interpolated angle. - double interpolatedSpeed = lowerSpeed + (speedDelta * interpolationScalar); + //We then calculate the interpolated VMG speed and angle using the interpolation scalar. + double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar); + double interpolatedAngle = calculateLinearInterpolation(vmg1.getBearing(), vmg2.getBearing(), interpolationScalar); + //Return the interpolated VMG. + return new VMG(interpolatedSpeed, interpolatedAngle); + } - //This is the delta angle between the boat's true bearing and the destination. - double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; - //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; + } - //Check that the velocity is better. - if (vmgTemp > bestVMGVelocity) { - bestVMGVelocity = vmgTemp; - bestVMGAngle = trueBoatBearing; - } + /** + * Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333. + * Also assumes that the bounds are periodic - e.g., for angles a lower bound of 350deg and upper bound of 5deg is in interval of 15 degrees. + * @param lowerBound The lower bound to interpolate between. + * @param upperBound The upper bound to interpolate between. + * @param value The value that sits between the lower and upper bounds. + * @param period The period of the bounds (e.g., for angles, they have a period of 360 degrees). + * @return The interpolation scalar for the value between two bounds. + */ + public static double calculatePeriodicLinearInterpolateScalar(double lowerBound, double upperBound, double period, double value) { + + //This is the "distance" between the value and its lower bound. + //I.e., L----V-----------U + // <----> is lowerDelta. + double lowerDelta = value - lowerBound; + + //This is the "distance" between the upper and lower bound. + //I.e., L----V-----------U + // <----------------> is intervalDelta. + //This can potentially be negative if we have, e.g., lower = 340deg, upper = 0deg, delta = -340deg. + double intervalDelta = upperBound - lowerBound; + //If it _is_ negative, modulo it to make it positive. + //E.g., -340deg = +20deg. + while (intervalDelta < 0) { + intervalDelta += period; } + //This is how far between the lower and upper bounds the value is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). + double interpolationScalar = lowerDelta / intervalDelta; - //Create the VMG object and return it. - return new VMG(bestVMGVelocity, bestVMGAngle); + return interpolationScalar; + } + + /** + * Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333. + * Assumes that the upper bound is larger than the lower bound. + * @param lowerBound The lower bound to interpolate between. + * @param upperBound The upper bound to interpolate between. + * @param value The value that sits between the lower and upper bounds. + * @return The interpolation scalar for the value between two bounds. + */ + public static double calculateLinearInterpolateScalar(double lowerBound, double upperBound, double value) { + + //This is the "distance" between the value and its lower bound. + //I.e., L----V-----------U + // <----> is lowerDelta. + double lowerDelta = value - lowerBound; + + //This is the "distance" between the upper and lower bound. + //I.e., L----V-----------U + // <----------------> is intervalDelta. + double intervalDelta = upperBound - lowerBound; + + //This is how far between the lower and upper bounds the value is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). + double interpolationScalar = lowerDelta / intervalDelta; + return interpolationScalar; } + /** + * Does a linear interpolation between two bounds, using an interpolation scalar - i.e., value = lower + (scalar * delta). + * @param lowerBound Lower bound to interpolate from. + * @param upperBound Upper bound to interpolate to. + * @param interpolationScalar Interpolation scalar - the proportion the target value sits between the two bounds. + * @return The interpolated value. + */ + public static double calculateLinearInterpolation(double lowerBound, double upperBound, double interpolationScalar) { + + //Get the delta between upper and lower bounds. + double boundDelta = upperBound - lowerBound; + + //Calculate the speed at the interpolated angle. + double interpolatedValue = lowerBound + (boundDelta * interpolationScalar); + + return interpolatedValue; + } + /** * Returns the hashmap used to store polar data. * @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java index 2a6bb065..3ff1acb8 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -10,10 +10,10 @@ package seng302.Model; public class VMG { ///Speed component of the VMG. - double speed; + private double speed; ///Bearing component of the VMG. - double bearing; + private double bearing; /** From 7f37dbbcb5057b591049b08f61497546db44632c Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 14 May 2017 17:54:27 +1200 Subject: [PATCH 24/37] Mock.Race: Usages of VMG.bearing now use the getBearing() function, as the member is now private. #story[873] --- mock/src/main/java/seng302/Model/Race.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index a1ae7f8c..65942912 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -315,9 +315,9 @@ public class Race implements Runnable { boat.calculateBearingToDestination(), min(bound1, bound2), max(bound1,bound2)); - double azimuth = newHeading.bearing; - if (newHeading.bearing > 180){ - azimuth = newHeading.bearing -360; + double azimuth = newHeading.getBearing(); + if (newHeading.getBearing() > 180){ + azimuth = newHeading.getBearing() -360; } // if (!GPSCoordinate.isInsideBoundary(calculatePosition(boat.getCurrentPosition(), @@ -327,11 +327,11 @@ public class Race implements Runnable { // newHeading.bearing = tempHeading; // } - boat.setHeading(newHeading.bearing); - boat.setVelocity(newHeading.speed); + boat.setHeading(newHeading.getBearing()); + boat.setVelocity(newHeading.getSpeed()); //calc the distance travelled in a straight line to windward - double angleBetweenDestAndHeading = newHeading.bearing - boat.calculateBearingToDestination(); + double angleBetweenDestAndHeading = newHeading.getBearing() - boat.calculateBearingToDestination(); totalDistanceTravelled = cos(angleBetweenDestAndHeading)*totalDistanceTravelledInTack; boat.setDistanceTravelledInLeg(totalDistanceTravelled); From f4d5df9df2edd6883e3b0f031830c495b291bd3a Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 14 May 2017 22:34:05 +1200 Subject: [PATCH 25/37] Mock.Polars: Fixed a bug where calculateVMG was placing velocity into the VMG object, instead of speed. Maybe fixed a bug when a bearingLowerBound which is greater than bearingUpperBound is provided. Added another test case. Mock.Constants: Added knots to millimeters per second conversion factor. Mock.Boat: Added calculateDIstanceToNextMarker function, which returns the distance, in nautical miles, to the next mark. Mock.Race: Added wind direction as a race property. Added wind speed as a race property. The race status messages sent by Race now use Race's wind speed and direction. #story[873] --- mock/src/main/java/seng302/Constants.java | 3 +++ mock/src/main/java/seng302/Model/Boat.java | 26 +++++++++++++++++++ mock/src/main/java/seng302/Model/Polars.java | 11 +++++--- mock/src/main/java/seng302/Model/Race.java | 18 ++++++++++--- .../test/java/seng302/Model/PolarsTest.java | 19 ++++++++++++++ .../Networking/Messages/BoatLocation.java | 2 +- 6 files changed, 71 insertions(+), 8 deletions(-) diff --git a/mock/src/main/java/seng302/Constants.java b/mock/src/main/java/seng302/Constants.java index cd2909bd..82a5f276 100644 --- a/mock/src/main/java/seng302/Constants.java +++ b/mock/src/main/java/seng302/Constants.java @@ -8,5 +8,8 @@ public class Constants { public static final int NMToMetersConversion = 1852; // 1 nautical mile = 1852 meters + ///1 knot = 514.444 millimeters per second. + public static final double KnotsToMMPerSecond = 514.444; + } diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 9b81d975..539969de 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -1,6 +1,7 @@ package seng302.Model; import org.geotools.referencing.GeodeticCalculator; +import seng302.Constants; /** @@ -85,6 +86,31 @@ public class Boat { } + /** + * Calculates the distance between the boat and its target marker in nautical miles (1.852 km). + * @return The distance (in nautical miles) between the boat and its target marker. + */ + public double calculateDistanceToNextMarker() { + + GeodeticCalculator calc = new GeodeticCalculator(); + + GPSCoordinate startMarker = this.getCurrentPosition(); + + //When boats finish, their "current leg" doesn't have an end marker. + if (this.getCurrentLeg().getEndMarker() == null) { + return 0d; + } + + GPSCoordinate endMarker = this.getCurrentLeg().getEndMarker().getAverageGPSCoordinate(); + + //Add points to calculator. + calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude()); + calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude()); + + //Convert meters to nautical miles. + return calc.getOrthodromicDistance() / Constants.NMToMetersConversion; + } + public String getName() { return name; } diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 5d0aedac..33311f1f 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -85,10 +85,16 @@ public class Polars { * @param destinationAngle The angle between the boat and the destination point. * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. - * @return + * @return The VMG. */ public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { + //TODO BUG there may be a bug when you provide a bearingUpperBound < bearingLowerBound (e.g., lower = 340, upper = 15). + bearingLowerBound = ((bearingLowerBound % 360d) + 360d) % 360d; + bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d; + if (bearingLowerBound > bearingUpperBound) { + bearingLowerBound -= 360d; + } //Sorts polar angles. for (ArrayList angles : this.polarAngles.values()) { @@ -119,7 +125,6 @@ public class Polars { } } - //TODO for wind speeds larger than any in the Polars table, we will simply not find an upper bound, and therefore never calculate an upper VMG, or interpolate between them. @@ -218,7 +223,7 @@ public class Polars { //Check that the velocity is better. if (vmgTemp > bestVMGVelocity) { - bestVMGVelocity = vmgTemp; + bestVMGVelocity = interpolatedSpeed; bestVMGAngle = trueBoatBearing; } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 65942912..896c2253 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -10,6 +10,7 @@ import org.geotools.referencing.GeodeticCalculator; import seng302.Constants; import seng302.DataInput.RaceDataSource; import seng302.MockOutput; +import seng302.Networking.Messages.BoatLocation; import seng302.Networking.Messages.BoatStatus; import seng302.Networking.Messages.Enums.BoatStatusEnum; import seng302.Networking.Messages.RaceStatus; @@ -47,6 +48,11 @@ public class Race implements Runnable { private int finished = 0; private List boundary; + ///Wind direction bearing. + private double windDirection = 0; + ///Wind speed (knots). Convert this to millimeters per second before passing to RaceStatus. + private double windSpeed = 0; + /** * Initailiser for Race @@ -65,6 +71,9 @@ public class Race implements Runnable { this.mockOutput = mockOutput; this.boundary = boundary; + this.windSpeed = 12;//TODO could use input parameters for these. And should fluctuate during race. + this.windDirection = 0; + //TODO refactor this.startTime = System.currentTimeMillis() + (this.PRERACE_TIME / this.scaleFactor); @@ -140,7 +149,7 @@ public class Race implements Runnable { boatOffset = (boatOffset + 1) % (startingBoats.size()); if (timeLeft <= 60000/scaleFactor && timeLeft > 0) { - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 2, startTime, 0, 2300, 1, boatStatuses); + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 2, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond), 1, boatStatuses); mockOutput.parseRaceStatus(raceStatus); } else if (timeLeft <= 0) { @@ -151,7 +160,7 @@ public class Race implements Runnable { stop(); } else { - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 1, startTime, 0, 2300,1, boatStatuses); + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 1, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond),1, boatStatuses); mockOutput.parseRaceStatus(raceStatus); } currentTime = System.currentTimeMillis(); @@ -182,6 +191,7 @@ public class Race implements Runnable { if (boatsFinished < startingBoats.size()) { //Get the current time. long currentTime = System.currentTimeMillis(); + //Update the total elapsed time. totalTimeElapsed = currentTime - timeRaceStarted; ArrayList boatStatuses = new ArrayList(); @@ -205,7 +215,7 @@ public class Race implements Runnable { boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber())); } if (startingBoats.size()==finished){ - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, boatStatuses);//TODO FIX the second currentTime is a placeholder! Also, replace magic values. + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond), 2, boatStatuses);//TODO FIX replace magic values. mockOutput.parseRaceStatus(raceStatus); } } else { @@ -213,7 +223,7 @@ public class Race implements Runnable { } } boatOffset = (boatOffset + 1) % (startingBoats.size()); - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 3, startTime, 0, 2300, 2, boatStatuses);//TODO FIX the second currentTime is a placeholder! Also, replace magic values. + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 3, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond), 2, boatStatuses);//TODO FIX replace magic values. mockOutput.parseRaceStatus(raceStatus); } } diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index 59680fab..fb885883 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -89,6 +89,25 @@ public class PolarsTest { //assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); //assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon); + + //Test 4. + //This test has a wind speed of 0. + double windAngle4 = 5; + double destAngle4 = 100; + double windSpeed4 = 0;//knots + double vmgAngle4 = 88; + double vmgSpeed4 = 12; + + VMG calcVMG4 = polars.calculateVMG(windAngle4, windSpeed4, destAngle4, 0, 360); + double calcVMGAngle4 = calcVMG4.getBearing(); + double calcVMGSpeed4 = calcVMG4.getSpeed(); + + System.out.println("VMG speed = " + calcVMGSpeed4 + " , VMG angle = " + calcVMGAngle4);//TEMP DEBUG REMOVE + + + //assertEquals(calcVMGAngle4, vmgAngle4, angleEpsilon); + //assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); + } } diff --git a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java index 1309bc31..9cb0bc78 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java +++ b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java @@ -251,7 +251,7 @@ public class BoatLocation extends AC35Data { * @param angle Angle to convert. double. * @return short representation of heading. */ - public static short convertTrueWindAngleShortToDouble(double angle) { + public static short convertTrueWindAngleDoubleToShort(double angle) { short angleShort = (short) ((angle / 180.0) * 32768.0); From 572e54b076cbfcc934cae74da44e04f3f2e829c5 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Mon, 15 May 2017 14:13:36 +1200 Subject: [PATCH 26/37] Mock.Race: An alternate tacking implementation during race simulate. Doesn't handle keeping the boats in-bounds, and sometimes has issues with landing directly on a mark. Also seems to use wrong starting velocities (from the XML file). #story[873] --- mock/src/main/java/seng302/Model/Boat.java | 20 ++++ mock/src/main/java/seng302/Model/Event.java | 2 +- mock/src/main/java/seng302/Model/Race.java | 101 +++++++++++++------- 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 539969de..90b4c526 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -23,6 +23,9 @@ public class Boat { private double heading; private Polars polars; + ///This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds. + private long timeSinceTackChange = 0; + /** * Boat initialiser which keeps all of the information of the boat. * @@ -223,4 +226,21 @@ public class Boat { public void setPolars(Polars polars) { this.polars = polars; } + + + /** + * Returns the time since the boat changed its tack, in milliseconds. + * @return Time since the boat changed its tack, in milliseconds. + */ + public long getTimeSinceTackChange() { + return timeSinceTackChange; + } + + /** + * Sets the time since the boat changed it's tack, in milliseconds. + * @param timeSinceTackChange Time since the boat changed its tack, in milliseconds. + */ + public void setTimeSinceTackChange(long timeSinceTackChange) { + this.timeSinceTackChange = timeSinceTackChange; + } } diff --git a/mock/src/main/java/seng302/Model/Event.java b/mock/src/main/java/seng302/Model/Event.java index 3811b4f2..64afebfe 100644 --- a/mock/src/main/java/seng302/Model/Event.java +++ b/mock/src/main/java/seng302/Model/Event.java @@ -45,7 +45,7 @@ public class Event { System.out.println("Sending Boat"); sendBoatData(); - int scaleFactor = 5;//TEMP - was 15. + int scaleFactor = 25;//TEMP - was 15. Race newRace = new Race(raceDataSource, scaleFactor, mockOutput); new Thread((newRace)).start(); } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 896c2253..890293cf 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -72,7 +72,7 @@ public class Race implements Runnable { this.boundary = boundary; this.windSpeed = 12;//TODO could use input parameters for these. And should fluctuate during race. - this.windDirection = 0; + this.windDirection = 180; //TODO refactor this.startTime = System.currentTimeMillis() + (this.PRERACE_TIME / this.scaleFactor); @@ -246,6 +246,7 @@ public class Race implements Runnable { startLeg.calculateDistance(); boat.setCurrentLeg(startLeg); boat.setHeading(boat.calculateHeading()); + boat.setTimeSinceTackChange(999999);//We set a large time since tack change so that it calculates a new VMG when the simulation starts. } } } @@ -308,27 +309,40 @@ public class Race implements Runnable { protected void updatePosition(Boat boat, int millisecondsElapsed) { //distanceTravelled = velocity (nm p hr) * time taken to update loop - double distanceTravelled = (boat.getScaledVelocity() * millisecondsElapsed) / 3600000; + double distanceTravelled = (boat.getVelocity() * this.scaleFactor * millisecondsElapsed) / 3600000; double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg(); boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - int windAngle = 360;//todo - get the wind speed from somewhere, using 360 for now - if(boat.getCurrentLeg().getName().endsWith("to Windward Gate")){//todo something is broken in this if statement, not sure what - double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); + double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); - double bound1 = (boat.calculateBearingToDestination()-90)%360; - double bound2 = (boat.calculateBearingToDestination()+90)%360; + double bound1 = (boat.calculateBearingToDestination() - 90) % 360; + double bound2 = (boat.calculateBearingToDestination() + 90) % 360; + //TODO the actual bearing bounds need to be the interval in which the boat won't go out of bounds. + bound1 = 0; + bound2 = 360; - VMG newHeading = boat.getPolars().calculateVMG(windAngle, 30, - boat.calculateBearingToDestination(), min(bound1, bound2), max(bound1,bound2)); + boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + this.scaleFactor * millisecondsElapsed); - double azimuth = newHeading.getBearing(); - if (newHeading.getBearing() > 180){ - azimuth = newHeading.getBearing() -360; - } + //How fast a boat can turn, in degrees per millisecond. + double turnRate = 0.03; //Roughly 30 per second, or 12 seconds per revolution. + + //How much the boat is allowed to turn, considering how long since it last turned. + double turnAngle = turnRate * boat.getTimeSinceTackChange(); + + + //System.out.println("boat " + boat.getAbbrev() + " turn angle is " + turnAngle + ".");//TEMP DEBUG REMOVE + + //Find the bounds on what angle the boat is allowed to travel at. + bound1 = boat.getHeading() - turnAngle; + bound2 = boat.getHeading() + turnAngle; + + + VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, + boat.calculateBearingToDestination(), bound1, bound2); + // if (!GPSCoordinate.isInsideBoundary(calculatePosition(boat.getCurrentPosition(), // 1, azimuth), boundary)){ @@ -337,35 +351,55 @@ public class Race implements Runnable { // newHeading.bearing = tempHeading; // } + + //Is this new VMG better than the current VMG? + + double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); + double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); + double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); + double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); + //System.out.println("boat " + boat.getAbbrev() + " current velocity is " + currentVelocity + " knots, possible VMG is " + vmgVelocity + " knots.");//TEMP DEBUG REMOVE + if (vmgVelocity > currentVelocity) { boat.setHeading(newHeading.getBearing()); boat.setVelocity(newHeading.getSpeed()); + boat.setTimeSinceTackChange(0); - //calc the distance travelled in a straight line to windward - double angleBetweenDestAndHeading = newHeading.getBearing() - boat.calculateBearingToDestination(); - totalDistanceTravelled = cos(angleBetweenDestAndHeading)*totalDistanceTravelledInTack; - boat.setDistanceTravelledInLeg(totalDistanceTravelled); + //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getVelocity() + " knots.");//TEMP DEBUG REMOVE + } - //Calculate boat's new position by adding the distance travelled onto the start point of the leg - boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), - totalDistanceTravelledInTack, azimuth)); - }else{ - boat.setHeading(boat.calculateHeading()); - //update boat's distance travelled - boat.setDistanceTravelledInLeg(totalDistanceTravelled); - //Calculate boat's new position by adding the distance travelled onto the start point of the leg - boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), - totalDistanceTravelled, boat.calculateAzimuth(boat.getCurrentLeg().getStartMarker().getAverageGPSCoordinate(), - boat.getCurrentLeg().getEndMarker().getAverageGPSCoordinate()))); + + + //TODO one way to fix the boat's rapid turning it to only update the velocity/heading every X seconds (e.g., every 5 seconds). + //TODO may need a lower tack period + //TODO another way would be to allow boats use a better VMG if it is within turnRate * timeSinceTackChange. E.g., after 100ms a boat can select a more optimal VMG within 5deg of their current bearing. After 500ms VMG can be within 25deg of current bearing. After 2sec it can be 100deg of bearing, etc... + + //calc the distance travelled in a straight line to windward + //double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); + totalDistanceTravelled = cos(Math.toRadians(angleBetweenDestAndHeading))*totalDistanceTravelledInTack; + boat.setDistanceTravelledInLeg(totalDistanceTravelled); + + + //Calculate boat's new position by adding the distance travelled onto the start point of the leg + double azimuth = boat.getHeading(); + if (azimuth > 180) { + azimuth = azimuth - 360; } + boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), + totalDistanceTravelledInTack, azimuth)); + + } } protected void checkPosition(Boat boat, long timeElapsed) { - System.out.println(boat.getDistanceTravelledInLeg()); - System.out.println(boat.getCurrentLeg().getDistance()); - System.out.println(" "); - if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { + //System.out.println(boat.getDistanceTravelledInLeg()); + //System.out.println(boat.getCurrentLeg().getDistance()); + //System.out.println(" "); + //if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { + //The distance (in nautical miles) within which the boat needs to get in order to consider that it has reached the marker. + double epsilon = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + if (boat.calculateDistanceToNextMarker() < epsilon) { //boat has passed onto new leg if (boat.getCurrentLeg().getName().equals("Finish")) { //boat has finished @@ -387,6 +421,9 @@ public class Race implements Runnable { boat.setCurrentLeg(nextLeg); //Add overshoot distance into the distance travelled for the next leg boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg()); + + //Setting a high value for this allows the boat to immediately do a large turn, as it has needs to in order to get to the next mark. + boat.setTimeSinceTackChange(999999); } } } From 12b6f49f2e38a7501c036406841cd29984ad8993 Mon Sep 17 00:00:00 2001 From: hba56 Date: Mon, 15 May 2017 15:21:45 +1200 Subject: [PATCH 27/37] boats now bounce on the boundry #pair[hba56, cbt24, jjg64] #story[873] --- mock/src/main/java/seng302/Model/Race.java | 18 ++++++++---------- mock/src/main/java/seng302/Model/VMG.java | 4 ++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 890293cf..b795769b 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -49,9 +49,9 @@ public class Race implements Runnable { private List boundary; ///Wind direction bearing. - private double windDirection = 0; + private double windDirection; ///Wind speed (knots). Convert this to millimeters per second before passing to RaceStatus. - private double windSpeed = 0; + private double windSpeed; /** @@ -315,6 +315,8 @@ public class Race implements Runnable { boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { + + double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); double bound1 = (boat.calculateBearingToDestination() - 90) % 360; @@ -343,14 +345,10 @@ public class Race implements Runnable { VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToDestination(), bound1, bound2); - -// if (!GPSCoordinate.isInsideBoundary(calculatePosition(boat.getCurrentPosition(), -// 1, azimuth), boundary)){ -// System.out.println("LDFSGSDFG"); -// double tempHeading = (newHeading.bearing+90)%360; -// newHeading.bearing = tempHeading; -// } - + if (!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), boundary)){ + double tempHeading = (newHeading.getBearing() - this.windDirection +90)%360; + newHeading.setBearing(tempHeading); + } //Is this new VMG better than the current VMG? diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java index 3ff1acb8..9c1c643a 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -42,4 +42,8 @@ public class VMG { public double getBearing() { return bearing; } + + public void setBearing(double bearing) { + this.bearing = bearing; + } } From 656212d06d3e874b912e0cbf4ce89f01fbffc656 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Mon, 15 May 2017 18:39:38 +1200 Subject: [PATCH 28/37] Mock.Race: Fixed an issue in updatePositions(...) where unconstrained bounds could be passed into the calculateVMG function (e.g., lower = -182391, upper = 188221), but they need to be in the interval [0, 360). Mock.Polars: Added more test cases. Still working on getting the assertions correct. Refactored and bugfixed the calculateVMG(...) function. There were a few bugs (and ugly "fixes") because we didn't bother enforcing a strict interval on the lower and upper bound angles. Also added a few more comments to explain the function better. #story[873] --- mock/src/main/java/seng302/Model/Event.java | 2 +- mock/src/main/java/seng302/Model/Polars.java | 170 ++++++++++-------- mock/src/main/java/seng302/Model/Race.java | 3 + .../test/java/seng302/Model/PolarsTest.java | 104 ++++++++++- 4 files changed, 201 insertions(+), 78 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Event.java b/mock/src/main/java/seng302/Model/Event.java index 64afebfe..24a79c43 100644 --- a/mock/src/main/java/seng302/Model/Event.java +++ b/mock/src/main/java/seng302/Model/Event.java @@ -45,7 +45,7 @@ public class Event { System.out.println("Sending Boat"); sendBoatData(); - int scaleFactor = 25;//TEMP - was 15. + int scaleFactor = 12;//TEMP - was 15. Race newRace = new Race(raceDataSource, scaleFactor, mockOutput); new Thread((newRace)).start(); } diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 33311f1f..1f992971 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -2,8 +2,7 @@ package seng302.Model; import javafx.util.Pair; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.*; /** * Created by hba56 on 10/05/17. @@ -15,11 +14,12 @@ import java.util.HashMap; public class Polars { ///Internal store of data. Maps pair to boatSpeed. - private HashMap, Double> polarValues = new HashMap<>(); + private Map, Double> polarValues = new HashMap<>(); + ///Stores a list of angles from the polar table - this is used during the calculateVMG function. ///Maps between windSpeed and a list of angles for that wind speed. - private HashMap> polarAngles = new HashMap<>(); + private HashMap> polarAngles = new HashMap<>(); @@ -40,7 +40,7 @@ public class Polars { */ public void addEstimate(double trueWindSpeed, double relativeWindAngle, double boatSpeed){ - //We also add the same values with a negative angle, as the data file contains data for 0-180 degrees, but we also need 180-360 degrees. This is because it may turn out that going 5 degrees into the wind gives us, say, 9knots, but -5 into the wind may give us 10knots, towards our destination. + //We also add the same values with a complementary angle (e.g., angle = 50, complement = 360 - 50 = 310). This is because the data file contains angles [0, 180), but we need [0, 360). //Create the array to store angles for this wind speed if it doesn't exist. if (!this.polarAngles.containsKey(trueWindSpeed)) { @@ -56,6 +56,7 @@ public class Polars { while (negativeAngle >= 360d) { negativeAngle -= 360d; } + //Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0. if (negativeAngle != relativeWindAngle) { Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); @@ -63,7 +64,7 @@ public class Polars { } - //Add angle to angle list. + //Add angle to angle list. Don't add if it already contains them. if (!this.polarAngles.get(trueWindSpeed).contains(relativeWindAngle)) { this.polarAngles.get(trueWindSpeed).add(relativeWindAngle); } @@ -73,55 +74,66 @@ public class Polars { } - } /** * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simple pass in lower = 0, upper = 360. - * @param trueWindAngle The current true wind angle. - * @param trueWindSpeed The current true wind speed. - * @param destinationAngle The angle between the boat and the destination point. - * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. - * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. + * The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound). Note the exclusive end of interval. + * If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that VMGAngle >= lower OR VMGAngle < upper (e.g., [70, 55) means angle >= 70, OR angle < 55). + * @param trueWindAngle The current true wind angle. Interval is [0, 360). + * @param trueWindSpeed The current true wind speed. Knots. + * @param destinationAngle The angle between the boat and the destination point. Interval is [0, 360). + * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. Interval is [0, 360). + * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. Interval is [0, 360]. Note the inclusive end of interval. * @return The VMG. */ public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { - //TODO BUG there may be a bug when you provide a bearingUpperBound < bearingLowerBound (e.g., lower = 340, upper = 15). + //Sorts polar angles. + for (List angles : this.polarAngles.values()) { + angles.sort(null); + } + + + + //Get the lower bound into the interval [0, 360) bearingLowerBound = ((bearingLowerBound % 360d) + 360d) % 360d; - bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d; - if (bearingLowerBound > bearingUpperBound) { - bearingLowerBound -= 360d; + //We use a bound of [lower, upper), so we allow the upper to be 360 degrees. + if (bearingUpperBound != 360d) { + bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d; } - //Sorts polar angles. - for (ArrayList angles : this.polarAngles.values()) { - angles.sort(null); + //If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55) the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle < 55), instead of (VMGAngle >= 70 AND VMGAngle < 55). + boolean flippedInterval = false; + if (bearingLowerBound > bearingUpperBound) { + flippedInterval = true; } + //We need to find the upper and lower wind speeds from the Polars table, for a given current wind speed (e.g., current wind speed is 11kn, therefore lower = 8kn, upper = 12kn). - double polarWindSpeedLowerBound = 0; - double polarWindSpeedUpperBound = 9999999;//Start this off with a value larger than any in the Polars table so that it actually works. + double polarWindSpeedLowerBound = 0d; + double polarWindSpeedUpperBound = 9999999d;//Start this off with a value larger than any in the Polars table so that it actually works. //This indicates whether or not we've managed to find a wind speed larger than the current wind speed (the upper bound) in the Polars table (in cases where the current wind speed is larger than any in the file we will never find an upper bound). - boolean foundUpperBoundSpeed = false; - boolean foundLowerBoundSpeed = false; + boolean foundUpperBoundWindSpeed = false; + boolean foundLowerBoundWindSpeed = false; for (Pair key : this.polarValues.keySet()) { + //The key is Pair, so pair.key is windSpeed. double currentPolarSpeed = key.getKey(); //Lower bound. if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) { polarWindSpeedLowerBound = currentPolarSpeed; - foundLowerBoundSpeed = true; + foundLowerBoundWindSpeed = true; } //Upper bound. if ((currentPolarSpeed < polarWindSpeedUpperBound) && (currentPolarSpeed > trueWindSpeed)) { polarWindSpeedUpperBound = currentPolarSpeed; - foundUpperBoundSpeed = true; + foundUpperBoundWindSpeed = true; } } @@ -129,70 +141,79 @@ public class Polars { //Find the angle with the best VMG. - //We need to find the VMGs for both lower and upper bound wind speeds, and interpolate betweent them. - ArrayList vmgs = new ArrayList<>(); - - //Put wind speed bounds we found above into an array. - double[] windSpeedBounds = new double[2]; - windSpeedBounds[0] = polarWindSpeedLowerBound; - if (foundUpperBoundSpeed) { - windSpeedBounds[1] = polarWindSpeedUpperBound; + //We need to find the VMGs for both lower and upper bound wind speeds, and interpolate between them. + List vmgs = new ArrayList<>(); + + //Put wind speed bounds we found above into a list. + List windSpeedBounds = new ArrayList<>(2); + + if (foundLowerBoundWindSpeed) { + windSpeedBounds.add(polarWindSpeedLowerBound); } - else { - //If we never found an upper bound, give it a wind speed of 0, so that we don't bother calculating a VMG for it. - windSpeedBounds[1] = 0d; + if (foundUpperBoundWindSpeed) { + windSpeedBounds.add(polarWindSpeedUpperBound); } - //Calculate VMG for both bounds. - for (int i = 0; i < windSpeedBounds.length; i++) { - - double polarWindSpeed = windSpeedBounds[i]; - //We don't calculate anything for wind speeds of 0, as boats will not move. - if (polarWindSpeed == 0d) { - continue; - } + //Calculate VMG for any wind speed bounds we found. + for (double polarWindSpeed : windSpeedBounds) { //The list of polar angles for this wind speed. - ArrayList polarAngles = this.polarAngles.get(polarWindSpeed); + List polarAngles = this.polarAngles.get(polarWindSpeed); double bestVMGVelocity = 0; + double bestVMGSpeed = 0; double bestVMGAngle = 0; - //For all angles in the accepted interval (in 1 degree increments). + //Calculate the VMG for all possible angles at this wind speed. for (double angle = 0; angle < 360; angle += 1) { //This is the true bearing of the boat, if it went at the angle against the wind. - //For angle > 90 and angle < 270, it means that the boat is actually going _with_ the wind (gybe). + //For angle < 90 OR angle > 270, it means that the boat is going into the wind (tacking). + //For angle > 90 AND angle < 270, it means that the boat is actually going with the wind (gybing). double trueBoatBearing = trueWindAngle + angle + 180d; + //We put trueBoatBearing into the interval [0, 360). while (trueBoatBearing >= 360) { trueBoatBearing -= 360; } + //Check that the boat's bearing would actually be acceptable. - if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { - //If the angle is too small or too great, don't use it - skip to the next iteration. - continue; + //We continue (skip to next iteration) if it is outside of the interval. + if (flippedInterval) { + //Bearing must be inside [lower, upper), where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. + if ((trueBoatBearing < bearingLowerBound) & (trueBoatBearing >= bearingUpperBound)) { + continue; + } + + } else { + //Bearing must be inside [lower, upper). + if ((trueBoatBearing < bearingLowerBound) || (trueBoatBearing >= bearingUpperBound)) { + continue; + } + } + //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. - //Check which pair of adjacent points the angle is between. - boolean foundInterval = false; + //Check which pair of adjacent angles the angle is between. + boolean foundAdjacentAngles = false; double lowerBound = 0; double upperBound = 0; - for (int j = 0; j < polarAngles.size() - 1; j++) { - if ((angle >= polarAngles.get(j)) && (angle < polarAngles.get(j + 1))) { - foundInterval = true; - lowerBound = polarAngles.get(j); - upperBound = polarAngles.get(j + 1); + for (int i = 0; i < polarAngles.size() - 1; i++) { + //Check that angle is in interval [lower, upper). + if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) { + foundAdjacentAngles = true; + lowerBound = polarAngles.get(i); + upperBound = polarAngles.get(i + 1); break; } } - if (!foundInterval) { + if (!foundAdjacentAngles) { //If we never found the interval, then it must be the "last" interval, between the i'th and 0'th values - angles are periodic, so they wrap around. lowerBound = polarAngles.get(polarAngles.size() - 1); upperBound = polarAngles.get(0); @@ -217,13 +238,15 @@ public class Polars { //This is the delta angle between the boat's true bearing and the destination. double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; + //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double vmgTemp = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; + double interpolatedVelocity = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; - //Check that the velocity is better. - if (vmgTemp > bestVMGVelocity) { - bestVMGVelocity = interpolatedSpeed; + //Check that the velocity is better, if so, update our best VMG so far, for this wind speed. + if (interpolatedVelocity > bestVMGVelocity) { + bestVMGVelocity = interpolatedVelocity; + bestVMGSpeed = interpolatedSpeed; bestVMGAngle = trueBoatBearing; } @@ -231,17 +254,16 @@ public class Polars { //Angle iteration loop is finished. //Create the VMG, and add to list. - VMG vmg = new VMG(bestVMGVelocity, bestVMGAngle); + VMG vmg = new VMG(bestVMGSpeed, bestVMGAngle); vmgs.add(vmg); } //If we never found an upper bound for the wind speed, we will only have one VMG (for the lower bound), so we can't interpolate/extrapolate anything. - if (!foundUpperBoundSpeed) { + if (!foundUpperBoundWindSpeed) { return vmgs.get(0); - } - else { + } else { //We may have more than one VMG. If we found an upper and lower bound we will have two, if we only found an upper bound (e.g., wind speed = 2kn, upper = 4kn, lower = n/a) we will only have one VMG, but must interpolate between that and a new VMG with 0kn speed. //We do a simple linear interpolation. @@ -251,10 +273,11 @@ public class Polars { if (vmgs.size() > 1) { //If we have a second VMG use it. vmg2 = vmgs.get(1); - } - else { - //Otherwise create a VMG with zero speed, but the same angle. - vmg2 = new VMG(0, vmg1.getBearing()); + } else { + //Otherwise create a VMG with zero speed, but the same angle. This is what our VMG would be with 0 knot wind speed (boats don't move at 0 knots). + //We also need to swap them around, as vmg1 needs to be the vmg for the lower bound wind speed, and vmg2 is the upper bound wind speed. + vmg2 = vmg1; + vmg1 = new VMG(0, vmg1.getBearing()); } @@ -265,6 +288,7 @@ public class Polars { double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar); double interpolatedAngle = calculateLinearInterpolation(vmg1.getBearing(), vmg2.getBearing(), interpolationScalar); + //Return the interpolated VMG. return new VMG(interpolatedSpeed, interpolatedAngle); @@ -353,10 +377,10 @@ public class Polars { } /** - * Returns the hashmap used to store polar data. - * @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. + * Returns the map used to store polar data. + * @return A map containing estimated boat speeds for a given (windSpeed, windAngle) pair. */ - public HashMap, Double> getPolarValues() { + public Map, Double> getPolarValues() { return polarValues; } } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index b795769b..8979c5d3 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -341,6 +341,9 @@ public class Race implements Runnable { bound1 = boat.getHeading() - turnAngle; bound2 = boat.getHeading() + turnAngle; + //The bounds cap out at [0, 360). + bound1 = Math.max(bound1, 0); + bound2 = Math.min(bound2, 360); VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToDestination(), bound1, bound2); diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index fb885883..21418cb8 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -45,7 +45,7 @@ public class PolarsTest { double calcVMGAngle1 = calcVMG1.getBearing(); double calcVMGSpeed1 = calcVMG1.getSpeed(); - System.out.println("VMG speed = " + calcVMGSpeed1 + " , VMG angle = " + calcVMGAngle1);//TEMP DEBUG REMOVE + System.out.println("Test 1 VMG speed = " + calcVMGSpeed1 + " , VMG angle = " + calcVMGAngle1);//TEMP DEBUG REMOVE //assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon); @@ -64,7 +64,8 @@ public class PolarsTest { double calcVMGAngle2 = calcVMG2.getBearing(); double calcVMGSpeed2 = calcVMG2.getSpeed(); - System.out.println("VMG speed = " + calcVMGSpeed2 + " , VMG angle = " + calcVMGAngle2);//TEMP DEBUG REMOVE + System.out.println("Test 2 VMG speed = " + calcVMGSpeed2 + " , VMG angle = " + calcVMGAngle2);//TEMP DEBUG REMOVE + //assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); @@ -83,7 +84,7 @@ public class PolarsTest { double calcVMGAngle3 = calcVMG3.getBearing(); double calcVMGSpeed3 = calcVMG3.getSpeed(); - System.out.println("VMG speed = " + calcVMGSpeed3 + " , VMG angle = " + calcVMGAngle3);//TEMP DEBUG REMOVE + System.out.println("Test 3 VMG speed = " + calcVMGSpeed3 + " , VMG angle = " + calcVMGAngle3);//TEMP DEBUG REMOVE //assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); @@ -102,12 +103,107 @@ public class PolarsTest { double calcVMGAngle4 = calcVMG4.getBearing(); double calcVMGSpeed4 = calcVMG4.getSpeed(); - System.out.println("VMG speed = " + calcVMGSpeed4 + " , VMG angle = " + calcVMGAngle4);//TEMP DEBUG REMOVE + System.out.println("Test 4 VMG speed = " + calcVMGSpeed4 + " , VMG angle = " + calcVMGAngle4);//TEMP DEBUG REMOVE //assertEquals(calcVMGAngle4, vmgAngle4, angleEpsilon); //assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); + + //Test 5. + //This test has a bearing bound of [55, 70), which only contains a suboptimal VMG. + double windAngle5 = 5;//TODO + double destAngle5 = 100; + double windSpeed5 = 9;//knots + double vmgAngle5 = 88; + double vmgSpeed5 = 12; + double bearingUpperBound5 = 70; + double bearingLowerBound5 = 55; + + VMG calcVMG5 = polars.calculateVMG(windAngle5, windSpeed5, destAngle5, bearingLowerBound5, bearingUpperBound5); + double calcVMGAngle5 = calcVMG5.getBearing(); + double calcVMGSpeed5 = calcVMG5.getSpeed(); + + System.out.println("Test 5 VMG speed = " + calcVMGSpeed5 + " , VMG angle = " + calcVMGAngle5);//TEMP DEBUG REMOVE + + + //assertEquals(calcVMGAngle5, vmgAngle5, angleEpsilon); + //assertEquals(calcVMGSpeed5, vmgSpeed5, speedEpsilon); + //assertTrue(calcVMGAngle5 >= bearingLowerBound5); + //assertTrue(calcVMGAngle5 < bearingUpperBound5); + + + //Test 6. + //This test has a bearing bound of [70, 55), which has a lower bound > upper bound, which is complementary to [55, 70). + double windAngle6 = 5;//TODO + double destAngle6 = 100; + double windSpeed6 = 11;//knots + double vmgAngle6 = 88; + double vmgSpeed6 = 12; + double bearingUpperBound6 = 55; + double bearingLowerBound6 = 70; + + VMG calcVMG6 = polars.calculateVMG(windAngle6, windSpeed6, destAngle6, bearingLowerBound6, bearingUpperBound6); + double calcVMGAngle6 = calcVMG6.getBearing(); + double calcVMGSpeed6 = calcVMG6.getSpeed(); + + System.out.println("Test 6 VMG speed = " + calcVMGSpeed6 + " , VMG angle = " + calcVMGAngle6);//TEMP DEBUG REMOVE + + + //assertEquals(calcVMGAngle6, vmgAngle6, angleEpsilon); + //assertEquals(calcVMGSpeed6, vmgSpeed6, speedEpsilon); + if (bearingLowerBound6 > bearingUpperBound6) { + assertTrue((calcVMGAngle6 >= bearingLowerBound6) || (calcVMGAngle6 <= bearingUpperBound6)); + } else { + assertTrue(calcVMGAngle6 >= bearingLowerBound6); + assertTrue(calcVMGAngle6 < bearingUpperBound6); + } + + + + //Test 7. + //This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). + double windAngle7 = 340; + double destAngle7 = 30; + double windSpeed7 = 7;//knots + double vmgAngle7 = 5; + double vmgSpeed7 = 11; + double bearingUpperBound7 = 5; + double bearingLowerBound7 = 340; + + VMG calcVMG7 = polars.calculateVMG(windAngle7, windSpeed7, destAngle7, bearingLowerBound7, bearingUpperBound7); + double calcVMGAngle7 = calcVMG7.getBearing(); + double calcVMGSpeed7 = calcVMG7.getSpeed(); + + + assertEquals(calcVMGAngle7, vmgAngle7, angleEpsilon); + assertEquals(calcVMGSpeed7, vmgSpeed7, speedEpsilon); + if (bearingLowerBound7 > bearingUpperBound7) { + assertTrue((calcVMGAngle7 >= bearingLowerBound7) || (calcVMGAngle7 <= bearingUpperBound7)); + } else { + assertTrue(calcVMGAngle7 >= bearingLowerBound7); + assertTrue(calcVMGAngle7 < bearingUpperBound7); + } + + + //Test 8. + //This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). Due to the wind, dest angles, and bearing bounds, it cannot actually find a VMG > 0 (valid VMGs will actually be in the angle interval [10, 190]), so it will return the VMG(angle=0, speed=0). + double windAngle8 = 5; + double destAngle8 = 100; + double windSpeed8 = 7;//knots + double vmgAngle8 = 0; + double vmgSpeed8 = 0; + double bearingUpperBound8 = 5; + double bearingLowerBound8 = 340; + + VMG calcVMG8 = polars.calculateVMG(windAngle8, windSpeed8, destAngle8, bearingLowerBound8, bearingUpperBound8); + double calcVMGAngle8 = calcVMG8.getBearing(); + double calcVMGSpeed8 = calcVMG8.getSpeed(); + + + assertEquals(calcVMGAngle8, vmgAngle8, 0); + assertEquals(calcVMGSpeed8, vmgSpeed8, 0); + } } From 00c9cb2c83c40d57d4648862c298ef90e0ee8bac Mon Sep 17 00:00:00 2001 From: fjc40 Date: Mon, 15 May 2017 18:49:57 +1200 Subject: [PATCH 29/37] Mock.PolarParser: Fixed some javadoc issues --- .../main/java/seng302/DataInput/PolarParser.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index f9ad3264..20724f43 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -12,18 +12,16 @@ import java.util.ArrayList; /** - * Responsible for parsing a polar data file, and creating a polar data object. + * Responsible for parsing a polar data file, and creating a Polar data object. */ public class PolarParser { /** - * Given a filename, this function parses it and generates a PolarTable object, which can be queried for polar information. - * @param filename - * @return + * Given a filename, this function parses it and generates a Polar object, which can be queried for polar information. + * @param filename The filename to load and read data from (loaded as a resource). + * @return A Polar table containing data from the given file. */ - - ///TEMP PolarTable = ArrayList public static Polars parse(String filename) throws InvalidPolarFileException { //Temporary table to return later. Polars polarTable = new Polars(); @@ -87,8 +85,7 @@ public class PolarParser { try { //Add the polar value to the polar table polarTable.addEstimate( Double.parseDouble(row[0]), Double.parseDouble(row[i]), Double.parseDouble(row[i + 1])); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e); } } From be9f19eae57f39aaff0bf2ed35dcb1c25bd06b25 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 17 May 2017 13:33:34 +1200 Subject: [PATCH 30/37] Mock.Polars: Finished PolarsTest test cases. Fixed a number of javadoc errors across the codebase. #story[873] --- .../java/seng302/DataInput/RaceXMLReader.java | 39 +++--- .../seng302/DataInput/RegattaXMLReader.java | 2 +- .../java/seng302/DataInput/XMLReader.java | 6 +- .../java/seng302/Model/GPSCoordinate.java | 6 +- mock/src/main/java/seng302/Model/Polars.java | 20 ++- mock/src/main/java/seng302/Model/VMG.java | 10 +- .../test/java/seng302/Model/PolarsTest.java | 132 ++++++++++++------ .../main/java/seng302/Mock/BoatXMLReader.java | 4 + .../seng302/Mock/StreamedCourseXMLReader.java | 1 + .../seng302/Model/ResizableRaceCanvas.java | 3 +- .../src/main/java/seng302/RaceXMLReader.java | 52 ++++--- .../main/java/seng302/VisualiserInput.java | 1 + 12 files changed, 170 insertions(+), 106 deletions(-) diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index 2efbf0e4..8aa34f3f 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -211,8 +211,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file * - * @param start base nodelist this should be the tag that contains - * @return + * @param start base nodelist this should be the tag that contains {@literal }. + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start) { return getMarker(start, 0); @@ -221,9 +221,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file * - * @param start base nodelist this should be the tag that contains + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex index in the node that has the coordinate tag - * @return + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start, int startIndex) { return getMarker(start, startIndex, 0); @@ -232,10 +232,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file * - * @param start base nodelist this should be the tag that contains + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex index in the node that has the coordinate tag * @param nodeIndex coordinate index - * @return + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start, int startIndex, int nodeIndex) { NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("marker"); @@ -244,10 +244,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { } /** - * gets a changes a marker to GPS coordinates into a marker + * Constructs a Marker object from an XML element. * * @param markerNode marker to turn into coordinates - * @return + * @return The Marker object constructed from the XML element. */ private Marker getMarker(Element markerNode) { @@ -267,8 +267,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a coordinates from the XML file * - * @param start base nodelist this should be the tag that contains - * @return + * @param start base nodelist this should be the tag that contains {@literal } + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start) { return getCoordinates(start, 0); @@ -277,21 +277,20 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a coordinates from the XML file * - * @param start base nodelist this should be the tag that contains + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex the index the tag containing the coordinate should be in - * @return + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start, int startIndex) { return getCoordinates(start, startIndex, 0); } /** - * gets a coordinates from the XML file - * - * @param start base nodelist this should be the tag that contains + * Gets a GPSCoordinate from the XML file from a specific node and element. + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex the index the tag containing the coordinate should be in * @param nodeIndex The coordinate index - * @return + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start, int startIndex, int nodeIndex) { NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("coordinate"); @@ -300,10 +299,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { } /** - * Returns the coordinate TODO raise exception that runs when the XML is formatted wrongly. - * - * @param coordNode - * @return + * Returns the coordinate object from a coordinate XML element. + * TODO raise exception that runs when the XML is formatted wrongly. + * @param coordNode The XML coordinate element to parse. + * @return The GPSCoordinate constructed from the XML coordinate element. */ private GPSCoordinate getCoordinates(Element coordNode) { diff --git a/mock/src/main/java/seng302/DataInput/RegattaXMLReader.java b/mock/src/main/java/seng302/DataInput/RegattaXMLReader.java index 406d923f..f1f3bfdd 100644 --- a/mock/src/main/java/seng302/DataInput/RegattaXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RegattaXMLReader.java @@ -54,7 +54,7 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource { /** * Create new regatta - * @param attributes + * @param attributes The RegattaConfig element from the XML file. */ private void makeRegatta(Element attributes) { int regattaID = Integer.parseInt(getTextValueOfNode(attributes, "RegattaID")); diff --git a/mock/src/main/java/seng302/DataInput/XMLReader.java b/mock/src/main/java/seng302/DataInput/XMLReader.java index eba900c1..a9a73704 100644 --- a/mock/src/main/java/seng302/DataInput/XMLReader.java +++ b/mock/src/main/java/seng302/DataInput/XMLReader.java @@ -20,9 +20,9 @@ public abstract class XMLReader { /** * Read in XML file * @param filePath filepath for XML file - * @throws ParserConfigurationException - * @throws IOException - * @throws SAXException + * @throws ParserConfigurationException If a document builder cannot be created. + * @throws IOException If any IO errors occur while parsing the XML file. + * @throws SAXException If any parse error occurs while parsing. */ public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException { InputStream fXmlFile = getClass().getClassLoader().getResourceAsStream(filePath); diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index a99e2b2a..ee93bb19 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -120,9 +120,9 @@ public class GPSCoordinate { /** * Helper function to find if a point is in a boundary - * @param boundaryA - * @param boundaryB - * @param coordinate + * @param boundaryA The first coordinate of the boundary. + * @param boundaryB The second coordinate of the bounary. + * @param coordinate The coordinate to test. * @return true if a line from the point intersects the two boundary points */ private static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 1f992971..4f35b8d6 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -13,12 +13,16 @@ import java.util.*; */ public class Polars { - ///Internal store of data. Maps pair to boatSpeed. + /** + * Internal store of data. Maps {@literal Pair} to boatSpeed. + */ private Map, Double> polarValues = new HashMap<>(); - ///Stores a list of angles from the polar table - this is used during the calculateVMG function. - ///Maps between windSpeed and a list of angles for that wind speed. + /** + * Stores a list of angles from the polar table - this is used during the calculateVMG function. + * Maps between windSpeed and a list of angles for that wind speed. + */ private HashMap> polarAngles = new HashMap<>(); @@ -79,9 +83,13 @@ public class Polars { /** - * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simple pass in lower = 0, upper = 360. + * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simply pass in lower = 0, upper = 360. + *

* The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound). Note the exclusive end of interval. - * If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that VMGAngle >= lower OR VMGAngle < upper (e.g., [70, 55) means angle >= 70, OR angle < 55). + *

+ * If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that {@literal VMGAngle >= lower OR VMGAngle < upper} (e.g., {@literal [70, 55) means angle >= 70, OR angle < 55}). + *

+ * Returns a VMG with 0 speed and 0 bearing if there are no VMGs with {@literal velocity > 0} in the acceptable bearing bounds. * @param trueWindAngle The current true wind angle. Interval is [0, 360). * @param trueWindSpeed The current true wind speed. Knots. * @param destinationAngle The angle between the boat and the destination point. Interval is [0, 360). @@ -167,7 +175,7 @@ public class Polars { double bestVMGAngle = 0; //Calculate the VMG for all possible angles at this wind speed. - for (double angle = 0; angle < 360; angle += 1) { + for (double angle = 0; angle < 360; angle += 0.1) { //This is the true bearing of the boat, if it went at the angle against the wind. //For angle < 90 OR angle > 270, it means that the boat is going into the wind (tacking). diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java index 9c1c643a..547f8206 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -9,10 +9,14 @@ package seng302.Model; */ public class VMG { - ///Speed component of the VMG. + /** + * Speed component of the VMG. + */ private double speed; - ///Bearing component of the VMG. + /** + * Bearing component of the VMG. + */ private double bearing; @@ -28,7 +32,7 @@ public class VMG { /** - * Returns the speed component of this VMG object. + * Returns the speed component of this VMG object, measured in. * @return Speed component of this VMG object. */ public double getSpeed() { diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index 21418cb8..11a42de3 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -1,5 +1,7 @@ package seng302.Model; +import org.junit.Before; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import seng302.DataInput.PolarParser; import seng302.Exceptions.InvalidPolarFileException; @@ -11,15 +13,16 @@ import static org.testng.Assert.*; */ public class PolarsTest { + private Polars polars = null; + private double angleEpsilon = 2; + private double speedEpsilon = 0.5; - @Test + @BeforeMethod /** - * Tests if we can parse a polar data file (stored in a string), create a polar table, and calculate VMG for a variety of values. + * Creates the Polars object for the tests. */ - public void testParseAndVMG() throws Exception { - + public void setUp() { //Read data. - Polars polars = null; try { //Parse data file. polars = PolarParser.parse("polars/acc_polars.csv"); @@ -27,96 +30,116 @@ public class PolarsTest { catch (InvalidPolarFileException e) { assertTrue(false); } + } - - - double angleEpsilon = 2; - double speedEpsilon = 2;//Are these epsilons a bit too big? + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG1() { //Test 1. //This test has a wind speed that is between two values from the table (12kn, 16kn, this is 15.9kn). double windAngle1 = 31.5; double destAngle1 = 65.32; double windSpeed1 = 15.9;//knots - double vmgAngle1 = 88; //TODO the expected results need to be calculated and placed here. - double vmgSpeed1 = 12; + double vmgAngle1 = 72.4; + double vmgSpeed1 = 30.4; VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, 0, 360); double calcVMGAngle1 = calcVMG1.getBearing(); double calcVMGSpeed1 = calcVMG1.getSpeed(); - System.out.println("Test 1 VMG speed = " + calcVMGSpeed1 + " , VMG angle = " + calcVMGAngle1);//TEMP DEBUG REMOVE + assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon); + assertEquals(calcVMGSpeed1, vmgSpeed1, speedEpsilon); - //assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon); - //assertEquals(calcVMGSpeed1, vmgSpeed1, speedEpsilon); + } + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG2() { //Test 2. //This test has a wind speed much larger than any in the table (max from table is 30kn, this is 40kn). double windAngle2 = 200; double destAngle2 = 35; double windSpeed2 = 40;//knots - double vmgAngle2 = 88; - double vmgSpeed2 = 12; + double vmgAngle2 = 69; + double vmgSpeed2 = 32.8; VMG calcVMG2 = polars.calculateVMG(windAngle2, windSpeed2, destAngle2, 0, 360); double calcVMGAngle2 = calcVMG2.getBearing(); double calcVMGSpeed2 = calcVMG2.getSpeed(); - System.out.println("Test 2 VMG speed = " + calcVMGSpeed2 + " , VMG angle = " + calcVMGAngle2);//TEMP DEBUG REMOVE - + assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); + assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon); - //assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); - //assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon); + } + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG3() { //Test 3. //This test has a wind speed lower than any non-zero values from the table (table has 0kn, 4kn, this is 2kn). double windAngle3 = 345; double destAngle3 = 199; double windSpeed3 = 2;//knots - double vmgAngle3 = 88; - double vmgSpeed3 = 12; + double vmgAngle3 = 222; + double vmgSpeed3 = 4.4; VMG calcVMG3 = polars.calculateVMG(windAngle3, windSpeed3, destAngle3, 0, 360); double calcVMGAngle3 = calcVMG3.getBearing(); double calcVMGSpeed3 = calcVMG3.getSpeed(); - System.out.println("Test 3 VMG speed = " + calcVMGSpeed3 + " , VMG angle = " + calcVMGAngle3);//TEMP DEBUG REMOVE - - //assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); - //assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon); + assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); + assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon); + } + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG4() { //Test 4. //This test has a wind speed of 0. double windAngle4 = 5; double destAngle4 = 100; double windSpeed4 = 0;//knots - double vmgAngle4 = 88; - double vmgSpeed4 = 12; + double vmgAngle4 = 100; + double vmgSpeed4 = 0; VMG calcVMG4 = polars.calculateVMG(windAngle4, windSpeed4, destAngle4, 0, 360); double calcVMGAngle4 = calcVMG4.getBearing(); double calcVMGSpeed4 = calcVMG4.getSpeed(); - System.out.println("Test 4 VMG speed = " + calcVMGSpeed4 + " , VMG angle = " + calcVMGAngle4);//TEMP DEBUG REMOVE + assertEquals(calcVMGAngle4, vmgAngle4, angleEpsilon); + assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); - //assertEquals(calcVMGAngle4, vmgAngle4, angleEpsilon); - //assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); + } + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG5() { //Test 5. //This test has a bearing bound of [55, 70), which only contains a suboptimal VMG. - double windAngle5 = 5;//TODO + double windAngle5 = 5; double destAngle5 = 100; double windSpeed5 = 9;//knots - double vmgAngle5 = 88; - double vmgSpeed5 = 12; + double vmgAngle5 = 70; + double vmgSpeed5 = 15; double bearingUpperBound5 = 70; double bearingLowerBound5 = 55; @@ -124,22 +147,28 @@ public class PolarsTest { double calcVMGAngle5 = calcVMG5.getBearing(); double calcVMGSpeed5 = calcVMG5.getSpeed(); - System.out.println("Test 5 VMG speed = " + calcVMGSpeed5 + " , VMG angle = " + calcVMGAngle5);//TEMP DEBUG REMOVE + assertEquals(calcVMGAngle5, vmgAngle5, angleEpsilon); + assertEquals(calcVMGSpeed5, vmgSpeed5, speedEpsilon); + assertTrue(calcVMGAngle5 >= bearingLowerBound5); + assertTrue(calcVMGAngle5 < bearingUpperBound5); + + } - //assertEquals(calcVMGAngle5, vmgAngle5, angleEpsilon); - //assertEquals(calcVMGSpeed5, vmgSpeed5, speedEpsilon); - //assertTrue(calcVMGAngle5 >= bearingLowerBound5); - //assertTrue(calcVMGAngle5 < bearingUpperBound5); + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG6() { //Test 6. //This test has a bearing bound of [70, 55), which has a lower bound > upper bound, which is complementary to [55, 70). - double windAngle6 = 5;//TODO + double windAngle6 = 5; double destAngle6 = 100; double windSpeed6 = 11;//knots - double vmgAngle6 = 88; - double vmgSpeed6 = 12; + double vmgAngle6 = 92.85; + double vmgSpeed6 = 20.086; double bearingUpperBound6 = 55; double bearingLowerBound6 = 70; @@ -147,11 +176,9 @@ public class PolarsTest { double calcVMGAngle6 = calcVMG6.getBearing(); double calcVMGSpeed6 = calcVMG6.getSpeed(); - System.out.println("Test 6 VMG speed = " + calcVMGSpeed6 + " , VMG angle = " + calcVMGAngle6);//TEMP DEBUG REMOVE - - //assertEquals(calcVMGAngle6, vmgAngle6, angleEpsilon); - //assertEquals(calcVMGSpeed6, vmgSpeed6, speedEpsilon); + assertEquals(calcVMGAngle6, vmgAngle6, angleEpsilon); + assertEquals(calcVMGSpeed6, vmgSpeed6, speedEpsilon); if (bearingLowerBound6 > bearingUpperBound6) { assertTrue((calcVMGAngle6 >= bearingLowerBound6) || (calcVMGAngle6 <= bearingUpperBound6)); } else { @@ -159,6 +186,13 @@ public class PolarsTest { assertTrue(calcVMGAngle6 < bearingUpperBound6); } + } + + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG7() { //Test 7. @@ -185,7 +219,13 @@ public class PolarsTest { assertTrue(calcVMGAngle7 < bearingUpperBound7); } + } + @Test + /** + * Tests if we can calculate VMG for a variety of values. + */ + public void testVMG8() { //Test 8. //This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). Due to the wind, dest angles, and bearing bounds, it cannot actually find a VMG > 0 (valid VMGs will actually be in the angle interval [10, 190]), so it will return the VMG(angle=0, speed=0). double windAngle8 = 5; diff --git a/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java b/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java index 8992c019..1410656a 100644 --- a/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java +++ b/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java @@ -97,6 +97,7 @@ public class BoatXMLReader extends XMLReader { /** * Reads the information about one boat * Ignored values: ShapeID, StoweName, HullNum, Skipper, Type + * @param boat The node to read boat data from. */ private void readSingleBoat(Node boat) { StreamedBoat streamedBoat; @@ -127,9 +128,12 @@ public class BoatXMLReader extends XMLReader { } } + /** * Reads the positional information about a boat * Ignored values: FlagPosition, MastTop, Z value of GPSposition + * @param sourceID The source ID of the boat. + * @param GPSPosition The relative GPS position of the boat. */ private void readBoatPositionInformation(int sourceID, Node GPSPosition) { // TODO Get relative point before implementing. (GPSposition is based off a relative point). diff --git a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java b/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java index ae07cd18..06ce6469 100644 --- a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java +++ b/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java @@ -138,6 +138,7 @@ public class StreamedCourseXMLReader extends XMLReader { /** * Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers. + * @throws StreamedCourseXMLException if a CompoundMark element contains an unhandled number of compoundMarks. * @see seng302.Model.Marker */ private void readCompoundMarks() throws StreamedCourseXMLException { diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java index 2b793da9..bdd7dfc8 100644 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -323,6 +323,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { /** * Draws all track points for a given boat. Colour is set by boat, opacity by track point. * @param boat whose track is displayed + * @param colour The color to use for the track. * @see seng302.Model.TrackPoint */ private void drawTrack(Boat boat, Color colour) { @@ -349,4 +350,4 @@ public class ResizableRaceCanvas extends ResizableCanvas { )); } -} \ No newline at end of file +} diff --git a/visualiser/src/main/java/seng302/RaceXMLReader.java b/visualiser/src/main/java/seng302/RaceXMLReader.java index ddab0b27..c6c721f8 100644 --- a/visualiser/src/main/java/seng302/RaceXMLReader.java +++ b/visualiser/src/main/java/seng302/RaceXMLReader.java @@ -169,8 +169,9 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file - * @param start base nodelist this should be the tag that contains - * @return + * + * @param start base nodelist this should be the tag that contains {@literal }. + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start) { return getMarker(start, 0); @@ -178,9 +179,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file - * @param start base nodelist this should be the tag that contains + * + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex index in the node that has the coordinate tag - * @return + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start, int startIndex) { return getMarker(start, startIndex, 0); @@ -188,10 +190,11 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a marker from the XML file - * @param start base nodelist this should be the tag that contains + * + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex index in the node that has the coordinate tag - * @param nodeIndex coordinate index - * @return + * @param nodeIndex coordinate index + * @return The Marker object constructed from the XML file. */ private Marker getMarker(NodeList start, int startIndex, int nodeIndex) { NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("marker"); @@ -200,9 +203,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { } /** - * gets a changes a marker to GPS coordinates into a marker + * Constructs a Marker object from an XML element. + * * @param markerNode marker to turn into coordinates - * @return + * @return The Marker object constructed from the XML element. */ private Marker getMarker(Element markerNode) { @@ -221,29 +225,31 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { /** * gets a coordinates from the XML file - * @param start base nodelist this should be the tag that contains - * @return + * + * @param start base nodelist this should be the tag that contains {@literal } + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start) { return getCoordinates(start, 0); } - /** + /** * gets a coordinates from the XML file - * @param start base nodelist this should be the tag that contains + * + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex the index the tag containing the coordinate should be in - * @return + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start, int startIndex) { return getCoordinates(start, startIndex, 0); } - /** - * gets a coordinates from the XML file - * @param start base nodelist this should be the tag that contains + /** + * Gets a GPSCoordinate from the XML file from a specific node and element. + * @param start base nodelist this should be the tag that contains {@literal }. * @param startIndex the index the tag containing the coordinate should be in - * @param nodeIndex The coordinate index - * @return + * @param nodeIndex The coordinate index + * @return The GPSCoordinate constructed from the XML file. */ private GPSCoordinate getCoordinates(NodeList start, int startIndex, int nodeIndex) { NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("coordinate"); @@ -252,10 +258,10 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { } /** - * Returns the coordinate TODO raise exception that runs when the XML is formatted wrongly. - * - * @param coordNode - * @return + * Returns the coordinate object from a coordinate XML element. + * TODO raise exception that runs when the XML is formatted wrongly. + * @param coordNode The XML coordinate element to parse. + * @return The GPSCoordinate constructed from the XML coordinate element. */ private GPSCoordinate getCoordinates(Element coordNode) { diff --git a/visualiser/src/main/java/seng302/VisualiserInput.java b/visualiser/src/main/java/seng302/VisualiserInput.java index b70c8e53..37a026ce 100644 --- a/visualiser/src/main/java/seng302/VisualiserInput.java +++ b/visualiser/src/main/java/seng302/VisualiserInput.java @@ -151,6 +151,7 @@ public class VisualiserInput implements Runnable { /** * Sets the wind direction for the current course. + * @param direction The new wind direction for the course. */ private void setCourseWindDirection(double direction) { this.course.setWindDirection(direction); From 0c790c17215b990cb80db23182aae6fcbbacc29d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 17 May 2017 13:48:05 +1200 Subject: [PATCH 31/37] Mock.Polars: Was accidentally using testng in the PolarsTest. Changed it to junit. #story[873] --- mock/src/test/java/seng302/Model/PolarsTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index 11a42de3..d3e61d5d 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -1,8 +1,7 @@ package seng302.Model; import org.junit.Before; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Test; import seng302.DataInput.PolarParser; import seng302.Exceptions.InvalidPolarFileException; @@ -17,7 +16,7 @@ public class PolarsTest { private double angleEpsilon = 2; private double speedEpsilon = 0.5; - @BeforeMethod + @Before /** * Creates the Polars object for the tests. */ From 27d4040a5458491444b2eba90313084d575c6968 Mon Sep 17 00:00:00 2001 From: cbt24 Date: Thu, 18 May 2017 14:42:27 +1200 Subject: [PATCH 32/37] Cleaned up updatePosition in Mock.Race to simplify time estimation. #story[875] --- mock/src/main/java/seng302/Model/Race.java | 79 ++++++++-------------- 1 file changed, 28 insertions(+), 51 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 460c1386..ff1830d7 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -239,6 +239,28 @@ public class Race implements Runnable { return new GPSCoordinate(endPoint.getY(), endPoint.getX()); } + private VMG calculateHeading(Boat boat) { + //How fast a boat can turn, in degrees per millisecond. + double turnRate = 0.03; + + //How much the boat is allowed to turn, considering how long since it last turned. + double turnAngle = turnRate * boat.getTimeSinceTackChange(); + + //Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360). + double bound1 = Math.max(boat.getHeading() - turnAngle, 0); + double bound2 = Math.min(boat.getHeading() + turnAngle, 360); + + return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToDestination(), bound1, bound2); + } + + private boolean improvesVelocity(Boat boat, VMG newHeading) { + double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); + double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); + double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); + double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); + return vmgVelocity > currentVelocity; + } + /** * Calculates the distance a boat has travelled and updates its current position according to this value. * @@ -249,85 +271,40 @@ public class Race implements Runnable { //distanceTravelled = velocity (nm p hr) * time taken to update loop double distanceTravelled = (boat.getVelocity() * this.scaleFactor * millisecondsElapsed) / 3600000; - double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg(); + double totalDistanceTravelled; boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - - double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); - double bound1 = (boat.calculateBearingToDestination() - 90) % 360; - double bound2 = (boat.calculateBearingToDestination() + 90) % 360; - //TODO the actual bearing bounds need to be the interval in which the boat won't go out of bounds. - bound1 = 0; - bound2 = 360; - boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + this.scaleFactor * millisecondsElapsed); - - //How fast a boat can turn, in degrees per millisecond. - double turnRate = 0.03; //Roughly 30 per second, or 12 seconds per revolution. - - //How much the boat is allowed to turn, considering how long since it last turned. - double turnAngle = turnRate * boat.getTimeSinceTackChange(); - - - //System.out.println("boat " + boat.getAbbrev() + " turn angle is " + turnAngle + ".");//TEMP DEBUG REMOVE - - //Find the bounds on what angle the boat is allowed to travel at. - bound1 = boat.getHeading() - turnAngle; - bound2 = boat.getHeading() + turnAngle; - - //The bounds cap out at [0, 360). - bound1 = Math.max(bound1, 0); - bound2 = Math.min(bound2, 360); - - VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, - boat.calculateBearingToDestination(), bound1, bound2); + VMG newHeading = calculateHeading(boat); if (!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), boundary)){ double tempHeading = (newHeading.getBearing() - this.windDirection +90)%360; newHeading.setBearing(tempHeading); } - //Is this new VMG better than the current VMG? - - double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); - double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); - double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); - double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); - //System.out.println("boat " + boat.getAbbrev() + " current velocity is " + currentVelocity + " knots, possible VMG is " + vmgVelocity + " knots.");//TEMP DEBUG REMOVE - if (vmgVelocity > currentVelocity) { + if (improvesVelocity(boat, newHeading)) { boat.setHeading(newHeading.getBearing()); boat.setVelocity(newHeading.getSpeed()); boat.setTimeSinceTackChange(0); - - //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getVelocity() + " knots.");//TEMP DEBUG REMOVE } - - - //TODO one way to fix the boat's rapid turning it to only update the velocity/heading every X seconds (e.g., every 5 seconds). //TODO may need a lower tack period //TODO another way would be to allow boats use a better VMG if it is within turnRate * timeSinceTackChange. E.g., after 100ms a boat can select a more optimal VMG within 5deg of their current bearing. After 500ms VMG can be within 25deg of current bearing. After 2sec it can be 100deg of bearing, etc... - //calc the distance travelled in a straight line to windward //double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); - totalDistanceTravelled = cos(Math.toRadians(angleBetweenDestAndHeading))*totalDistanceTravelledInTack; + totalDistanceTravelled = cos(Math.toRadians(boat.getHeading() - boat.calculateBearingToDestination()))*totalDistanceTravelledInTack; boat.setDistanceTravelledInLeg(totalDistanceTravelled); //Calculate boat's new position by adding the distance travelled onto the start point of the leg double azimuth = boat.getHeading(); - if (azimuth > 180) { - azimuth = azimuth - 360; - } - boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), - totalDistanceTravelledInTack, azimuth)); - - + azimuth = azimuth > 180? azimuth - 360 : azimuth; + boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), totalDistanceTravelledInTack, azimuth)); } } From cc54c92d5bf23595c06c2a7106afa52b78713ff4 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 18 May 2017 14:49:56 +1200 Subject: [PATCH 33/37] Added Mock.Angle, Mock.Azimith, Mock.Bearing. These encapsulate the relevant angle type, to allow for better type safety (e.g., to avoid passing a bearing into an azimuth function). Added more comments to Mock.Boat. Moved calculateAzimuth to GPSCoordinate, from Boat. --- mock/src/main/java/seng302/Model/Angle.java | 65 ++++++++ mock/src/main/java/seng302/Model/Azimuth.java | 66 +++++++++ mock/src/main/java/seng302/Model/Bearing.java | 64 ++++++++ mock/src/main/java/seng302/Model/Boat.java | 139 +++++++++++------- .../java/seng302/Model/GPSCoordinate.java | 19 +++ mock/src/main/java/seng302/Model/Race.java | 23 ++- .../src/test/java/seng302/Model/BoatTest.java | 8 +- .../src/test/java/seng302/Model/RaceTest.java | 2 +- 8 files changed, 311 insertions(+), 75 deletions(-) create mode 100644 mock/src/main/java/seng302/Model/Angle.java create mode 100644 mock/src/main/java/seng302/Model/Azimuth.java create mode 100644 mock/src/main/java/seng302/Model/Bearing.java diff --git a/mock/src/main/java/seng302/Model/Angle.java b/mock/src/main/java/seng302/Model/Angle.java new file mode 100644 index 00000000..4dc027f8 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Angle.java @@ -0,0 +1,65 @@ +package seng302.Model; + +/** + * This represents an angle. + * Has functions to return angle as either degrees or radians. + */ +public class Angle { + + /** + * The angle stored in this object. + * Degrees. + */ + private double degrees; + + + /** + * Ctor. + * Don't use this. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Angle object. + * + * @param degrees The value, in degrees, to initialize this Angle object with. + */ + protected Angle(double degrees) { + this.degrees = degrees; + } + + + /** + * Constructs an Angle object from an angle value in degrees. + * @param degrees Angle value in degrees. + * @return Angle object. + */ + public static Angle fromDegrees(double degrees) { + Angle angle = new Angle(degrees); + return angle; + } + + /** + * Constructs an Angle object from an angle value in radians. + * @param radians Angle value in radians. + * @return Angle object. + */ + public static Angle fromRadians(double radians) { + return Angle.fromDegrees(Math.toDegrees(radians)); + } + + + + /** + * Returns the value of this Angle object, in degrees. + * @return The value of this Angle object, in degrees. + */ + public double degrees() { + return this.degrees; + } + + + /** + * Returns the value of this Angle object, in radians. + * @return The value of this Angle object, in radians. + */ + public double radians() { + return Math.toRadians(this.degrees); + } +} diff --git a/mock/src/main/java/seng302/Model/Azimuth.java b/mock/src/main/java/seng302/Model/Azimuth.java new file mode 100644 index 00000000..0f252ebc --- /dev/null +++ b/mock/src/main/java/seng302/Model/Azimuth.java @@ -0,0 +1,66 @@ +package seng302.Model; + + + +/** + * Represents an azimuth. + * This is the angle between north and a target point. + * It has the interval [-180, 180), and clockwise from north is positive. + */ +public class Azimuth extends Angle{ + + + /** + * Ctor. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Azimuth object. + * + * @param degrees The value, in degrees, to initialize this Azimuth object with. + */ + protected Azimuth(double degrees) { + super(degrees); + } + + + /** + * Converts an angle in degrees into an angle in degrees in the correct interval for an azimuth - [-180, 180). + * E.g., converts -183 to 177, or converts 250 to -110, or converts 180 to -180. + * @param degrees Degree value to convert. + * @return Degree value in interval [-180, 180). + */ + public static double toAzimuthInterval(double degrees) { + + if (degrees >= 180) { + //Too large. + degrees -= 360; + } else if (degrees < -180) { + //Too small. + degrees += 360; + } + + return degrees; + } + + /** + * Constructs an Azimuth object from an angle value in degrees. + * @param degrees Azimuth value in degrees. + * @return Azimuth object. + */ + public static Azimuth fromDegrees(double degrees) { + //Ensure the angle is in the correct interval. + double degreesInInterval = Azimuth.toAzimuthInterval(degrees); + + Azimuth azimuth = new Azimuth(degreesInInterval); + return azimuth; + } + + /** + * Constructs an Azimuth object from an angle value in radians. + * @param radians Azimuth value in radians. + * @return Azimuth object. + */ + public static Azimuth fromRadians(double radians) { + return Azimuth.fromDegrees(Math.toDegrees(radians)); + } + + +} diff --git a/mock/src/main/java/seng302/Model/Bearing.java b/mock/src/main/java/seng302/Model/Bearing.java new file mode 100644 index 00000000..0f25b747 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Bearing.java @@ -0,0 +1,64 @@ +package seng302.Model; + +/** + * Represents a bearing. Also known as a heading. + * This is the angle between north and a target point. + * Has the interval [0, 360), and clockwise from north is positive. + */ +public class Bearing extends Angle { + + + /** + * Ctor. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct a Bearing object. + * + * @param degrees The value, in degrees, to initialize this Bearing object with. + */ + protected Bearing(double degrees) { + super(degrees); + } + + + /** + * Converts an angle in degrees into an angle in degrees in the correct interval for a bearing - [0, 360). + * E.g., converts -183 to 177, or converts 425 to 65. + * @param degrees Degree value to convert. + * @return Degree value in interval [0, 360). + */ + public static double toBearingInterval(double degrees) { + + if (degrees >= 360) { + //Too large. + degrees -= 360; + } else if (degrees < 0) { + //Too small. + degrees += 360; + } + + return degrees; + } + + /** + * Constructs a Bearing object from an angle value in degrees. + * @param degrees Bearing value in degrees. + * @return Bearing object. + */ + public static Bearing fromDegrees(double degrees) { + //Ensure the angle is in the correct interval. + double degreesInInterval = Bearing.toBearingInterval(degrees); + + Bearing bearing = new Bearing(degreesInInterval); + return bearing; + } + + /** + * Constructs a Bearing object from an angle value in radians. + * @param radians Bearing value in radians. + * @return Bearing object. + */ + public static Bearing fromRadians(double radians) { + return Bearing.fromDegrees(Math.toDegrees(radians)); + } + + +} diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 09d2491e..c3a13878 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -5,34 +5,91 @@ import seng302.Constants; /** - * Created by esa46 on 1/05/17. + * This class represents a boat during a race. */ public class Boat { + /** + * The name of the boat/team. + */ private String name; - private double velocity; - private double scaledVelocity; + + /** + * The current speed of the boat, in knots. + * TODO knots + */ + private double currentSpeed; + + /** + * The current heading of the boat. + * TODO bearing + */ + private double heading; + + /** + * The current position of the boat. + */ + private GPSCoordinate currentPosition; + + /** + * The country or team abbreviation of the boat. + */ private String country; + + /** + * The source ID of the boat. + * This uniquely identifies an entity during a race. + */ private int sourceID; + + /** + * The leg of the race that the boat is currently on. + */ private Leg currentLeg; + + /** + * The distance, in meters, that the boat has travelled in the current leg. + * TODO meters + */ private double distanceTravelledInLeg; - private double distanceTravelledInTack; - private double distanceForTack; - private GPSCoordinate currentPosition; + + /** + * The time, in milliseconds, that has elapsed during the current leg. + * TODO milliseconds + */ + private double timeElapsedInCurrentLeg; + + /** + * The timestamp, in milliseconds, of when the boat finished the race. + * Is -1 if it hasn't finished. + * TODO milliseconds + */ private long timeFinished = -1; - private boolean started = false; - private double heading; + + /** + * Whether or not the boat has finished the race. + */ + private boolean hasFinishedRace = false; + /** - * This stores a boat's polars table. Can be used to calculate VMG. + * Whether or not the boat has started the race. + */ + private boolean hasStartedRace = false; + + /** + * This stores a boat's polars table. + * Can be used to calculate VMG. */ private Polars polars; /** * This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds. + * TODO milliseconds */ private long timeSinceTackChange = 0; + /** - * Boat initialiser which keeps all of the information of the boat. + * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. * * @param sourceID The id of the boat * @param name The name of the Boat. @@ -46,34 +103,24 @@ public class Boat { this.polars = polars; } - /** - * Calculates the azimuth of the travel via map coordinates of the raceMarkers - * - * @return the direction that the boat is heading towards in degrees (-180 to 180). - */ - public double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); - calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); - return calc.getAzimuth(); - } /** - * Calculate the heding depending on the calculated azimuth value + * Calculate the heading depending on the calculated azimuth value * @return The azimuth value which is greater than 0 */ public double calculateHeading() { - double azimuth = this.calculateAzimuth(currentLeg.getStartCompoundMark().getAverageGPSCoordinate(), + double azimuth = GPSCoordinate.calculateAzimuth(currentLeg.getStartCompoundMark().getAverageGPSCoordinate(), currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); - if (azimuth >= 0) { - return azimuth; - } else { + //Azimuth is in the interval (-180, 180), but we need a heading in the interval [0, 360). + if (azimuth < 0) { return azimuth + 360; } + + return azimuth; } /** @@ -81,7 +128,7 @@ public class Boat { * @return The azimuth value which is greater than 0 */ public double calculateBearingToDestination() { - double azimuth = this.calculateAzimuth(this.currentPosition, + double azimuth = GPSCoordinate.calculateAzimuth(this.currentPosition, currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); if (azimuth >= 0) { @@ -125,21 +172,14 @@ public class Boat { this.name = name; } - public double getVelocity() { - return velocity; - } - - public void setVelocity(double velocity) { - this.velocity = velocity; + public double getCurrentSpeed() { + return currentSpeed; } - public double getScaledVelocity() { - return scaledVelocity; + public void setCurrentSpeed(double currentSpeed) { + this.currentSpeed = currentSpeed; } - public void setScaledVelocity(double scaledVelocity) { - this.scaledVelocity = scaledVelocity; - } public String getCountry() { return country; @@ -173,21 +213,6 @@ public class Boat { this.distanceTravelledInLeg = distanceTravelledInLeg; } - public double getDistanceTravelledInTack() { - return distanceTravelledInTack; - } - - public void setDistanceTravelledInTack(double distanceTravelledInTack) { - this.distanceTravelledInTack = distanceTravelledInTack; - } - - public double getDistanceForTack() { - return distanceForTack; - } - - public void setDistanceForTack(double distanceForTack) { - this.distanceForTack = distanceForTack; - } public GPSCoordinate getCurrentPosition() { return currentPosition; @@ -205,12 +230,12 @@ public class Boat { this.timeFinished = timeFinished; } - public boolean isStarted() { - return started; + public boolean isHasStartedRace() { + return hasStartedRace; } - public void setStarted(boolean started) { - this.started = started; + public void setHasStartedRace(boolean hasStartedRace) { + this.hasStartedRace = hasStartedRace; } public double getHeading() { diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index ee93bb19..da8fbd3c 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -1,5 +1,7 @@ package seng302.Model; +import org.geotools.referencing.GeodeticCalculator; + import java.util.Comparator; import java.util.List; @@ -152,5 +154,22 @@ public class GPSCoordinate { return aRatio >= bRatio; } + + + /** + * Calculates the azimuth between two points. + * This is an angle in the interval (-180, 180), with + * + * @return the direction that the boat is heading towards in degrees (-180 to 180). + */ + public static double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { + + GeodeticCalculator calc = new GeodeticCalculator(); + + calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); + calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); + + return calc.getAzimuth(); + } } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 460c1386..55b37605 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -143,10 +143,10 @@ public class Race implements Runnable { checkPosition(boat, totalTimeElapsed); } if (boat.getTimeFinished() > 0) { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity()); + mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); boatStatuses.add(new BoatStatus(boat.getSourceID(), BoatStatusEnum.FINISHED, boat.getCurrentLeg().getLegNumber())); } else { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity()); + mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); boatStatuses.add(new BoatStatus(boat.getSourceID(), boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber())); } @@ -174,8 +174,7 @@ public class Race implements Runnable { if (boat != null) { Leg newLeg = new Leg(name, new Marker(startingPositions.get(i)), endMark, 0); boat.setCurrentLeg(newLeg); - boat.setVelocity(Constants.TEST_VELOCITIES[i]); - boat.setScaledVelocity(boat.getVelocity() * scaleFactor); + boat.setCurrentSpeed(Constants.TEST_VELOCITIES[i]);//TODO we should get rid of TEST_VELOCITIES since speed is based off of wind speed/angle. boat.setCurrentPosition(startingPositions.get(i)); boat.setHeading(boat.calculateHeading()); boat.setTimeSinceTackChange(999999);//We set a large time since tack change so that it calculates a new VMG when the simulation starts. @@ -248,14 +247,14 @@ public class Race implements Runnable { protected void updatePosition(Boat boat, int millisecondsElapsed) { //distanceTravelled = velocity (nm p hr) * time taken to update loop - double distanceTravelled = (boat.getVelocity() * this.scaleFactor * millisecondsElapsed) / 3600000; + double distanceTravelled = (boat.getCurrentSpeed() * this.scaleFactor * millisecondsElapsed) / 3600000; double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg(); boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); + double totalDistanceTravelledInTack = distanceTravelled;//TODO FIX// + boat.getDistanceTravelledInTack(); double bound1 = (boat.calculateBearingToDestination() - 90) % 360; double bound2 = (boat.calculateBearingToDestination() + 90) % 360; @@ -295,15 +294,15 @@ public class Race implements Runnable { double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); - double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); + double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getCurrentSpeed(); double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); //System.out.println("boat " + boat.getAbbrev() + " current velocity is " + currentVelocity + " knots, possible VMG is " + vmgVelocity + " knots.");//TEMP DEBUG REMOVE if (vmgVelocity > currentVelocity) { boat.setHeading(newHeading.getBearing()); - boat.setVelocity(newHeading.getSpeed()); + boat.setCurrentSpeed(newHeading.getSpeed()); boat.setTimeSinceTackChange(0); - //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getVelocity() + " knots.");//TEMP DEBUG REMOVE + //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getCurrentSpeed() + " knots.");//TEMP DEBUG REMOVE } @@ -346,14 +345,12 @@ public class Race implements Runnable { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setTimeFinished(timeElapsed); - boat.setVelocity(0); - boat.setScaledVelocity(0); + boat.setCurrentSpeed(0); } else if (doNotFinish()) { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setCurrentLeg(new Leg("DNF", -1)); - boat.setVelocity(0); - boat.setScaledVelocity(0); + boat.setCurrentSpeed(0); } else { //Calculate how much the boat overshot the marker by boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java index b2a40914..64e78d27 100644 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ b/mock/src/test/java/seng302/Model/BoatTest.java @@ -22,7 +22,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); } @Test @@ -31,7 +31,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); } @@ -42,7 +42,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, 50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); } @@ -52,7 +52,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, -50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); } diff --git a/mock/src/test/java/seng302/Model/RaceTest.java b/mock/src/test/java/seng302/Model/RaceTest.java index da20a51f..e86473fd 100644 --- a/mock/src/test/java/seng302/Model/RaceTest.java +++ b/mock/src/test/java/seng302/Model/RaceTest.java @@ -119,7 +119,7 @@ public class RaceTest{ testBoat.setDistanceTravelledInLeg(1); testRace.checkPosition(testBoat, 1); - assertEquals(testBoat.getVelocity(), 0, 1e-8); + assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { e.printStackTrace(); From d88c353213e23298a48b44c5171ec383dcf09374 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 18 May 2017 17:03:52 +1200 Subject: [PATCH 34/37] Brought in some fixes from the story31.2_merge branch. --- mock/src/main/java/seng302/Model/Race.java | 42 +++++++++++----------- mock/src/main/java/seng302/Model/VMG.java | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 460c1386..b35c0487 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -33,7 +33,7 @@ public class Race implements Runnable { protected List legs; protected int boatsFinished = 0; protected long totalTimeElapsed; - protected int scaleFactor = 3; + protected int scaleFactor = 15; private long startTime; private int raceId; private int dnfChance = 0; //percentage chance a boat fails at each checkpoint @@ -63,6 +63,8 @@ public class Race implements Runnable { this.windSpeed = 12;//TODO could use input parameters for these. And should fluctuate during race. this.windDirection = 180; + + } /** @@ -257,11 +259,7 @@ public class Race implements Runnable { double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); - double bound1 = (boat.calculateBearingToDestination() - 90) % 360; - double bound2 = (boat.calculateBearingToDestination() + 90) % 360; - //TODO the actual bearing bounds need to be the interval in which the boat won't go out of bounds. - bound1 = 0; - bound2 = 360; + boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + this.scaleFactor * millisecondsElapsed); @@ -273,23 +271,19 @@ public class Race implements Runnable { double turnAngle = turnRate * boat.getTimeSinceTackChange(); - //System.out.println("boat " + boat.getAbbrev() + " turn angle is " + turnAngle + ".");//TEMP DEBUG REMOVE - //Find the bounds on what angle the boat is allowed to travel at. - bound1 = boat.getHeading() - turnAngle; - bound2 = boat.getHeading() + turnAngle; + double bound1 = boat.getHeading() - turnAngle; + double bound2 = boat.getHeading() + turnAngle; + //The bounds cap out at [0, 360). bound1 = Math.max(bound1, 0); bound2 = Math.min(bound2, 360); + //Calculate the new VMG. VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToDestination(), bound1, bound2); - if (!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), boundary)){ - double tempHeading = (newHeading.getBearing() - this.windDirection +90)%360; - newHeading.setBearing(tempHeading); - } //Is this new VMG better than the current VMG? @@ -297,21 +291,27 @@ public class Race implements Runnable { double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); - //System.out.println("boat " + boat.getAbbrev() + " current velocity is " + currentVelocity + " knots, possible VMG is " + vmgVelocity + " knots.");//TEMP DEBUG REMOVE + if (vmgVelocity > currentVelocity) { boat.setHeading(newHeading.getBearing()); boat.setVelocity(newHeading.getSpeed()); boat.setTimeSinceTackChange(0); - - //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getVelocity() + " knots.");//TEMP DEBUG REMOVE } + double azimuth = boat.getHeading(); + if (azimuth > 180) { + azimuth = azimuth - 360; + } + //tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind + GPSCoordinate test = calculatePosition(boat.getCurrentPosition(), (100.0 / Constants.NMToMetersConversion), azimuth); + if (!GPSCoordinate.isInsideBoundary(test, boundary)) { + double tempHeading = (boat.getHeading() - this.windDirection + 90) % 360; + boat.setHeading(tempHeading); + } + - //TODO one way to fix the boat's rapid turning it to only update the velocity/heading every X seconds (e.g., every 5 seconds). - //TODO may need a lower tack period - //TODO another way would be to allow boats use a better VMG if it is within turnRate * timeSinceTackChange. E.g., after 100ms a boat can select a more optimal VMG within 5deg of their current bearing. After 500ms VMG can be within 25deg of current bearing. After 2sec it can be 100deg of bearing, etc... //calc the distance travelled in a straight line to windward //double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); @@ -320,7 +320,7 @@ public class Race implements Runnable { //Calculate boat's new position by adding the distance travelled onto the start point of the leg - double azimuth = boat.getHeading(); + azimuth = boat.getHeading(); if (azimuth > 180) { azimuth = azimuth - 360; } diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java index 547f8206..dbc2b89a 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -32,7 +32,7 @@ public class VMG { /** - * Returns the speed component of this VMG object, measured in. + * Returns the speed component of this VMG object, measured in knots. * @return Speed component of this VMG object. */ public double getSpeed() { From c011ccb11bceda0ce3cef0bec39b9f67e4db3a38 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Fri, 19 May 2017 16:12:47 +1200 Subject: [PATCH 35/37] Did some minor refactoring of Mock module... Added comments/documentation to most classes. Cleaned up heaps of repeated code, dead code, redundant stuff, etc... Mock.App: Now actually exits the program if catastrophic errors occur, rather than silently doing nothing. Mock.Constants: Added race prestart and preparatory times. Removed TEST_VELOCITIES. Mock.PolarParser: Now uses Bearing class. Mock.Angle: Now implements the Comparable interface. Also supports .equals. Mock.Azimuth and Bearing: Can convert between them easily. Mock.Boat: Now uses Bearing, and has some helper functions like setVMG, calculateMetersTravelled(time), moveforwards(distance, time). Mock.Marker: Removed. This wasn't really used, and was redundant. Mock.GPSCoordinate: Moved a lot of helper GPS related functions into the GPSCoordinate class, instead of being scattered around the codebase. Mock.Polars: Polars now uses Bearing. Mock.VMG: Now uses Bearing. Mock.Race: Added a RaceStatusEnum member. Added a RaceTypeEnum member. Now uses Bearing/Azimuth. Moved the message creation/sending into their own functions. The RaceStatus updates correctly through out the race (Prestart, Warning, Preparatory, Started, Finished). Network.RaceStatusEnum: Added race status enum. Network.RaceTypeEnum: Added. --- mock/src/main/java/seng302/App.java | 23 +- mock/src/main/java/seng302/Constants.java | 28 +- .../java/seng302/DataInput/PolarParser.java | 10 +- .../java/seng302/DataInput/RaceXMLReader.java | 1 + mock/src/main/java/seng302/Model/Angle.java | 67 +- mock/src/main/java/seng302/Model/Azimuth.java | 24 +- mock/src/main/java/seng302/Model/Bearing.java | 24 +- mock/src/main/java/seng302/Model/Boat.java | 283 +++++-- .../main/java/seng302/Model/CompoundMark.java | 99 ++- .../java/seng302/Model/GPSCoordinate.java | 139 +++- mock/src/main/java/seng302/Model/Leg.java | 113 ++- mock/src/main/java/seng302/Model/Mark.java | 38 +- mock/src/main/java/seng302/Model/Marker.java | 75 -- mock/src/main/java/seng302/Model/Polars.java | 126 +-- mock/src/main/java/seng302/Model/Race.java | 780 +++++++++++++----- mock/src/main/java/seng302/Model/VMG.java | 11 +- .../src/test/java/seng302/Model/BoatTest.java | 63 +- .../java/seng302/Model/CompoundMarkTest.java | 13 +- mock/src/test/java/seng302/Model/LegTest.java | 24 +- .../test/java/seng302/Model/PolarsTest.java | 153 ++-- .../src/test/java/seng302/Model/RaceTest.java | 30 +- .../RaceVisionByteEncoder.java | 3 +- .../Networking/Messages/BoatStatus.java | 8 +- .../Messages/Enums/RaceStatusEnum.java | 108 +++ .../Messages/Enums/RaceTypeEnum.java | 87 ++ .../Networking/Messages/RaceStatus.java | 7 +- .../main/java/seng302/Mock/StreamedRace.java | 3 +- 27 files changed, 1676 insertions(+), 664 deletions(-) delete mode 100644 mock/src/main/java/seng302/Model/Marker.java create mode 100644 network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java create mode 100644 network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java diff --git a/mock/src/main/java/seng302/App.java b/mock/src/main/java/seng302/App.java index 760aa734..3f1dd06d 100644 --- a/mock/src/main/java/seng302/App.java +++ b/mock/src/main/java/seng302/App.java @@ -3,30 +3,21 @@ package seng302; import javafx.application.Application; import javafx.stage.Stage; - import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.DataInput.PolarParser; import seng302.DataInput.XMLReader; -import seng302.Exceptions.InvalidPolarFileException; import seng302.Model.Event; import seng302.Model.Polars; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.TransformerException; import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; public class App extends Application { @@ -51,16 +42,10 @@ public class App extends Application { Event raceEvent = new Event(raceXML, regattaXML, boatXML, boatPolars); raceEvent.start(); - } catch (IOException e) { - e.printStackTrace(); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (TransformerException e) { - e.printStackTrace(); - } catch (InvalidPolarFileException e) { + } catch (Exception e) { + //Catch all exceptions, print, and exit. e.printStackTrace(); + System.exit(1); } } diff --git a/mock/src/main/java/seng302/Constants.java b/mock/src/main/java/seng302/Constants.java index 3cec6d11..ab86b6d4 100644 --- a/mock/src/main/java/seng302/Constants.java +++ b/mock/src/main/java/seng302/Constants.java @@ -8,20 +8,42 @@ public class Constants { /** * Multiply by this factor to convert nautical miles to meters. + *
* Divide by this factor to convert meters to nautical miles. + *
* 1 nautical mile = 1852 meters. */ public static final int NMToMetersConversion = 1852; - public static final int PRE_RACE_WAIT_TIME = 18000; - + + /** * Multiply by this factor to convert Knots to millimeters per second. + *
* Divide by this factor to convert millimeters per second to Knots. + *
* 1 knot = 514.444 millimeters per second. */ public static final double KnotsToMMPerSecond = 514.444; - public static final int TEST_VELOCITIES[] = new int[] { 30, 15, 64, 52, 25, 24 }; + /** + * The race pre-start time, in milliseconds. 3 minutes. + */ + public static final long RacePreStartTime = 3 * 60 * 1000; + + + /** + * The race preparatory time, in milliseconds. 1 minutes. + */ + public static final long RacePreparatoryTime = 1 * 60 * 1000; + + + /** + * The number of milliseconds in one hour. + */ + public static long OneHourMilliseconds = 1 * 60 * 60 * 1000; + + + } diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/mock/src/main/java/seng302/DataInput/PolarParser.java index 20724f43..59878edd 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/mock/src/main/java/seng302/DataInput/PolarParser.java @@ -5,6 +5,7 @@ package seng302.DataInput; */ import seng302.Exceptions.InvalidPolarFileException; +import seng302.Model.Bearing; import seng302.Model.Polars; import java.io.*; @@ -83,10 +84,17 @@ public class PolarParser { //Add angle+speed=velocity estimate to polar table. try { + //Add the polar value to the polar table - polarTable.addEstimate( Double.parseDouble(row[0]), Double.parseDouble(row[i]), Double.parseDouble(row[i + 1])); + double windSpeedKnots = Double.parseDouble(row[0]); + double angleDegrees = Double.parseDouble(row[i]); + Bearing angle = Bearing.fromDegrees(angleDegrees); + double boatSpeedKnots = Double.parseDouble(row[i + 1]); + polarTable.addEstimate(windSpeedKnots, angle, boatSpeedKnots); + } catch (NumberFormatException e) { throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e); + } } diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java index 60b01d79..08fa73a7 100644 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java @@ -204,6 +204,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { lastCompoundMark = currentCompoundMark; legName = getCompoundMarkName(getCompoundMarkID(markXML)); } + } private void readCourseLimit() { diff --git a/mock/src/main/java/seng302/Model/Angle.java b/mock/src/main/java/seng302/Model/Angle.java index 4dc027f8..8fa767dd 100644 --- a/mock/src/main/java/seng302/Model/Angle.java +++ b/mock/src/main/java/seng302/Model/Angle.java @@ -4,7 +4,7 @@ package seng302.Model; * This represents an angle. * Has functions to return angle as either degrees or radians. */ -public class Angle { +public class Angle implements Comparable { /** * The angle stored in this object. @@ -62,4 +62,69 @@ public class Angle { public double radians() { return Math.toRadians(this.degrees); } + + + + /** + * Returns true if two Angle objects have equal values. + * @param obj Other angle object to compare. + * @return True if they are equal, false otherwise. + */ + @Override + public boolean equals(Object obj) { + //Cast other side. + Angle other = (Angle) obj; + + //Compare values. + if (this.degrees() == other.degrees()) { + return true; + } else { + return false; + } + } + + + /** + * Returns an int describing the ordering between this angle object, and another. + * @param o Other angle to compare to. + * @return {@literal int < 0} if this angle is less than the other angle, {@literal int > 0} if this angle is greater than the other angle, and {@literal int = 0} if this angle is equal to the other angle, + */ + @Override + public int compareTo(Angle o) { + + if (this.degrees() < o.degrees()) { + return -1; + } else if (this.degrees() > o.degrees()) { + return 1; + } else { + return 0; + } + + } + + + /** + * Converts an angle to an angle in a given periodic interval (e.g., degrees have a periodic interval of 360, radians have a periodic interval of 2Pi) of [lowerBound, upperBound). + * @param angle The angle to convert. + * @param lowerBound The lower bound of the interval. + * @param upperBound The upper bound of the interval. + * @param period The period of the interval. + * @return The angle in the desired periodic interval. + */ + public static double toPeriodicInterval(double angle, double lowerBound, double upperBound, double period) { + + + while (angle >= upperBound) { + //Too large. + angle -= period; + } + + while (angle < lowerBound) { + //Too small. + angle += period; + } + + return angle; + + } } diff --git a/mock/src/main/java/seng302/Model/Azimuth.java b/mock/src/main/java/seng302/Model/Azimuth.java index 0f252ebc..cbbaa2e6 100644 --- a/mock/src/main/java/seng302/Model/Azimuth.java +++ b/mock/src/main/java/seng302/Model/Azimuth.java @@ -4,8 +4,9 @@ package seng302.Model; /** * Represents an azimuth. - * This is the angle between north and a target point. - * It has the interval [-180, 180), and clockwise from north is positive. + * If treated as an absolute azimuth this is the angle between north and a target point. + * If treated as a relative azimuth, this is the angle between from one target point to the other. + * It has the interval [-180, 180) degrees, and clockwise is positive. */ public class Azimuth extends Angle{ @@ -29,15 +30,7 @@ public class Azimuth extends Angle{ */ public static double toAzimuthInterval(double degrees) { - if (degrees >= 180) { - //Too large. - degrees -= 360; - } else if (degrees < -180) { - //Too small. - degrees += 360; - } - - return degrees; + return Angle.toPeriodicInterval(degrees, -180d, 180d, 360d); } /** @@ -63,4 +56,13 @@ public class Azimuth extends Angle{ } + /** + * Constructs an Azimuth object from a Bearing object. + * @param bearing Bearing object to read value from. + * @return Azimuth object. + */ + public static Azimuth fromBearing(Bearing bearing) { + return Azimuth.fromDegrees(bearing.degrees()); + } + } diff --git a/mock/src/main/java/seng302/Model/Bearing.java b/mock/src/main/java/seng302/Model/Bearing.java index 0f25b747..27ff1235 100644 --- a/mock/src/main/java/seng302/Model/Bearing.java +++ b/mock/src/main/java/seng302/Model/Bearing.java @@ -2,8 +2,9 @@ package seng302.Model; /** * Represents a bearing. Also known as a heading. - * This is the angle between north and a target point. - * Has the interval [0, 360), and clockwise from north is positive. + * If treated as an absolute bearing this is the angle between north and a target point. + * If treated as a relative bearing, this is the angle between from one target point to the other. + * Has the interval [0, 360) degrees, and clockwise is positive. */ public class Bearing extends Angle { @@ -27,15 +28,7 @@ public class Bearing extends Angle { */ public static double toBearingInterval(double degrees) { - if (degrees >= 360) { - //Too large. - degrees -= 360; - } else if (degrees < 0) { - //Too small. - degrees += 360; - } - - return degrees; + return Angle.toPeriodicInterval(degrees, -0d, 360d, 360d); } /** @@ -61,4 +54,13 @@ public class Bearing extends Angle { } + /** + * Constructs a Bearing object from an Azimuth object. + * @param azimuth Azimuth object to read value from. + * @return Bearing object. + */ + public static Bearing fromAzimuth(Azimuth azimuth) { + return Bearing.fromDegrees(azimuth.degrees()); + } + } diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index c3a13878..9c3eb895 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -1,7 +1,7 @@ package seng302.Model; -import org.geotools.referencing.GeodeticCalculator; import seng302.Constants; +import seng302.Networking.Messages.Enums.BoatStatusEnum; /** @@ -20,10 +20,9 @@ public class Boat { private double currentSpeed; /** - * The current heading of the boat. - * TODO bearing + * The current bearing/heading of the boat. */ - private double heading; + private Bearing bearing; /** * The current position of the boat. @@ -56,7 +55,7 @@ public class Boat { * The time, in milliseconds, that has elapsed during the current leg. * TODO milliseconds */ - private double timeElapsedInCurrentLeg; + private long timeElapsedInCurrentLeg; /** * The timestamp, in milliseconds, of when the boat finished the race. @@ -66,14 +65,9 @@ public class Boat { private long timeFinished = -1; /** - * Whether or not the boat has finished the race. + * The current status of the boat. */ - private boolean hasFinishedRace = false; - - /** - * Whether or not the boat has started the race. - */ - private boolean hasStartedRace = false; + private BoatStatusEnum status; /** * This stores a boat's polars table. @@ -101,53 +95,41 @@ public class Boat { this.name = name; this.sourceID = sourceID; this.polars = polars; - } + this.bearing = Bearing.fromDegrees(0d); + + this.status = BoatStatusEnum.UNDEFINED; + } /** - * Calculate the heading depending on the calculated azimuth value - * @return The azimuth value which is greater than 0 + * Calculate the bearing of the boat to its next marker. + * @return The bearing to the next marker. */ - public double calculateHeading() { - double azimuth = GPSCoordinate.calculateAzimuth(currentLeg.getStartCompoundMark().getAverageGPSCoordinate(), - currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); + public Bearing calculateBearingToNextMarker() { - //Azimuth is in the interval (-180, 180), but we need a heading in the interval [0, 360). - if (azimuth < 0) { - return azimuth + 360; - } + //Get the start and end points. + GPSCoordinate currentPosition = this.getCurrentPosition(); + GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); - return azimuth; - } + //Calculate bearing. + Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); - /** - * Calculate the heading depending on the calculated azimuth value - * @return The azimuth value which is greater than 0 - */ - public double calculateBearingToDestination() { - double azimuth = GPSCoordinate.calculateAzimuth(this.currentPosition, - currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); - - if (azimuth >= 0) { - return azimuth; - } else { - return azimuth + 360; - } + return bearing; } + /** - * Calculates the distance between the boat and its target marker in nautical miles (1.852 km). + * Calculates the distance between the boat and its target marker in nautical miles. * @return The distance (in nautical miles) between the boat and its target marker. */ public double calculateDistanceToNextMarker() { - GeodeticCalculator calc = new GeodeticCalculator(); - - GPSCoordinate startMarker = this.getCurrentPosition(); + //Get start and end markers. + GPSCoordinate startPosition = this.getCurrentPosition(); //When boats finish, their "current leg" doesn't have an end marker. if (this.getCurrentLeg().getEndCompoundMark() == null) { @@ -156,101 +138,183 @@ public class Boat { GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); - //Add points to calculator. - calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude()); - calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude()); - //Convert meters to nautical miles. - return calc.getOrthodromicDistance() / Constants.NMToMetersConversion; + //Calculate distance. + double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker); + + return distanceNauticalMiles; } + + /** + * Returns the name of the boat/team. + * @return Name of the boat/team. + */ public String getName() { return name; } + /** + * Sets the name of the boat/team. + * @param name Name of the boat/team. + */ public void setName(String name) { this.name = name; } + + /** + * Returns the current speed of the boat, in knots. + * @return The current speed of the boat, in knots. + */ public double getCurrentSpeed() { return currentSpeed; } + /** + * Sets the speed of the boat, in knots. + * @param currentSpeed The new speed of the boat, in knots. + */ public void setCurrentSpeed(double currentSpeed) { this.currentSpeed = currentSpeed; } + /** + * Gets the country/team abbreviation of the boat. + * @return The country/team abbreviation of the boat. + */ public String getCountry() { return country; } + /** + * Sets the country/team abbreviation of the boat. + * @param country The new country/team abbreviation for the boat. + */ public void setCountry(String country) { this.country = country; } + + /** + * Returns the source ID of the boat. + * @return The source ID of the boat. + */ public int getSourceID() { return sourceID; } + /** + * Sets the source ID of the boat. + * @param sourceID The new source ID for the boat. + */ public void setSourceID(int sourceID) { this.sourceID = sourceID; } + /** + * Returns the current leg of the race the boat is in. + * @return The current leg of the race the boat is in. + */ public Leg getCurrentLeg() { return currentLeg; } + /** + * Sets the current leg of the race the boat is in. + * Clears time elapsed in current leg and distance travelled in current leg. + * @param currentLeg The new leg of the race the boat is in. + */ public void setCurrentLeg(Leg currentLeg) { this.currentLeg = currentLeg; + this.setTimeElapsedInCurrentLeg(0); + this.setDistanceTravelledInLeg(0); } + + /** + * Returns the distance, in meters, the boat has travelled in the current leg. + * @return The distance, in meters, the boat has travelled in the current leg. + */ public double getDistanceTravelledInLeg() { return distanceTravelledInLeg; } + /** + * Sets the distance, in meters, the boat has travelled in the current leg. + * @param distanceTravelledInLeg The distance, in meters, the boat has travelled in the current leg. + */ public void setDistanceTravelledInLeg(double distanceTravelledInLeg) { this.distanceTravelledInLeg = distanceTravelledInLeg; } + /** + * Returns the current position of the boat. + * @return The current position of the boat. + */ public GPSCoordinate getCurrentPosition() { return currentPosition; } + /** + * Sets the current position of the boat. + * @param currentPosition The new position for the boat. + */ public void setCurrentPosition(GPSCoordinate currentPosition) { this.currentPosition = currentPosition; } + + /** + * Gets the timestamp, in milliseconds, at which the boat finished the race. + * @return The timestamp, in milliseconds, at which the boat finished the race. + */ public long getTimeFinished() { return timeFinished; } + /** + * Sets the timestamp, in milliseconds, at which the boat finished the race. + * @param timeFinished The timestamp, in milliseconds, at which the boat finished the race. + */ public void setTimeFinished(long timeFinished) { this.timeFinished = timeFinished; } - public boolean isHasStartedRace() { - return hasStartedRace; - } - public void setHasStartedRace(boolean hasStartedRace) { - this.hasStartedRace = hasStartedRace; - } - public double getHeading() { - return heading; + + /** + * Returns the current bearing of the boat. + * @return The current bearing of the boat. + */ + public Bearing getBearing() { + return bearing; } - public void setHeading(double heading) { - this.heading = heading; + /** + * Sets the current bearing of the boat. + * @param bearing The new bearing of the boat. + */ + public void setBearing(Bearing bearing) { + this.bearing = bearing; } + /** + * Returns the polars table for this boat. + * @return The polars table for this boat. + */ public Polars getPolars() { return polars; } + /** + * Sets the polars table for this boat. + * @param polars The new polars table for this boat. + */ public void setPolars(Polars polars) { this.polars = polars; } @@ -271,4 +335,111 @@ public class Boat { public void setTimeSinceTackChange(long timeSinceTackChange) { this.timeSinceTackChange = timeSinceTackChange; } + + + /** + * Returns the time, in milliseconds, that has elapsed since the boat started the current leg. + * @return The time, in milliseconds, that has elapsed since the boat started the current leg. + */ + public long getTimeElapsedInCurrentLeg() { + return timeElapsedInCurrentLeg; + } + + /** + * Sets the time, in milliseconds, that has elapsed since the boat started the current leg. + * @param timeElapsedInCurrentLeg The new time, in milliseconds, that has elapsed since the boat started the current leg. + */ + public void setTimeElapsedInCurrentLeg(long timeElapsedInCurrentLeg) { + this.timeElapsedInCurrentLeg = timeElapsedInCurrentLeg; + } + + + /** + * Returns the status of the boat. + * @return The sttus of the boat. + */ + public BoatStatusEnum getStatus() { + return status; + } + + /** + * Sets the status of the boat. + * @param status The new status of the boat. + */ + public void setStatus(BoatStatusEnum status) { + this.status = status; + } + + + /** + * Moves the boat meters forward in the direction that it is facing + * @param meters The number of meters to move forward. + * @param milliseconds The number of milliseconds to advance the boat's timers by. + */ + public void moveForwards(double meters, long milliseconds) { + + + //Update the boat's time since last tack. + this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds); + + //Update the time into the current leg. + this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds); + + //Update the distance into the current leg. + this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters); + + //Updates the current position of the boat. + GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing())); + this.setCurrentPosition(newPosition); + + } + + + /** + * Sets the boats speed and bearing to those in the given VMG. + * @param newVMG The new VMG to use for the boat - contains speed and bearing. + */ + public void setVMG(VMG newVMG) { + this.setBearing(newVMG.getBearing()); + this.setCurrentSpeed(newVMG.getSpeed()); + this.setTimeSinceTackChange(0); + } + + + /** + * Calculates the number of nautical miles the boat will travel in a given time slice. + * E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles. + * @param timeSlice The timeslice to use. + * @return The distance travelled, in nautical miles, over the given timeslice. + */ + public double calculateNauticalMilesTravelled(long timeSlice) { + + //The proportion of one hour the current timeslice is. + //This will be a low fractional number, so we need to go from long -> double. + double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds; + + //Calculates the distance travelled, in nautical miles, in the current timeslice. + //distanceTravelledNM = speed (nm p hr) * time taken to update loop + double distanceTravelledNM = this.getCurrentSpeed() * hourProportion; + + return distanceTravelledNM; + } + + /** + * Calculates the number of meters the boat will travel in a given time slice. + * E.g., in 53 milliseconds a boat may travel 0.02 meters. + * @param timeSlice The timeslice to use. + * @return The distance travelled, in meters, over the given timeslice. + */ + public double calculateMetersTravelled(long timeSlice) { + + //Calculate the distance travelled, in nautical miles. + double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice); + + //Convert to meters. + double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion; + + return distanceTravelledMeters; + } + } diff --git a/mock/src/main/java/seng302/Model/CompoundMark.java b/mock/src/main/java/seng302/Model/CompoundMark.java index 68e4a5ca..546ed079 100644 --- a/mock/src/main/java/seng302/Model/CompoundMark.java +++ b/mock/src/main/java/seng302/Model/CompoundMark.java @@ -1,61 +1,110 @@ package seng302.Model; -import org.geotools.referencing.GeodeticCalculator; -import java.awt.geom.Point2D; /** - * Created by esa46 on 29/03/17. + * Represents a compound mark - that is, either one or two individual marks which form a single compound mark. */ -public class CompoundMark extends Marker{ +public class CompoundMark { - private GPSCoordinate averageGPSCoordinate; + /** + * The first mark in the compound mark. + */ private Mark mark1; - private Mark mark2 = null; + /** + * The second mark in the compound mark. + */ + private Mark mark2; + + /** + * The average coordinate of the compound mark. + */ + private GPSCoordinate averageGPSCoordinate; + + + /** + * Constructs a compound mark from a single mark. + * @param mark1 The individual mark that comprises this compound mark. + */ public CompoundMark(Mark mark1) { - super(mark1.getPosition()); this.mark1 = mark1; this.averageGPSCoordinate = calculateAverage(); } + + /** + * Constructs a compound mark from a pair of marks. + * @param mark1 The first individual mark that comprises this compound mark. + * @param mark2 The second individual mark that comprises this compound mark. + */ public CompoundMark(Mark mark1, Mark mark2) { - super(mark1.getPosition(), mark2.getPosition()); this.mark1 = mark1; this.mark2 = mark2; this.averageGPSCoordinate = calculateAverage(); } - public Mark getMark1Source() { return mark1; } - public Mark getMark2Source() { return mark2; } + /** + * Returns the first mark of the compound mark. + * @return The first mark of the compound mark. + */ + public Mark getMark1() { + return mark1; + } + + /** + * Returns the second mark of the compound mark. + * @return The second mark of the compound mark. + */ + public Mark getMark2() { + return mark2; + } - public GPSCoordinate getMark1() { + + /** + * Returns the position of the first mark in the compound mark. + * @return The position of the first mark in the compound mark. + */ + public GPSCoordinate getMark1Position() { return mark1.getPosition(); } - public GPSCoordinate getMark2() { + /** + * Returns the position of the second mark in the compound mark. + * @return The position of the second mark in the compound mark. + */ + public GPSCoordinate getMark2Position() { return mark2.getPosition(); } + + /** + * Returns the average coordinate of the compound mark. + * @return The average coordinate of the compound mark. + */ public GPSCoordinate getAverageGPSCoordinate() { return averageGPSCoordinate; } + + /** + * Calculates the average coordinate of the compound mark. + * @return The average coordinate of the compound mark. + */ private GPSCoordinate calculateAverage() { - if(mark2 != null) { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude()); - calc.setDestinationGeographicPoint(mark2.getPosition().getLongitude(), mark2.getPosition().getLatitude()); - double azimuth = calc.getAzimuth(); - double distance = calc.getOrthodromicDistance(); - - GeodeticCalculator middleCalc = new GeodeticCalculator(); - middleCalc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude()); - middleCalc.setDirection(azimuth, distance / 2); - Point2D middlePoint = middleCalc.getDestinationGeographicPoint(); - return new GPSCoordinate(middlePoint.getY(), middlePoint.getX()); - } else return mark1.getPosition(); + + //If the compound mark only contains one mark, the average is simply the first mark's position. + if (this.mark2 == null) { + return this.getMark1Position(); + } + + + //Otherwise, calculate the average of both marks. + GPSCoordinate averageCoordinate = GPSCoordinate.calculateAverageCoordinate(this.getMark1Position(), this.getMark2Position()); + + return averageCoordinate; + } } diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index da8fbd3c..3f9527e2 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -1,23 +1,34 @@ package seng302.Model; import org.geotools.referencing.GeodeticCalculator; +import org.opengis.geometry.DirectPosition; +import seng302.Constants; +import java.awt.geom.Point2D; import java.util.Comparator; import java.util.List; /** - * GPS Coordinate for the world map. + * GPS Coordinate for the world map, containing a longitude and latitude. * Created by esa46 on 15/03/17. */ public class GPSCoordinate { + /** + * The latitude of the coordinate. + */ private double latitude; + + /** + * The longitude of the coordinate. + */ private double longitude; + + /** - * Constructor Method - * - * @param latitude latitude the coordinate is located at. + * Constructs a GPSCoordinate from a latitude and longitude value. + * @param latitude Latitude the coordinate is located at. * @param longitude Longitude that the coordinate is located at. */ public GPSCoordinate(double latitude, double longitude) { @@ -25,6 +36,8 @@ public class GPSCoordinate { this.longitude = longitude; } + + /** * Gets the Latitude that the Coordinate is at. * @@ -74,6 +87,8 @@ public class GPSCoordinate { return result; } + + /** * Calculates min and max values and passed it to calculate if coordinate is in the boundary * @param coordinate coordinate of interest @@ -120,6 +135,7 @@ public class GPSCoordinate { } } + /** * Helper function to find if a point is in a boundary * @param boundaryA The first coordinate of the boundary. @@ -156,20 +172,125 @@ public class GPSCoordinate { } + /** * Calculates the azimuth between two points. - * This is an angle in the interval (-180, 180), with - * - * @return the direction that the boat is heading towards in degrees (-180 to 180). + * @param start The starting point. + * @param end The ending point. + * @return The azimuth from the start point to the end point. */ - public static double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { + public static Azimuth calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { GeodeticCalculator calc = new GeodeticCalculator(); calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); - return calc.getAzimuth(); + return Azimuth.fromDegrees(calc.getAzimuth()); } + + + /** + * Calculates the bearing between two points. + * @param start The starting point. + * @param end The ending point. + * @return The Bearing from the start point to the end point. + */ + public static Bearing calculateBearing(GPSCoordinate start, GPSCoordinate end) { + + //Calculates the azimuth between the two points. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(start, end); + + //And converts it into a bearing. + return Bearing.fromAzimuth(azimuth); + } + + + /** + * Calculates the distance, in meters, between two points. + * Note: all other distance calculations (e.g., nautical miles between two points) should use this, and convert from meters to their desired unit. + * @param start The starting point. + * @param end The ending point. + * @return The distance, in meters, between the two given points. + */ + public static double calculateDistanceMeters(GPSCoordinate start, GPSCoordinate end) { + + GeodeticCalculator calc = new GeodeticCalculator(); + + calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); + calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); + + double distanceMeters = calc.getOrthodromicDistance(); + + return distanceMeters; + } + + + /** + * Calculates the distance, in nautical miles, between two points. + * @param start The starting point. + * @param end The ending point. + * @return The distance, in nautical miles, between the two given points. + */ + public static double calculateDistanceNauticalMiles(GPSCoordinate start, GPSCoordinate end) { + //Find distance in meters. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(start, end); + + //Convert to nautical miles. + double distanceNauticalMiles = distanceMeters / Constants.NMToMetersConversion; + + return distanceNauticalMiles; + } + + + /** + * Calculates the GPS position an entity will be at, given a starting position, distance (in meters), and an azimuth. + * + * @param oldCoordinates GPS coordinates of the entity's starting position. + * @param distanceMeters The distance in meters. + * @param azimuth The entity's current azimuth. + * @return The entity's new coordinate. + */ + public static GPSCoordinate calculateNewPosition(GPSCoordinate oldCoordinates, double distanceMeters, Azimuth azimuth) { + + + GeodeticCalculator calc = new GeodeticCalculator(); + + //Set starting position. + calc.setStartingGeographicPoint(oldCoordinates.getLongitude(), oldCoordinates.getLatitude()); + + //Set direction. + calc.setDirection(azimuth.degrees(), distanceMeters); + + //Get the destination. + Point2D destinationPoint = calc.getDestinationGeographicPoint(); + + return new GPSCoordinate(destinationPoint.getY(), destinationPoint.getX()); + } + + + /** + * Calculates the average coordinate of two coordinates. + * @param point1 The first coordinate to average. + * @param point2 The second coordinate to average. + * @return The average of the two coordinates. + */ + public static GPSCoordinate calculateAverageCoordinate(GPSCoordinate point1, GPSCoordinate point2) { + + //Calculate distance between them. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(point1, point2); + //We want the average, so get half the distance between points. + distanceMeters = distanceMeters / 2d; + + //Calculate azimuth between them. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(point1, point2); + + //Calculate the middle coordinate. + GPSCoordinate middleCoordinate = GPSCoordinate.calculateNewPosition(point1, distanceMeters, azimuth); + + return middleCoordinate; + + } + } diff --git a/mock/src/main/java/seng302/Model/Leg.java b/mock/src/main/java/seng302/Model/Leg.java index 22c5adce..8e784164 100644 --- a/mock/src/main/java/seng302/Model/Leg.java +++ b/mock/src/main/java/seng302/Model/Leg.java @@ -1,102 +1,127 @@ package seng302.Model; -import org.geotools.referencing.GeodeticCalculator; -import seng302.Constants; - /** - * Created by cbt24 on 6/03/17. + * Represents a leg of a race. */ public class Leg { - private String name; //nautical miles - private double distance; - private Marker startCompoundMark; - private Marker endCompoundMark; + + /** + * The name of the leg. + */ + private String name; + + /** + * The distance of the leg, in nautical miles. + */ + private double distanceNauticalMiles; + + /** + * The starting marker of the leg. + */ + private CompoundMark startCompoundMark; + + /** + * The ending marking of the leg. + */ + private CompoundMark endCompoundMark; + + /** + * The leg number within a race. + */ private int legNumber; + + /** - * Leg Initialiser + * Constructs a leg from a name, start marker, end marker, and leg number. * - * @param name Name of the Leg - * @param start marker - * @param end marker - * @param number Leg's position in race + * @param name Name of the Leg. + * @param start Starting marker of the leg. + * @param end Ending marker of the leg. + * @param number Leg's position within the race. */ - public Leg(String name, Marker start, Marker end, int number) { + public Leg(String name, CompoundMark start, CompoundMark end, int number) { this.name = name; this.startCompoundMark = start; this.endCompoundMark = end; this.legNumber = number; - calculateDistance(); + this.calculateLegDistance(); } + /** - * Construction Method + * Constructs a leg from a name and leg number. + * This is currently used for constructing "dummy" DNF and Finish legs. * - * @param name Name of the Leg - * @param number leg number + * @param name Name of the leg. + * @param number Leg's position within the race. */ public Leg(String name, int number) { this.name = name; this.legNumber = number; } + /** - * Returns the name of the Leg - * - * @return Returns the name of the Leg + * Returns the name of the Leg. + * @return The name of the Leg. */ public String getName() { return name; } + /** - * Get the distance in nautical miles - * - * @return Returns the total distance of the leg. + * Get the distance in nautical miles. + * @return The total distance of the leg. */ - public double getDistance() { - return distance; + public double getDistanceNauticalMiles() { + return distanceNauticalMiles; } + /** - * Returns the leg number that the leg exists in the Race - * - * @return Returns the Leg + * Returns the leg number of the leg within a race. + * @return The leg number of the leg within a race */ public int getLegNumber() { return legNumber; } - public Marker getStartCompoundMark() { + /** + * Returns the starting marker of the leg. + * @return The starting marker of the leg. + */ + public CompoundMark getStartCompoundMark() { return startCompoundMark; } - public void setStartCompoundMark(Marker startCompoundMark) { - this.startCompoundMark = startCompoundMark; - } - public Marker getEndCompoundMark() { + /** + * Returns the ending marker of the leg. + * @return The ending marker of the leg. + */ + public CompoundMark getEndCompoundMark() { return endCompoundMark; } - public void setEndCompoundMark(Marker endCompoundMark) { - this.endCompoundMark = endCompoundMark; - } + /** - * Calculates the distance that the legs are in nautical miles (1.852 km). + * Calculates the distance of the leg, in nautical miles. */ - public void calculateDistance() { + public void calculateLegDistance() { - GeodeticCalculator calc = new GeodeticCalculator(); - //Load start and end of leg + //Gets the start and end coordinates. GPSCoordinate startMarker = this.startCompoundMark.getAverageGPSCoordinate(); GPSCoordinate endMarker = this.endCompoundMark.getAverageGPSCoordinate(); - calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude()); - calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude()); - this.distance = calc.getOrthodromicDistance() / Constants.NMToMetersConversion; + + //Calculates the distance between markers. + double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startMarker, endMarker); + + this.distanceNauticalMiles = distanceNauticalMiles; } diff --git a/mock/src/main/java/seng302/Model/Mark.java b/mock/src/main/java/seng302/Model/Mark.java index 8a09493d..e2a8c7e7 100644 --- a/mock/src/main/java/seng302/Model/Mark.java +++ b/mock/src/main/java/seng302/Model/Mark.java @@ -1,28 +1,64 @@ package seng302.Model; /** - * Created by cbt24 on 10/05/17. + * Represents an individual mark. + * Has a source ID, name, and position. */ public class Mark { + + /** + * The source ID of the mark. + */ private int sourceID; + + /** + * The name of the mark. + */ private String name; + + /** + * The position of the mark. + */ private GPSCoordinate position; + + + /** + * Constructs a mark with a given source ID, name, and position. + * @param sourceID The source ID of the mark. + * @param name The name of the mark. + * @param position The position of the mark. + */ public Mark(int sourceID, String name, GPSCoordinate position) { this.sourceID = sourceID; this.name = name; this.position = position; } + + /** + * Returns the name of the mark. + * @return The name of the mark. + */ public String getName() { return name; } + /** + * Returns the source ID of the mark. + * @return The source ID of the mark. + */ public int getSourceID() { return sourceID; } + /** + * Returns the position of the mark. + * @return The position of the mark. + */ public GPSCoordinate getPosition() { return position; } + + } diff --git a/mock/src/main/java/seng302/Model/Marker.java b/mock/src/main/java/seng302/Model/Marker.java deleted file mode 100644 index 3be606dd..00000000 --- a/mock/src/main/java/seng302/Model/Marker.java +++ /dev/null @@ -1,75 +0,0 @@ -package seng302.Model; - -import org.geotools.referencing.GeodeticCalculator; - -import java.awt.geom.Point2D; - -/** - * Created by esa46 on 29/03/17. - */ -public class Marker { - - private GPSCoordinate averageGPSCoordinate; - private GPSCoordinate mark1; - private GPSCoordinate mark2; - private String name; - private boolean doubleMarker = false; - - public Marker(GPSCoordinate mark1) { - - this.mark1 = mark1; - this.mark2 = mark1; - this.averageGPSCoordinate = calculateAverage(); - - } - - public Marker(GPSCoordinate mark1, GPSCoordinate mark2) { - - this.mark1 = mark1; - this.mark2 = mark2; - this.averageGPSCoordinate = calculateAverage(); - - } - - public Marker(String name, GPSCoordinate mark1, GPSCoordinate mark2) { - - this.name = name; - this.mark1 = mark1; - this.mark2 = mark2; - this.averageGPSCoordinate = calculateAverage(); - - } - - public GPSCoordinate getMark1() { - return mark1; - } - - public GPSCoordinate getMark2() { - return mark2; - } - - public GPSCoordinate getAverageGPSCoordinate() { - return averageGPSCoordinate; - } - - public String getName() { - return name; - } - - private GPSCoordinate calculateAverage() { - - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude()); - calc.setDestinationGeographicPoint(mark2.getLongitude(), mark2.getLatitude()); - double azimuth = calc.getAzimuth(); - double distance = calc.getOrthodromicDistance(); - - GeodeticCalculator middleCalc = new GeodeticCalculator(); - middleCalc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude()); - middleCalc.setDirection(azimuth, distance / 2); - Point2D middlePoint = middleCalc.getDestinationGeographicPoint(); - return new GPSCoordinate(middlePoint.getY(), middlePoint.getX()); - - } - -} \ No newline at end of file diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 4f35b8d6..1e3251f3 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -16,14 +16,14 @@ public class Polars { /** * Internal store of data. Maps {@literal Pair} to boatSpeed. */ - private Map, Double> polarValues = new HashMap<>(); + private Map, Double> polarValues = new HashMap<>(); /** * Stores a list of angles from the polar table - this is used during the calculateVMG function. * Maps between windSpeed and a list of angles for that wind speed. */ - private HashMap> polarAngles = new HashMap<>(); + private HashMap> polarAngles = new HashMap<>(); @@ -42,7 +42,7 @@ public class Polars { * @param relativeWindAngle The relative wind angle between the wind direction + 180 degrees and the boat's direction of the estimate. * @param boatSpeed The boat speed of the estimate. */ - public void addEstimate(double trueWindSpeed, double relativeWindAngle, double boatSpeed){ + public void addEstimate(double trueWindSpeed, Bearing relativeWindAngle, double boatSpeed) { //We also add the same values with a complementary angle (e.g., angle = 50, complement = 360 - 50 = 310). This is because the data file contains angles [0, 180), but we need [0, 360). @@ -52,19 +52,17 @@ public class Polars { } //Add estimate to map. - Pair newKeyPositive = new Pair(trueWindSpeed, relativeWindAngle); - polarValues.put(newKeyPositive, boatSpeed); + Pair newKeyPositive = new Pair<>(trueWindSpeed, relativeWindAngle); + this.polarValues.put(newKeyPositive, boatSpeed); + + //Get the "negative" bearing - that is, the equivalent bearing between [180, 360). + Bearing negativeBearing = Bearing.fromDegrees(360d - relativeWindAngle.degrees()); - double negativeAngle = 360d - relativeWindAngle; - //This essentially does angle modulo 360, to get something in the interval [0, 360). - while (negativeAngle >= 360d) { - negativeAngle -= 360d; - } //Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0. - if (negativeAngle != relativeWindAngle) { - Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); - polarValues.put(newKeyNegative, boatSpeed); + if (!negativeBearing.equals(relativeWindAngle)) { + Pair newKeyNegative = new Pair<>(trueWindSpeed, negativeBearing); + this.polarValues.put(newKeyNegative, boatSpeed); } @@ -73,8 +71,8 @@ public class Polars { this.polarAngles.get(trueWindSpeed).add(relativeWindAngle); } - if (!this.polarAngles.get(trueWindSpeed).contains(negativeAngle)) { - this.polarAngles.get(trueWindSpeed).add(negativeAngle); + if (!this.polarAngles.get(trueWindSpeed).contains(negativeBearing)) { + this.polarAngles.get(trueWindSpeed).add(negativeBearing); } @@ -83,39 +81,45 @@ public class Polars { /** - * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. If you don't care about bearing bounds, simply pass in lower = 0, upper = 360. + * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. + *
+ * If you don't care about bearing bounds, simply pass in lower = 0, upper = 359.9. + *
+ * Passing in lower = 0, upper = 0, or lower = 0, upper = 360 will both be treated the same as lower = 0, upper = 359.99999. *

- * The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound). Note the exclusive end of interval. + * The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound]. *

- * If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that {@literal VMGAngle >= lower OR VMGAngle < upper} (e.g., {@literal [70, 55) means angle >= 70, OR angle < 55}). + * If the lower bound is greater than the upper bound (e.g., lower = 70, upper = 55), then it checks that {@literal VMGAngle >= lower OR VMGAngle <= upper} (e.g., {@literal [70, 55] means angle >= 70, OR angle =< 55}). *

* Returns a VMG with 0 speed and 0 bearing if there are no VMGs with {@literal velocity > 0} in the acceptable bearing bounds. - * @param trueWindAngle The current true wind angle. Interval is [0, 360). + * @param trueWindAngle The current true wind angle. * @param trueWindSpeed The current true wind speed. Knots. - * @param destinationAngle The angle between the boat and the destination point. Interval is [0, 360). - * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. Interval is [0, 360). - * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. Interval is [0, 360]. Note the inclusive end of interval. + * @param destinationAngle The angle between the boat and the destination point. + * @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. + * @param bearingUpperBound The highest bearing (angle) that the boat may travel on. * @return The VMG. */ - public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { + public VMG calculateVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing destinationAngle, Bearing bearingLowerBound, Bearing bearingUpperBound) { //Sorts polar angles. - for (List angles : this.polarAngles.values()) { + for (List angles : this.polarAngles.values()) { angles.sort(null); } - - //Get the lower bound into the interval [0, 360) - bearingLowerBound = ((bearingLowerBound % 360d) + 360d) % 360d; - //We use a bound of [lower, upper), so we allow the upper to be 360 degrees. - if (bearingUpperBound != 360d) { - bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d; + //If the user enters [0, 360] for their bounds, there won't be any accepted angles, as Bearing(360) turn into Bearing(0) (it has the interval [0, 360)). + //So if both bearing bounds are zero, we assume that the user wanted [0, 360) for the interval. + //So, we give them Bearing(359.99999) as the upper bound. + if ((bearingLowerBound.degrees() == 0d) && (bearingUpperBound.degrees() == 0d)) { + bearingUpperBound = Bearing.fromDegrees(359.99999d); } - //If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55) the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle < 55), instead of (VMGAngle >= 70 AND VMGAngle < 55). + + + + //If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55] the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle =< 55), instead of (VMGAngle >= 70 AND VMGAngle =< 55). boolean flippedInterval = false; - if (bearingLowerBound > bearingUpperBound) { + if (bearingLowerBound.degrees() > bearingUpperBound.degrees()) { flippedInterval = true; } @@ -127,7 +131,7 @@ public class Polars { //This indicates whether or not we've managed to find a wind speed larger than the current wind speed (the upper bound) in the Polars table (in cases where the current wind speed is larger than any in the file we will never find an upper bound). boolean foundUpperBoundWindSpeed = false; boolean foundLowerBoundWindSpeed = false; - for (Pair key : this.polarValues.keySet()) { + for (Pair key : this.polarValues.keySet()) { //The key is Pair, so pair.key is windSpeed. double currentPolarSpeed = key.getKey(); @@ -167,37 +171,35 @@ public class Polars { for (double polarWindSpeed : windSpeedBounds) { //The list of polar angles for this wind speed. - List polarAngles = this.polarAngles.get(polarWindSpeed); + List polarAngles = this.polarAngles.get(polarWindSpeed); double bestVMGVelocity = 0; double bestVMGSpeed = 0; - double bestVMGAngle = 0; + Bearing bestVMGAngle = Bearing.fromDegrees(0d); //Calculate the VMG for all possible angles at this wind speed. - for (double angle = 0; angle < 360; angle += 0.1) { + for (double angleDegree = 0; angleDegree < 360; angleDegree += 0.1) { + Bearing angle = Bearing.fromDegrees(angleDegree); //This is the true bearing of the boat, if it went at the angle against the wind. //For angle < 90 OR angle > 270, it means that the boat is going into the wind (tacking). //For angle > 90 AND angle < 270, it means that the boat is actually going with the wind (gybing). - double trueBoatBearing = trueWindAngle + angle + 180d; - //We put trueBoatBearing into the interval [0, 360). - while (trueBoatBearing >= 360) { - trueBoatBearing -= 360; - } + double trueBoatBearingDegrees = trueWindAngle.degrees() + angle.degrees() + 180d; + Bearing trueBoatBearing = Bearing.fromDegrees(trueBoatBearingDegrees); //Check that the boat's bearing would actually be acceptable. //We continue (skip to next iteration) if it is outside of the interval. if (flippedInterval) { - //Bearing must be inside [lower, upper), where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. - if ((trueBoatBearing < bearingLowerBound) & (trueBoatBearing >= bearingUpperBound)) { + //Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. + if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) & (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { continue; } } else { - //Bearing must be inside [lower, upper). - if ((trueBoatBearing < bearingLowerBound) || (trueBoatBearing >= bearingUpperBound)) { + //Bearing must be inside [lower, upper]. + if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) || (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { continue; } @@ -209,14 +211,16 @@ public class Polars { //Check which pair of adjacent angles the angle is between. boolean foundAdjacentAngles = false; - double lowerBound = 0; - double upperBound = 0; + Bearing lowerBound = Bearing.fromDegrees(0d); + Bearing upperBound = Bearing.fromDegrees(0d); for (int i = 0; i < polarAngles.size() - 1; i++) { + Bearing currentAngle = polarAngles.get(i); + Bearing nextAngle = polarAngles.get(i + 1); //Check that angle is in interval [lower, upper). - if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) { + if ((angle.degrees() >= currentAngle.degrees()) && (angle.degrees() < nextAngle.degrees())) { foundAdjacentAngles = true; - lowerBound = polarAngles.get(i); - upperBound = polarAngles.get(i + 1); + lowerBound = currentAngle; + upperBound = nextAngle; break; } } @@ -232,11 +236,11 @@ public class Polars { //This is how far between the lower and upper bounds the angle is, as a proportion (e.g., 0.5 = half-way, 0.9 = close to upper). - double interpolationScalar = calculatePeriodicLinearInterpolateScalar(lowerBound, upperBound, 360, angle); + double interpolationScalar = calculatePeriodicLinearInterpolateScalar(lowerBound.degrees(), upperBound.degrees(), 360, angle.degrees()); //Get the estimated boat speeds for the lower and upper angles. - Pair lowerKey = new Pair<>(polarWindSpeed, lowerBound); - Pair upperKey = new Pair<>(polarWindSpeed, upperBound); + Pair lowerKey = new Pair<>(polarWindSpeed, lowerBound); + Pair upperKey = new Pair<>(polarWindSpeed, upperBound); double lowerSpeed = this.polarValues.get(lowerKey); double upperSpeed = this.polarValues.get(upperKey); @@ -245,10 +249,11 @@ public class Polars { //This is the delta angle between the boat's true bearing and the destination. - double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; + double angleBetweenDestAndTackDegrees = trueBoatBearing.degrees() - destinationAngle.degrees(); + Bearing angleBetweenDestAndTack = Bearing.fromDegrees(angleBetweenDestAndTackDegrees); //This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). - double interpolatedVelocity = Math.cos(Math.toRadians(angleBetweenDestAndTack)) * interpolatedSpeed; + double interpolatedVelocity = Math.cos(angleBetweenDestAndTack.radians()) * interpolatedSpeed; //Check that the velocity is better, if so, update our best VMG so far, for this wind speed. @@ -294,7 +299,9 @@ public class Polars { //We then calculate the interpolated VMG speed and angle using the interpolation scalar. double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar); - double interpolatedAngle = calculateLinearInterpolation(vmg1.getBearing(), vmg2.getBearing(), interpolationScalar); + double interpolatedAngleDegrees = calculateLinearInterpolation(vmg1.getBearing().degrees(), vmg2.getBearing().degrees(), interpolationScalar); + + Bearing interpolatedAngle = Bearing.fromDegrees(interpolatedAngleDegrees); //Return the interpolated VMG. @@ -384,11 +391,4 @@ public class Polars { return interpolatedValue; } - /** - * Returns the map used to store polar data. - * @return A map containing estimated boat speeds for a given (windSpeed, windAngle) pair. - */ - public Map, Double> getPolarValues() { - return polarValues; - } } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 63ef3537..92b51144 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -3,7 +3,6 @@ package seng302.Model; import javafx.animation.AnimationTimer; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import org.geotools.referencing.GeodeticCalculator; import seng302.Constants; import seng302.DataInput.RaceDataSource; @@ -11,39 +10,94 @@ import seng302.MockOutput; import seng302.Networking.Messages.BoatLocation; import seng302.Networking.Messages.BoatStatus; import seng302.Networking.Messages.Enums.BoatStatusEnum; +import seng302.Networking.Messages.Enums.RaceStatusEnum; +import seng302.Networking.Messages.Enums.RaceTypeEnum; import seng302.Networking.Messages.RaceStatus; -import java.awt.geom.Point2D; + +import java.io.*; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Random; -import static java.lang.Math.cos; -import static java.lang.Math.max; -import static java.lang.Math.min; /** - * Parent class for races - * Created by fwy13 on 3/03/17. + * Represents a yacht race. + * Has a course, boats, boundaries, etc... + * Is responsible for simulating the race, and sending messages to a MockOutput instance. */ public class Race implements Runnable { - protected ObservableList startingBoats; - protected ObservableList compoundMarks; - protected List legs; - protected int boatsFinished = 0; - protected long totalTimeElapsed; - protected int scaleFactor = 15; + /** + * An observable list of boats in the race. + */ + private ObservableList boats; + + /** + * An observable list of compound marks in the race. + */ + private ObservableList compoundMarks; + + /** + * A list of legs in the race. + */ + private List legs; + + /** + * A list of coordinates describing the boundary of the course. + */ + private List boundary; + + /** + * The elapsed time, in milliseconds, of the race. + */ + private long totalTimeElapsed; + + /** + * The starting timestamp, in milliseconds, of the race. + */ private long startTime; + + /** + * The scale factor of the race. + * Frame periods are multiplied by this to get the amount of time a single frame represents. + * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. + */ + private int scaleFactor = 25; + + /** + * The race ID of the course. + */ private int raceId; - private int dnfChance = 0; //percentage chance a boat fails at each checkpoint + + /** + * The current status of the race. + */ + private RaceStatusEnum raceStatusEnum; + + /** + * The type of race this is. + */ + private RaceTypeEnum raceType; + + /** + * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint. + * 0 = 0%, 100 = 100%. + */ + private int dnfChance = 0; + + + /** + * The mockOutput to send messages to. + */ private MockOutput mockOutput; - private List boundary; + /** * Wind direction bearing. */ - private double windDirection; + private Bearing windDirection; /** * Wind speed (knots). @@ -51,18 +105,33 @@ public class Race implements Runnable { */ private double windSpeed; + + + /** + * Constructs a race object with a given RaceDataSource and sends events to the given mockOutput. + * @param raceData Data source for race related data (boats, legs, etc...). + * @param mockOutput The mockOutput to send events to. + */ public Race(RaceDataSource raceData, MockOutput mockOutput) { - this.startingBoats = FXCollections.observableArrayList(raceData.getBoats()); - this.legs = raceData.getLegs(); + + this.mockOutput = mockOutput; + + this.boats = FXCollections.observableArrayList(raceData.getBoats()); this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks()); + this.boundary = raceData.getBoundary(); + this.legs = raceData.getLegs(); this.legs.add(new Leg("Finish", this.legs.size())); + this.raceId = raceData.getRaceId(); - this.mockOutput = mockOutput; - this.boundary = raceData.getBoundary(); - this.startTime = System.currentTimeMillis() + (Constants.PRE_RACE_WAIT_TIME / this.scaleFactor); - this.windSpeed = 12;//TODO could use input parameters for these. And should fluctuate during race. - this.windDirection = 180; + //The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another. + this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 1000)) / this.scaleFactor); + + this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); + this.raceType = RaceTypeEnum.FLEET_RACE; + + this.windSpeed = 12; + this.windDirection = Bearing.fromDegrees(180); } @@ -76,171 +145,348 @@ public class Race implements Runnable { } /** - * Parse the marker boats through mock output + * Parse the compound marker boats through mock output. */ - public void parseMarks() { - for (CompoundMark mark : compoundMarks){ - mockOutput.parseBoatLocation(mark.getMark1Source().getSourceID(), mark.getMark1().getLatitude(), mark.getMark1().getLongitude(),0,0); - if (mark.getMark2Source()!=null){ - mockOutput.parseBoatLocation(mark.getMark2Source().getSourceID(), mark.getMark2().getLatitude(), mark.getMark2().getLongitude(),0,0); + private void parseMarks() { + for (CompoundMark compoundMark : this.compoundMarks) { + + //Get the individual marks from the compound mark. + Mark mark1 = compoundMark.getMark1(); + Mark mark2 = compoundMark.getMark2(); + + //If they aren't null, parse them (some compound marks only have one mark). + if (mark1 != null) { + this.parseIndividualMark(mark1); } + + if (mark2 != null) { + this.parseIndividualMark(mark2); + } + } } + /** + * Parses an individual marker boat, and sends it to mockOutput. + * @param mark The marker boat to parse. + */ + private void parseIndividualMark(Mark mark) { + + this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0); + + } /** - * Countdown timer until race starts. + * Parse the boats in the race, and send it to mockOutput. + */ + private void parseBoatLocations() { + + //Parse each boat. + for (Boat boat : this.boats) { + + this.parseIndividualBoatLocation(boat); + + } + + } + + /** + * Parses an individual boat, and sends it to mockOutput. + * @param boat The boat to parse. + */ + private void parseIndividualBoatLocation(Boat boat) { + + this.mockOutput.parseBoatLocation( + boat.getSourceID(), + boat.getCurrentPosition().getLatitude(), + boat.getCurrentPosition().getLongitude(), + boat.getBearing().degrees(), + boat.getCurrentSpeed() + ); + + } + + + /** + * Updates the race status enumeration based on the current time, in milliseconds. + * @param currentTime The current time, in milliseconds. + */ + private void updateRaceStatusEnum(long currentTime) { + + //The amount of milliseconds until the race starts. + long timeToStart = this.startTime - currentTime; + + //Scale the time to start based on the scale factor. + long timeToStartScaled = timeToStart / this.scaleFactor; + + + if (timeToStartScaled > Constants.RacePreStartTime) { + //Time > 3 minutes is the prestart period. + this.setRaceStatusEnum(RaceStatusEnum.PRESTART); + + } else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) { + //Time between [1, 3] minutes is the warning period. + this.setRaceStatusEnum(RaceStatusEnum.WARNING); + + } else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) { + //Time between (0, 1] minutes is the preparatory period. + this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY); + + } else { + //Otherwise, the race has started! + this.setRaceStatusEnum(RaceStatusEnum.STARTED); + + } + + + } + + /** + * Parses the race status, and sends it to mockOutput. + */ + private void parseRaceStatus() { + + //A race status message contains a list of boat statuses. + List boatStatuses = new ArrayList<>(); + + //Add each boat status to the status list. + for (Boat boat : boats) { + + BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber()); + + boatStatuses.add(boatStatus); + } + + //TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue. + + //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. + int windDirectionInt = BoatLocation.convertHeadingDoubleToInt(this.windDirection.degrees()); + int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond); + + //Create race status object, and send it. + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses); + + mockOutput.parseRaceStatus(raceStatus); + + + } + + + /** + * Sets the status of all boats in the race to RACING. */ + private void setBoatsStatusToRacing() { + + for (Boat boat : this.boats) { + boat.setStatus(BoatStatusEnum.RACING); + } + } + + /** + * Countdown timer until race starts. + */ protected AnimationTimer countdownTimer = new AnimationTimer() { + + long currentTime = System.currentTimeMillis(); - long timeLeft; + @Override public void handle(long arg0) { - timeLeft = startTime - currentTime; - if (timeLeft <= 0) { + + //Update the race status based on the current time. + updateRaceStatusEnum(this.currentTime); + + //Parse the boat locations. + parseBoatLocations(); + + //Parse the marks. + parseMarks(); + + //Parse the race status. + parseRaceStatus(); + + + if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { System.setProperty("javafx.animation.fullspeed", "true"); + setBoatsStatusToRacing(); raceTimer.start(); - stop(); + this.stop(); } - ArrayList boatStatuses = new ArrayList<>(); - for (Boat boat : startingBoats) { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), - boat.getCurrentPosition().getLongitude(), boat.getHeading(), 0); - boatStatuses.add(new BoatStatus(boat.getSourceID(), BoatStatusEnum.PRESTART, 0)); - } - parseMarks(); - - int raceStatusNumber = timeLeft <= 60000 / scaleFactor && timeLeft > 0? 2 : 1; - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, raceStatusNumber, startTime, 0, 2300, 1, boatStatuses); - mockOutput.parseRaceStatus(raceStatus); + //Update the animations timer's time. currentTime = System.currentTimeMillis(); } }; + /** + * Timer that runs for the duration of the race, until all boats finish. + */ private AnimationTimer raceTimer = new AnimationTimer() { - //Start time of loop. + + /** + * Start time of loop, in milliseconds. + */ long timeRaceStarted = System.currentTimeMillis(); - int boatOffset = 0; + + /** + * The time of the previous frame, in milliseconds. + */ + long lastFrameTime = timeRaceStarted; @Override public void handle(long arg0) { - if (boatsFinished < startingBoats.size()) { - //Get the current time. - long currentTime = System.currentTimeMillis(); - //Update the total elapsed time. - totalTimeElapsed = currentTime - timeRaceStarted; - ArrayList boatStatuses = new ArrayList<>(); + //Get the current time. + long currentTime = System.currentTimeMillis(); + + //Update the total elapsed time. + totalTimeElapsed = currentTime - this.timeRaceStarted; + + //As long as there is at least one boat racing, we still simulate the race. + if (getNumberOfActiveBoats() != 0) { + + //Get the time period of this frame. + long framePeriod = currentTime - lastFrameTime; + //We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues. + framePeriod = 20; + + //For each boat, we update its position, and generate a BoatLocationMessage. - for (int i = 0; i < startingBoats.size(); i++) { - Boat boat = startingBoats.get((i + boatOffset) % startingBoats.size()); - if (boat != null) { - //Update position. - if (boat.getTimeFinished() < 0) { - updatePosition(boat, 15); - checkPosition(boat, totalTimeElapsed); - } - if (boat.getTimeFinished() > 0) { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); - boatStatuses.add(new BoatStatus(boat.getSourceID(), BoatStatusEnum.FINISHED, boat.getCurrentLeg().getLegNumber())); - } else { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); - boatStatuses.add(new BoatStatus(boat.getSourceID(), - boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber())); - } - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond), 2, boatStatuses);//TODO FIX replace magic values. - } else { - stop(); + for (Boat boat : boats) { + + //If it is still racing, update its position. + if (boat.getStatus() == BoatStatusEnum.RACING) { + + updatePosition(boat, framePeriod, totalTimeElapsed); + } + } - parseMarks(); - boatOffset = (boatOffset + 1) % (startingBoats.size()); - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 3, startTime, BoatLocation.convertHeadingDoubleToInt(windDirection), (int) (windSpeed * Constants.KnotsToMMPerSecond), 2, boatStatuses);//TODO FIX replace magic values. - mockOutput.parseRaceStatus(raceStatus); + + } else { + //Otherwise, the race is over! + + setRaceStatusEnum(RaceStatusEnum.FINISHED); + this.stop(); } + + //Parse the boat locations. + parseBoatLocations(); + + //Parse the marks. + parseMarks(); + + //Parse the race status. + parseRaceStatus(); + + + //Update the last frame time. + this.lastFrameTime = currentTime; } }; + + /** + * Initialise the boats in the race. + * This sets their starting positions and current legs. + */ public void initialiseBoats() { - Leg officialStart = legs.get(0); - String name = officialStart.getName(); - Marker endMark = officialStart.getEndCompoundMark(); - ArrayList startingPositions = getSpreadStartingPositions(); - - for (int i = 0; i < startingBoats.size(); i++) { - Boat boat = startingBoats.get(i); - if (boat != null) { - Leg newLeg = new Leg(name, new Marker(startingPositions.get(i)), endMark, 0); - boat.setCurrentLeg(newLeg); - boat.setCurrentSpeed(Constants.TEST_VELOCITIES[i]);//TODO we should get rid of TEST_VELOCITIES since speed is based off of wind speed/angle. - boat.setCurrentPosition(startingPositions.get(i)); - boat.setHeading(boat.calculateHeading()); - boat.setTimeSinceTackChange(999999);//We set a large time since tack change so that it calculates a new VMG when the simulation starts. - } + + //Gets the starting positions of the boats. + List startingPositions = getSpreadStartingPositions(); + + //Get iterators for our boat and position lists. + Iterator boatIt = this.boats.iterator(); + Iterator startPositionIt = startingPositions.iterator(); + + //Iterate over the pair of lists. + while (boatIt.hasNext() && startPositionIt.hasNext()) { + + //Get the next boat and position. + Boat boat = boatIt.next(); + GPSCoordinate startPosition = startPositionIt.next(); + + + //The boat starts on the first leg of the race. + boat.setCurrentLeg(this.legs.get(0)); + + //Boats start with 0 knots speed. + boat.setCurrentSpeed(0d); + + //Place the boat at its starting position. + boat.setCurrentPosition(startPosition); + + //Boats start facing their next marker. + boat.setBearing(boat.calculateBearingToNextMarker()); + + //Sets the boats status to prestart - it changes to racing when the race starts. + boat.setStatus(BoatStatusEnum.PRESTART); + + //We set a large time since tack change so that it calculates a new VMG when the simulation starts. + boat.setTimeSinceTackChange(999999); + } + } + + /** - * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line + * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line. * - * @return list of starting positions + * @return A list of starting positions. */ - public ArrayList getSpreadStartingPositions() { + public List getSpreadStartingPositions() { + + //The first compound marker of the race - the starting gate. + CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); - int nBoats = startingBoats.size(); - Marker compoundMark = legs.get(0).getStartCompoundMark(); + //The position of the two markers from the compound marker. + GPSCoordinate mark1Position = compoundMark.getMark1Position(); + GPSCoordinate mark2Position = compoundMark.getMark2Position(); - GeodeticCalculator initialCalc = new GeodeticCalculator(); - initialCalc.setStartingGeographicPoint(compoundMark.getMark1().getLongitude(), compoundMark.getMark1().getLatitude()); - initialCalc.setDestinationGeographicPoint(compoundMark.getMark2().getLongitude(), compoundMark.getMark2().getLatitude()); - double azimuth = initialCalc.getAzimuth(); - double distanceBetweenMarkers = initialCalc.getOrthodromicDistance(); - double distanceBetweenBoats = distanceBetweenMarkers / (nBoats + 1); + //Calculates the azimuth between the two points. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position); - GeodeticCalculator positionCalc = new GeodeticCalculator(); - positionCalc.setStartingGeographicPoint(compoundMark.getMark1().getLongitude(), compoundMark.getMark1().getLatitude()); - ArrayList positions = new ArrayList<>(); + //Calculates the distance between the two points. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position); - for (int i = 0; i < nBoats; i++) { - positionCalc.setDirection(azimuth, distanceBetweenBoats); - Point2D position = positionCalc.getDestinationGeographicPoint(); - positions.add(new GPSCoordinate(position.getY(), position.getX())); + //The number of boats in the race. + int numberOfBoats = this.boats.size(); + //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks. + double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1); + + + //List to store coordinates in. + List positions = new ArrayList<>(); + + //We start spacing boats out from mark 1. + GPSCoordinate position = mark1Position; + + //For each boat, displace position, and store it. + for (int i = 0; i < numberOfBoats; i++) { + + position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth); + + positions.add(position); - positionCalc = new GeodeticCalculator(); - positionCalc.setStartingGeographicPoint(position); } + return positions; } + /** - * Calculates the boats next GPS position based on its distance travelled and heading - * - * @param oldCoordinates GPS coordinates of the boat's starting position - * @param distanceTravelled distance in nautical miles - * @param azimuth boat's current direction. Value between -180 and 180 - * @return The boat's new coordinate - */ - public static GPSCoordinate calculatePosition(GPSCoordinate oldCoordinates, double distanceTravelled, double azimuth) { - - //Find new coordinate using current heading and distance - GeodeticCalculator geodeticCalculator = new GeodeticCalculator(); - //Load start point into calculator - Point2D startPoint = new Point2D.Double(oldCoordinates.getLongitude(), oldCoordinates.getLatitude()); - geodeticCalculator.setStartingGeographicPoint(startPoint); - //load direction and distance travelled into calculator - geodeticCalculator.setDirection(azimuth, distanceTravelled * Constants.NMToMetersConversion); - //get new point - Point2D endPoint = geodeticCalculator.getDestinationGeographicPoint(); - - return new GPSCoordinate(endPoint.getY(), endPoint.getX()); - } + * Calculates a boat's VMG. + * @param boat The boat to calculate VMG for. + * @return VMG for the specified boat. + */ + private VMG calculateVMG(Boat boat) { - private VMG calculateHeading(Boat boat) { //How fast a boat can turn, in degrees per millisecond. double turnRate = 0.03; @@ -248,110 +494,188 @@ public class Race implements Runnable { double turnAngle = turnRate * boat.getTimeSinceTackChange(); //Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360). - double bound1 = Math.max(boat.getHeading() - turnAngle, 0); - double bound2 = Math.min(boat.getHeading() + turnAngle, 360); + double bound1Degrees = Math.max(boat.getBearing().degrees() - turnAngle, 0); + double bound2Degrees = Math.min(boat.getBearing().degrees() + turnAngle, 360); + + Bearing bound1 = Bearing.fromDegrees(bound1Degrees); + Bearing bound2 = Bearing.fromDegrees(bound2Degrees); - return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToDestination(), bound1, bound2); + return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2); } - private boolean improvesVelocity(Boat boat, VMG newHeading) { - double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); - double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); - double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); - double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); + + /** + * Determines whether or not a given VMG improves the velocity of a boat. + * @param boat The boat to test. + * @param vmg The new VMG to test. + * @return True if the new VMG is improves velocity, false otherwise. + */ + private boolean improvesVelocity(Boat boat, VMG vmg) { + + //Calculates the angle between the boat and its destination. + Angle angleBetweenDestAndHeading = Angle.fromDegrees(boat.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees()); + + //Calculates the angle between the new VMG and the boat's destination. + Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(vmg.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees()); + + + //Calculate the boat's current velocity. + double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * boat.getCurrentSpeed(); + + //Calculate the potential velocity with the new VMG. + double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * vmg.getSpeed(); + + //Return whether or not the new VMG gives better velocity. return vmgVelocity > currentVelocity; + } + /** * Calculates the distance a boat has travelled and updates its current position according to this value. * - * @param boat to be updated - * @param millisecondsElapsed since last update + * @param boat The boat to be updated. + * @param updatePeriodMilliseconds The time, in milliseconds, since the last update. + * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race. */ - protected void updatePosition(Boat boat, int millisecondsElapsed) { + protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { - //distanceTravelled = velocity (nm p hr) * time taken to update loop - double distanceTravelled = (boat.getCurrentSpeed() * this.scaleFactor * millisecondsElapsed) / 3600000; - double totalDistanceTravelled; + //Checks if the current boat has finished the race or not. + boolean finish = this.isLastLeg(boat.getCurrentLeg()); - boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - double totalDistanceTravelledInTack = distanceTravelled;//TODO FIX// + boat.getDistanceTravelledInTack(); - boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + this.scaleFactor * millisecondsElapsed); + //Calculates the distance travelled, in meters, in the current timeslice. + double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); + + //Scale it. + distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor; + + + //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. + boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); + - VMG newHeading = calculateHeading(boat); //Calculate the new VMG. + VMG newVMG = this.calculateVMG(boat); - if (improvesVelocity(boat, newHeading)) { - boat.setHeading(newHeading.getBearing()); - boat.setCurrentSpeed(newHeading.getSpeed()); - boat.setTimeSinceTackChange(0); + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { + boat.setVMG(newVMG); } - double azimuth = boat.getHeading(); - if (azimuth > 180) { - azimuth = azimuth - 360; - } - //tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind - GPSCoordinate test = calculatePosition(boat.getCurrentPosition(), (100.0 / Constants.NMToMetersConversion), azimuth); - if (!GPSCoordinate.isInsideBoundary(test, boundary)) { - double tempHeading = (boat.getHeading() - this.windDirection + 90) % 360; - boat.setHeading(tempHeading); - } - //calc the distance travelled in a straight line to windward - //double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); - totalDistanceTravelled = cos(Math.toRadians(boat.getHeading() - boat.calculateBearingToDestination()))*totalDistanceTravelledInTack; - boat.setDistanceTravelledInLeg(totalDistanceTravelled); + //Ensure that the boat doesn't leave the course bounds. + this.forceBoatBearingInBounds(boat); + + + //Check the boats position (update leg and stuff). + checkPosition(boat, totalTimeElapsed); + + } + + } + + + /** + * Checks if the boat's current bearing would put it out of course bounds, and adjusts it if it does. + * @param boat The boat to check. + */ + private void forceBoatBearingInBounds(Boat boat) { + + //Get the boat's azimuth. + Azimuth azimuth = Azimuth.fromBearing(boat.getBearing()); + //Tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind. + double epsilonMeters = 100d; + GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(boat.getCurrentPosition(), epsilonMeters, azimuth); - //Calculate boat's new position by adding the distance travelled onto the start point of the leg - azimuth = boat.getHeading(); - azimuth = azimuth > 180? azimuth - 360 : azimuth; - boat.setCurrentPosition(calculatePosition(boat.getCurrentPosition(), totalDistanceTravelledInTack, azimuth)); + //If it isn't inside the boundary, calculate new bearing. + if (!GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { + Bearing tempBearing = Bearing.fromDegrees(boat.getBearing().degrees() - this.windDirection.degrees() + 90); + boat.setBearing(tempBearing); } + } + + /** + * Checks if a boat has finished any legs, or has pulled out of race (DNF). + * @param boat The boat to check. + * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. + */ protected void checkPosition(Boat boat, long timeElapsed) { - //System.out.println(boat.getDistanceTravelledInLeg()); - //System.out.println(boat.getCurrentLeg().getDistance()); - //System.out.println(" "); - //if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { - //The distance (in nautical miles) within which the boat needs to get in order to consider that it has reached the marker. - double epsilon = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. - if (boat.calculateDistanceToNextMarker() < epsilon) { - //boat has passed onto new leg - - if (boat.getCurrentLeg().getName().equals("Finish")) { - //boat has finished - boatsFinished++; - boat.setTimeFinished(timeElapsed); + + //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. + double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + + if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { + //Boat has reached its target marker, and has moved on to a new leg. + + + + //Calculate how much the boat overshot the marker by. + double overshootMeters = boat.calculateDistanceToNextMarker(); + + + //Move boat on to next leg. + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + + //Add overshoot distance into the distance travelled for the next leg. + boat.setDistanceTravelledInLeg(overshootMeters); + + //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. + boat.setTimeSinceTackChange(999999); + + + //Check if the boat has finished or stopped racing. + + if (this.isLastLeg(boat.getCurrentLeg())) { + //Boat has finished. boat.setTimeFinished(timeElapsed); boat.setCurrentSpeed(0); + boat.setStatus(BoatStatusEnum.FINISHED); + } else if (doNotFinish()) { - boatsFinished++; + //Boat has pulled out of race. boat.setTimeFinished(timeElapsed); boat.setCurrentLeg(new Leg("DNF", -1)); boat.setCurrentSpeed(0); - } else { - //Calculate how much the boat overshot the marker by - boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); - //Move boat on to next leg - Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); - //Add overshoot distance into the distance travelled for the next leg - boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg()); - - //Setting a high value for this allows the boat to immediately do a large turn, as it has needs to in order to get to the next mark. - boat.setTimeSinceTackChange(999999); + boat.setStatus(BoatStatusEnum.DNF); + } + } + + } + + + /** + * Determines whether or not a specific leg is the last leg in the race. + * @param leg The leg to check. + * @return Returns true if it is the last, false otherwse. + */ + private boolean isLastLeg(Leg leg) { + + //Get the last leg. + Leg lastLeg = this.legs.get(this.legs.size() - 1); + + //Check its ID. + int lastLegID = lastLeg.getLegNumber(); + + //Get the specified leg's ID. + int legID = leg.getLegNumber(); + + + //Check if they are the same. + return legID == lastLegID; } + /** * Sets the chance each boat has of failing at a gate or marker * @@ -363,9 +687,69 @@ public class Race implements Runnable { } } + /** + * Decides if a boat should received a DNF status. + * @return True means it should DNF, false means it shouldn't. + */ protected boolean doNotFinish() { Random rand = new Random(); return rand.nextInt(100) < dnfChance; } + + /** + * Returns the current race status. + * @return The current race status. + */ + public RaceStatusEnum getRaceStatusEnum() { + return raceStatusEnum; + } + + /** + * Sets the current race status. + * @param raceStatusEnum The new status of the race. + */ + private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + this.raceStatusEnum = raceStatusEnum; + } + + + /** + * Returns the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { + return raceType; + } + + + /** + * Returns the number of boats that are still active in the race. + * They become inactive by either finishing or withdrawing. + * @return The number of boats still active in the race. + */ + protected int getNumberOfActiveBoats() { + + int numberofActiveBoats = 0; + + for (Boat boat : this.boats) { + + //If the boat is currently racing, count it. + if (boat.getStatus() == BoatStatusEnum.RACING) { + numberofActiveBoats++; + } + + } + + return numberofActiveBoats; + } + + + /** + * Returns an observable list of boats in the race. + * @return List of boats in the race. + */ + public ObservableList getBoats() { + return boats; + } } diff --git a/mock/src/main/java/seng302/Model/VMG.java b/mock/src/main/java/seng302/Model/VMG.java index dbc2b89a..67bc5297 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/mock/src/main/java/seng302/Model/VMG.java @@ -10,14 +10,14 @@ package seng302.Model; public class VMG { /** - * Speed component of the VMG. + * Speed component of the VMG, in knots. */ private double speed; /** * Bearing component of the VMG. */ - private double bearing; + private Bearing bearing; /** @@ -25,7 +25,7 @@ public class VMG { * @param speed Speed component of the VMG. * @param bearing Bearing component of the VMG. */ - public VMG(double speed, double bearing) { + public VMG(double speed, Bearing bearing) { this.speed = speed; this.bearing = bearing; } @@ -43,11 +43,8 @@ public class VMG { * Returns the bearing component of this VMG object. * @return Bearing component of this VMG object. */ - public double getBearing() { + public Bearing getBearing() { return bearing; } - public void setBearing(double bearing) { - this.bearing = bearing; - } } diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java index 64e78d27..10c898c9 100644 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ b/mock/src/test/java/seng302/Model/BoatTest.java @@ -1,10 +1,10 @@ package seng302.Model; +import org.junit.Before; import org.junit.Test; - -import static junit.framework.TestCase.*; +import static org.junit.Assert.assertEquals; /** * Created by esa46 on 22/03/17. @@ -12,85 +12,92 @@ import static junit.framework.TestCase.*; public class BoatTest { - private GPSCoordinate ORIGIN_COORDS = new GPSCoordinate(0, 0); - private Boat TEST_BOAT = new Boat(1, "Test", "tt", new Polars()); + private GPSCoordinate ORIGIN_COORDS; + private Boat TEST_BOAT; + + @Before + public void setUp() { + ORIGIN_COORDS = new GPSCoordinate(0, 0); + TEST_BOAT = new Boat(1, "Test", "tt", new Polars()); + TEST_BOAT.setCurrentPosition(ORIGIN_COORDS); + } @Test public void calculateDueNorthAzimuthReturns0() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(50, 0)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8); } @Test public void calculateDueSouthAzimuthReturns180() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8); } @Test public void calculateDueEastAzimuthReturns90() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(0, 50)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8); } @Test public void calculateDueWestAzimuthReturnsNegative90() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(0, -50)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -90, 1e-8); } @Test public void calculateDueNorthHeadingReturns0() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(50, 0)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateHeading(), 0, 1e-8); + assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 0, 1e-8); } @Test public void calculateDueEastHeadingReturns90() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(0, 50)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateHeading(), 90, 1e-8); + assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 90, 1e-8); } @Test public void calculateDueSouthHeadingReturns180() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateHeading(), 180, 1e-8); + assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 180, 1e-8); } @Test public void calculateDueWestHeadingReturns270() { - Marker startMarker = new Marker(ORIGIN_COORDS); - Marker endMarker = new Marker(new GPSCoordinate(0, -50)); + CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); + CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50))); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateHeading(), 270, 1e-8); + assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 270, 1e-8); } } diff --git a/mock/src/test/java/seng302/Model/CompoundMarkTest.java b/mock/src/test/java/seng302/Model/CompoundMarkTest.java index 00035995..ca8da8cd 100644 --- a/mock/src/test/java/seng302/Model/CompoundMarkTest.java +++ b/mock/src/test/java/seng302/Model/CompoundMarkTest.java @@ -16,7 +16,7 @@ public class CompoundMarkTest { @Test public void averageOfSingleMarkAtOriginIsSingleMark() { - Marker testMark = new Marker(ORIGIN_COORD); + CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD)); assertTrue(testMark.getAverageGPSCoordinate().equals(ORIGIN_COORD)); } @@ -25,7 +25,7 @@ public class CompoundMarkTest { public void averageOfSingleMarkIsSingleMark() { GPSCoordinate testCoord = new GPSCoordinate(20, 25); - Marker testMark = new Marker(testCoord); + CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord)); assertTrue(testMark.getAverageGPSCoordinate().equals(testCoord)); } @@ -34,15 +34,16 @@ public class CompoundMarkTest { public void averageLatOfTwoMarksIsAccurate() { GPSCoordinate testCoord = new GPSCoordinate(0.001, 0); - Marker testMark = new Marker(ORIGIN_COORD, testCoord); + CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord)); assertEquals(testMark.getAverageGPSCoordinate(), new GPSCoordinate(0.0005, 0)); + } @Test public void averageLongOfTwoMarksIsAccurate() { GPSCoordinate testCoord = new GPSCoordinate(0, 10); - Marker testMark = new Marker(ORIGIN_COORD, testCoord); + CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord)); assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(0, 5))); } @@ -52,7 +53,9 @@ public class CompoundMarkTest { GPSCoordinate testCoord1 = new GPSCoordinate(0.0, 30); GPSCoordinate testCoord2 = new GPSCoordinate(0.001, 60); - Marker testMark = new Marker(testCoord1, testCoord2); + + CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord1), new Mark(2, "test origin 2", testCoord2)); + assertEquals(testMark.getAverageGPSCoordinate().getLatitude(), 0.00051776, 1e-8); assertEquals(testMark.getAverageGPSCoordinate().getLongitude(), 45.000000, 1e-8); } diff --git a/mock/src/test/java/seng302/Model/LegTest.java b/mock/src/test/java/seng302/Model/LegTest.java index cae7b203..06ac325b 100644 --- a/mock/src/test/java/seng302/Model/LegTest.java +++ b/mock/src/test/java/seng302/Model/LegTest.java @@ -14,7 +14,7 @@ import static junit.framework.TestCase.assertEquals; */ public class LegTest { - private Marker ORIGIN_Compound_MARKER = new Marker(new GPSCoordinate(0, 0)); + private CompoundMark ORIGIN_Compound_MARKER = new CompoundMark(new Mark(1, "test mark1", new GPSCoordinate(0, 0))); @Test public void calculateDistanceHandles5nmNorth() { @@ -22,9 +22,9 @@ public class LegTest { calc.setStartingGeographicPoint(0, 0); calc.setDirection(0, 5 * Constants.NMToMetersConversion); - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); + CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint()); Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0); - assertEquals(test.getDistance(), 5, 1e-8); + assertEquals(test.getDistanceNauticalMiles(), 5, 1e-8); } @Test @@ -33,9 +33,9 @@ public class LegTest { calc.setStartingGeographicPoint(0, 0); calc.setDirection(90, 12 * Constants.NMToMetersConversion); - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); + CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint()); Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0); - assertEquals(test.getDistance(), 12, 1e-8); + assertEquals(test.getDistanceNauticalMiles(), 12, 1e-8); } @Test @@ -44,9 +44,9 @@ public class LegTest { calc.setStartingGeographicPoint(0, 0); calc.setDirection(180, 0.5 * Constants.NMToMetersConversion); - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); + CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint()); Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0); - assertEquals(test.getDistance(), 0.5, 1e-8); + assertEquals(test.getDistanceNauticalMiles(), 0.5, 1e-8); } @Test @@ -55,23 +55,23 @@ public class LegTest { calc.setStartingGeographicPoint(0, 0); calc.setDirection(-90, 0.1 * Constants.NMToMetersConversion); - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); + CompoundMark endMarker = getEndMarker(calc.getDestinationGeographicPoint()); Leg test = new Leg("Test", ORIGIN_Compound_MARKER, endMarker, 0); - assertEquals(test.getDistance(), 0.1, 1e-8); + assertEquals(test.getDistanceNauticalMiles(), 0.1, 1e-8); } @Test public void calculateDistanceHandlesZeroDifference() { Leg test = new Leg("Test", ORIGIN_Compound_MARKER, ORIGIN_Compound_MARKER, 0); - assertEquals(test.getDistance(), 0, 1e-8); + assertEquals(test.getDistanceNauticalMiles(), 0, 1e-8); } - private Marker getEndMarker(Point2D point) { + private CompoundMark getEndMarker(Point2D point) { GPSCoordinate coords = new GPSCoordinate(point.getY(), point.getX()); - return new Marker(coords); + return new CompoundMark(new Mark(3, "test mark3", coords)); } } diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/mock/src/test/java/seng302/Model/PolarsTest.java index d3e61d5d..fbdcae2d 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/mock/src/test/java/seng302/Model/PolarsTest.java @@ -16,10 +16,11 @@ public class PolarsTest { private double angleEpsilon = 2; private double speedEpsilon = 0.5; - @Before + /** * Creates the Polars object for the tests. */ + @Before public void setUp() { //Read data. try { @@ -31,216 +32,226 @@ public class PolarsTest { } } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG1() { //Test 1. //This test has a wind speed that is between two values from the table (12kn, 16kn, this is 15.9kn). - double windAngle1 = 31.5; - double destAngle1 = 65.32; + Bearing windAngle1 = Bearing.fromDegrees(31.5); + Bearing destAngle1 = Bearing.fromDegrees(65.32); double windSpeed1 = 15.9;//knots - double vmgAngle1 = 72.4; + Bearing vmgAngle1 = Bearing.fromDegrees(72.4); double vmgSpeed1 = 30.4; - VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, 0, 360); - double calcVMGAngle1 = calcVMG1.getBearing(); + VMG calcVMG1 = polars.calculateVMG(windAngle1, windSpeed1, destAngle1, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9)); + Bearing calcVMGAngle1 = calcVMG1.getBearing(); double calcVMGSpeed1 = calcVMG1.getSpeed(); - assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon); + assertEquals(calcVMGAngle1.degrees(), vmgAngle1.degrees(), angleEpsilon); assertEquals(calcVMGSpeed1, vmgSpeed1, speedEpsilon); } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG2() { //Test 2. //This test has a wind speed much larger than any in the table (max from table is 30kn, this is 40kn). - double windAngle2 = 200; - double destAngle2 = 35; + Bearing windAngle2 = Bearing.fromDegrees(200); + Bearing destAngle2 = Bearing.fromDegrees(35); double windSpeed2 = 40;//knots - double vmgAngle2 = 69; + Bearing vmgAngle2 = Bearing.fromDegrees(69); double vmgSpeed2 = 32.8; - VMG calcVMG2 = polars.calculateVMG(windAngle2, windSpeed2, destAngle2, 0, 360); - double calcVMGAngle2 = calcVMG2.getBearing(); + VMG calcVMG2 = polars.calculateVMG(windAngle2, windSpeed2, destAngle2, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9)); + Bearing calcVMGAngle2 = calcVMG2.getBearing(); double calcVMGSpeed2 = calcVMG2.getSpeed(); - assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon); + assertEquals(calcVMGAngle2.degrees(), vmgAngle2.degrees(), angleEpsilon); assertEquals(calcVMGSpeed2, vmgSpeed2, speedEpsilon); } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG3() { //Test 3. //This test has a wind speed lower than any non-zero values from the table (table has 0kn, 4kn, this is 2kn). - double windAngle3 = 345; - double destAngle3 = 199; + Bearing windAngle3 = Bearing.fromDegrees(345); + Bearing destAngle3 = Bearing.fromDegrees(199); double windSpeed3 = 2;//knots - double vmgAngle3 = 222; + Bearing vmgAngle3 = Bearing.fromDegrees(222); double vmgSpeed3 = 4.4; - VMG calcVMG3 = polars.calculateVMG(windAngle3, windSpeed3, destAngle3, 0, 360); - double calcVMGAngle3 = calcVMG3.getBearing(); + VMG calcVMG3 = polars.calculateVMG(windAngle3, windSpeed3, destAngle3, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9)); + Bearing calcVMGAngle3 = calcVMG3.getBearing(); double calcVMGSpeed3 = calcVMG3.getSpeed(); - assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon); + assertEquals(calcVMGAngle3.degrees(), vmgAngle3.degrees(), angleEpsilon); assertEquals(calcVMGSpeed3, vmgSpeed3, speedEpsilon); } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG4() { //Test 4. //This test has a wind speed of 0. - double windAngle4 = 5; - double destAngle4 = 100; + Bearing windAngle4 = Bearing.fromDegrees(5); + Bearing destAngle4 = Bearing.fromDegrees(100); double windSpeed4 = 0;//knots - double vmgAngle4 = 100; + Bearing vmgAngle4 = Bearing.fromDegrees(100); double vmgSpeed4 = 0; - VMG calcVMG4 = polars.calculateVMG(windAngle4, windSpeed4, destAngle4, 0, 360); - double calcVMGAngle4 = calcVMG4.getBearing(); + VMG calcVMG4 = polars.calculateVMG(windAngle4, windSpeed4, destAngle4, Bearing.fromDegrees(0), Bearing.fromDegrees(359.9)); + Bearing calcVMGAngle4 = calcVMG4.getBearing(); double calcVMGSpeed4 = calcVMG4.getSpeed(); - assertEquals(calcVMGAngle4, vmgAngle4, angleEpsilon); + assertEquals(calcVMGAngle4.degrees(), vmgAngle4.degrees(), angleEpsilon); assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG5() { //Test 5. //This test has a bearing bound of [55, 70), which only contains a suboptimal VMG. - double windAngle5 = 5; - double destAngle5 = 100; + Bearing windAngle5 = Bearing.fromDegrees(5); + Bearing destAngle5 = Bearing.fromDegrees(100); double windSpeed5 = 9;//knots - double vmgAngle5 = 70; + Bearing vmgAngle5 = Bearing.fromDegrees(70); double vmgSpeed5 = 15; - double bearingUpperBound5 = 70; - double bearingLowerBound5 = 55; + Bearing bearingUpperBound5 = Bearing.fromDegrees(70); + Bearing bearingLowerBound5 = Bearing.fromDegrees(55); VMG calcVMG5 = polars.calculateVMG(windAngle5, windSpeed5, destAngle5, bearingLowerBound5, bearingUpperBound5); - double calcVMGAngle5 = calcVMG5.getBearing(); + Bearing calcVMGAngle5 = calcVMG5.getBearing(); double calcVMGSpeed5 = calcVMG5.getSpeed(); - assertEquals(calcVMGAngle5, vmgAngle5, angleEpsilon); + assertEquals(calcVMGAngle5.degrees(), vmgAngle5.degrees(), angleEpsilon); assertEquals(calcVMGSpeed5, vmgSpeed5, speedEpsilon); - assertTrue(calcVMGAngle5 >= bearingLowerBound5); - assertTrue(calcVMGAngle5 < bearingUpperBound5); + assertTrue(calcVMGAngle5.degrees() >= bearingLowerBound5.degrees()); + assertTrue(calcVMGAngle5.degrees() <= bearingUpperBound5.degrees()); } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG6() { //Test 6. //This test has a bearing bound of [70, 55), which has a lower bound > upper bound, which is complementary to [55, 70). - double windAngle6 = 5; - double destAngle6 = 100; + Bearing windAngle6 = Bearing.fromDegrees(5); + Bearing destAngle6 = Bearing.fromDegrees(100); double windSpeed6 = 11;//knots - double vmgAngle6 = 92.85; + Bearing vmgAngle6 = Bearing.fromDegrees(92.85); double vmgSpeed6 = 20.086; - double bearingUpperBound6 = 55; - double bearingLowerBound6 = 70; + Bearing bearingUpperBound6 = Bearing.fromDegrees(55); + Bearing bearingLowerBound6 = Bearing.fromDegrees(70); VMG calcVMG6 = polars.calculateVMG(windAngle6, windSpeed6, destAngle6, bearingLowerBound6, bearingUpperBound6); - double calcVMGAngle6 = calcVMG6.getBearing(); + Bearing calcVMGAngle6 = calcVMG6.getBearing(); double calcVMGSpeed6 = calcVMG6.getSpeed(); - assertEquals(calcVMGAngle6, vmgAngle6, angleEpsilon); + assertEquals(calcVMGAngle6.degrees(), vmgAngle6.degrees(), angleEpsilon); assertEquals(calcVMGSpeed6, vmgSpeed6, speedEpsilon); - if (bearingLowerBound6 > bearingUpperBound6) { - assertTrue((calcVMGAngle6 >= bearingLowerBound6) || (calcVMGAngle6 <= bearingUpperBound6)); + if (bearingLowerBound6.degrees() > bearingUpperBound6.degrees()) { + assertTrue((calcVMGAngle6.degrees() >= bearingLowerBound6.degrees()) || (calcVMGAngle6.degrees() <= bearingUpperBound6.degrees())); } else { - assertTrue(calcVMGAngle6 >= bearingLowerBound6); - assertTrue(calcVMGAngle6 < bearingUpperBound6); + assertTrue(calcVMGAngle6.degrees() >= bearingLowerBound6.degrees()); + assertTrue(calcVMGAngle6.degrees() <= bearingUpperBound6.degrees()); } } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG7() { //Test 7. //This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). - double windAngle7 = 340; - double destAngle7 = 30; + Bearing windAngle7 = Bearing.fromDegrees(340); + Bearing destAngle7 = Bearing.fromDegrees(30); double windSpeed7 = 7;//knots - double vmgAngle7 = 5; + Bearing vmgAngle7 = Bearing.fromDegrees(5); double vmgSpeed7 = 11; - double bearingUpperBound7 = 5; - double bearingLowerBound7 = 340; + Bearing bearingUpperBound7 = Bearing.fromDegrees(5); + Bearing bearingLowerBound7 = Bearing.fromDegrees(340); VMG calcVMG7 = polars.calculateVMG(windAngle7, windSpeed7, destAngle7, bearingLowerBound7, bearingUpperBound7); - double calcVMGAngle7 = calcVMG7.getBearing(); + Bearing calcVMGAngle7 = calcVMG7.getBearing(); double calcVMGSpeed7 = calcVMG7.getSpeed(); - assertEquals(calcVMGAngle7, vmgAngle7, angleEpsilon); + assertEquals(calcVMGAngle7.degrees(), vmgAngle7.degrees(), angleEpsilon); assertEquals(calcVMGSpeed7, vmgSpeed7, speedEpsilon); - if (bearingLowerBound7 > bearingUpperBound7) { - assertTrue((calcVMGAngle7 >= bearingLowerBound7) || (calcVMGAngle7 <= bearingUpperBound7)); + if (bearingLowerBound7.degrees() > bearingUpperBound7.degrees()) { + assertTrue((calcVMGAngle7.degrees() >= bearingLowerBound7.degrees()) || (calcVMGAngle7.degrees() <= bearingUpperBound7.degrees())); + } else { - assertTrue(calcVMGAngle7 >= bearingLowerBound7); - assertTrue(calcVMGAngle7 < bearingUpperBound7); + assertTrue(calcVMGAngle7.degrees() >= bearingLowerBound7.degrees()); + assertTrue(calcVMGAngle7.degrees() <= bearingUpperBound7.degrees()); + } } - @Test + /** * Tests if we can calculate VMG for a variety of values. */ + @Test public void testVMG8() { //Test 8. //This test has a bearing bound of [340, 5), which has a lower bound > upper bound, which is complementary to [5, 340). Due to the wind, dest angles, and bearing bounds, it cannot actually find a VMG > 0 (valid VMGs will actually be in the angle interval [10, 190]), so it will return the VMG(angle=0, speed=0). - double windAngle8 = 5; - double destAngle8 = 100; + Bearing windAngle8 = Bearing.fromDegrees(5); + Bearing destAngle8 = Bearing.fromDegrees(100); double windSpeed8 = 7;//knots - double vmgAngle8 = 0; + Bearing vmgAngle8 = Bearing.fromDegrees(0); double vmgSpeed8 = 0; - double bearingUpperBound8 = 5; - double bearingLowerBound8 = 340; + Bearing bearingUpperBound8 = Bearing.fromDegrees(5); + Bearing bearingLowerBound8 = Bearing.fromDegrees(340); VMG calcVMG8 = polars.calculateVMG(windAngle8, windSpeed8, destAngle8, bearingLowerBound8, bearingUpperBound8); - double calcVMGAngle8 = calcVMG8.getBearing(); + Bearing calcVMGAngle8 = calcVMG8.getBearing(); double calcVMGSpeed8 = calcVMG8.getSpeed(); - assertEquals(calcVMGAngle8, vmgAngle8, 0); + assertEquals(calcVMGAngle8.degrees(), vmgAngle8.degrees(), 0); assertEquals(calcVMGSpeed8, vmgSpeed8, 0); } diff --git a/mock/src/test/java/seng302/Model/RaceTest.java b/mock/src/test/java/seng302/Model/RaceTest.java index e86473fd..abfa5a8a 100644 --- a/mock/src/test/java/seng302/Model/RaceTest.java +++ b/mock/src/test/java/seng302/Model/RaceTest.java @@ -29,9 +29,9 @@ import static org.mockito.Mockito.*; public class RaceTest{ - public static final Marker ORIGIN = new Marker(new GPSCoordinate(0, 0)); - public static final Marker THREE_NM_FROM_ORIGIN = new Marker(new GPSCoordinate(0.050246769, 0)); - public static final Marker FIFTEEN_NM_FROM_ORIGIN = new Marker(new GPSCoordinate(0.251233845, 0)); + public static final CompoundMark ORIGIN = new CompoundMark(new Mark(1, "test origin 1", new GPSCoordinate(0, 0))); + public static final CompoundMark THREE_NM_FROM_ORIGIN = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0))); + public static final CompoundMark FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0))); public static ArrayList TEST_LEGS = new ArrayList<>(); public static final int START_LEG_DISTANCE = 3; public static final int MIDDLE_LEG_DISTANCE = 12; @@ -92,12 +92,12 @@ public class RaceTest{ RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); Race testRace = new Race(dataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(FINISH_LEG); testBoat.setDistanceTravelledInLeg(1); testRace.checkPosition(testBoat, 1); - assertEquals(testRace.boatsFinished, 1); + assertEquals(testRace.getNumberOfActiveBoats(), 0); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { e.printStackTrace(); @@ -114,7 +114,7 @@ public class RaceTest{ RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); Race testRace = new Race(dataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(FINISH_LEG); testBoat.setDistanceTravelledInLeg(1); testRace.checkPosition(testBoat, 1); @@ -136,7 +136,7 @@ public class RaceTest{ RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); Race testRace = new Race(dataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(FINISH_LEG); testBoat.setDistanceTravelledInLeg(1); testRace.checkPosition(testBoat, 1); @@ -158,12 +158,12 @@ public class RaceTest{ RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); Race testRace = new Race(dataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(START_LEG); testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE); testRace.checkPosition(testBoat, 1); - assertEquals(testRace.boatsFinished, 0); + assertEquals(testRace.getNumberOfActiveBoats(), 1); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { e.printStackTrace(); @@ -181,7 +181,7 @@ public class RaceTest{ RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); Race testRace = new Race(dataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(START_LEG); testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); testRace.checkPosition(testBoat, 0); @@ -241,7 +241,7 @@ public class RaceTest{ RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); Race testRace = new Race(raceDataSource, mockOutput); testRace.setDnfChance(100); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(START_LEG); testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); testRace.checkPosition(testBoat, 1); @@ -262,10 +262,10 @@ public class RaceTest{ BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); Race testRace = new Race(raceDataSource, mockOutput); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(FINISH_LEG); testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); - testRace.updatePosition(testBoat, 1); + testRace.updatePosition(testBoat, 1, 1); assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate()); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { @@ -283,11 +283,11 @@ public class RaceTest{ RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); Race testRace = new Race(raceDataSource, mockOutput); testRace.initialiseBoats(); - Boat testBoat = testRace.startingBoats.get(0); + Boat testBoat = testRace.getBoats().get(0); testBoat.setCurrentLeg(START_LEG); testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1); testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); - testRace.updatePosition(testBoat, 100); + testRace.updatePosition(testBoat, 100, 100); assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate()); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { diff --git a/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java b/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java index 4f9efa6f..cc7a4ae6 100644 --- a/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java +++ b/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java @@ -5,6 +5,7 @@ import seng302.Networking.Messages.*; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.List; import static seng302.Networking.Utils.ByteConverter.*; @@ -32,7 +33,7 @@ public class RaceVisionByteEncoder { */ public static byte[] raceStatus(RaceStatus raceStatus){ - ArrayList boatStatuses = raceStatus.getBoatStatuses(); + List boatStatuses = raceStatus.getBoatStatuses(); ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size()); //Version Number 1 bytes diff --git a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java b/network/src/main/java/seng302/Networking/Messages/BoatStatus.java index ab625b95..dacb5d5e 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java +++ b/network/src/main/java/seng302/Networking/Messages/BoatStatus.java @@ -31,10 +31,10 @@ public class BoatStatus { this.sourceID = sourceID; this.boatStatus = boatStatusEnum.getValue(); this.legNumber = ByteConverter.intToBytes(legNum)[0]; - numPenaltiesAwarded = 0; - numPenaltiesServed = 0; - estTimeAtFinish = 0; - estTimeAtNextMark = 0; + this.numPenaltiesAwarded = 0; + this.numPenaltiesServed = 0; + this.estTimeAtFinish = 0; + this.estTimeAtNextMark = 0; } diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java b/network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java new file mode 100644 index 00000000..2563545d --- /dev/null +++ b/network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java @@ -0,0 +1,108 @@ +package seng302.Networking.Messages.Enums; + + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various statuses a race can have. See AC35 streaming spec, 4.2. + */ +public enum RaceStatusEnum { + + NOT_ACTIVE(0), + + /** + * Between 3:00 and 1:00 minutes before start. + */ + WARNING(1), + + /** + * Less than 1:00 minutes before start. + */ + PREPARATORY(2), + STARTED(3), + + /** + * Obsolete. + */ + FINISHED(4), + + /** + * Obsolete. + */ + RETIRED(5), + ABANDONED(6), + POSTPONED(7), + TERMINATED(8), + RACE_START_TIME_NOT_SET(9), + + /** + * More than 3:00 minutes until start. + */ + PRESTART(10), + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_STATUS(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RaceStatusEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RaceStatusEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + /** + * Stores a mapping between Byte values and RaceStatusEnum values. + */ + private static final Map byteToStatusMap = new HashMap<>(); + + + /** + * Static initialization block. Initializes the byteToStatusMap. + */ + static { + for (RaceStatusEnum type : RaceStatusEnum.values()) { + RaceStatusEnum.byteToStatusMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param raceStatusByte Byte value to convert to a RaceStatusEnum value. + * @return The RaceStatusEnum value which corresponds to the given byte value. + */ + public static RaceStatusEnum fromByte(byte raceStatusByte) { + //Gets the corresponding MessageType from the map. + RaceStatusEnum type = RaceStatusEnum.byteToStatusMap.get(raceStatusByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_STATUS RaceStatusEnum. + return RaceStatusEnum.NOT_A_STATUS; + } else { + //Otherwise, return the RaceStatusEnum. + return type; + } + + } + +} diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java b/network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java new file mode 100644 index 00000000..01c1539e --- /dev/null +++ b/network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java @@ -0,0 +1,87 @@ +package seng302.Networking.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types of races. See AC35 streaming spec, 4.2. + */ +public enum RaceTypeEnum { + + + /** + * A race between two boats. + */ + MATCH_RACE(1), + + /** + * A race between a fleet of boats. + */ + FLEET_RACE(2), + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_STATUS(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RaceTypeEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RaceTypeEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + /** + * Stores a mapping between Byte values and RaceStatusEnum values. + */ + private static final Map byteToStatusMap = new HashMap<>(); + + + /** + * Static initialization block. Initializes the byteToStatusMap. + */ + static { + for (RaceTypeEnum type : RaceTypeEnum.values()) { + RaceTypeEnum.byteToStatusMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param raceTypeEnum Byte value to convert to a RaceTypeEnum value. + * @return The RaceTypeEnum value which corresponds to the given byte value. + */ + public static RaceTypeEnum fromByte(byte raceTypeEnum) { + //Gets the corresponding MessageType from the map. + RaceTypeEnum type = RaceTypeEnum.byteToStatusMap.get(raceTypeEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_STATUS RaceTypeEnum. + return RaceTypeEnum.NOT_A_STATUS; + } else { + //Otherwise, return the RaceTypeEnum. + return type; + } + + } + + +} diff --git a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java b/network/src/main/java/seng302/Networking/Messages/RaceStatus.java index 87ea6342..89c7b90c 100644 --- a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java +++ b/network/src/main/java/seng302/Networking/Messages/RaceStatus.java @@ -3,6 +3,7 @@ package seng302.Networking.Messages; import seng302.Networking.Messages.Enums.MessageType; import java.util.ArrayList; +import java.util.List; /** * Created by fwy13 on 25/04/17. @@ -16,10 +17,10 @@ public class RaceStatus extends AC35Data { private int windDirection; private int windSpeed; private int raceType; - private ArrayList boatStatuses; + private List boatStatuses; private static final double windDirectionScalar = 360.0 / 32768.0; // 0x8000 / 360 - public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, ArrayList boatStatuses){ + public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ super(MessageType.RACESTATUS); this.currentTime = currentTime; this.raceID = raceID; @@ -74,7 +75,7 @@ public class RaceStatus extends AC35Data { return raceType; } - public ArrayList getBoatStatuses() + public List getBoatStatuses() { return boatStatuses; } diff --git a/visualiser/src/main/java/seng302/Mock/StreamedRace.java b/visualiser/src/main/java/seng302/Mock/StreamedRace.java index 2ca0fd5c..afcd7d5b 100644 --- a/visualiser/src/main/java/seng302/Mock/StreamedRace.java +++ b/visualiser/src/main/java/seng302/Mock/StreamedRace.java @@ -83,6 +83,7 @@ public class StreamedRace implements Runnable { int legNumber = boatStatusMessage.getLegNumber(); + if (legNumber >= 1 && legNumber < legs.size()) { boat.setCurrentLeg(legs.get(legNumber)); } @@ -95,9 +96,9 @@ public class StreamedRace implements Runnable { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setFinished(true); - //System.out.println("Boat finished"); } } + //Update the boat display table in the GUI to reflect the leg change updatePositions(); } From 81ed07330bb453d989a88903181f512ee430279f Mon Sep 17 00:00:00 2001 From: fjc40 Date: Fri, 19 May 2017 21:50:14 +1200 Subject: [PATCH 36/37] Fixed the bug where the race clock wasn't visible some of the time. It appears to have been due to a threading/javaFX thread issue with prop1.bind(prop2). --- .../main/java/seng302/Controllers/RaceController.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/visualiser/src/main/java/seng302/Controllers/RaceController.java b/visualiser/src/main/java/seng302/Controllers/RaceController.java index 106a0089..5781f9e7 100644 --- a/visualiser/src/main/java/seng302/Controllers/RaceController.java +++ b/visualiser/src/main/java/seng302/Controllers/RaceController.java @@ -1,6 +1,7 @@ package seng302.Controllers; +import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.*; @@ -118,7 +119,14 @@ public class RaceController extends Controller { //Initialize save annotation array, fps listener, and annotation listeners timeZone.setText(raceClock.getTimeZone()); - timer.textProperty().bind(raceClock.durationProperty()); + + //RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update. + raceClock.durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); + initializeFPS(); initializeAnnotations(); From de0abf94e0c33e4c71094f1dceb10ee9dc9c3d69 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 20 May 2017 00:18:42 +1200 Subject: [PATCH 37/37] Mock.Race: Fixed an issue where boats sometimes sailed towards the wind when attempting to turn from bearing just under 360 degrees to a bearing just over 360 degrees. This required an update to the boundary detection code. Visualiser: Appear to have fixed issue where clock didn't display correctly. Previous commit only fixed certain circumstances. #story[873] --- mock/src/main/java/seng302/Model/Race.java | 114 +++++++++++++++--- .../seng302/Controllers/StartController.java | 34 ++++-- .../main/java/seng302/Model/RaceClock.java | 3 + 3 files changed, 124 insertions(+), 27 deletions(-) diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 92b51144..fa9223fe 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -64,7 +64,7 @@ public class Race implements Runnable { * Frame periods are multiplied by this to get the amount of time a single frame represents. * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. */ - private int scaleFactor = 25; + private int scaleFactor = 5; /** * The race ID of the course. @@ -483,24 +483,43 @@ public class Race implements Runnable { /** * Calculates a boat's VMG. * @param boat The boat to calculate VMG for. + * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. * @return VMG for the specified boat. */ - private VMG calculateVMG(Boat boat) { + private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) { //How fast a boat can turn, in degrees per millisecond. double turnRate = 0.03; //How much the boat is allowed to turn, considering how long since it last turned. double turnAngle = turnRate * boat.getTimeSinceTackChange(); + //This ensures that the boat bounds don't flip around. + turnAngle = Math.min(turnAngle, 179.999); //Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360). - double bound1Degrees = Math.max(boat.getBearing().degrees() - turnAngle, 0); - double bound2Degrees = Math.min(boat.getBearing().degrees() + turnAngle, 360); + double bound1Degrees = boat.getBearing().degrees() - turnAngle; + double bound2Degrees = boat.getBearing().degrees() + turnAngle; Bearing bound1 = Bearing.fromDegrees(bound1Degrees); Bearing bound2 = Bearing.fromDegrees(bound2Degrees); + + //Get the lower and upper acceptable bounds. + Bearing lowerAcceptableBound = bearingBounds[0]; + Bearing upperAcceptableBound = bearingBounds[1]; + + + //Find the bounds on what angle the boat can travel on, taking into account both turning rate and the acceptable bounds. + bound1Degrees = Math.max(bound1.degrees(), lowerAcceptableBound.degrees()); + bound2Degrees = Math.min(bound2.degrees(), upperAcceptableBound.degrees()); + + bound1 = Bearing.fromDegrees(bound1Degrees); + bound2 = Bearing.fromDegrees(bound2Degrees); + + + //Calculate VMG using these bounds. return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2); + } @@ -557,9 +576,11 @@ public class Race implements Runnable { boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); + //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. + Bearing[] bearingBounds = this.calculateBearingBounds(boat); //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat); + VMG newVMG = this.calculateVMG(boat, bearingBounds); //If the new vmg improves velocity, use it. @@ -568,12 +589,8 @@ public class Race implements Runnable { } - //Ensure that the boat doesn't leave the course bounds. - this.forceBoatBearingInBounds(boat); - - //Check the boats position (update leg and stuff). - checkPosition(boat, totalTimeElapsed); + this.checkPosition(boat, totalTimeElapsed); } @@ -581,22 +598,81 @@ public class Race implements Runnable { /** - * Checks if the boat's current bearing would put it out of course bounds, and adjusts it if it does. + * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. * @param boat The boat to check. + * @return An array of bearings. The first is the lower bound, the second is the upper bound. + */ + private Bearing[] calculateBearingBounds(Boat boat) { + + Bearing[] bearings = new Bearing[2]; + + Bearing lowerBearing = Bearing.fromDegrees(0); + Bearing upperBearing = Bearing.fromDegrees(359.999); + + + boolean foundAnyBadBearing = false; + boolean foundLowerBearing = false; + + //Check all bearings between [0, 360). + for (double angle = 0; angle < 360; angle += 1) { + + //Create bearing from angle. + Bearing bearing = Bearing.fromDegrees(angle); + + //Check that if it is acceptable. + boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); + + if (bearingIsGood) { + + //The lower bearing will be the first acceptable bearing after finding any unacceptable bearing. + if (foundAnyBadBearing && !foundLowerBearing) { + lowerBearing = bearing; + foundLowerBearing = true; + } + + //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing. + if (!foundAnyBadBearing) { + upperBearing = bearing; + } + + + } else { + + foundAnyBadBearing = true; + + } + + } + + + bearings[0] = lowerBearing; + bearings[1] = upperBearing; + + return bearings; + } + + + + /** + * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries. + * @param bearing The bearing to check. + * @param position The position to start from. */ - private void forceBoatBearingInBounds(Boat boat) { + private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { - //Get the boat's azimuth. - Azimuth azimuth = Azimuth.fromBearing(boat.getBearing()); + //Get azimuth from bearing. + Azimuth azimuth = Azimuth.fromBearing(bearing); - //Tests to see if a point in front of the boat is out of bounds, if so mirror heading in the wind. + + //Tests to see if a point in front of the boat is out of bounds. double epsilonMeters = 100d; - GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(boat.getCurrentPosition(), epsilonMeters, azimuth); + GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); //If it isn't inside the boundary, calculate new bearing. - if (!GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { - Bearing tempBearing = Bearing.fromDegrees(boat.getBearing().degrees() - this.windDirection.degrees() + 90); - boat.setBearing(tempBearing); + if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) { + return true; + } else { + return false; } } diff --git a/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java index 464599a6..3004ab6c 100644 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ b/visualiser/src/main/java/seng302/Controllers/StartController.java @@ -54,6 +54,9 @@ public class StartController extends Controller implements Observer { ///Tracks whether the race has been started (that is, has startRaceNoScaling() be called). private boolean hasRaceStarted = false; + //Tracks whether or not a clock has been created and setup, which occurs after receiving enough information. + private boolean hasCreatedClock = false; + /** * Begins the race with a scale factor of 1 */ @@ -106,7 +109,14 @@ public class StartController extends Controller implements Observer { private void setRaceClock() { raceClock = new RaceClock(raceData.getZonedDateTime()); - timeZoneTime.textProperty().bind(raceClock.timeStringProperty()); + + raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timeZoneTime.setText(newValue); + }); + }); +//TEMP REMOVE + //timeZoneTime.textProperty().bind(raceClock.timeStringProperty()); raceClock.run(); } @@ -116,7 +126,12 @@ public class StartController extends Controller implements Observer { long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime(); raceClock.setStartingTime(raceClock.getLocalTime(utcTime)); raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime())); - timer.textProperty().bind(raceClock.durationProperty()); + + raceClock.durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); }); } @@ -138,12 +153,15 @@ public class StartController extends Controller implements Observer { } if (streamedCourse.hasReadCourse()) { Platform.runLater(() -> { - setRaceClock(); - if(visualiserInput.getRaceStatus() == null){ - return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop. - }// TODO - replace with observer on VisualiserInput - setStartingTime(); - setCurrentTime(); + if (!this.hasCreatedClock) { + this.hasCreatedClock = true; + setRaceClock(); + if (visualiserInput.getRaceStatus() == null) { + return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop. + }// TODO - replace with observer on VisualiserInput + setStartingTime(); + setCurrentTime(); + } }); } diff --git a/visualiser/src/main/java/seng302/Model/RaceClock.java b/visualiser/src/main/java/seng302/Model/RaceClock.java index f17b9e12..1e36224d 100644 --- a/visualiser/src/main/java/seng302/Model/RaceClock.java +++ b/visualiser/src/main/java/seng302/Model/RaceClock.java @@ -59,6 +59,8 @@ public class RaceClock implements Runnable { this.time = time; this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time)); this.lastTime = System.currentTimeMillis(); + + if(startingTime != null) { long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds(); if(seconds < 0) @@ -66,6 +68,7 @@ public class RaceClock implements Runnable { else duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60)); } + } /**