diff --git a/include/Boat.h b/include/Boat.h index 4b31c4d4..76470881 100644 --- a/include/Boat.h +++ b/include/Boat.h @@ -25,6 +25,10 @@ #include "Polar.h" +/* + * This class is responsible for loading and saving the polars of a given boat to disk. + * and for finding the fastest polar for a given wind and heading. +*/ class Boat { public: diff --git a/include/RouteMap.h b/include/RouteMap.h index 3a7836b5..ccb18e00 100644 --- a/include/RouteMap.h +++ b/include/RouteMap.h @@ -38,6 +38,7 @@ class IsoRoute; typedef std::list IsoRouteList; class PlotData; + class RoutePoint { public: RoutePoint(double latitude = 0., double longitude = 0., int sp = -1, int t=0, bool d = false) : @@ -53,21 +54,39 @@ class RoutePoint { bool grib_is_data_deficient; bool GetPlotData(RoutePoint *next, double dt, RouteMapConfiguration &configuration, PlotData &data); + // @brief Return the wind data at the route point. bool GetWindData(RouteMapConfiguration &configuration, double &W, double &VW, int &data_mask); + // @brief Return the current data at the route point. bool GetCurrentData(RouteMapConfiguration &configuration, double &C, double &VC, int &data_mask); + // @brief Return true if the route point crosses land. bool CrossesLand(double dlat, double dlon); + // @brief Return true if the route point enters a boundary. bool EntersBoundary(double dlat, double dlon); + // @brief Propagate the route point to a specific point. double PropagateToPoint(double dlat, double dlon, RouteMapConfiguration &cf, double &H, int &data_mask, bool end = true); }; +/* + * A RoutePoint that has a time associated with it, along with navigation and weather data. +*/ class PlotData : public RoutePoint { public: - wxDateTime time; - double delta; - double VBG, BG, VB, B, VW, W, VWG, WG, VC, C, WVHT; - double VW_GUST; + wxDateTime time; // The time when the boat reaches this position, based on the route calculation. + double delta; // The time in seconds from the previous position to this position. + double VBG; // Velocity of boat over ground, in knots. + double BG; // Boat direction over ground. + double VB; // Velocity of boat over water, in knots. + double B; // Boat direction over water. + double VW; // Velocity of wind over water, in knots. + double W; // Wind direction over water. + double VWG; // Velocity of wind over ground, in knots. + double WG; // Wind direction over ground. + double VC; // Velocity of current over ground, in knots. + double C; // Sea current direction over ground. + double WVHT; // Swell height, in meters. + double VW_GUST; // Gust wind speed, in knots. }; class SkipPosition; @@ -121,7 +140,7 @@ class SkipPosition int quadrant; }; -/* a closed loop of positions */ +/* A closed loop of isochrone positions */ class IsoRoute { public: @@ -305,50 +324,109 @@ struct RouteMapPosition { }; +/* Configuration parameters for a route between two points. */ struct RouteMapConfiguration { RouteMapConfiguration () : StartLon(0), EndLon(0), grib(nullptr), grib_is_data_deficient(false) {} /* avoid waiting forever in update longitudes */ bool Update(); wxString RouteGUID; /* Route GUID if any */ - wxString Start; + wxString Start; /* The name of starting position, which is resolved to StartLat/StartLon. */ wxString StartGUID; - wxString End; + wxString End; /* The name of the destination position, which is resolved to EndLat/EndLon. */ wxString EndGUID; - wxDateTime StartTime; + wxDateTime StartTime; /* The time when the boat leaves the starting position. */ double DeltaTime; /* default time in seconds between propagations */ double UsedDeltaTime; /* time in seconds between propagations */ - Boat boat; - wxString boatFileName; + Boat boat; /* The polars of the boat, used for the route calculation. */ + wxString boatFileName; /* The name of the boat XML file referencing polars. */ enum IntegratorType { NEWTON, RUNGE_KUTTA } Integrator; - double MaxDivertedCourse, MaxCourseAngle, MaxSearchAngle, MaxTrueWindKnots, MaxApparentWindKnots; - double MaxSwellMeters, MaxLatitude, TackingTime, WindVSCurrent; + // The maximum angle the boat can be diverted from the bearing to the destination, + // at each step of the route calculation, in degrees. + // This represents the angle away from Start to End bearing (StartEndBearing). + // The normal setting is 100 degrees, which speeds up calculations. + // If the route needs to go around land, islands or peninsulas, the user can increase the value. + // E.g. the boat may have to go in the opposite direction then back to the destination bearing. + double MaxDivertedCourse; + // The maximum deviation away from the direct route. + double MaxCourseAngle; + // The maximum angle at each step of the route calculation. + double MaxSearchAngle; + // The calculated route will avoid a path where the true wind is above this value in knots. + double MaxTrueWindKnots; + // The calculated route will avoid a path where the gust wind is above this value in knots. + double MaxApparentWindKnots; + + // The calculated route will avoid swells larger than this value in meters. + // If the grib data does not contain swell information, the maximum swell value is ignored. + // If there is no route within the maximum swell value, the route calculation will fail. + double MaxSwellMeters; + // The calculated route will not go beyond this latitude, as an absolute value. + // If the starting or destination position is beyond this latitude, the route calculation will fail. + double MaxLatitude; + // The penalty time to tack the boat, in seconds. + // The penalty time is added to the route calculation for each tack. + double TackingTime; + // Balance the influence of the wind and the ocean current on the route calculation. + double WindVSCurrent; + // The minimum safety distance to land, in nautical miles. + // The calculated route will avoid land within this distance. double SafetyMarginLand; bool AvoidCycloneTracks; - int CycloneMonths, CycloneDays; + // Avoid cyclone tracks within ( 30*CycloneMonths + CycloneDays ) days of climatology data. + int CycloneMonths; + int CycloneDays; bool UseGrib; enum ClimatologyDataType {DISABLED, CURRENTS_ONLY, CUMULATIVE_MAP, CUMULATIVE_MINUS_CALMS, MOST_LIKELY, AVERAGE}; enum ClimatologyDataType ClimatologyType; bool AllowDataDeficient; - double WindStrength; // wind speed multiplier + double WindStrength; // wind speed multiplier. 1.0 is 100% of the wind speed in the grib. + + // If true, the route calculation will avoid land, outside the SafetyMarginLand. + bool DetectLand; + // If true, the route calculation will avoid exclusion boundaries. + bool DetectBoundary; + // If true and grib data contains ocean currents, the route calculation will use ocean current data. + bool Currents; + // If true, avoid polar dead zones. + // If false, avoid upwind course (polar angle too low) or downwind no-go zone (polar angle too high). + bool OptimizeTacking; + bool InvertedRegions; + bool Anchoring; + + // Do not go below this minimum True Wind angle at each step of the route calculation. + // The default value is 0 degrees. + double FromDegree; + // Do not go above this maximum True Wind angle at each step of the route calculation. + // The default value is 180 degrees. + double ToDegree; + // The angular resolution at each step of the route calculation, in degrees. + // Lower values provide finer resolution but increase computation time. + // Higher values provide coarser resolution, but faster computation time. + // The allowed range of resolution is from 0.1 to 60 degrees. + // The default value is 5 degrees. + double ByDegrees; - bool DetectLand, DetectBoundary, Currents, OptimizeTacking, InvertedRegions, Anchoring; - - double FromDegree, ToDegree, ByDegrees; /* computed values */ std::list DegreeSteps; - double StartLat, StartLon, EndLat, EndLon; - - double StartEndBearing; /* calculated from start and end */ + double StartLat, StartLon; // The latitude and longitude of the starting position. + double EndLat, EndLon; // The latitude and longitude of the destination position. + + /* + * The initial bearing from Start position to End position, following the Great Circle + * route and taking into consideration the ellipsoidal shape of the Earth. + * Note: a boat sailing the great circle route will gradually change the bearing to the destination. + */ + double StartEndBearing; bool positive_longitudes; /* longitudes are either 0 to 360 or -180 to 180, this means the map cannot cross both 0 and 180 longitude. To fully support this requires a lot more logic and would probably slow the algorithm @@ -356,6 +434,9 @@ struct RouteMapConfiguration { // parameters WR_GribRecordSet *grib; + // The time of the current isochrone step in the route calculation. + // The time starts at StartTime and is incremented for each step until the destination is reached, + // or the route calculation fails. wxDateTime time; bool grib_is_data_deficient, polar_failed, wind_data_failed; bool land_crossing, boundary_crossing; diff --git a/src/Polar.cpp b/src/Polar.cpp index 0e4ed97f..18f94225 100644 --- a/src/Polar.cpp +++ b/src/Polar.cpp @@ -481,7 +481,17 @@ bool Polar::VMGAngle(SailingWindSpeed &ws1, SailingWindSpeed &ws2, float VW, flo return true; } -/* compute boat speed from true wind angle and true wind speed +/** + * 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 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. */ double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking) { @@ -498,6 +508,7 @@ double Polar::Speed(double W, double VW, bool bound, bool optimize_tacking) W = 360 - W; 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). return NAN; if(bound && (VW < wind_speeds[0].VW || VW > wind_speeds[wind_speeds.size()-1].VW)) diff --git a/src/RouteMap.cpp b/src/RouteMap.cpp index 63e646db..df94707a 100644 --- a/src/RouteMap.cpp +++ b/src/RouteMap.cpp @@ -117,6 +117,8 @@ static Json::Value RequestGRIB(const wxDateTime &t, const wxString &what, double return error; } +// Return the swell height at the specified lat/long location. +// @return the swell height in meters. 0 if no data is available. static double Swell(RouteMapConfiguration &configuration, double lat, double lon) { WR_GribRecordSet *grib = configuration.grib; @@ -144,6 +146,7 @@ static double Swell(RouteMapConfiguration &configuration, double lat, double lon return height; } +// Return the wind gust speed for the specified lat/long location, in knots. static double Gust(RouteMapConfiguration &configuration, double lat, double lon) { WR_GribRecordSet *grib = configuration.grib; @@ -171,6 +174,7 @@ static double Gust(RouteMapConfiguration &configuration, double lat, double lon) } +// Return the wind speed from the grib at the specified lat/long location, in knots. static bool GribWind(RouteMapConfiguration &configuration, double lat, double lon, double &WG, double &VWG) { @@ -752,6 +756,7 @@ bool Position::Propagate(IsoRouteList &routelist, RouteMapConfiguration &configu return false; if(configuration.WindVSCurrent) { + /* Calculate the wind vector (Wx, Wy) and ocean current vector (Cx, Cy). */ /* these are already computed in OverWater.. could optimize by reusing them */ double Wx = VW*cos(deg2rad(W)), Wy = VW*sin(deg2rad(W)); double Cx = VC*cos(deg2rad(C) + M_PI), Cy = VC*sin(deg2rad(C) + M_PI); @@ -929,7 +934,7 @@ bool Position::Propagate(IsoRouteList &routelist, RouteMapConfiguration &configu double latBorderDown1, lonBorderDown1, latBorderDown2, lonBorderDown2; // Test if land is found within a rectangle with - // dimensiosn (dist, distSecure). Tests borders, plus diag, + // dimensions (dist, distSecure). Tests borders, plus diag, // and middle of each side... // <- dist -> // |-------------------------------|