1 /***************************************************************************
2  *
3  * Project:  OpenCPN
4  * Purpose:  Navigation Utility Functions
5  * Authors:   David Register
6  *            Sean D'Epagnier
7  * Project:  OpenCPN
8  * Purpose:  Navigation Utility Functions
9  * Author:   David Register
10  *
11  ***************************************************************************
12  *   Copyright (C) 2016 by David S. Register                               *
13  *                                                                         *
14  *   This program is free software; you can redistribute it and/or modify  *
15  *   it under the terms of the GNU General Public License as published by  *
16  *   the Free Software Foundation; either version 2 of the License, or     *
17  *   (at your option) any later version.                                   *
18  *                                                                         *
19  *   This program is distributed in the hope that it will be useful,       *
20  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
21  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
22  *   GNU General Public License for more details.                          *
23  *                                                                         *
24  *   You should have received a copy of the GNU General Public License     *
25  *   along with this program; if not, write to the                         *
26  *   Free Software Foundation, Inc.,                                       *
27  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
28  **************************************************************************/
29 
30 /* Tracks are broken into SubTracks to allow for efficient rendering and
31    selection on tracks with thousands or millions of track points
32 
33    Each level of subtracks has exactly half the number of the previous level
34    forming a binary tree of subtracks.
35    The 0th level contains n-1 subtracks where n is the number of track points
36 
37 For example, a track with 5 points:
38 
39 Subtracks[2]                     0
40                             __/     \__
41                            /           \
42 Subtracks[1]             0               1
43                        /   \           /   \
44 Subtracks[0]         0       1       2       3
45                    /    \ /     \ /     \ /    \
46 TrackPoints      0       1       2       3       5
47 
48 
49 The BoundingBox for Subtracks[2][0] will include the entire track and is the
50 starting point for assembling the track.
51 
52 Subtracks[1][0] is from 0 to 2
53 Subtracks[1][1] is from 2 to 5
54 Subtracks[0][2] is from 2 to 3
55 
56 The scale factor in Subtracks[2] will determine if it's allowed to just
57 draw a simple line segment from 0 to 5, or if we need to recurse to find
58 more detail.
59 
60 At large scale factors, a long track will mostly be off screen, so
61 the bounding box tests quickly eliminate the invisible sections.
62 
63 At small scale factors, the scale test allows representing a section
64 of track using a single line segment greatly reducing the number of
65 segments rendered.  The scale is set so the difference is less than 1 pixel
66 and mostly impossible to notice.
67 
68 In practice I never exceed 170 segments in all cases assembling a real track
69 with over 86,000 segments.  If the track is particularly not-straight, and
70 the size of the screen particularly large (lots of pixels) the number
71 of segments will be higher, though it should be managable with tracks with
72 millions of points.
73 */
74 
75 #include "wx/wxprec.h"
76 
77 
78 #include "Route.h"
79 #include "Track.h"
80 #include "routeman.h"
81 #include "ocpndc.h"
82 #include "georef.h"
83 #include "chartbase.h"
84 #include "navutil.h"
85 #include "Select.h"
86 #include "chcanv.h"
87 
88 #include "pluginmanager.h"
89 
90 #ifdef ocpnUSE_GL
91 #include "glChartCanvas.h"
92 extern ocpnGLOptions g_GLOptions;
93 #endif
94 
95 extern WayPointman *pWayPointMan;
96 extern Routeman *g_pRouteMan;
97 extern Select *pSelect;
98 extern MyConfig *pConfig;
99 extern double gLat, gLon;
100 extern double           g_PlanSpeed;
101 extern int              g_nTrackPrecision;
102 extern bool             g_bTrackDaily;
103 extern bool             g_bHighliteTracks;
104 extern double           g_TrackDeltaDistance;
105 extern float            g_GLMinSymbolLineWidth;
106 extern wxColour         g_colourTrackLineColour;
107 extern PlugInManager    *g_pi_manager;
108 extern wxColor GetDimColor(wxColor c);
109 
110 #if defined( __UNIX__ ) && !defined(__WXOSX__)  // high resolution stopwatch for profiling
111 class OCPNStopWatch
112 {
113 public:
OCPNStopWatch()114     OCPNStopWatch() { Reset(); }
Reset()115     void Reset() { clock_gettime(CLOCK_REALTIME, &tp); }
116 
GetTime()117     double GetTime() {
118         timespec tp_end;
119         clock_gettime(CLOCK_REALTIME, &tp_end);
120         return (tp_end.tv_sec - tp.tv_sec) * 1.e3 + (tp_end.tv_nsec - tp.tv_nsec) / 1.e6;
121     }
122 
123 private:
124     timespec tp;
125 };
126 #endif
127 
128 #include <wx/listimpl.cpp>
129 WX_DEFINE_LIST ( TrackList );
130 
TrackPoint(double lat,double lon,wxString ts)131 TrackPoint::TrackPoint(double lat, double lon, wxString ts)
132     : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1), m_timestring(NULL)
133 {
134     SetCreateTime(ts);
135 }
136 
TrackPoint(double lat,double lon,wxDateTime dt)137 TrackPoint::TrackPoint(double lat, double lon, wxDateTime dt)
138     : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1), m_timestring(NULL)
139 {
140     SetCreateTime(dt);
141 }
142 
143 // Copy Constructor
TrackPoint(TrackPoint * orig)144 TrackPoint::TrackPoint( TrackPoint* orig )
145     : m_lat(orig->m_lat), m_lon(orig->m_lon), m_GPXTrkSegNo(1), m_timestring(NULL)
146 {
147     SetCreateTime(orig->GetCreateTime());
148 }
149 
~TrackPoint()150 TrackPoint::~TrackPoint()
151 {
152     delete [] m_timestring;
153 }
154 
GetCreateTime()155 wxDateTime TrackPoint::GetCreateTime()
156 {
157     wxDateTime CreateTimeX;
158 
159     if(m_timestring) {
160         wxString ts = m_timestring;
161         ParseGPXDateTime( CreateTimeX, ts );
162     }
163     return CreateTimeX;
164 }
165 
SetCreateTime(wxDateTime dt)166 void TrackPoint::SetCreateTime( wxDateTime dt )
167 {
168     wxString ts;
169     if(dt.IsValid())
170         ts = dt.FormatISODate().Append(_T("T")).Append(dt.FormatISOTime()).Append(_T("Z"));
171 
172     SetCreateTime(ts);
173 }
174 
SetCreateTime(wxString ts)175 void TrackPoint::SetCreateTime( wxString ts )
176 {
177     delete [] m_timestring;
178     if(ts.Length()) {
179         m_timestring = new char[ts.Length()+1];
180         strcpy(m_timestring, ts.mb_str());
181     } else
182         m_timestring = NULL;
183 }
184 
Draw(ChartCanvas * cc,ocpnDC & dc)185 void TrackPoint::Draw(ChartCanvas *cc, ocpnDC& dc )
186 {
187     wxPoint r;
188     wxRect hilitebox;
189 
190     cc->GetCanvasPointPix( m_lat, m_lon, &r );
191 
192     wxPen *pen;
193     pen = g_pRouteMan->GetRoutePointPen();
194 
195     int sx2 = 8;
196     int sy2 = 8;
197 
198      wxRect r1( r.x - sx2, r.y - sy2, sx2 * 2, sy2 * 2 );           // the bitmap extents
199 
200      hilitebox = r1;
201      hilitebox.x -= r.x;
202      hilitebox.y -= r.y;
203      float radius;
204      hilitebox.Inflate( 4 );
205      radius = 4.0f;
206 
207      wxColour hi_colour = pen->GetColour();
208      unsigned char transparency = 100;
209 
210      //  Highlite any selected point
211      AlphaBlending( dc, r.x + hilitebox.x, r.y + hilitebox.y, hilitebox.width, hilitebox.height, radius,
212                                hi_colour, transparency );
213 
214 }
215 
216 
217 
218 //---------------------------------------------------------------------------------
219 //    Track Implementation
220 //---------------------------------------------------------------------------------
221 
_distance2(vector2D & a,vector2D & b)222 double _distance2( vector2D& a, vector2D& b ) { return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y); }
_distance(vector2D & a,vector2D & b)223 double _distance( vector2D& a, vector2D& b ) { return sqrt( _distance2( a, b ) ); }
_magnitude2(vector2D & a)224 double _magnitude2( vector2D& a ) { return a.x*a.x + a.y*a.y; }
225 
Track()226 Track::Track()
227 {
228     m_bVisible = true;
229     m_bListed = true;
230 
231     m_width = WIDTH_UNDEFINED;
232     m_style = wxPENSTYLE_INVALID;
233 
234     m_GUID = pWayPointMan->CreateGUID( NULL );
235     m_bIsInLayer = false;
236     m_btemp = false;
237 
238     m_HyperlinkList = new HyperlinkList;
239     m_HighlightedTrackPoint = -1;
240 }
241 
~Track(void)242 Track::~Track( void )
243 {
244     for(size_t i = 0; i < TrackPoints.size(); i++)
245         delete TrackPoints[i];
246 
247     delete m_HyperlinkList;
248 }
249 
250 #define TIMER_TRACK1           778
251 
BEGIN_EVENT_TABLE(ActiveTrack,wxEvtHandler)252 BEGIN_EVENT_TABLE ( ActiveTrack, wxEvtHandler )
253     EVT_TIMER ( TIMER_TRACK1, ActiveTrack::OnTimerTrack )
254 END_EVENT_TABLE()
255 
256 ActiveTrack::ActiveTrack()
257 {
258     m_TimerTrack.SetOwner( this, TIMER_TRACK1 );
259     m_TimerTrack.Stop();
260     m_bRunning = false;
261 
262     SetPrecision( g_nTrackPrecision );
263 
264     m_prev_time = wxInvalidDateTime;
265     m_lastStoredTP = NULL;
266 
267     wxDateTime now = wxDateTime::Now();
268 //    m_ConfigRouteNum = now.GetTicks();        // a unique number....
269     trackPointState = firstPoint;
270     m_lastStoredTP = NULL;
271     m_removeTP = NULL;
272     m_fixedTP = NULL;
273     m_track_run = 0;
274     m_CurrentTrackSeg = 0;
275     m_prev_dist = 999.0;
276 }
277 
~ActiveTrack()278 ActiveTrack::~ActiveTrack()
279 {
280     Stop();
281 }
282 
SetPrecision(int prec)283 void ActiveTrack::SetPrecision( int prec ) {
284     m_nPrecision = prec;
285     switch( m_nPrecision ) {
286         case 0: { // Low
287             m_allowedMaxAngle = 10;
288             m_allowedMaxXTE = 0.008;
289             m_TrackTimerSec = 8;
290             m_minTrackpoint_delta = .004;
291             break;
292         }
293         case 1: { // Medium
294             m_allowedMaxAngle = 10;
295             m_allowedMaxXTE = 0.004;
296             m_TrackTimerSec = 4;
297             m_minTrackpoint_delta = .002;
298             break;
299         }
300         case 2: { // High
301             m_allowedMaxAngle = 10;
302             m_allowedMaxXTE = 0.0015;
303             m_TrackTimerSec = 2;
304             m_minTrackpoint_delta = .001;
305             break;
306         }
307     }
308 }
309 
Start(void)310 void ActiveTrack::Start( void )
311 {
312     if( !m_bRunning ) {
313         AddPointNow( true );                   // Add initial point
314         m_TimerTrack.Start( 1000, wxTIMER_CONTINUOUS );
315         m_bRunning = true;
316     }
317 }
318 
Stop(bool do_add_point)319 void ActiveTrack::Stop( bool do_add_point )
320 {
321     if(m_bRunning){
322         if(do_add_point)
323             AddPointNow( true );                   // Force add last point
324         else{
325             double delta = 0.0;
326             if( m_lastStoredTP )
327                 delta = DistGreatCircle( gLat, gLon, m_lastStoredTP->m_lat, m_lastStoredTP->m_lon );
328 
329             if(  delta > m_minTrackpoint_delta )
330                 AddPointNow( true );                   // Add last point
331         }
332     }
333 
334     m_TimerTrack.Stop();
335     m_bRunning = false;
336     m_track_run = 0;
337 }
338 
339 extern TrackList        *pTrackList;
DoExtendDaily()340 Track *ActiveTrack::DoExtendDaily()
341 {
342     Track *pExtendTrack = NULL;
343     TrackPoint *pExtendPoint = NULL;
344 
345     TrackPoint *pLastPoint = GetPoint( 0 );
346 
347     wxTrackListNode *track_node = pTrackList->GetFirst();
348     while( track_node ) {
349         Track *ptrack = track_node->GetData();
350 
351         if( !ptrack->m_bIsInLayer && ptrack->m_GUID != m_GUID ) {
352             TrackPoint *track_node = ptrack->GetLastPoint();
353             if( track_node->GetCreateTime() <= pLastPoint->GetCreateTime() ) {
354                 if( !pExtendPoint  || track_node->GetCreateTime() > pExtendPoint->GetCreateTime() ) {
355                     pExtendPoint = track_node;
356                     pExtendTrack = ptrack;
357                 }
358             }
359         }
360         track_node = track_node->GetNext();                         // next track
361     }
362     if( pExtendTrack
363         && pExtendTrack->GetPoint( 0 )->GetCreateTime().FromTimezone( wxDateTime::GMT0 ).IsSameDate(pLastPoint->GetCreateTime().FromTimezone( wxDateTime::GMT0 ) ) ) {
364         int begin = 1;
365         if( pLastPoint->GetCreateTime() == pExtendPoint->GetCreateTime() ) begin = 2;
366         pSelect->DeleteAllSelectableTrackSegments( pExtendTrack );
367         wxString suffix = _T("");
368         if( GetName().IsNull() ) {
369             suffix = pExtendTrack->GetName();
370             if( suffix.IsNull() ) suffix = wxDateTime::Today().FormatISODate();
371         }
372         pExtendTrack->Clone( this, begin, GetnPoints(), suffix );
373         pSelect->AddAllSelectableTrackSegments( pExtendTrack );
374         pSelect->DeleteAllSelectableTrackSegments( this );
375 
376         return pExtendTrack;
377     } else {
378         if( GetName().IsNull() )
379             SetName(wxDateTime::Today().FormatISODate());
380         return NULL;
381     }
382 }
383 
Clone(Track * psourcetrack,int start_nPoint,int end_nPoint,const wxString & suffix)384 void Track::Clone( Track *psourcetrack, int start_nPoint, int end_nPoint, const wxString & suffix)
385 {
386     if( psourcetrack->m_bIsInLayer )
387         return;
388 
389     m_TrackNameString = psourcetrack->m_TrackNameString + suffix;
390     m_TrackStartString = psourcetrack->m_TrackStartString;
391     m_TrackEndString = psourcetrack->m_TrackEndString;
392 
393     bool b_splitting = GetnPoints() == 0;
394 
395     int startTrkSegNo;
396     if( b_splitting ) {
397         startTrkSegNo = psourcetrack->GetPoint( start_nPoint )->m_GPXTrkSegNo;
398     } else {
399         startTrkSegNo = GetLastPoint()->m_GPXTrkSegNo;
400     }
401     int i;
402     for( i = start_nPoint; i <= end_nPoint; i++ ) {
403 
404         TrackPoint *psourcepoint = psourcetrack->GetPoint( i );
405         if( psourcepoint ) {
406             TrackPoint *ptargetpoint = new TrackPoint( psourcepoint);
407 
408             AddPoint( ptargetpoint );
409         }
410     }
411 }
412 
AdjustCurrentTrackPoint(TrackPoint * prototype)413 void ActiveTrack::AdjustCurrentTrackPoint( TrackPoint *prototype )
414 {
415     if(prototype) {
416         *m_lastStoredTP = *prototype;
417         m_prev_time = prototype->GetCreateTime().FromUTC();
418     }
419 }
420 
OnTimerTrack(wxTimerEvent & event)421 void ActiveTrack::OnTimerTrack( wxTimerEvent& event )
422 {
423     m_TimerTrack.Stop();
424     m_track_run++;
425 
426     if( m_lastStoredTP )
427         m_prev_dist = DistGreatCircle( gLat, gLon, m_lastStoredTP->m_lat, m_lastStoredTP->m_lon );
428     else
429         m_prev_dist = 999.0;
430 
431     bool b_addpoint = false;
432 
433     if( ( m_TrackTimerSec > 0. ) && ( (double) m_track_run >= m_TrackTimerSec )
434             && ( m_prev_dist > m_minTrackpoint_delta ) ) {
435         b_addpoint = true;
436         m_track_run = 0;
437     }
438 
439     if( b_addpoint )
440         AddPointNow();
441     else   //continuously update track beginning point timestamp if no movement.
442         if( ( trackPointState == firstPoint ) && !g_bTrackDaily )
443         {
444             wxDateTime now = wxDateTime::Now();
445             if(TrackPoints.empty())
446                 TrackPoints.front()->SetCreateTime(now.ToUTC());
447         }
448 
449     m_TimerTrack.Start( 1000, wxTIMER_CONTINUOUS );
450 }
451 
AddPointNow(bool do_add_point)452 void ActiveTrack::AddPointNow( bool do_add_point )
453 {
454     wxDateTime now = wxDateTime::Now();
455 
456     if( m_prev_dist < 0.0005 )              // avoid zero length segs
457         if( !do_add_point ) return;
458 
459     if( m_prev_time.IsValid() ) if( m_prev_time == now )                    // avoid zero time segs
460         if( !do_add_point ) return;
461 
462     vector2D gpsPoint( gLon, gLat );
463 
464     // The dynamic interval algorithm will gather all track points in a queue,
465     // and analyze the cross track errors for each point before actually adding
466     // a point to the track.
467 
468     switch( trackPointState ) {
469         case firstPoint: {
470             TrackPoint *pTrackPoint = AddNewPoint( gpsPoint, now.ToUTC() );
471             m_lastStoredTP = pTrackPoint;
472             trackPointState = secondPoint;
473             do_add_point = false;
474             break;
475         }
476         case secondPoint: {
477             vector2D pPoint( gLon, gLat );
478             skipPoints.push_back( pPoint );
479             skipTimes.push_back( now.ToUTC() );
480             trackPointState = potentialPoint;
481             break;
482         }
483         case potentialPoint: {
484             if( gpsPoint == skipPoints[skipPoints.size()-1] ) break;
485 
486             unsigned int xteMaxIndex = 0;
487             double xteMax = 0;
488 
489             // Scan points skipped so far and see if anyone has XTE over the threshold.
490             for( unsigned int i=0; i<skipPoints.size(); i++ ) {
491                 double xte = GetXTE( m_lastStoredTP->m_lat, m_lastStoredTP->m_lon, gLat, gLon, skipPoints[i].lat, skipPoints[i].lon );
492                 if( xte > xteMax ) {
493                     xteMax = xte;
494                     xteMaxIndex = i;
495                 }
496             }
497             if( xteMax > m_allowedMaxXTE ) {
498                 TrackPoint *pTrackPoint = AddNewPoint( skipPoints[xteMaxIndex], skipTimes[xteMaxIndex] );
499                 pSelect->AddSelectableTrackSegment( m_lastStoredTP->m_lat, m_lastStoredTP->m_lon,
500                         pTrackPoint->m_lat, pTrackPoint->m_lon,
501                         m_lastStoredTP, pTrackPoint, this );
502 
503                 m_prevFixedTP = m_fixedTP;
504                 m_fixedTP = m_removeTP;
505                 m_removeTP = m_lastStoredTP;
506                 m_lastStoredTP = pTrackPoint;
507                 for( unsigned int i=0; i<=xteMaxIndex; i++ ) {
508                     skipPoints.pop_front();
509                     skipTimes.pop_front();
510                 }
511 
512                 // Now back up and see if we just made 3 points in a straight line and the middle one
513                 // (the next to last) point can possibly be eliminated. Here we reduce the allowed
514                 // XTE as a function of leg length. (Half the XTE for very short legs).
515                 if( GetnPoints() > 2 ) {
516                     double dist = DistGreatCircle( m_fixedTP->m_lat, m_fixedTP->m_lon, m_lastStoredTP->m_lat, m_lastStoredTP->m_lon );
517                     double xte = GetXTE( m_fixedTP, m_lastStoredTP, m_removeTP );
518                     if( xte < m_allowedMaxXTE / wxMax(1.0, 2.0 - dist*2.0) ) {
519                         TrackPoints.pop_back();
520                         TrackPoints.pop_back();
521                         TrackPoints.push_back( m_lastStoredTP );
522                         pSelect->DeletePointSelectableTrackSegments( m_removeTP );
523                         pSelect->AddSelectableTrackSegment( m_fixedTP->m_lat, m_fixedTP->m_lon,
524                                 m_lastStoredTP->m_lat, m_lastStoredTP->m_lon,
525                                 m_fixedTP, m_lastStoredTP, this );
526                         delete m_removeTP;
527                         m_removeTP = m_fixedTP;
528                         m_fixedTP = m_prevFixedTP;
529                     }
530                 }
531             }
532 
533             skipPoints.push_back( gpsPoint );
534             skipTimes.push_back( now.ToUTC() );
535             break;
536         }
537     }
538 
539     // Check if this is the last point of the track.
540     if( do_add_point ) {
541         TrackPoint *pTrackPoint = AddNewPoint( gpsPoint, now.ToUTC() );
542         pSelect->AddSelectableTrackSegment( m_lastStoredTP->m_lat, m_lastStoredTP->m_lon,
543                 pTrackPoint->m_lat, pTrackPoint->m_lon,
544                 m_lastStoredTP, pTrackPoint, this );
545     }
546 
547     m_prev_time = now;
548 }
549 
AddPointToList(ChartCanvas * cc,std::list<std::list<wxPoint>> & pointlists,int n)550 void Track::AddPointToList(ChartCanvas *cc, std::list< std::list<wxPoint> > &pointlists, int n)
551 {
552     wxPoint r(INVALID_COORD, INVALID_COORD);
553     if ( (size_t)n < TrackPoints.size() )
554         cc->GetCanvasPointPix( TrackPoints[n]->m_lat, TrackPoints[n]->m_lon, &r );
555 
556     std::list<wxPoint> &pointlist = pointlists.back();
557     if(r.x == INVALID_COORD) {
558         if(pointlist.size()) {
559             std::list<wxPoint> new_list;
560             pointlists.push_back(new_list);
561         }
562         return;
563     }
564 
565     if(pointlist.size() == 0)
566         pointlist.push_back(r);
567     else {
568         wxPoint l = pointlist.back();
569         // ensure the segment is at least 2 pixels
570         if((abs(r.x - l.x) > 1) || (abs(r.y - l.y) > 1))
571             pointlist.push_back(r);
572     }
573 }
574 
575 /* assembles lists of line strips from the given track recursively traversing
576    the subtracks data */
Assemble(ChartCanvas * cc,std::list<std::list<wxPoint>> & pointlists,const LLBBox & box,double scale,int & last,int level,int pos)577 void Track::Assemble(ChartCanvas *cc, std::list< std::list<wxPoint> > &pointlists, const LLBBox &box, double scale, int &last, int level, int pos)
578 {
579     if(pos == (int)SubTracks[level].size())
580         return;
581 
582     SubTrack &s = SubTracks[level][pos];
583     if(box.IntersectOut(s.m_box))
584         return;
585 
586     if(s.m_scale < scale) {
587         pos <<= level;
588 
589         if(last < pos - 1) {
590             std::list<wxPoint> new_list;
591             pointlists.push_back(new_list);
592         }
593 
594         if(last < pos)
595             AddPointToList(cc, pointlists, pos);
596         last = wxMin(pos + (1<<level), TrackPoints.size() - 1);
597         AddPointToList(cc, pointlists, last);
598     } else {
599         Assemble(cc, pointlists, box, scale, last, level-1, pos<<1);
600         Assemble(cc, pointlists, box, scale, last, level-1, (pos<<1)+1);
601     }
602 }
603 
604 // Entry to recursive Assemble at the head of the SubTracks tree
Segments(ChartCanvas * cc,std::list<std::list<wxPoint>> & pointlists,const LLBBox & box,double scale)605 void Track::Segments(ChartCanvas *cc, std::list< std::list<wxPoint> > &pointlists, const LLBBox &box, double scale)
606 {
607     if(!SubTracks.size())
608         return;
609 
610     int level = SubTracks.size()-1, last = -2;
611     Assemble(cc, pointlists, box, 1/scale/scale, last, level, 0);
612 }
613 
ClearHighlights()614 void Track::ClearHighlights()
615 {
616     m_HighlightedTrackPoint = -1;
617 }
618 
Draw(ChartCanvas * cc,ocpnDC & dc,ViewPort & VP,const LLBBox & box)619 void Track::Draw( ChartCanvas *cc, ocpnDC& dc, ViewPort &VP, const LLBBox &box )
620 {
621     std::list< std::list<wxPoint> > pointlists;
622     GetPointLists(cc, pointlists, VP, box);
623 
624     if(!pointlists.size())
625         return;
626 
627     //  Establish basic colour
628     wxColour basic_colour;
629     if( IsRunning() )
630         basic_colour = GetGlobalColor( _T ( "URED" ) );
631     else
632         basic_colour = GetDimColor(g_colourTrackLineColour);
633 
634     wxPenStyle style = wxPENSTYLE_SOLID;
635     int width = g_pRouteMan->GetTrackPen()->GetWidth();
636     wxColour col;
637     if( m_style != wxPENSTYLE_INVALID )
638         style = m_style;
639     if( m_width != WIDTH_UNDEFINED )
640         width = m_width;
641     if( m_Colour == wxEmptyString ) {
642         col = basic_colour;
643     } else {
644         for( unsigned int i = 0; i < sizeof( ::GpxxColorNames ) / sizeof(wxString); i++ ) {
645             if( m_Colour == ::GpxxColorNames[i] ) {
646                 col = ::GpxxColors[i];
647                 break;
648             }
649         }
650     }
651 
652     double radius = 0.;
653     if( g_bHighliteTracks ) {
654         double radius_meters = 20;          // 1.5 mm at original scale
655         radius = radius_meters * VP.view_scale_ppm;
656         if(radius < 1.0)
657             radius = 0;
658     }
659 
660 #ifndef USE_ANDROID_GLES2
661     if(dc.GetDC() || radius)
662 #else
663     if(1)
664 #endif
665     {
666         dc.SetPen( *wxThePenList->FindOrCreatePen( col, width, style ) );
667         dc.SetBrush( *wxTheBrushList->FindOrCreateBrush( col, wxBRUSHSTYLE_SOLID ) );
668         for(std::list< std::list<wxPoint> >::iterator lines = pointlists.begin();
669         lines != pointlists.end(); lines++) {
670             // convert from linked list to array
671             wxPoint *points = new wxPoint[lines->size()];
672             int i = 0;
673             for(std::list<wxPoint>::iterator line = lines->begin();
674                 line != lines->end(); line++) {
675                 points[i] = *line;
676                 i++;
677             }
678 
679             int hilite_width = radius;
680             if( hilite_width >= 1.0 ) {
681                 wxPen psave = dc.GetPen();
682 
683                 dc.StrokeLines( i, points );
684 
685                 wxColor trackLine_dim_colour = GetDimColor(g_colourTrackLineColour);
686                 wxColour hilt( trackLine_dim_colour.Red(), trackLine_dim_colour.Green(), trackLine_dim_colour.Blue(), 128 );
687 
688                 wxPen HiPen( hilt, hilite_width, wxPENSTYLE_SOLID );
689                 dc.SetPen( HiPen );
690 
691                 dc.StrokeLines( i, points );
692 
693                 dc.SetPen( psave );
694             } else
695                 dc.StrokeLines( i, points );
696 
697             delete [] points;
698         }
699     }
700 #ifdef ocpnUSE_GL
701 #ifndef USE_ANDROID_GLES2
702     else { // opengl version
703         glColor3ub(col.Red(), col.Green(), col.Blue());
704         glLineWidth( wxMax( g_GLMinSymbolLineWidth, width ) );
705         if( g_GLOptions.m_GLLineSmoothing )
706             glEnable( GL_LINE_SMOOTH );
707         glEnable( GL_BLEND );
708 
709         int size = 0;
710         // convert from linked list to array, allocate array just once
711         for(std::list< std::list<wxPoint> >::iterator lines = pointlists.begin();
712             lines != pointlists.end(); lines++)
713             size = wxMax(size, lines->size());
714         int *points = new int[2*size];
715         glVertexPointer(2, GL_INT, 0, points);
716 
717         glEnableClientState(GL_VERTEX_ARRAY);
718         for(std::list< std::list<wxPoint> >::iterator lines = pointlists.begin();
719             lines != pointlists.end(); lines++) {
720 
721             // convert from linked list to array
722             int i = 0;
723             for(std::list<wxPoint>::iterator line = lines->begin();
724                 line != lines->end(); line++) {
725                 points[i+0] = line->x;
726                 points[i+1] = line->y;
727                 i+=2;
728             }
729 
730             if(i > 2)
731                 glDrawArrays(GL_LINE_STRIP, 0, i >> 1);
732         }
733         glDisableClientState(GL_VERTEX_ARRAY);
734 
735         delete [] points;
736         glDisable( GL_LINE_SMOOTH );
737         glDisable( GL_BLEND );
738 
739     }
740 #endif
741 #endif
742     if(m_HighlightedTrackPoint >= 0)
743         TrackPoints[m_HighlightedTrackPoint]->Draw(cc, dc);
744 }
745 
GetPoint(int nWhichPoint)746 TrackPoint *Track::GetPoint( int nWhichPoint )
747 {
748     if(nWhichPoint < (int) TrackPoints.size())
749         return TrackPoints[nWhichPoint];
750     else
751         return NULL;
752 }
753 
GetLastPoint()754 TrackPoint *Track::GetLastPoint()
755 {
756     if(TrackPoints.empty())
757         return NULL;
758 
759     return TrackPoints.back();
760 }
761 
heading_diff(double x)762 static double heading_diff(double x)
763 {
764     if(x > 180)
765         return 360 - x;
766     if(x < -180)
767         return -360 + x;
768     return x;
769 }
770 
771 /* Computes the scale factor when these particular segments
772    essentially are smaller than 1 pixel,  This is assuming
773    a simplistic flat projection, it might be useful to
774    add a mercator or other term, but this works in practice */
ComputeScale(int left,int right)775 double Track::ComputeScale(int left, int right)
776 {
777     const double z = WGS84_semimajor_axis_meters * mercator_k0;
778     const double mult = DEGREE * z;
779     // could multiply by a smaller factor to get
780     // better performance with loss of rendering track accuracy
781 
782     double max_dist = 0;
783     double lata = TrackPoints[left]->m_lat, lona = TrackPoints[left]->m_lon;
784     double latb = TrackPoints[right]->m_lat, lonb = TrackPoints[right]->m_lon;
785 
786     double bx = heading_diff(lonb - lona), by = latb - lata;
787 
788     double lengthSquared = bx*bx + by*by;
789 
790     // avoid this calculation for large distances... slight optimization
791     // at building with expense rendering zoomed out. is it needed?
792     if(lengthSquared > 3)
793         return INFINITY;
794 
795     if ( lengthSquared == 0.0 ) {
796         for(int i = left+1; i < right; i++) {
797             double lat = TrackPoints[i]->m_lat, lon = TrackPoints[i]->m_lon;
798             // v == w case
799             double vx = heading_diff(lon - lona);
800             double vy = lat - lata;
801             double dist = vx*vx + vy*vy;
802 
803             if(dist > max_dist)
804                 max_dist = dist;
805         }
806     } else {
807         double invLengthSquared = 1/lengthSquared;
808         for(int i = left+1; i < right; i++) {
809             double lat = TrackPoints[i]->m_lat, lon = TrackPoints[i]->m_lon;
810 
811             double vx = heading_diff(lon - lona);
812             double vy = lat - lata;
813             double t = (vx*bx + vy*by) * invLengthSquared;
814             double dist;
815 
816             if (t < 0.0)
817                 dist = vx*vx + vy*vy;       // Beyond the 'v' end of the segment
818             else if (t > 1.0) {
819                 double wx = heading_diff(lona - lon);
820                 double wy = lata - lat;
821                 dist = wx*wx + wy*wy;  // Beyond the 'w' end of the segment
822             } else {
823                 double projx = vx - t * bx;     // Projection falls on the segment
824                 double projy = vy - t * by;
825                 dist = projx*projx + projy*projy;
826             }
827 
828             if(dist > max_dist)
829                 max_dist = dist;
830         }
831     }
832 
833     return max_dist * mult * mult;
834 }
835 
836 /* Add a point to a track, should be iterated
837    on to build up a track from data.  If a track
838    is being slowing enlarged, see AddPointFinalized below */
AddPoint(TrackPoint * pNewPoint)839 void Track::AddPoint( TrackPoint *pNewPoint )
840 {
841     TrackPoints.push_back( pNewPoint );
842     SubTracks.clear(); // invalidate subtracks
843 }
844 
GetPointLists(ChartCanvas * cc,std::list<std::list<wxPoint>> & pointlists,ViewPort & VP,const LLBBox & box)845 void Track::GetPointLists(ChartCanvas *cc, std::list< std::list<wxPoint> > &pointlists,
846                           ViewPort &VP, const LLBBox &box )
847 {
848     if( !IsVisible() || GetnPoints() == 0 ) return;
849     Finalize();
850 //    OCPNStopWatch sw;
851     Segments(cc, pointlists, box, VP.view_scale_ppm);
852 
853 #if 0
854     if(GetnPoints() > 40000) {
855         double t = sw.GetTime();
856         double c = 0;
857         for(std::list< std::list<wxPoint> >::iterator lines = pointlists.begin();
858         lines != pointlists.end(); lines++) {
859             if(lines->size() > 1)
860                 c += lines->size();
861                 continue;
862         }
863         printf("assemble time %f %f segments %f seg/ms\n", sw.GetTime(), c, c/t);
864     }
865 #endif
866 
867     //    Add last segment, dynamically, maybe.....
868     // we should not add this segment if it is not on the screen...
869     if( IsRunning() ) {
870         std::list<wxPoint> new_list;
871         pointlists.push_back(new_list);
872         AddPointToList(cc, pointlists, TrackPoints.size()-1);
873         wxPoint r;
874         cc->GetCanvasPointPix( gLat, gLon, &r );
875         pointlists.back().push_back(r);
876     }
877 }
878 
879 /* ensures the SubTracks are valid for assembly use */
Finalize()880 void Track::Finalize()
881 {
882     if(SubTracks.size()) // subtracks already computed
883         return;
884 
885 //    OCPNStopWatch sw1;
886 
887     int n = TrackPoints.size() - 1;
888     int level = 0;
889     while(n > 0) {
890         std::vector <SubTrack> new_level;
891         new_level.resize(n);
892         if(level == 0)
893             for(int i=0; i<n; i++) {
894                 new_level[i].m_box.SetFromSegment(TrackPoints[i]->m_lat,
895                                                   TrackPoints[i]->m_lon,
896                                                   TrackPoints[i+1]->m_lat,
897                                                   TrackPoints[i+1]->m_lon);
898                 new_level[i].m_scale = 0;
899             }
900         else {
901             for(int i=0; i<n; i++) {
902                 int p = i<<1;
903                 new_level[i].m_box = SubTracks[level-1][p].m_box;
904                 if(p+1 < (int)SubTracks[level-1].size())
905                     new_level[i].m_box.Expand(SubTracks[level-1][p+1].m_box);
906 
907                 int left = i << level;
908                 int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
909                 new_level[i].m_scale = ComputeScale(left, right);
910             }
911         }
912         SubTracks.push_back(new_level);
913 
914         if(n > 1 && n&1)
915             n++;
916         n >>= 1;
917         level++;
918     }
919 //    if(TrackPoints.size() > 100)
920 //        printf("fin time %f %d\n", sw1.GetTime(), (int)TrackPoints.size());
921 }
922 
923 // recursive subtracks fixer for appending a single point
InsertSubTracks(LLBBox & box,int level,int pos)924 void Track::InsertSubTracks(LLBBox &box, int level, int pos)
925 {
926     if(level == (int)SubTracks.size()) {
927         std::vector <SubTrack> new_level;
928         if(level > 0)
929             box.Expand(SubTracks[level-1][0].m_box);
930         new_level.push_back(SubTrack());
931         new_level[pos].m_box = box;
932         SubTracks.push_back(new_level);
933     } else
934     if(pos < (int)SubTracks[level].size())
935         SubTracks[level][pos].m_box.Expand(box);
936     else {
937         SubTracks[level].push_back(SubTrack());
938         SubTracks[level][pos].m_box = box;
939     }
940 
941     if(level == 0)
942         SubTracks[level][pos].m_scale = 0;
943     else {
944         int left = pos << level;
945         int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
946         SubTracks[level][pos].m_scale = ComputeScale(left, right);
947     }
948 
949     if(pos > 0)
950         InsertSubTracks(box, level + 1, pos >> 1);
951 }
952 
953 /* This function adds a new point ensuring the resulting track is finalized
954    The runtime of this routine is O(log(n)) which is an an improvment over
955    blowing away the subtracks and calling Finalize which is O(n),
956    but should not be used for building a large track O(n log(n)) which
957    _is_ worse than blowing the subtracks and calling Finalize.
958 */
AddPointFinalized(TrackPoint * pNewPoint)959 void Track::AddPointFinalized( TrackPoint *pNewPoint )
960 {
961     TrackPoints.push_back( pNewPoint );
962 
963     int pos = TrackPoints.size() - 1;
964 
965     if(pos > 0) {
966         LLBBox box;
967         box.SetFromSegment(TrackPoints[pos-1]->m_lat,
968                            TrackPoints[pos-1]->m_lon,
969                            TrackPoints[pos]->m_lat,
970                            TrackPoints[pos]->m_lon);
971         InsertSubTracks(box, 0, pos-1);
972     }
973 }
974 
AddNewPoint(vector2D point,wxDateTime time)975 TrackPoint* Track::AddNewPoint( vector2D point, wxDateTime time )
976 {
977     TrackPoint *tPoint = new TrackPoint( point.lat, point.lon, time );
978 
979     AddPointFinalized( tPoint );
980 
981     pConfig->AddNewTrackPoint( tPoint, m_GUID );        // This will update the "changes" file only
982 
983     //send a wxJson message to all plugins
984     wxJSONValue v;
985     v[_T("lat")] = tPoint->m_lat;
986     v[_T("lon")] = tPoint->m_lon;
987     v[_T("Track_ID")] = m_GUID;
988     wxString msg_id( _T("OCPN_TRK_POINT_ADDED") );
989     g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
990     //
991 
992     return tPoint;
993 }
994 
DouglasPeuckerReducer(std::vector<TrackPoint * > & list,std::vector<bool> & keeplist,int from,int to,double delta)995 void Track::DouglasPeuckerReducer( std::vector<TrackPoint*>& list,
996                                    std::vector<bool> & keeplist,
997                                    int from, int to, double delta ) {
998     keeplist[from] = true;
999     keeplist[to] = true;
1000 
1001     int maxdistIndex = -1;
1002     double maxdist = 0;
1003 
1004     for( int i=from+1; i<to; i++ ) {
1005 
1006         double dist = 1852.0 * GetXTE( list[from], list[to], list[i] );
1007 
1008         if( dist > maxdist ) {
1009             maxdist = dist;
1010             maxdistIndex = i;
1011         }
1012     }
1013 
1014     if( maxdist > delta ) {
1015         DouglasPeuckerReducer( list, keeplist, from, maxdistIndex, delta );
1016         DouglasPeuckerReducer( list, keeplist, maxdistIndex, to, delta );
1017     }
1018 }
1019 
Length()1020 double Track::Length()
1021 {
1022     TrackPoint *l = NULL;
1023     double total = 0.0;
1024     for(size_t i = 0; i < TrackPoints.size(); i++) {
1025         TrackPoint *t = TrackPoints[i];
1026         if(l) {
1027             const double offsetLat = 1e-6;
1028             const double deltaLat = l->m_lat - t->m_lat;
1029             if ( fabs( deltaLat ) > offsetLat )
1030                 total += DistGreatCircle( l->m_lat, l->m_lon, t->m_lat, t->m_lon );
1031             else
1032                 total += DistGreatCircle( l->m_lat + copysign( offsetLat, deltaLat ), l->m_lon, t->m_lat, t->m_lon );
1033         }
1034         l = t;
1035     }
1036 
1037     return total;
1038 }
1039 
Simplify(double maxDelta)1040 int Track::Simplify( double maxDelta )
1041 {
1042     int reduction = 0;
1043 
1044     std::vector<TrackPoint*> pointlist;
1045     std::vector<bool> keeplist;
1046 
1047     ::wxBeginBusyCursor();
1048 
1049     for(size_t i = 0; i < TrackPoints.size(); i++) {
1050         TrackPoint *trackpoint = TrackPoints[i];
1051 
1052         pointlist.push_back(trackpoint);
1053         keeplist.push_back(false);
1054     }
1055 
1056     DouglasPeuckerReducer( pointlist, keeplist, 0, pointlist.size()-1, maxDelta );
1057 
1058     pSelect->DeleteAllSelectableTrackSegments( this );
1059     TrackPoints.clear();
1060 
1061     for( size_t i=0; i<pointlist.size(); i++ ) {
1062         if( keeplist[i] )
1063             TrackPoints.push_back( pointlist[i] );
1064         else {
1065             delete pointlist[i];
1066             reduction++;
1067         }
1068     }
1069 
1070     pSelect->AddAllSelectableTrackSegments( this );
1071 
1072 //    UpdateSegmentDistances();
1073     ::wxEndBusyCursor();
1074     return reduction;
1075 }
1076 
RouteFromTrack(wxGenericProgressDialog * pprog)1077 Route *Track::RouteFromTrack( wxGenericProgressDialog *pprog )
1078 {
1079 
1080     Route *route = new Route();
1081 
1082     TrackPoint *pWP_src = TrackPoints.front();
1083     size_t prpnodeX;
1084     RoutePoint *pWP_dst, *pWP_prev;
1085     TrackPoint *prp_OK = NULL;  // last routepoint known not to exceed xte limit, if not yet added
1086 
1087     wxString icon = _T("xmblue");
1088     if( g_TrackDeltaDistance >= 0.1 ) icon = _T("diamond");
1089 
1090     int next_ic = 0;
1091     int back_ic = 0;
1092     int nPoints = TrackPoints.size();
1093     bool isProminent = true;
1094     double delta_dist = 0.;
1095     double delta_hdg, xte;
1096     double leg_speed = 0.1;
1097 
1098     leg_speed = g_PlanSpeed;
1099 
1100 // add first point
1101 
1102     pWP_dst = new RoutePoint( pWP_src->m_lat, pWP_src->m_lon, icon, _T ( "" ), wxEmptyString );
1103     route->AddPoint( pWP_dst );
1104 
1105     pWP_dst->m_bShowName = false;
1106 
1107     pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst );
1108     pWP_prev = pWP_dst;
1109 // add intermediate points as needed
1110     int dProg = 0;
1111     for(size_t i = 1; i < TrackPoints.size();) {
1112         TrackPoint *prp = TrackPoints[i];
1113         prpnodeX = i;
1114         pWP_dst->m_lat = pWP_prev->m_lat;
1115         pWP_dst->m_lon = pWP_prev->m_lon;
1116         pWP_prev = pWP_dst;
1117 
1118         delta_dist = 0.0;
1119         delta_hdg = 0.0;
1120         back_ic = next_ic;
1121 
1122         DistanceBearingMercator( prp->m_lat, prp->m_lon, pWP_prev->m_lat, pWP_prev->m_lon, &delta_hdg,
1123                 &delta_dist );
1124 
1125         if( ( delta_dist > ( leg_speed * 6.0 ) ) && !prp_OK ) {
1126             int delta_inserts = floor( delta_dist / ( leg_speed * 4.0 ) );
1127             delta_dist = delta_dist / ( delta_inserts + 1 );
1128             double tlat = 0.0;
1129             double tlon = 0.0;
1130 
1131             while( delta_inserts-- ) {
1132                 ll_gc_ll( pWP_prev->m_lat, pWP_prev->m_lon, delta_hdg, delta_dist, &tlat, &tlon );
1133                 pWP_dst = new RoutePoint( tlat, tlon, icon, _T ( "" ), wxEmptyString );
1134                 route->AddPoint( pWP_dst );
1135                 pWP_dst->m_bShowName = false;
1136                 pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst );
1137 
1138                 pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat,
1139                         pWP_dst->m_lon, pWP_prev, pWP_dst, route );
1140 
1141                 pWP_prev = pWP_dst;
1142             }
1143             prpnodeX = i;
1144             pWP_dst = pWP_prev;
1145             next_ic = 0;
1146             delta_dist = 0.0;
1147             back_ic = next_ic;
1148             prp_OK = prp;
1149             isProminent = true;
1150         } else {
1151             isProminent = false;
1152             if( delta_dist >= ( leg_speed * 4.0 ) ) isProminent = true;
1153             if( !prp_OK ) prp_OK = prp;
1154         }
1155         while( prpnodeX < TrackPoints.size() ) {
1156 
1157             TrackPoint *prpX = TrackPoints[prpnodeX];
1158 //            TrackPoint src(pWP_prev->m_lat, pWP_prev->m_lon);
1159             xte = GetXTE( pWP_src, prpX, prp );
1160             if( isProminent || ( xte > g_TrackDeltaDistance ) ) {
1161 
1162                 pWP_dst = new RoutePoint( prp_OK->m_lat, prp_OK->m_lon, icon, _T ( "" ),
1163                         wxEmptyString );
1164 
1165                 route->AddPoint( pWP_dst );
1166                 pWP_dst->m_bShowName = false;
1167 
1168                 pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst );
1169 
1170                 pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat,
1171                         pWP_dst->m_lon, pWP_prev, pWP_dst, route );
1172 
1173                 pWP_prev = pWP_dst;
1174                 next_ic = 0;
1175                 prpnodeX = TrackPoints.size();
1176                 prp_OK = NULL;
1177             }
1178 
1179             if( prpnodeX != TrackPoints.size()) prpnodeX--;
1180             if( back_ic-- <= 0 ) {
1181                 prpnodeX = TrackPoints.size();
1182             }
1183         }
1184 
1185         if( prp_OK ) {
1186             prp_OK = prp;
1187         }
1188 
1189         DistanceBearingMercator( prp->m_lat, prp->m_lon, pWP_prev->m_lat, pWP_prev->m_lon, NULL,
1190                 &delta_dist );
1191 
1192         if( !( ( delta_dist > ( g_TrackDeltaDistance ) ) && !prp_OK ) ) {
1193             i++;
1194             next_ic++;
1195         }
1196         int iProg = (i * 100) / nPoints;
1197         if (pprog && (iProg > dProg))
1198         {
1199             dProg = iProg;
1200             pprog->Update(dProg);
1201         }
1202     }
1203 
1204 // add last point, if needed
1205     if( delta_dist >= g_TrackDeltaDistance ) {
1206         pWP_dst = new RoutePoint( TrackPoints.back()->m_lat,
1207                                   TrackPoints.back()->m_lon,
1208                                   icon, _T ( "" ), wxEmptyString );
1209         route->AddPoint( pWP_dst );
1210 
1211         pWP_dst->m_bShowName = false;
1212 
1213         pSelect->AddSelectableRoutePoint( pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst );
1214 
1215         pSelect->AddSelectableRouteSegment( pWP_prev->m_lat, pWP_prev->m_lon, pWP_dst->m_lat,
1216                 pWP_dst->m_lon, pWP_prev, pWP_dst, route );
1217     }
1218     route->m_RouteNameString = m_TrackNameString;
1219     route->m_RouteStartString = m_TrackStartString;
1220     route->m_RouteEndString = m_TrackEndString;
1221     route->m_bDeleteOnArrival = false;
1222 
1223     return route;
1224 }
1225 
GetXTE(double fm1Lat,double fm1Lon,double fm2Lat,double fm2Lon,double toLat,double toLon)1226 double Track::GetXTE( double fm1Lat, double fm1Lon, double fm2Lat, double fm2Lon, double toLat, double toLon  )
1227 {
1228     vector2D v, w, p;
1229 
1230     // First we get the cartesian coordinates to the line endpoints, using
1231     // the current position as origo.
1232 
1233     double brg1, dist1, brg2, dist2;
1234     DistanceBearingMercator( toLat, toLon, fm1Lat, fm1Lon, &brg1, &dist1 );
1235     w.x = dist1 * sin( brg1 * PI / 180. );
1236     w.y = dist1 * cos( brg1 * PI / 180. );
1237 
1238     DistanceBearingMercator( toLat, toLon, fm2Lat, fm2Lon, &brg2, &dist2 );
1239     v.x = dist2 * sin( brg2 * PI / 180. );
1240     v.y = dist2 * cos( brg2 * PI / 180. );
1241 
1242     p.x = 0.0; p.y = 0.0;
1243 
1244     const double lengthSquared = _distance2( v, w );
1245     if ( lengthSquared == 0.0 ) {
1246         // v == w case
1247         return _distance( p, v );
1248     }
1249 
1250     // Consider the line extending the segment, parameterized as v + t (w - v).
1251     // We find projection of origo onto the line.
1252     // It falls where t = [(p-v) . (w-v)] / |w-v|^2
1253 
1254     vector2D a = p - v;
1255     vector2D b = w - v;
1256 
1257     double t = vDotProduct( &a, &b ) / lengthSquared;
1258 
1259     if (t < 0.0) return _distance(p, v);       // Beyond the 'v' end of the segment
1260     else if (t > 1.0) return _distance(p, w);  // Beyond the 'w' end of the segment
1261     vector2D projection = v + t * (w - v);     // Projection falls on the segment
1262     return _distance(p, projection);
1263 }
1264 
GetXTE(TrackPoint * fm1,TrackPoint * fm2,TrackPoint * to)1265 double Track::GetXTE( TrackPoint *fm1, TrackPoint *fm2, TrackPoint *to )
1266 {
1267     if( !fm1 || !fm2 || !to ) return 0.0;
1268     if( fm1 == to ) return 0.0;
1269     if( fm2 == to ) return 0.0;
1270     return GetXTE( fm1->m_lat, fm1->m_lon, fm2->m_lat, fm2->m_lon, to->m_lat, to->m_lon );
1271 ;
1272 }
1273