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]
main
fjc40 9 years ago
parent 2dc17a688f
commit 6f2c5d6c40

@ -89,135 +89,264 @@ public class Polars {
*/ */
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) {
//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. //Sorts polar angles.
for (ArrayList<Double> angles : this.polarAngles.values()) { for (ArrayList<Double> angles : this.polarAngles.values()) {
angles.sort(null); 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<Double, Double> key : this.polarValues.keySet()) { for (Pair<Double, Double> key : this.polarValues.keySet()) {
double currentPolarSpeed = key.getKey(); 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. //Lower bound.
if ((currentPolarSpeed > polarWindSpeed) && (currentPolarSpeed <= trueWindSpeed)) { if ((currentPolarSpeed >= polarWindSpeedLowerBound) && (currentPolarSpeed <= trueWindSpeed)) {
polarWindSpeed = currentPolarSpeed; 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. //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<VMG> 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; //Calculate VMG for both bounds.
double bestVMGVelocity = 0; for (int i = 0; i < windSpeedBounds.length; i++) {
//The list of polar angles for this wind speed.
ArrayList<Double> 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;
}
//Check that the boat's bearing would actually be acceptable. double polarWindSpeed = windSpeedBounds[i];
if ((trueBoatBearing <= bearingLowerBound) || (trueBoatBearing > bearingUpperBound)) { //We don't calculate anything for wind speeds of 0, as boats will not move.
//If the angle is too small or too great, don't use it - skip to the next iteration. if (polarWindSpeed == 0d) {
continue; 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<Double> polarAngles = this.polarAngles.get(polarWindSpeed);
//Check which pair of adjacent points the angle is between.
boolean foundInterval = false; double bestVMGVelocity = 0;
double lowerBound = 0; double bestVMGAngle = 0;
double upperBound = 0;
for (int i = 0; i < polarAngles.size() - 1; i++) { //For all angles in the accepted interval (in 1 degree increments).
if ((angle >= polarAngles.get(i)) && (angle < polarAngles.get(i + 1))) { for (double angle = 0; angle < 360; angle += 1) {
foundInterval = true;
lowerBound = polarAngles.get(i); //This is the true bearing of the boat, if it went at the angle against the wind.
upperBound = polarAngles.get(i + 1); //For angle > 90 and angle < 270, it means that the boat is actually going _with_ the wind (gybe).
break; 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<Double,Double> lowerKey = new Pair<>(polarWindSpeed, lowerBound);
Pair<Double,Double> 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. //We do a simple linear interpolation.
//I.e., L----A-----------U
// <----> is lowerDelta. VMG vmg1 = vmgs.get(0);
double lowerDelta = angle - lowerBound; VMG vmg2;
if (vmgs.size() > 1) {
//This is the "distance" between the upper and lower bound. //If we have a second VMG use it.
//I.e., L----A-----------U vmg2 = vmgs.get(1);
// <----------------> is intervalDelta. }
//This can potentially be negative if we have, e.g., lower = 340deg, upper = 0deg, delta = -340deg. else {
double intervalDelta = upperBound - lowerBound; //Otherwise create a VMG with zero speed, but the same angle.
//If it _is_ negative, modulo it to make it positive. vmg2 = new VMG(0, vmg1.getBearing());
//E.g., -340deg = +20deg.
if (intervalDelta < 0) {
intervalDelta += 360d;
} }
//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. //Get the interpolation scalar for the current wind speed.
Pair<Double,Double> lowerKey = new Pair<>(polarWindSpeed, lowerBound); double interpolationScalar = calculateLinearInterpolateScalar(polarWindSpeedLowerBound, polarWindSpeedUpperBound, trueWindSpeed);
Pair<Double,Double> 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;
//Calculate the speed at the interpolated angle. //We then calculate the interpolated VMG speed and angle using the interpolation scalar.
double interpolatedSpeed = lowerSpeed + (speedDelta * interpolationScalar); 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 interpolationScalar;
return new VMG(bestVMGVelocity, bestVMGAngle); }
/**
* 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. * Returns the hashmap used to store polar data.
* @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair. * @return A hashmap containing estimated boat speeds for a given (windSpeed, windAngle) pair.

@ -10,10 +10,10 @@ package seng302.Model;
public class VMG { public class VMG {
///Speed component of the VMG. ///Speed component of the VMG.
double speed; private double speed;
///Bearing component of the VMG. ///Bearing component of the VMG.
double bearing; private double bearing;
/** /**

Loading…
Cancel
Save