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