RoutePoint *Routeman::FindBestActivatePoint( Route *pR, double lat, double lon, double cog, double sog ) { if( !pR ) return NULL; // Walk thru all the points to find the "best" RoutePoint *best_point = NULL; double min_time_found = 1e6; wxRoutePointListNode *node = ( pR->pRoutePointList )->GetFirst(); while( node ) { RoutePoint *pn = node->GetData(); double brg, dist; DistanceBearingMercator( pn->m_lat, pn->m_lon, lat, lon, &brg, &dist ); double angle = brg - cog; double soa = cos( angle * PI / 180. ); double time_to_wp = dist / soa; if( time_to_wp > 0 ) { if( time_to_wp < min_time_found ) { min_time_found = time_to_wp; best_point = pn; } } node = node->GetNext(); } return best_point; }
/* Update a single route segment lengths Also, compute total route length by summing segment distances. */ void Route::UpdateSegmentDistance( RoutePoint *prp0, RoutePoint *prp, double planspeed ) { double slat1 = prp0->m_lat, slon1 = prp0->m_lon; double slat2 = prp->m_lat, slon2 = prp->m_lon; // Calculate the absolute distance from 1->2 double dd; // why are we using mercator rather than great circle here?? [sean 8-11-2015] DistanceBearingMercator( slat1, slon1, slat2, slon2, 0, &dd ); // And store in Point 2 prp->m_seg_len = dd; m_route_length += dd; // If Point1 Description contains VMG, store it for Properties Dialog in Point2 // If Point1 Description contains ETD, store it in Point1 if( planspeed > 0. ) { double vmg = 0.0; wxDateTime etd; if( prp0->m_MarkDescription.Find( _T("VMG=") ) != wxNOT_FOUND ) { wxString s_vmg = ( prp0->m_MarkDescription.Mid( prp0->m_MarkDescription.Find( _T("VMG=") ) + 4 ) ).BeforeFirst( ';' ); if( !s_vmg.ToDouble( &vmg ) ) vmg = planspeed; } double legspeed = planspeed; if( vmg > 0.1 && vmg < 1000. ) legspeed = vmg; if( legspeed > 0.1 && legspeed < 1000. ) { m_route_time += 3600. * dd / legspeed; prp->m_seg_vmg = legspeed; } prp0->m_seg_etd = wxInvalidDateTime; if( prp0->m_MarkDescription.Find( _T("ETD=") ) != wxNOT_FOUND ) { wxString s_etd = ( prp0->m_MarkDescription.Mid( prp0->m_MarkDescription.Find( _T("ETD=") ) + 4 ) ).BeforeFirst( ';' ); const wxChar *parse_return = etd.ParseDateTime( s_etd ); if( parse_return ) { wxString tz( parse_return ); if( tz.Find( _T("UT") ) != wxNOT_FOUND ) prp0->m_seg_etd = etd; else if( tz.Find( _T("LMT") ) != wxNOT_FOUND ) { prp0->m_seg_etd = etd; long lmt_offset = (long) ( ( prp0->m_lon * 3600. ) / 15. ); wxTimeSpan lmt( 0, 0, (int) lmt_offset, 0 ); prp0->m_seg_etd -= lmt; } else prp0->m_seg_etd = etd.ToUTC(); } } } }
double Track::GetXTE( double fm1Lat, double fm1Lon, double fm2Lat, double fm2Lon, double toLat, double toLon ) { vector2D v, w, p; // First we get the cartesian coordinates to the line endpoints, using // the current position as origo. double brg1, dist1, brg2, dist2; DistanceBearingMercator( toLat, toLon, fm1Lat, fm1Lon, &brg1, &dist1 ); w.x = dist1 * sin( brg1 * PI / 180. ); w.y = dist1 * cos( brg1 * PI / 180. ); DistanceBearingMercator( toLat, toLon, fm2Lat, fm2Lon, &brg2, &dist2 ); v.x = dist2 * sin( brg2 * PI / 180. ); v.y = dist2 * cos( brg2 * PI / 180. ); p.x = 0.0; p.y = 0.0; const double lengthSquared = _distance2( v, w ); if ( lengthSquared == 0.0 ) { // v == w case return _distance( p, v ); } // Consider the line extending the segment, parameterized as v + t (w - v). // We find projection of origo onto the line. // It falls where t = [(p-v) . (w-v)] / |w-v|^2 vector2D a = p - v; vector2D b = w - v; double t = vDotProduct( &a, &b ) / lengthSquared; if (t < 0.0) return _distance(p, v); // Beyond the 'v' end of the segment else if (t > 1.0) return _distance(p, w); // Beyond the 'w' end of the segment vector2D projection = v + t * (w - v); // Projection falls on the segment return _distance(p, projection); }
/* Update the route segment lengths, storing each segment length in <destination> point. Also, compute total route length by summing segment distances. */ void Route::UpdateSegmentDistances( double planspeed ) { wxPoint rpt, rptn; float slat1, slon1, slat2, slon2; double route_len = 0.0; double route_time = 0.0; wxRoutePointListNode *node = pRoutePointList->GetFirst(); if( node ) { RoutePoint *prp0 = node->GetData(); slat1 = prp0->m_lat; slon1 = prp0->m_lon; node = node->GetNext(); while( node ) { RoutePoint *prp = node->GetData(); slat2 = prp->m_lat; slon2 = prp->m_lon; // Calculate the absolute distance from 1->2 double brg, dd; DistanceBearingMercator( slat1, slon1, slat2, slon2, &brg, &dd ); // And store in Point 2 prp->m_seg_len = dd; route_len += dd; slat1 = slat2; slon1 = slon2; // If Point1 Description contains VMG, store it for Properties Dialog in Point2 // If Point1 Description contains ETD, store it in Point1 if( planspeed > 0. ) { double vmg = 0.0; wxDateTime etd; if( prp0->m_MarkDescription.Find( _T("VMG=") ) != wxNOT_FOUND ) { wxString s_vmg = ( prp0->m_MarkDescription.Mid( prp0->m_MarkDescription.Find( _T("VMG=") ) + 4 ) ).BeforeFirst( ';' ); if( !s_vmg.ToDouble( &vmg ) ) vmg = planspeed; } double legspeed = planspeed; if( vmg > 0.1 && vmg < 1000. ) legspeed = vmg; if( legspeed > 0.1 && legspeed < 1000. ) { route_time += dd / legspeed; prp->m_seg_vmg = legspeed; } prp0->m_seg_etd = wxInvalidDateTime; if( prp0->m_MarkDescription.Find( _T("ETD=") ) != wxNOT_FOUND ) { wxString s_etd = ( prp0->m_MarkDescription.Mid( prp0->m_MarkDescription.Find( _T("ETD=") ) + 4 ) ).BeforeFirst( ';' ); const wxChar *parse_return = etd.ParseDateTime( s_etd ); if( parse_return ) { wxString tz( parse_return ); if( tz.Find( _T("UT") ) != wxNOT_FOUND ) prp0->m_seg_etd = etd; else if( tz.Find( _T("LMT") ) != wxNOT_FOUND ) { prp0->m_seg_etd = etd; long lmt_offset = (long) ( ( prp0->m_lon * 3600. ) / 15. ); wxTimeSpan lmt( 0, 0, (int) lmt_offset, 0 ); prp0->m_seg_etd -= lmt; } else prp0->m_seg_etd = etd.ToUTC(); } } } prp0 = prp; node = node->GetNext(); } } m_route_length = route_len; m_route_time = route_time * 3600.; }
wxString OCPNTrackListCtrl::OnGetItemText( long item, long column ) const { wxString ret; if( item != g_prev_item ) { if( g_prev_point_index == ( item - 1 ) ) { if( !g_prev_point_node ) return wxEmptyString; g_prev_point = g_this_point; g_this_point_node = g_prev_point_node->GetNext(); if( g_this_point_node ) g_this_point = g_this_point_node->GetData(); else g_this_point = NULL; } else { wxRoutePointListNode *node = m_pRoute->pRoutePointList->GetFirst(); if( node ) { if( item > 0 ) { int i = 0; while( node && ( i < ( item - 1 ) ) ) { node = node->GetNext(); i++; } g_prev_point_node = node; if( ! node ) return wxEmptyString; g_prev_point = g_prev_point_node->GetData(); g_this_point_node = g_prev_point_node->GetNext(); if( g_this_point_node ) g_this_point = g_this_point_node->GetData(); else g_this_point = NULL; } else { g_prev_point_node = NULL; g_prev_point = NULL; g_this_point_node = node; if( g_this_point_node ) g_this_point = g_this_point_node->GetData(); else g_this_point = NULL; } } else { g_prev_point_node = NULL; g_prev_point = NULL; g_this_point_node = NULL; g_this_point = NULL; } } // Update for next time g_prev_point_node = g_this_point_node; g_prev_point_index = item; g_prev_item = item; } if( ! g_this_point ) return wxEmptyString; switch( column ) { case 0: if( item == 0 ) ret = _T("---"); else ret.Printf( _T("%ld"), item ); break; case 1: double slat, slon; if( item == 0 ) { slat = gLat; slon = gLon; } else { slat = g_prev_point->m_lat; slon = g_prev_point->m_lon; } DistanceBearingMercator( g_this_point->m_lat, g_this_point->m_lon, slat, slon, >_brg, >_leg_dist ); ret.Printf( _T("%6.2f ") + getUsrDistanceUnit(), toUsrDistance( gt_leg_dist ) ); break; case 2: ret.Printf( _T("%03.0f \u00B0T"), gt_brg ); break; case 3: ret = toSDMM( 1, g_this_point->m_lat, 1 ); break; case 4: ret = toSDMM( 2, g_this_point->m_lon, 1 ); break; case 5: { wxDateTime timestamp = g_this_point->GetCreateTime(); if( timestamp.IsValid() ) ret = timestamp2s( timestamp, m_tz_selection, m_LMT_Offset, TIMESTAMP_FORMAT ); else ret = _T("----"); } break; case 6: if( ( item > 0 ) && g_this_point->GetCreateTime().IsValid() && g_prev_point->GetCreateTime().IsValid() ) { double speed = 0.; double seconds = g_this_point->GetCreateTime().Subtract( g_prev_point->GetCreateTime() ).GetSeconds().ToDouble(); if( seconds > 0. ) speed = gt_leg_dist / seconds * 3600; ret.Printf( _T("%5.2f"), toUsrSpeed( speed ) ); } else ret = _("--"); break; default: break; } return ret; }
bool Routeman::UpdateProgress() { bool bret_val = false; if( pActiveRoute ) { // Update bearing, range, and crosstrack error // Bearing is calculated as Mercator Sailing, i.e. a cartographic "bearing" double north, east; toSM( pActivePoint->m_lat, pActivePoint->m_lon, gLat, gLon, &east, &north ); double a = atan( north / east ); if( fabs( pActivePoint->m_lon - gLon ) < 180. ) { if( pActivePoint->m_lon > gLon ) CurrentBrgToActivePoint = 90. - ( a * 180 / PI ); else CurrentBrgToActivePoint = 270. - ( a * 180 / PI ); } else { if( pActivePoint->m_lon > gLon ) CurrentBrgToActivePoint = 270. - ( a * 180 / PI ); else CurrentBrgToActivePoint = 90. - ( a * 180 / PI ); } // Calculate range using Great Circle Formula double d5 = DistGreatCircle( gLat, gLon, pActivePoint->m_lat, pActivePoint->m_lon ); CurrentRngToActivePoint = d5; // Get the XTE vector, normal to current segment vector2D va, vb, vn; double brg1, dist1, brg2, dist2; DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon, pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &brg1, &dist1 ); vb.x = dist1 * sin( brg1 * PI / 180. ); vb.y = dist1 * cos( brg1 * PI / 180. ); DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon, gLat, gLon, &brg2, &dist2 ); va.x = dist2 * sin( brg2 * PI / 180. ); va.y = dist2 * cos( brg2 * PI / 180. ); double sdelta = vGetLengthOfNormal( &va, &vb, &vn ); // NM CurrentXTEToActivePoint = sdelta; // Calculate the distance to the arrival line, which is perpendicular to the current route segment // Taking advantage of the calculated normal from current position to route segment vn vector2D vToArriveNormal; vSubtractVectors( &va, &vn, &vToArriveNormal ); CurrentRangeToActiveNormalCrossing = vVectorMagnitude( &vToArriveNormal ); // Compute current segment course // Using simple Mercater projection double x1, y1, x2, y2; toSM( pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &x1, &y1 ); toSM( pActivePoint->m_lat, pActivePoint->m_lon, pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &x2, &y2 ); double e1 = atan2( ( x2 - x1 ), ( y2 - y1 ) ); CurrentSegmentCourse = e1 * 180 / PI; if( CurrentSegmentCourse < 0 ) CurrentSegmentCourse += 360; // Compute XTE direction double h = atan( vn.y / vn.x ); if( vn.x > 0 ) CourseToRouteSegment = 90. - ( h * 180 / PI ); else CourseToRouteSegment = 270. - ( h * 180 / PI ); h = CurrentBrgToActivePoint - CourseToRouteSegment; if( h < 0 ) h = h + 360; if( h > 180 ) XTEDir = 1; else XTEDir = -1; // Determine Arrival bool bDidArrival = false; if( CurrentRangeToActiveNormalCrossing <= pActiveRoute->GetRouteArrivalRadius() ) { m_bArrival = true; UpdateAutopilot(); bDidArrival = true; if( !ActivateNextPoint( pActiveRoute, false ) ) // at the end? { Route *pthis_route = pActiveRoute; DeactivateRoute( true ); // this is an arrival if( pthis_route->m_bDeleteOnArrival ) { pConfig->DeleteConfigRoute( pthis_route ); DeleteRoute( pthis_route ); if( pRoutePropDialog ) { pRoutePropDialog->SetRouteAndUpdate( NULL ); pRoutePropDialog->UpdateProperties(); } if( pRouteManagerDialog ) pRouteManagerDialog->UpdateRouteListCtrl(); } } } if( !bDidArrival ) // Only once on arrival UpdateAutopilot(); bret_val = true; // a route is active } m_bDataValid = true; return bret_val; }
Route *Track::RouteFromTrack( wxGenericProgressDialog *pprog ) { Route *route = new Route(); TrackPoint *pWP_src = TrackPoints.front(); size_t prpnodeX; RoutePoint *pWP_dst, *pWP_prev; TrackPoint *prp_OK = NULL; // last routepoint known not to exceed xte limit, if not yet added wxString icon = _T("xmblue"); if( g_TrackDeltaDistance >= 0.1 ) icon = _T("diamond"); int next_ic = 0; int back_ic = 0; int nPoints = TrackPoints.size(); bool isProminent = true; double delta_dist = 0.; double delta_hdg, xte; double leg_speed = 0.1; if( pRoutePropDialog ) leg_speed = pRoutePropDialog->m_planspeed; else leg_speed = g_PlanSpeed; // add first point pWP_dst = new RoutePoint( pWP_src->m_lat, pWP_src->m_lon, icon, _T ( "" ), wxEmptyString ); route->AddPoint( pWP_dst ); pWP_dst->m_bShowName = false; pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst ); pWP_prev = pWP_dst; // add intermediate points as needed for(size_t i = 1; i < TrackPoints.size();) { TrackPoint *prp = TrackPoints[i]; prpnodeX = i; pWP_dst->m_lat = pWP_prev->m_lat; pWP_dst->m_lon = pWP_prev->m_lon; pWP_prev = pWP_dst; delta_dist = 0.0; delta_hdg = 0.0; back_ic = next_ic; DistanceBearingMercator( prp->m_lat, prp->m_lon, pWP_prev->m_lat, pWP_prev->m_lon, &delta_hdg, &delta_dist ); if( ( delta_dist > ( leg_speed * 6.0 ) ) && !prp_OK ) { int delta_inserts = floor( delta_dist / ( leg_speed * 4.0 ) ); delta_dist = delta_dist / ( delta_inserts + 1 ); double tlat = 0.0; double tlon = 0.0; while( delta_inserts-- ) { ll_gc_ll( pWP_prev->m_lat, pWP_prev->m_lon, delta_hdg, delta_dist, &tlat, &tlon ); pWP_dst = new RoutePoint( tlat, tlon, icon, _T ( "" ), wxEmptyString ); route->AddPoint( pWP_dst ); pWP_dst->m_bShowName = false; pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst ); pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat, pWP_dst->m_lon, pWP_prev, pWP_dst, route ); pWP_prev = pWP_dst; } prpnodeX = i; pWP_dst = pWP_prev; next_ic = 0; delta_dist = 0.0; back_ic = next_ic; prp_OK = prp; isProminent = true; } else { isProminent = false; if( delta_dist >= ( leg_speed * 4.0 ) ) isProminent = true; if( !prp_OK ) prp_OK = prp; } while( prpnodeX < TrackPoints.size() ) { TrackPoint *prpX = TrackPoints[prpnodeX]; // TrackPoint src(pWP_prev->m_lat, pWP_prev->m_lon); xte = GetXTE( pWP_src, prpX, prp ); if( isProminent || ( xte > g_TrackDeltaDistance ) ) { pWP_dst = new RoutePoint( prp_OK->m_lat, prp_OK->m_lon, icon, _T ( "" ), wxEmptyString ); route->AddPoint( pWP_dst ); pWP_dst->m_bShowName = false; pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst ); pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat, pWP_dst->m_lon, pWP_prev, pWP_dst, route ); pWP_prev = pWP_dst; next_ic = 0; prpnodeX = TrackPoints.size(); prp_OK = NULL; } if( prpnodeX != TrackPoints.size()) prpnodeX--; if( back_ic-- <= 0 ) { prpnodeX = TrackPoints.size(); } } if( prp_OK ) { prp_OK = prp; } DistanceBearingMercator( prp->m_lat, prp->m_lon, pWP_prev->m_lat, pWP_prev->m_lon, NULL, &delta_dist ); if( !( ( delta_dist > ( g_TrackDeltaDistance ) ) && !prp_OK ) ) { i++; next_ic++; } if( pprog ) pprog->Update( ( i * 100 ) / nPoints ); } // add last point, if needed if( delta_dist >= g_TrackDeltaDistance ) { pWP_dst = new RoutePoint( TrackPoints.back()->m_lat, TrackPoints.back()->m_lon, icon, _T ( "" ), wxEmptyString ); route->AddPoint( pWP_dst ); pWP_dst->m_bShowName = false; pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst ); pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat, pWP_dst->m_lon, pWP_prev, pWP_dst, route ); } route->m_RouteNameString = m_TrackNameString; route->m_RouteStartString = m_TrackStartString; route->m_RouteEndString = m_TrackEndString; route->m_bDeleteOnArrival = false; return route; }
bool Routeman::UpdateAutopilot() { //Send all known Autopilot messages upstream //RMB { m_NMEA0183.TalkerID = _T("EC"); SENTENCE snt; m_NMEA0183.Rmb.IsDataValid = NTrue; m_NMEA0183.Rmb.CrossTrackError = CurrentXTEToActivePoint; if( XTEDir < 0 ) m_NMEA0183.Rmb.DirectionToSteer = Left; else m_NMEA0183.Rmb.DirectionToSteer = Right; m_NMEA0183.Rmb.To = pActivePoint->GetName().Truncate( 6 ); m_NMEA0183.Rmb.From = pActiveRouteSegmentBeginPoint->GetName().Truncate( 6 ); if( pActivePoint->m_lat < 0. ) m_NMEA0183.Rmb.DestinationPosition.Latitude.Set( -pActivePoint->m_lat, _T("S") ); else m_NMEA0183.Rmb.DestinationPosition.Latitude.Set( pActivePoint->m_lat, _T("N") ); if( pActivePoint->m_lon < 0. ) m_NMEA0183.Rmb.DestinationPosition.Longitude.Set( -pActivePoint->m_lon, _T("W") ); else m_NMEA0183.Rmb.DestinationPosition.Longitude.Set( pActivePoint->m_lon, _T("E") ); m_NMEA0183.Rmb.RangeToDestinationNauticalMiles = CurrentRngToActivePoint; m_NMEA0183.Rmb.BearingToDestinationDegreesTrue = CurrentBrgToActivePoint; m_NMEA0183.Rmb.DestinationClosingVelocityKnots = gSog; if( m_bArrival ) m_NMEA0183.Rmb.IsArrivalCircleEntered = NTrue; else m_NMEA0183.Rmb.IsArrivalCircleEntered = NFalse; m_NMEA0183.Rmb.Write( snt ); g_pMUX->SendNMEAMessage( snt.Sentence ); } // RMC { m_NMEA0183.TalkerID = _T("EC"); SENTENCE snt; m_NMEA0183.Rmc.IsDataValid = NTrue; if( gLat < 0. ) m_NMEA0183.Rmc.Position.Latitude.Set( -gLat, _T("S") ); else m_NMEA0183.Rmc.Position.Latitude.Set( gLat, _T("N") ); if( gLon < 0. ) m_NMEA0183.Rmc.Position.Longitude.Set( -gLon, _T("W") ); else m_NMEA0183.Rmc.Position.Longitude.Set( gLon, _T("E") ); m_NMEA0183.Rmc.SpeedOverGroundKnots = gSog; m_NMEA0183.Rmc.TrackMadeGoodDegreesTrue = gCog; if( !wxIsNaN(gVar) ) { if( gVar < 0. ) { m_NMEA0183.Rmc.MagneticVariation = -gVar; m_NMEA0183.Rmc.MagneticVariationDirection = West; } else { m_NMEA0183.Rmc.MagneticVariation = gVar; m_NMEA0183.Rmc.MagneticVariationDirection = East; } } else m_NMEA0183.Rmc.MagneticVariation = 361.; // A signal to NMEA converter, gVAR is unknown wxDateTime now = wxDateTime::Now(); wxDateTime utc = now.ToUTC(); wxString time = utc.Format( _T("%H%M%S") ); m_NMEA0183.Rmc.UTCTime = time; wxString date = utc.Format( _T("%d%m%y") ); m_NMEA0183.Rmc.Date = date; m_NMEA0183.Rmc.Write( snt ); g_pMUX->SendNMEAMessage( snt.Sentence ); } // APB { m_NMEA0183.TalkerID = _T("EC"); SENTENCE snt; m_NMEA0183.Apb.IsLoranBlinkOK = NTrue; m_NMEA0183.Apb.IsLoranCCycleLockOK = NTrue; m_NMEA0183.Apb.CrossTrackErrorMagnitude = CurrentXTEToActivePoint; if( XTEDir < 0 ) m_NMEA0183.Apb.DirectionToSteer = Left; else m_NMEA0183.Apb.DirectionToSteer = Right; m_NMEA0183.Apb.CrossTrackUnits = _T("N"); if( m_bArrival ) m_NMEA0183.Apb.IsArrivalCircleEntered = NTrue; else m_NMEA0183.Apb.IsArrivalCircleEntered = NFalse; // We never pass the perpendicular, since we declare arrival before reaching this point m_NMEA0183.Apb.IsPerpendicular = NFalse; double brg1, dist1; DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon, pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &brg1, &dist1 ); m_NMEA0183.Apb.BearingOriginToDestination = brg1; m_NMEA0183.Apb.BearingOriginToDestinationUnits = _("T"); m_NMEA0183.Apb.To = pActivePoint->GetName().Truncate( 6 ); m_NMEA0183.Apb.BearingPresentPositionToDestination = CurrentBrgToActivePoint; m_NMEA0183.Apb.BearingPresentPositionToDestinationUnits = _("T"); m_NMEA0183.Apb.To = pActivePoint->GetName().Truncate( 6 ); m_NMEA0183.Apb.HeadingToSteer = CurrentBrgToActivePoint; m_NMEA0183.Apb.HeadingToSteerUnits = _("T"); m_NMEA0183.Apb.Write( snt ); g_pMUX->SendNMEAMessage( snt.Sentence ); } return true; }