void BoatPlan::SetSpeedsFromTable(BoatSpeedTable &table) { for(int VWi = 0; VWi < num_wind_speeds; VWi++) for(int Wi = 0; Wi <= DEGREE_COUNT/2; Wi++) { double VB = table.InterpolateSpeed(wind_speeds[VWi], Wi*DEGREE_STEP); Set(Wi, VWi, VB); if(Wi != 0) Set(DEGREE_COUNT-Wi, VWi, VB); } CalculateVMG(); computed = false; }
/* instead of traveling in the direction given, allow traveling at angles closer to +- 90. Basically we are trying every two tack angles and combining to determine if alternating between these two courses is actually faster than the current strategy for the given course, and if so, use it. TODO: Make this this work for asymmetrical polars correctly */ void BoatPlan::OptimizeTackingSpeed() { for(unsigned int VWi = 0; VWi < wind_speeds.size(); VWi++) { CalculateVMG(VWi); for(unsigned int Wi = 0; Wi < degree_steps.size(); Wi++) { double at = degree_steps[Wi]; double bt, ct; if(at > 90 && at < 270) { bt = wind_speeds[VWi].VMG.values[SailingVMG::STARBOARD_DOWNWIND]; ct = wind_speeds[VWi].VMG.values[SailingVMG::PORT_DOWNWIND]; } else { bt = wind_speeds[VWi].VMG.values[SailingVMG::STARBOARD_UPWIND]; ct = wind_speeds[VWi].VMG.values[SailingVMG::PORT_UPWIND]; } if(isnan(bt) || isnan(ct)) continue; SailingWindSpeed::SailingSpeed &a = wind_speeds[VWi].speeds[Wi]; double b = Speed(bt, wind_speeds[VWi].VW); double c = Speed(ct, wind_speeds[VWi].VW); /* w is the weight between b and c tacks (.5 for equal time on each t is the improvement factor] bcVB * sin(a) = w*b.VB*sin(b) + (1-w)*c.VB*sin(c) bcVB * cos(a) = w*b.VB*cos(b) + (1-w)*c.VB*cos(c) */ double ar = deg2rad(at); double br = deg2rad(bt); double cr = deg2rad(ct); double sa = sin(ar), ca = cos(ar); double sb = sin(br), cb = cos(br); double sc = sin(cr), cc = cos(cr); double X = ca*sc-sa*cc, Y = sa*cb-ca*sb; double d = (X*c + Y*b); double w = X*c / d; if(w > 0 && w < 1) { double Z = cb*sc-sb*cc; double bcVB = Z*b*c / d; if(bcVB > a.VB) { a.VB = bcVB; if(at > 180) a.b = bt, a.c = ct, a.w = w; else a.b = ct, a.c = bt, a.w = (1-w); } } } } }
/* instead of traveling in the direction given, allow traveling at angles closer to +- 90. Basically we are trying every two tack angles and combining to determine if alternating between these two courses is actually faster than the current strategy for the given course, and if so, use it. TODO: Make this this work for asymmetrical polars correctly */ void BoatPlan::OptimizeTackingSpeed() { CalculateVMG(); for(int VWi = 0; VWi < num_wind_speeds; VWi++) { for(int Wi = 0; Wi <= DEGREE_COUNT; Wi++) { int at = Wi, bt, ct; if(Wi >= DEGREE_COUNT*1/4 && Wi < DEGREE_COUNT*3/4) { bt = VMG[VWi].StarboardTackDownWind; ct = VMG[VWi].PortTackDownWind; } else { bt = VMG[VWi].StarboardTackUpWind; ct = VMG[VWi].PortTackUpWind; } SailingSpeed &a = speed[VWi][at]; SailingSpeed &b = speed[VWi][bt]; SailingSpeed &c = speed[VWi][ct]; /* w is the weight between b and c tacks (.5 for equal time on each t is the improvement factor] bcVB * sin(a) = w*b.VB*sin(b) + (1-w)*c.VB*sin(c) bcVB * cos(a) = w*b.VB*cos(b) + (1-w)*c.VB*cos(c) */ double ar = deg2rad(at*DEGREE_STEP); double br = deg2rad(bt*DEGREE_STEP); double cr = deg2rad(ct*DEGREE_STEP); double sa = sin(ar), ca = cos(ar); double sb = sin(br), cb = cos(br); double sc = sin(cr), cc = cos(cr); double X = ca*sc-sa*cc, Y = sa*cb-ca*sb; double d = (X*c.VB + Y*b.VB); double w = X*c.VB / d; if(w > 0 && w < 1) { double Z = cb*sc-sb*cc; double bcVB = Z*b.VB*c.VB / d; if(bcVB > a.VB) { a.VB = bcVB; if(at > 180) a.b = bt, a.c = ct, a.w = w; else a.b = ct, a.c = bt, a.w = (1-w); } } } } }
/* eta is a measure of efficiency of the boat, from .01 for racing boats to .5 for heavy cruisers */ void BoatPlan::ComputeBoatSpeeds(Boat &boat) { csvFileName = _("<Computed>"); for(int VWi = 0; VWi < num_wind_speeds; VWi++) for(int Wi = 0; Wi <= DEGREE_COUNT/2; Wi++) { double VW = wind_speeds[VWi]; double W = Wi * DEGREE_STEP; double B, VB, A, VA; BoatSteadyState(deg2rad(W), VW, B, VB, A, VA, boat); Set(Wi, VWi, VB); if(W != 0) Set(DEGREE_COUNT-Wi, VWi, VB); } CalculateVMG(); computed = true; }
/* eta is a measure of efficiency of the boat, from .01 for racing boats to .5 for heavy cruisers */ void BoatPlan::ComputeBoatSpeeds(Boat &boat, PolarMethod method, int speed) { // fileFileName = _T(""); if(/*polarmethod == FROM_FILE ||*/ wind_speeds.size() != num_computed_wind_speeds || degree_steps.size() != computed_degree_count) { wind_speeds.clear(); degree_steps.clear(); for(unsigned int Wi = 0; Wi < computed_degree_count; Wi++) degree_steps.push_back(computed_degree_step*Wi); UpdateDegreeStepLookup(); for(unsigned int VWi = 0; VWi < num_computed_wind_speeds; VWi++) { wind_speeds.push_back(SailingWindSpeed(computed_wind_speeds[VWi])); wind_speeds[VWi].speeds.clear(); for(unsigned int Wi = 0; Wi < computed_degree_count; Wi++) wind_speeds[VWi].speeds.push_back(SailingWindSpeed::SailingSpeed(0, degree_steps[Wi])); } } // for IMF computation double SADR = boat.SailAreaDisplacementRatio(); double lwl_ft = boat.lwl_ft; double hull_speed = boat.HullSpeed(); int VW1i, VW2i; if(speed == -1) // all speeds VW1i = 0, VW2i = wind_speeds.size() - 1; else ClosestVWi(speed, VW1i, VW2i); for(int VWi = VW1i; VWi <= VW2i; VWi++) { for(unsigned int Wi = 0; Wi <= computed_degree_count/2; Wi++) { double VW = wind_speeds[VWi].VW; double W = Wi * computed_degree_step; double B, VB, A, VA; switch(method) { case TRANSFORM: BoatSteadyState(deg2rad(W), VW, B, VB, A, VA, boat); break; case IMF: { if(fabsf(W) < 30) VB = 0; else { double base_speed = 2.62 + .066*SADR + .051*lwl_ft; double b = 1 / sqrt(VW + 3); VB = base_speed*(sin(deg2rad(W)/2) + b*cos(deg2rad(W))) * sqrt(20*VW) / 8; if(VB > hull_speed) VB = hull_speed; } } default: printf("BoatPlan::ComputeBoatSpeeds called with invalid method: %d\n", method); return; } Set(Wi, VWi, VB, W); if(W != 0) // assume symmetric performance Set(computed_degree_count-Wi, VWi, VB, DEGREES-W); } CalculateVMG(VWi); } polarmethod = method; }
bool BoatPlan::Open(const char *filename, wxString &message) { wind_speeds.clear(); degree_steps.clear(); if(filename[0] == 0) return false; int linenum = 0; ZUFILE *f = zu_open(filename, "r"); char line[1024]; double lastentryW = -1; char *token, *saveptr; const char delim[] = ";, \t\r\n"; if(!f) PARSE_ERROR(_("Failed to open.")); // polar file has optional first line which is description for(;;) { if(!zu_gets(f, line, sizeof line)) PARSE_ERROR(_("Failed to read.")); token = strtok_r(line, delim, &saveptr); linenum++; /* chomp invisible bytes */ while(*token < 0) token++; if(!strcasecmp(token, "twa/tws") || !strcasecmp(token, "twa\\tws") || !strcasecmp(token, "twa")) break; if(linenum == 2) PARSE_ERROR(_("Unrecognized format.")); } while((token = strtok_r(NULL, delim, &saveptr))) { wind_speeds.push_back(SailingWindSpeed(strtod(token, 0))); if(wind_speeds.size() > MAX_WINDSPEEDS_IN_TABLE) PARSE_ERROR(_("Too many wind speeds.")); } wind_speed_step = (int)round(wind_speeds.back().VW / wind_speeds.size()); while(zu_gets(f, line, sizeof line)) { linenum++; #if 0 /* strip newline/linefeed */ for(unsigned int i=0; i<strlen(line); i++) if(line[i] == '\r' || line[i] == '\n') line[i] = '\0'; #endif if(!(token = strtok_r(line, delim, &saveptr))) break; double W = strtod(token, 0); if(W < 0 || W > 180) { PARSE_WARNING(_("Wind direction out of range.")); continue; } if(W <= lastentryW) { PARSE_WARNING(_("Wind direction out of order.")); continue; } // add zero speed for all wind speeds going against wind if not specified if(degree_steps.empty() && W > 0) { degree_steps.push_back(0); for(int VWi = 0; VWi < (int)wind_speeds.size(); VWi++) wind_speeds[VWi].speeds.push_back(SailingWindSpeed::SailingSpeed(0, 0)); } degree_steps.push_back(W); lastentryW = W; { for(int VWi = 0; VWi < (int)wind_speeds.size(); VWi++) { double s = 0; if((token = strtok_r(NULL, delim, &saveptr))) s = strtod(token, 0); else PARSE_WARNING(_("Too few tokens.")); wind_speeds[VWi].speeds.push_back (SailingWindSpeed::SailingSpeed(s, W)); } if(strtok_r(NULL, delim, &saveptr)) PARSE_WARNING(_("Too many tokens.")); } } zu_close(f); /* fill in port tack assuming symmetric */ { int Win = degree_steps.size()-1; if(degree_steps[Win] == 180) Win--; for(; Win >= 0; Win--) { if(degree_steps[Win] == 0) break; degree_steps.push_back(DEGREES - degree_steps[Win]); for(unsigned int VWi = 0; VWi < wind_speeds.size(); VWi++) wind_speeds[VWi].speeds.push_back(wind_speeds[VWi].speeds[Win]); } } UpdateDegreeStepLookup(); wind_degree_step = degree_steps.size() ? 360 / degree_steps.size() : 0; for(unsigned int VWi = 0; VWi < wind_speeds.size(); VWi++) CalculateVMG(VWi); fileFileName = wxString::FromUTF8(filename); polarmethod = FROM_FILE; return true; failed: wind_speeds.clear(); degree_steps.clear(); zu_close(f); return false; }