Vmg fix Note: there should be a small deadzone between upwind and reaching/running where pressing the key has no effect so that issues in judging the exact TWA of the boat don't lead to a surprising direction change. Turn off toggle Remember to close Issue#43 when you have finished DoD'ing See merge request !37main
commit
321359be07
@ -0,0 +1,204 @@
|
||||
package mock.model;
|
||||
|
||||
import shared.model.Bearing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* New Polars are the revampe of the old Polars class which interpolates the data after being parsed from the Polar Parser
|
||||
* There can only be one NewPolars instance stored statically however if a boat does happen to have a special case it can be assigned.
|
||||
*/
|
||||
public class NewPolars {
|
||||
|
||||
|
||||
//true wind speed, <true wind angle, best boat angle>
|
||||
private static Map<Double, TreeMap<Double, Double>> polars = new TreeMap<>();
|
||||
|
||||
public static NewPolars newPolars = null;
|
||||
|
||||
public NewPolars(){
|
||||
newPolars = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add polars from the polar table
|
||||
* @param trueWindSpeed True Wind Speed that the true wind angle and speed corresponds to
|
||||
* @param trueWindAngle True Wind Angle of the race
|
||||
* @param boatSpeed The speed the boat should be going at given the true wind angle
|
||||
*/
|
||||
public static void addPolars(double trueWindSpeed, Bearing trueWindAngle, double boatSpeed){
|
||||
double tws = trueWindSpeed;
|
||||
double bs = boatSpeed;
|
||||
double twa = trueWindAngle.degrees();
|
||||
if (!polars.containsKey(tws)){
|
||||
polars.put(tws, new TreeMap<>());
|
||||
}
|
||||
polars.get(tws).putIfAbsent(twa, bs);
|
||||
polars.get(tws).putIfAbsent(360d - twa, bs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linearly Interpolates this should only be called once per parsing of a polar table
|
||||
*/
|
||||
public static void linearInterpolatePolars(){
|
||||
TreeMap<Double, Double> prevTWS = null;
|
||||
TreeMap<Double, TreeMap<Double, Double>> iterablePolars = new TreeMap<>(polars);
|
||||
//this loop averages out the speed between tow angles
|
||||
//Example: Pair one: 0 degrees, 0 knots
|
||||
// Pair two: 3 degrees, 6 knots
|
||||
//This loop will add
|
||||
//Pair one: 0 degrees, 0 knots
|
||||
//Pair two: 1 degrees, 2 knots
|
||||
//Pair three: 2 degrees, 4 knots
|
||||
//Pair four: 3 degrees, 6 knots
|
||||
for (double windSpeed: iterablePolars.keySet()){
|
||||
TreeMap<Double, Double> tws = iterablePolars.get(windSpeed);
|
||||
|
||||
if (prevTWS == null){
|
||||
prevTWS = tws;
|
||||
continue;
|
||||
}
|
||||
|
||||
double previousTWA = -1;
|
||||
TreeMap<Double, Double> iterableTWS = new TreeMap<>(tws);
|
||||
|
||||
for (double twa: iterableTWS.keySet()){
|
||||
if (previousTWA == -1){
|
||||
previousTWA = twa;
|
||||
continue;
|
||||
}
|
||||
double twaDiff = twa - previousTWA;
|
||||
double speedDiff = iterableTWS.get(twa) - iterableTWS.get(previousTWA);
|
||||
double prevSpeed = iterableTWS.get(previousTWA);
|
||||
double diff = speedDiff/twaDiff;
|
||||
|
||||
for (double i = previousTWA; i < twa; i ++){
|
||||
double mult = i - previousTWA;
|
||||
double newSpeed = diff * mult + prevSpeed;
|
||||
tws.put(i, newSpeed);
|
||||
}
|
||||
previousTWA = twa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double getClosest(double value, Set<Double> set){
|
||||
double closestVal = 0;
|
||||
double smallestDiff = Double.MAX_VALUE;
|
||||
for (double d: set){
|
||||
double diff = Math.abs(value - d);
|
||||
if (diff < smallestDiff){
|
||||
closestVal = d;
|
||||
smallestDiff = diff;
|
||||
}
|
||||
}
|
||||
return closestVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which quadrant degrees are in
|
||||
* 0/360 Degrees
|
||||
* Quadrant 4 | Quadrant 1
|
||||
* -----------------------
|
||||
* Quadrant 3 | Quadrant 2
|
||||
* @param degrees
|
||||
* @return
|
||||
*/
|
||||
private static int getQuadrant(double degrees){
|
||||
return (int) modulateAngle(degrees) / 90 + 1;
|
||||
}
|
||||
|
||||
private static double getBestSpeedInQuadrant(int quad, Map<Double, Double> set){
|
||||
double min = (quad - 1)* 90;
|
||||
double max = quad * 90;
|
||||
double maxAngle = 0;
|
||||
double maxSpeed = 0;
|
||||
double dupAngle = 0;
|
||||
//DupAngle will average the angle between maxAngles that have the same speed
|
||||
//Example: if 150 degrees, 180 degrees, and 210 degrees all go at 10 knots
|
||||
//then the average will be taken as (150 + 210) / 2 and the angle will be returned on that.
|
||||
for (Double s: set.keySet()){
|
||||
if (s >= min && s < max){
|
||||
if (set.get(s) > maxSpeed){
|
||||
dupAngle = 0;
|
||||
maxAngle = s;
|
||||
maxSpeed = set.get(s);
|
||||
} else if (set.get(s) == maxSpeed){
|
||||
dupAngle = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dupAngle != 0 ){
|
||||
return getClosest((dupAngle + maxAngle) / 2, set.keySet());
|
||||
}
|
||||
return maxAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best VMG that the boat can change to given it's current diagonal heading direction.
|
||||
* @param trueWindAngle True wind angle of the race
|
||||
* @param trueWindSpeed True wind speed of the race
|
||||
* @param boatAngle Angle that the boat is currently at
|
||||
* @return the best vmg that the boat can change to
|
||||
*/
|
||||
public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
|
||||
//System.out.println("VMG AUTO CALLED");
|
||||
//speed
|
||||
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
|
||||
|
||||
double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
|
||||
int quad = getQuadrant(angle);
|
||||
double bestAngle = getBestSpeedInQuadrant(quad, polars.get(closestSpeed));
|
||||
|
||||
double boatSpeed = polars.get(closestSpeed).get(bestAngle);
|
||||
|
||||
double newAngle = modulateAngle(bestAngle + trueWindAngle.degrees());
|
||||
|
||||
return new VMG(boatSpeed, Bearing.fromDegrees(newAngle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the speed that a certain angle should be doing
|
||||
* @param trueWindAngle TrueWind Angle of the race
|
||||
* @param trueWindSpeed True Wind Speed of the race
|
||||
* @param boatAngle Angle that the boat is current at
|
||||
* @return the speed that the boat should be traveling at.
|
||||
*/
|
||||
public static double calculateSpeed(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
|
||||
//speed
|
||||
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
|
||||
|
||||
double angleDiff = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
|
||||
double closestAngle = getClosest(angleDiff, polars.get(closestSpeed).keySet());
|
||||
|
||||
double boatSpeed = polars.get(closestSpeed).get(closestAngle);
|
||||
|
||||
return boatSpeed;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static double modulateAngle(double angle){
|
||||
return (angle % 360 + 360) % 360;
|
||||
}
|
||||
|
||||
private Map<Double, TreeMap<Double, Double>> getPolars(){
|
||||
//this function is just for testing so therefore it is private
|
||||
return polars;
|
||||
}
|
||||
|
||||
private void printOutLinearInterpolated(){
|
||||
for (double tws: polars.keySet()){
|
||||
System.out.println("==================================================");
|
||||
System.out.println("Speed: " + tws);
|
||||
System.out.println("==================================================");
|
||||
for (double twa: polars.get(tws).keySet()){
|
||||
System.out.println("TWA: " + twa + ", Boat Speed: " + polars.get(tws).get(twa));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
package mock.model;
|
||||
|
||||
import mock.dataInput.PolarParser;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import shared.model.Bearing;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Created by fwy13 on 5/09/17.
|
||||
*/
|
||||
public class NewPolarsTest {
|
||||
|
||||
@Before
|
||||
public void setUp(){
|
||||
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
|
||||
NewPolars.linearInterpolatePolars();
|
||||
|
||||
|
||||
// Uncomment if you want to read the linear interpolation in text
|
||||
// Method getPolars = null;
|
||||
// try {
|
||||
// getPolars = NewPolars.class.getDeclaredMethod("printOutLinearInterpolated");
|
||||
// } catch (NoSuchMethodException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// getPolars.setAccessible(true);
|
||||
// try {
|
||||
// getPolars.invoke(NewPolars.newPolars);
|
||||
// } catch (IllegalAccessException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (InvocationTargetException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuads() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
//reflection for private class
|
||||
Class[] parameterTypes = new Class[1];
|
||||
parameterTypes[0] = Double.TYPE;
|
||||
Method getQuads = NewPolars.class.getDeclaredMethod("getQuadrant", parameterTypes);
|
||||
getQuads.setAccessible(true);
|
||||
|
||||
//start invoking
|
||||
Object[] paras1 = new Object[1];
|
||||
paras1[0] = (new Double(0)).doubleValue();
|
||||
int q1 = (int) getQuads.invoke(NewPolars.newPolars, paras1);
|
||||
assertEquals(q1, 1);
|
||||
|
||||
//start invoking
|
||||
Object[] paras2 = new Object[1];
|
||||
paras2[0] = (new Double(90)).doubleValue();
|
||||
int q2 = (int) getQuads.invoke(NewPolars.newPolars, paras2);
|
||||
assertEquals(q2, 2);
|
||||
|
||||
//start invoking
|
||||
Object[] paras3 = new Object[1];
|
||||
paras3[0] = (new Double(180)).doubleValue();
|
||||
int q3 = (int) getQuads.invoke(NewPolars.newPolars, paras3);
|
||||
assertEquals(q3, 3);
|
||||
|
||||
//start invoking
|
||||
Object[] paras4 = new Object[1];
|
||||
paras4[0] = (new Double(270)).doubleValue();
|
||||
int q4 = (int) getQuads.invoke(NewPolars.newPolars, paras4);
|
||||
assertEquals(q4, 4);
|
||||
|
||||
//start invoking
|
||||
Object[] paras5 = new Object[1];
|
||||
paras5[0] = (new Double(360)).doubleValue();
|
||||
int q5 = (int) getQuads.invoke(NewPolars.newPolars, paras5);
|
||||
assertEquals(q5, 1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEdgeSpeeds(){
|
||||
//just make sure that speeds at certain angles do not throw a null exception and are not negative
|
||||
double maxTWS = 30;
|
||||
|
||||
for (double tws = 0; tws < maxTWS; tws += 1){
|
||||
for (double j = 0; j < 360; j++){
|
||||
Bearing twa = Bearing.fromDegrees(j);
|
||||
for (double i = 0; i < 360; i++){
|
||||
Bearing boatBearing = Bearing.fromDegrees(i);
|
||||
double speed = NewPolars.calculateSpeed(twa, tws, boatBearing);
|
||||
assertTrue(speed >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClosestSpeeds() throws NoSuchMethodException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
|
||||
//reflection for private class
|
||||
Method getClosest = NewPolars.class.getDeclaredMethod("getClosest", double.class, Set.class);
|
||||
getClosest.setAccessible(true);
|
||||
|
||||
Method getPolars = NewPolars.class.getDeclaredMethod("getPolars");
|
||||
getPolars.setAccessible(true);
|
||||
|
||||
double maxTWS = 30;
|
||||
|
||||
//only catches for nulls
|
||||
for (double tws = 0; tws < maxTWS; tws += 1){
|
||||
Map<Double, TreeMap<Double, Double>> polars = (Map<Double, TreeMap<Double, Double>>) getPolars.invoke(NewPolars.newPolars);
|
||||
double speed = (double) getClosest.invoke(NewPolars.newPolars, tws, polars.keySet());
|
||||
assertTrue(speed >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoVSCalculated(){
|
||||
//test that the auto chosen speed is the same speed that is calculated
|
||||
double maxTWS = 30;
|
||||
for (double tws = 0; tws < maxTWS; tws ++){
|
||||
for (double twa = 0; twa < 360; twa ++){
|
||||
Bearing TW = Bearing.fromDegrees(twa);
|
||||
for (double ba = 0; ba < 360; ba ++){
|
||||
Bearing boatBearing = Bearing.fromDegrees(ba);
|
||||
VMG autoVMG = NewPolars.setBestVMG(TW, tws, boatBearing);
|
||||
double speed = NewPolars.calculateSpeed(TW, tws, autoVMG.getBearing());
|
||||
assertTrue(autoVMG.getSpeed() == speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in new issue