Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log polar error code, Log polar parsing warnings + polar parsing logic when value is zero #302

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions include/Polar.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ class Boat;

#define DEGREES 360

// Error codes when Polar::Speed returns NaN.
enum class PolarErrorCode {
None, // No error has occurred.
NegativeWindSpeed, // The input true wind speed is negative.
EmptyPolarData, // The polar file contains no data.
WindAngleOutOfRange, // The input heading is out of the polar range, either too much upwind or too much downwind.
WindSpeedOutOfBounds, // The input wind speed is either below the minimum polar wind or above the maximum polar wind.
NegativeBoatSpeed, // The calculated boat speed is negative.
BoatSpeedNaNValue // The polar data contains NaN boat speed value, which represents unknown speed.
};

class Polar
{
public:
Expand All @@ -63,7 +74,7 @@ class Polar
void OptimizeTackingSpeeds();
void ClosestVWi(double VW, int &VW1i, int &VW2i);

double Speed(double W, double VW, bool bound=false, bool optimize_tacking=false);
double Speed(double W, double VW, bool bound=false, bool optimize_tacking=false, PolarErrorCode* error_code=NULL);
double SpeedAtApparentWindDirection(double A, double VW, double *pW=0);
double SpeedAtApparentWindSpeed(double W, double VA);
double SpeedAtApparentWind(double A, double VA, double *pW=0);
Expand Down Expand Up @@ -102,13 +113,15 @@ class Polar
SailingWindSpeed(double nVW) : VW(nVW) {}

float VW;
std::vector<float> speeds; // by degree_count
std::vector<float> orig_speeds; // by degree_count, from polar file
std::vector<float> speeds; // by degree_count
std::vector<float> orig_speeds; // by degree_count, from polar file.
SailingVMG VMG;
}; // num_wind_speeds
bool VMGAngle(SailingWindSpeed &ws1, SailingWindSpeed &ws2, float VW, float &W);

// The true wind speed for each wind angle in 'degree_steps'.
std::vector<SailingWindSpeed> wind_speeds;
// The values of the true wind angle for which true wind speed is reported.
std::vector<double> degree_steps;
unsigned int degree_step_index[DEGREES];
};
55 changes: 44 additions & 11 deletions src/Polar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ bool Polar::Open(const wxString &filename, wxString &message)
"To specify interpolated, leave blank values.\n"
"To specify course as 'invalid' use 0.0 rather than 0\n"));
warn_zeros = true;
// Intentionally set s = NAN. This is to indicate the boat speed is unknow.
} else if(*token) {
s = strtod(token, 0);
if(s < .05)
Expand All @@ -326,6 +327,8 @@ bool Polar::Open(const wxString &filename, wxString &message)
UpdateSpeeds();

FileName = wxString::FromUTF8(filename);
if (message != wxEmptyString)
wxLogMessage(message);
return true;

failed:
Expand Down Expand Up @@ -439,7 +442,14 @@ void Polar::OptimizeTackingSpeed()
}
#endif

// return index of wind speed in table which less than our wind speed
/*
* Finds the indices of the two wind speeds in the polar table
* that are just below and above the given true wind speed value.
*
* @param VW The target true wind speed value.
* @param VW1i Output parameter: Index of the wind speed just below the target value.
* @param VW2i Output parameter: Index of the wind speed just above the target value.
*/
void Polar::ClosestVWi(double VW, int &VW1i, int &VW2i)
{
for(unsigned int VWi = 1; VWi < wind_speeds.size()-1; VWi++)
Expand All @@ -449,6 +459,7 @@ void Polar::ClosestVWi(double VW, int &VW1i, int &VW2i)
return;
}

// If the target wind speed is greater than all available wind speeds, set indices to the last wind speed.
VW2i = wind_speeds.size()-1;
VW1i = VW2i > 0 ? VW2i - 1 : 0;
}
Expand Down Expand Up @@ -484,35 +495,50 @@ bool Polar::VMGAngle(SailingWindSpeed &ws1, SailingWindSpeed &ws2, float VW, flo
/**
* Calculate the boat speed (VB) based on the wind angle (W) and wind speed (VW) using the polar data.
*
* @param W The wind angle in degrees.
* @param VW The wind speed in knots.
* @param W The true wind angle in degrees.
* @param VW The true wind speed in knots.
* @param bound If true, the wind speed must be within the bounds of the polar data.
* For example, if the wind speed is 25 knots and the polar data only goes up to 20 knots,
* the function will return NAN.
* If false, the wind speed is extrapolated if outside the bounds.
* @param optimize_tacking Flag indicating whether to optimize for tacking angles.
* @return The boat speed (VB) in knots.
* @param[out] error_code The error code if NaN is returned.
* @return The boat speed (VB) in knots, or NaN in the following scenarios:
* - If the wind speed (VW) is negative.
* - If the polar data is empty (no degree steps or wind speeds defined).
* - If optimize_tacking is false and the wind angle (W) is outside the range of the polar data.
* - If bound is true and the wind speed (VW) is outside the range of the polar data.
* - If the calculated boat speed is negative (which can happen with faulty polars or during extrapolation).
*/
double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking)
double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking, PolarErrorCode* error_code)
{
if(VW < 0)
if (error_code) *error_code = PolarErrorCode::None;
if(VW < 0) {
if (error_code) *error_code = PolarErrorCode::NegativeWindSpeed;
return NAN;
}

if(!degree_steps.size() || !wind_speeds.size())
if(!degree_steps.size() || !wind_speeds.size()) {
if (error_code) *error_code = PolarErrorCode::EmptyPolarData;
return NAN;
}

W = positive_degrees(W);

// assume symmetric
if(W > 180)
W = 360 - W;

if(!optimize_tacking && (W < degree_steps[0] || W > degree_steps[degree_steps.size()-1]))
if(!optimize_tacking && (W < degree_steps[0] || W > degree_steps[degree_steps.size()-1])) {
// Avoid upwind course (polar angle too low) or downwind no-go zone (polar angle too high).
if (error_code) *error_code = PolarErrorCode::WindAngleOutOfRange;
return NAN;
}

if(bound && (VW < wind_speeds[0].VW || VW > wind_speeds[wind_speeds.size()-1].VW))
if(bound && (VW < wind_speeds[0].VW || VW > wind_speeds[wind_speeds.size()-1].VW)) {
if (error_code) *error_code = PolarErrorCode::WindSpeedOutOfBounds;
return NAN;
}

unsigned int W1i = degree_step_index[(int)floor(W)];
unsigned int W2i = W1i +1;
Expand All @@ -529,7 +555,7 @@ double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking)
if(optimize_tacking) {
float vmgW = W;
if(VMGAngle(ws1, ws2, VW, vmgW))
return Speed(vmgW, VW, bound)*cos(deg2rad(vmgW))/cos(deg2rad(W));
return Speed(vmgW, VW, bound, false, error_code)*cos(deg2rad(vmgW))/cos(deg2rad(W));
}

double VW1 = ws1.VW, VW2 = ws2.VW;
Expand All @@ -542,8 +568,15 @@ double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking)

double VB = interp_value(W, W1, W2, VB1, VB2);

if(VB < 0) // with faulty polars, extrapolation, sometimes results in negative boat speed
if(VB < 0) {
// with faulty polars, extrapolation, sometimes results in negative boat speed
if (error_code) *error_code = PolarErrorCode::NegativeBoatSpeed;
return NAN;
} else if (isnan(VB)) {
// This happens if the Polar contains NAN values.
if (error_code) *error_code = PolarErrorCode::BoatSpeedNaNValue;
return NAN;
}

return VB;
}
Expand Down
13 changes: 8 additions & 5 deletions src/RouteMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ static inline bool ComputeBoatSpeed
double &B, double &VB, double &BG, double &VBG, double &dist, int newpolar)
{
Polar &polar = configuration.boat.Polars[newpolar];
PolarErrorCode error_code = PolarErrorCode::None;
if((data_mask & Position::CLIMATOLOGY_WIND) &&
(configuration.ClimatologyType == RouteMapConfiguration::CUMULATIVE_MAP ||
configuration.ClimatologyType == RouteMapConfiguration::CUMULATIVE_MINUS_CALMS)) {
Expand All @@ -652,23 +653,25 @@ static inline bool ComputeBoatSpeed
double VBc, mind = polar.MinDegreeStep();
// if tacking
if(fabs(dir) < mind)
VBc = polar.Speed(mind, atlas.VW[i], true, configuration.OptimizeTacking)
VBc = polar.Speed(mind, atlas.VW[i], true, configuration.OptimizeTacking, &error_code)
* cos(deg2rad(mind)) / cos(deg2rad(dir));
else
VBc = polar.Speed(dir, atlas.VW[i], true, configuration.OptimizeTacking);
VBc = polar.Speed(dir, atlas.VW[i], true, configuration.OptimizeTacking, &error_code);

VB += atlas.directions[i]*VBc;
}

if(configuration.ClimatologyType == RouteMapConfiguration::CUMULATIVE_MINUS_CALMS)
VB *= 1-atlas.calm;
} else
VB = polar.Speed(H, VW, true, configuration.OptimizeTacking);
} else {
VB = polar.Speed(H, VW, true, configuration.OptimizeTacking, &error_code);
}

/* failed to determine speed.. */
if(std::isnan(B) || std::isnan(VB)) {
// when does this hit??
printf("polar failed bad! %f %f %f %f\n", W, VW, B, VB);
wxLogWarning("polar failed. V: %f VW: %f B: %f VB: %f H: %f. Error: %d",
W, VW, B, VB, H, error_code);
configuration.polar_failed = true;
return false; //B = VB = 0;
}
Expand Down