diff --git a/include/Polar.h b/include/Polar.h index 861d42fd..e94d7311 100644 --- a/include/Polar.h +++ b/include/Polar.h @@ -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: @@ -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); @@ -102,13 +113,15 @@ class Polar SailingWindSpeed(double nVW) : VW(nVW) {} float VW; - std::vector speeds; // by degree_count - std::vector orig_speeds; // by degree_count, from polar file + std::vector speeds; // by degree_count + std::vector 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 wind_speeds; + // The values of the true wind angle for which true wind speed is reported. std::vector degree_steps; unsigned int degree_step_index[DEGREES]; }; diff --git a/src/Polar.cpp b/src/Polar.cpp index 18f94225..0e01469b 100644 --- a/src/Polar.cpp +++ b/src/Polar.cpp @@ -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) @@ -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: @@ -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++) @@ -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; } @@ -484,22 +495,33 @@ 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); @@ -507,12 +529,16 @@ double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking) 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; @@ -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; @@ -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; } diff --git a/src/RouteMap.cpp b/src/RouteMap.cpp index df94707a..bfe53405 100644 --- a/src/RouteMap.cpp +++ b/src/RouteMap.cpp @@ -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)) { @@ -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; }