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]
main
fjc40 9 years ago
parent 12b6f49f2e
commit 656212d06d

@ -45,7 +45,7 @@ public class Event {
System.out.println("Sending Boat"); System.out.println("Sending Boat");
sendBoatData(); sendBoatData();
int scaleFactor = 25;//TEMP - was 15. int scaleFactor = 12;//TEMP - was 15.
Race newRace = new Race(raceDataSource, scaleFactor, mockOutput); Race newRace = new Race(raceDataSource, scaleFactor, mockOutput);
new Thread((newRace)).start(); new Thread((newRace)).start();
} }

@ -2,8 +2,7 @@ package seng302.Model;
import javafx.util.Pair; import javafx.util.Pair;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
/** /**
* Created by hba56 on 10/05/17. * Created by hba56 on 10/05/17.
@ -15,11 +14,12 @@ import java.util.HashMap;
public class Polars { public class Polars {
///Internal store of data. Maps pair<windSpeed, windAngle> to boatSpeed. ///Internal store of data. Maps pair<windSpeed, windAngle> to boatSpeed.
private HashMap<Pair<Double, Double>, Double> polarValues = new HashMap<>(); private Map<Pair<Double, Double>, Double> polarValues = new HashMap<>();
///Stores a list of angles from the polar table - this is used during the calculateVMG function. ///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. ///Maps between windSpeed and a list of angles for that wind speed.
private HashMap<Double, ArrayList<Double>> polarAngles = new HashMap<>(); private HashMap<Double, List<Double>> polarAngles = new HashMap<>();
@ -40,7 +40,7 @@ public class Polars {
*/ */
public void addEstimate(double trueWindSpeed, double relativeWindAngle, double 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. //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. //Create the array to store angles for this wind speed if it doesn't exist.
if (!this.polarAngles.containsKey(trueWindSpeed)) { if (!this.polarAngles.containsKey(trueWindSpeed)) {
@ -56,6 +56,7 @@ public class Polars {
while (negativeAngle >= 360d) { while (negativeAngle >= 360d) {
negativeAngle -= 360d; negativeAngle -= 360d;
} }
//Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0. //Ensure that the positive and negative angles aren't the same (e.g., pos = 0, neg = 360 - 0 = 0.
if (negativeAngle != relativeWindAngle) { if (negativeAngle != relativeWindAngle) {
Pair newKeyNegative = new Pair(trueWindSpeed, negativeAngle); 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)) { if (!this.polarAngles.get(trueWindSpeed).contains(relativeWindAngle)) {
this.polarAngles.get(trueWindSpeed).add(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. * 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. * The resulting angle of the VMG will be within the interval [bearingLowerBound, bearingUpperBound). Note the exclusive end of interval.
* @param trueWindSpeed The current true wind speed. * 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 destinationAngle The angle between the boat and the destination point. * @param trueWindAngle The current true wind angle. Interval is [0, 360).
* @param bearingLowerBound The lowest bearing (angle) that the boat may travel on. * @param trueWindSpeed The current true wind speed. Knots.
* @param bearingUpperBound The highest bearing (angle) that the boat may travel on. * @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. * @return The VMG.
*/ */
public VMG calculateVMG(double trueWindAngle, double trueWindSpeed, double destinationAngle, double bearingLowerBound, double bearingUpperBound) { 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<Double> angles : this.polarAngles.values()) {
angles.sort(null);
}
//Get the lower bound into the interval [0, 360)
bearingLowerBound = ((bearingLowerBound % 360d) + 360d) % 360d; bearingLowerBound = ((bearingLowerBound % 360d) + 360d) % 360d;
bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d; //We use a bound of [lower, upper), so we allow the upper to be 360 degrees.
if (bearingLowerBound > bearingUpperBound) { if (bearingUpperBound != 360d) {
bearingLowerBound -= 360d; bearingUpperBound = ((bearingUpperBound % 360d) + 360d) % 360d;
} }
//Sorts polar angles. //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).
for (ArrayList<Double> angles : this.polarAngles.values()) { boolean flippedInterval = false;
angles.sort(null); 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). //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 polarWindSpeedLowerBound = 0d;
double polarWindSpeedUpperBound = 9999999;//Start this off with a value larger than any in the Polars table so that it actually works. 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). //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 foundUpperBoundWindSpeed = false;
boolean foundLowerBoundSpeed = false; boolean foundLowerBoundWindSpeed = false;
for (Pair<Double, Double> key : this.polarValues.keySet()) { for (Pair<Double, Double> key : this.polarValues.keySet()) {
//The key is Pair<windSpeed, windAngle>, so pair.key is windSpeed.
double currentPolarSpeed = key.getKey(); double currentPolarSpeed = key.getKey();
//Lower bound. //Lower bound.
if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) { if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) {
polarWindSpeedLowerBound = currentPolarSpeed; polarWindSpeedLowerBound = currentPolarSpeed;
foundLowerBoundSpeed = true; foundLowerBoundWindSpeed = true;
} }
//Upper bound. //Upper bound.
if ((currentPolarSpeed < polarWindSpeedUpperBound) && (currentPolarSpeed > trueWindSpeed)) { if ((currentPolarSpeed < polarWindSpeedUpperBound) && (currentPolarSpeed > trueWindSpeed)) {
polarWindSpeedUpperBound = currentPolarSpeed; polarWindSpeedUpperBound = currentPolarSpeed;
foundUpperBoundSpeed = true; foundUpperBoundWindSpeed = true;
} }
} }
@ -129,70 +141,79 @@ public class Polars {
//Find the angle with the best VMG. //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. //We need to find the VMGs for both lower and upper bound wind speeds, and interpolate between them.
ArrayList<VMG> vmgs = new ArrayList<>(); List<VMG> vmgs = new ArrayList<>();
//Put wind speed bounds we found above into an array. //Put wind speed bounds we found above into a list.
double[] windSpeedBounds = new double[2]; List<Double> windSpeedBounds = new ArrayList<>(2);
windSpeedBounds[0] = polarWindSpeedLowerBound;
if (foundUpperBoundSpeed) { if (foundLowerBoundWindSpeed) {
windSpeedBounds[1] = polarWindSpeedUpperBound; windSpeedBounds.add(polarWindSpeedLowerBound);
} }
else { if (foundUpperBoundWindSpeed) {
//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.add(polarWindSpeedUpperBound);
windSpeedBounds[1] = 0d;
} }
//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. //The list of polar angles for this wind speed.
ArrayList<Double> polarAngles = this.polarAngles.get(polarWindSpeed); List<Double> polarAngles = this.polarAngles.get(polarWindSpeed);
double bestVMGVelocity = 0; double bestVMGVelocity = 0;
double bestVMGSpeed = 0;
double bestVMGAngle = 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) { 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. //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; double trueBoatBearing = trueWindAngle + angle + 180d;
//We put trueBoatBearing into the interval [0, 360).
while (trueBoatBearing >= 360) { while (trueBoatBearing >= 360) {
trueBoatBearing -= 360; trueBoatBearing -= 360;
} }
//Check that the boat's bearing would actually be acceptable. //Check that the boat's bearing would actually be acceptable.
if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { //We continue (skip to next iteration) if it is outside of the interval.
//If the angle is too small or too great, don't use it - skip to the next iteration. if (flippedInterval) {
continue; //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. //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. //Check which pair of adjacent angles the angle is between.
boolean foundInterval = false; boolean foundAdjacentAngles = false;
double lowerBound = 0; double lowerBound = 0;
double upperBound = 0; double upperBound = 0;
for (int j = 0; j < polarAngles.size() - 1; j++) { for (int i = 0; i < polarAngles.size() - 1; i++) {
if ((angle >= polarAngles.get(j)) && (angle < polarAngles.get(j + 1))) { //Check that angle is in interval [lower, upper).
foundInterval = true; if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) {
lowerBound = polarAngles.get(j); foundAdjacentAngles = true;
upperBound = polarAngles.get(j + 1); lowerBound = polarAngles.get(i);
upperBound = polarAngles.get(i + 1);
break; 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. //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); lowerBound = polarAngles.get(polarAngles.size() - 1);
upperBound = polarAngles.get(0); 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. //This is the delta angle between the boat's true bearing and the destination.
double angleBetweenDestAndTack = trueBoatBearing - destinationAngle; double angleBetweenDestAndTack = trueBoatBearing - destinationAngle;
//This is the estimated velocity towards the target (e.g., angling away from the target reduces velocity). //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. //Check that the velocity is better, if so, update our best VMG so far, for this wind speed.
if (vmgTemp > bestVMGVelocity) { if (interpolatedVelocity > bestVMGVelocity) {
bestVMGVelocity = interpolatedSpeed; bestVMGVelocity = interpolatedVelocity;
bestVMGSpeed = interpolatedSpeed;
bestVMGAngle = trueBoatBearing; bestVMGAngle = trueBoatBearing;
} }
@ -231,17 +254,16 @@ public class Polars {
//Angle iteration loop is finished. //Angle iteration loop is finished.
//Create the VMG, and add to list. //Create the VMG, and add to list.
VMG vmg = new VMG(bestVMGVelocity, bestVMGAngle); VMG vmg = new VMG(bestVMGSpeed, bestVMGAngle);
vmgs.add(vmg); 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 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); 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 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. //We do a simple linear interpolation.
@ -251,10 +273,11 @@ public class Polars {
if (vmgs.size() > 1) { if (vmgs.size() > 1) {
//If we have a second VMG use it. //If we have a second VMG use it.
vmg2 = vmgs.get(1); vmg2 = vmgs.get(1);
} } else {
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).
//Otherwise create a VMG with zero speed, but the same angle. //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 = new VMG(0, vmg1.getBearing()); vmg2 = vmg1;
vmg1 = new VMG(0, vmg1.getBearing());
} }
@ -265,6 +288,7 @@ public class Polars {
double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar); double interpolatedSpeed = calculateLinearInterpolation(vmg1.getSpeed(), vmg2.getSpeed(), interpolationScalar);
double interpolatedAngle = calculateLinearInterpolation(vmg1.getBearing(), vmg2.getBearing(), interpolationScalar); double interpolatedAngle = calculateLinearInterpolation(vmg1.getBearing(), vmg2.getBearing(), interpolationScalar);
//Return the interpolated VMG. //Return the interpolated VMG.
return new VMG(interpolatedSpeed, interpolatedAngle); return new VMG(interpolatedSpeed, interpolatedAngle);
@ -353,10 +377,10 @@ public class Polars {
} }
/** /**
* Returns the hashmap used to store polar data. * Returns the map used to store polar data.
* @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. * @return A map containing estimated boat speeds for a given (windSpeed, windAngle) pair.
*/ */
public HashMap<Pair<Double, Double>, Double> getPolarValues() { public Map<Pair<Double, Double>, Double> getPolarValues() {
return polarValues; return polarValues;
} }
} }

@ -341,6 +341,9 @@ public class Race implements Runnable {
bound1 = boat.getHeading() - turnAngle; bound1 = boat.getHeading() - turnAngle;
bound2 = 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, VMG newHeading = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed,
boat.calculateBearingToDestination(), bound1, bound2); boat.calculateBearingToDestination(), bound1, bound2);

@ -45,7 +45,7 @@ public class PolarsTest {
double calcVMGAngle1 = calcVMG1.getBearing(); double calcVMGAngle1 = calcVMG1.getBearing();
double calcVMGSpeed1 = calcVMG1.getSpeed(); 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); //assertEquals(calcVMGAngle1, vmgAngle1, angleEpsilon);
@ -64,7 +64,8 @@ public class PolarsTest {
double calcVMGAngle2 = calcVMG2.getBearing(); double calcVMGAngle2 = calcVMG2.getBearing();
double calcVMGSpeed2 = calcVMG2.getSpeed(); 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); //assertEquals(calcVMGAngle2, vmgAngle2, angleEpsilon);
@ -83,7 +84,7 @@ public class PolarsTest {
double calcVMGAngle3 = calcVMG3.getBearing(); double calcVMGAngle3 = calcVMG3.getBearing();
double calcVMGSpeed3 = calcVMG3.getSpeed(); 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); //assertEquals(calcVMGAngle3, vmgAngle3, angleEpsilon);
@ -102,12 +103,107 @@ public class PolarsTest {
double calcVMGAngle4 = calcVMG4.getBearing(); double calcVMGAngle4 = calcVMG4.getBearing();
double calcVMGSpeed4 = calcVMG4.getSpeed(); 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(calcVMGAngle4, vmgAngle4, angleEpsilon);
//assertEquals(calcVMGSpeed4, vmgSpeed4, speedEpsilon); //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);
} }
} }

Loading…
Cancel
Save