1 /***************************************************************************
2  *
3  * Project:  OpenCPN
4  *
5  ***************************************************************************
6  *   Copyright (C) 2013 by David S. Register                               *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
22  **************************************************************************/
23 
24 #include "wx/wxprec.h"
25 
26 #include "Route.h"
27 #include "georef.h"
28 #include "routeman.h"
29 #include "ocpndc.h"
30 #include "cutil.h"
31 #include "navutil.h"
32 #include "multiplexer.h"
33 #include "Select.h"
34 #include "georef.h"
35 #include "OCPNPlatform.h"
36 #include "chcanv.h"
37 
38 extern WayPointman *pWayPointMan;
39 extern Routeman *g_pRouteMan;
40 extern int g_route_line_width;
41 extern Select *pSelect;
42 extern MyConfig *pConfig;
43 extern Multiplexer *g_pMUX;
44 extern double g_n_arrival_circle_radius;
45 extern float g_GLMinSymbolLineWidth;
46 extern double g_PlanSpeed;
47 extern OCPNPlatform *g_Platform;
48 extern wxString g_default_routepoint_icon;
49 
50 #include <wx/listimpl.cpp>
51 WX_DEFINE_LIST ( RouteList );
52 
Route()53 Route::Route()
54 {
55     m_bRtIsSelected = false;
56     m_bRtIsActive = false;
57     m_pRouteActivePoint = NULL;
58     m_bIsBeingEdited = false;
59     m_bIsBeingCreated = false;
60     m_nm_sequence = 1;
61     m_route_length = 0.0;
62     m_route_time = 0.0;
63     m_bVisible = true;
64     m_bListed = true;
65     m_bDeleteOnArrival = false;
66     m_width = WIDTH_UNDEFINED;
67     m_style = wxPENSTYLE_INVALID;
68     m_hiliteWidth = 0;
69 
70     pRoutePointList = new RoutePointList;
71     m_GUID = pWayPointMan->CreateGUID( NULL );
72     m_btemp = false;
73 
74     m_ArrivalRadius = g_n_arrival_circle_radius;        // Nautical Miles
75 
76     m_LayerID = 0;
77     m_bIsInLayer = false;
78 
79     m_Colour = wxEmptyString;
80 
81     m_lastMousePointIndex = 0;
82     m_NextLegGreatCircle = false;
83 
84     m_PlannedSpeed = ROUTE_DEFAULT_SPEED;
85     if(g_PlanSpeed != ROUTE_DEFAULT_SPEED)
86         m_PlannedSpeed = g_PlanSpeed;
87 
88     m_PlannedDeparture = RTE_UNDEF_DEPARTURE;
89     m_TimeDisplayFormat = RTE_TIME_DISP_PC;
90     m_HyperlinkList = new HyperlinkList;
91 
92     m_bsharedWPViz = false;
93 }
94 
~Route()95 Route::~Route()
96 {
97     pRoutePointList->DeleteContents( false );            // do not delete Marks
98     delete pRoutePointList;
99     delete m_HyperlinkList;
100 }
101 
102 // The following is used only for route splitting, assumes just created, empty route
103 //
CloneRoute(Route * psourceroute,int start_nPoint,int end_nPoint,const wxString & suffix,const bool duplicate_first_point)104 void Route::CloneRoute( Route *psourceroute, int start_nPoint, int end_nPoint, const wxString & suffix, const bool duplicate_first_point)
105 {
106     m_RouteNameString = psourceroute->m_RouteNameString + suffix;
107     m_RouteStartString = psourceroute->m_RouteStartString;
108     m_RouteEndString = psourceroute->m_RouteEndString;
109 
110     int i;
111     for( i = start_nPoint; i <= end_nPoint; i++ ) {
112         if( !psourceroute->m_bIsInLayer && !(i == start_nPoint && duplicate_first_point) ) {
113             AddPoint( psourceroute->GetPoint( i ), false );
114         } else {
115             RoutePoint *psourcepoint = psourceroute->GetPoint( i );
116             RoutePoint *ptargetpoint = new RoutePoint( psourcepoint->m_lat, psourcepoint->m_lon,
117                     psourcepoint->GetIconName(), psourcepoint->GetName(), wxEmptyString, false );
118 
119             AddPoint( ptargetpoint, false );
120         }
121     }
122 
123     FinalizeForRendering();
124 
125 }
126 
AddPoint(RoutePoint * pNewPoint,bool b_rename_in_sequence,bool b_deferBoxCalc)127 void Route::AddPoint( RoutePoint *pNewPoint, bool b_rename_in_sequence, bool b_deferBoxCalc )
128 {
129     if( pNewPoint->m_bIsolatedMark ) {
130         pNewPoint->m_bKeepXRoute = true;
131     }
132     pNewPoint->m_bIsolatedMark = false;       // definitely no longer isolated
133     pNewPoint->m_bIsInRoute = true;
134 
135     RoutePoint *prev = GetLastPoint();
136     pRoutePointList->Append( pNewPoint );
137 
138     if( !b_deferBoxCalc )
139         FinalizeForRendering();
140 
141     if( prev )
142         UpdateSegmentDistance( prev, pNewPoint );
143 
144     if( b_rename_in_sequence && pNewPoint->GetName().IsEmpty() && !pNewPoint->m_bKeepXRoute ) {
145         wxString name;
146         name.Printf( _T ( "%03d" ), GetnPoints() );
147         pNewPoint->SetName( name );
148         pNewPoint->m_bDynamicName = true;
149     }
150     return;
151 }
152 
GetPoint(int nWhichPoint)153 RoutePoint *Route::GetPoint( int nWhichPoint )
154 {
155     RoutePoint *prp;
156     wxRoutePointListNode *node = pRoutePointList->GetFirst();
157 
158     int i = 1;
159     while( node ) {
160         prp = node->GetData();
161         if( i == nWhichPoint ) {
162             return prp;
163         }
164         i++;
165         node = node->GetNext();
166     }
167 
168     return ( NULL );
169 }
170 
GetPoint(const wxString & guid)171 RoutePoint *Route::GetPoint( const wxString &guid )
172 {
173     RoutePoint *prp;
174     wxRoutePointListNode *node = pRoutePointList->GetFirst();
175 
176     while( node ) {
177         prp = node->GetData();
178         if( guid == prp->m_GUID ) return prp;
179 
180         node = node->GetNext();
181     }
182 
183     return ( NULL );
184 }
185 
DrawPointWhich(ocpnDC & dc,ChartCanvas * canvas,int iPoint,wxPoint * rpn)186 void Route::DrawPointWhich( ocpnDC& dc, ChartCanvas *canvas, int iPoint, wxPoint *rpn )
187 {
188     if( iPoint <= GetnPoints() )
189         GetPoint( iPoint )->Draw( dc, canvas, rpn );
190 }
191 
DrawSegment(ocpnDC & dc,ChartCanvas * canvas,wxPoint * rp1,wxPoint * rp2,ViewPort & vp,bool bdraw_arrow)192 void Route::DrawSegment( ocpnDC& dc, ChartCanvas *canvas, wxPoint *rp1, wxPoint *rp2, ViewPort &vp, bool bdraw_arrow )
193 {
194     if( m_bRtIsSelected ) dc.SetPen( *g_pRouteMan->GetSelectedRoutePen() );
195     else
196         if( m_bRtIsActive ) dc.SetPen( *g_pRouteMan->GetActiveRoutePen() );
197         else
198             dc.SetPen( *g_pRouteMan->GetRoutePen() );
199 
200     RenderSegment( dc, rp1->x, rp1->y, rp2->x, rp2->y, vp, bdraw_arrow );
201 }
202 
Draw(ocpnDC & dc,ChartCanvas * canvas,const LLBBox & box)203 void Route::Draw( ocpnDC& dc, ChartCanvas *canvas, const LLBBox &box )
204 {
205     if( pRoutePointList->empty() )
206         return;
207 
208     ViewPort vp = canvas->GetVP();
209 
210     LLBBox test_box = GetBBox();
211     if( box.IntersectOut( test_box ) ) // Route is wholly outside window
212         return;
213 
214     int width = g_route_line_width;
215     if( m_width != WIDTH_UNDEFINED ) width = m_width;
216 
217     if( m_bVisible && m_bRtIsSelected ) {
218         wxPen spen = *g_pRouteMan->GetSelectedRoutePen();
219         spen.SetWidth( width );
220         dc.SetPen( spen );
221         dc.SetBrush( *g_pRouteMan->GetSelectedRouteBrush() );
222     }
223     else if ( m_bVisible )
224     {
225         wxPenStyle style = wxPENSTYLE_SOLID;
226         wxColour col;
227         if( m_style != wxPENSTYLE_INVALID ) style = m_style;
228         if( m_Colour == wxEmptyString ) {
229             col = g_pRouteMan->GetRoutePen()->GetColour();
230         } else {
231             for( unsigned int i = 0; i < sizeof( ::GpxxColorNames ) / sizeof(wxString); i++ ) {
232                 if( m_Colour == ::GpxxColorNames[i] ) {
233                     col = ::GpxxColors[i];
234                     break;
235                 }
236             }
237         }
238         dc.SetPen( *wxThePenList->FindOrCreatePen( col, width, style ) );
239         dc.SetBrush( *wxTheBrushList->FindOrCreateBrush( col, wxBRUSHSTYLE_SOLID ) );
240     }
241 
242     if( m_bVisible && m_bRtIsActive )
243     {
244         wxPen spen = *g_pRouteMan->GetActiveRoutePen();
245         spen.SetWidth( width );
246         dc.SetPen( spen );
247         dc.SetBrush( *g_pRouteMan->GetActiveRouteBrush() );
248     }
249 
250     wxPoint rpt1, rpt2;
251     if ( m_bVisible )
252         DrawPointWhich( dc, canvas, 1, &rpt1 );
253 
254     bool sharedVizOveride = GetSharedWPViz();
255 
256     wxRoutePointListNode *node = pRoutePointList->GetFirst();
257     RoutePoint *prp1 = node->GetData();
258     node = node->GetNext();
259 
260     if ( !m_bVisible && prp1->m_bKeepXRoute )
261         prp1->Draw( dc, canvas, NULL, sharedVizOveride);
262 
263     while( node ) {
264 
265         RoutePoint *prp2 = node->GetData();
266         if ( !m_bVisible && prp2->m_bKeepXRoute )
267             prp2->Draw( dc, canvas, &rpt2, sharedVizOveride );
268         else if (m_bVisible)
269             prp2->Draw( dc, canvas, &rpt2 );
270 
271         if ( m_bVisible )
272         {
273             //    Handle offscreen points
274             bool b_2_on = vp.GetBBox().Contains( prp2->m_lat,  prp2->m_lon );
275             bool b_1_on = vp.GetBBox().Contains( prp1->m_lat,  prp1->m_lon );
276 
277             //Simple case
278             if( b_1_on && b_2_on ) RenderSegment( dc, rpt1.x, rpt1.y, rpt2.x, rpt2.y, vp, true, m_hiliteWidth ); // with arrows
279 
280             //    In the cases where one point is on, and one off
281             //    we must decide which way to go in longitude
282             //     Arbitrarily, we will go the shortest way
283 
284             double pix_full_circle = WGS84_semimajor_axis_meters * mercator_k0 * 2 * PI
285                 * vp.view_scale_ppm;
286             double dp = pow( (double) ( rpt1.x - rpt2.x ), 2 ) + pow( (double) ( rpt1.y - rpt2.y ), 2 );
287             double dtest;
288             int adder;
289             if( b_1_on && !b_2_on ) {
290                 if( rpt2.x < rpt1.x ) adder = (int) pix_full_circle;
291                 else
292                     adder = -(int) pix_full_circle;
293 
294                 dtest = pow( (double) ( rpt1.x - ( rpt2.x + adder ) ), 2 )
295                     + pow( (double) ( rpt1.y - rpt2.y ), 2 );
296 
297                 if( dp < dtest ) adder = 0;
298 
299                 RenderSegment( dc, rpt1.x, rpt1.y, rpt2.x + adder, rpt2.y, vp, true, m_hiliteWidth );
300             } else
301                 if( !b_1_on ) {
302                     if( rpt1.x < rpt2.x ) adder = (int) pix_full_circle;
303                     else
304                         adder = -(int) pix_full_circle;
305 
306                     float rxd = rpt2.x - ( rpt1.x + adder );
307                     float ryd = rpt1.y - rpt2.y;
308                     dtest = rxd*rxd + ryd*ryd;
309 
310                     if( dp < dtest ) adder = 0;
311 
312                     RenderSegment( dc, rpt1.x + adder, rpt1.y, rpt2.x, rpt2.y, vp, true, m_hiliteWidth );
313                 }
314         }
315 
316         rpt1 = rpt2;
317         prp1 = prp2;
318 
319         node = node->GetNext();
320     }
321 }
322 
323 
TestLongitude(double lon,double min,double max,bool & lonl,bool & lonr)324 static void TestLongitude(double lon, double min, double max, bool &lonl, bool &lonr)
325 {
326     double clon = (min + max)/2;
327     if(min - lon > 180)
328         lon += 360;
329 
330     lonl = lonr = false;
331     if(lon < min) {
332         if(lon < clon - 180)
333             lonr = true;
334         else
335             lonl = true;
336     } else if(lon > max) {
337         if(lon > clon + 180)
338             lonl = true;
339         else
340             lonr = true;
341     }
342 }
343 
DrawGLLines(ViewPort & vp,ocpnDC * dc,ChartCanvas * canvas)344 void Route::DrawGLLines( ViewPort &vp, ocpnDC *dc, ChartCanvas *canvas )
345 {
346 #ifdef ocpnUSE_GL
347     float pix_full_circle = WGS84_semimajor_axis_meters * mercator_k0 * 2 * PI * vp.view_scale_ppm;
348 
349     bool r1valid = false;
350     wxPoint2DDouble r1;
351     wxPoint2DDouble lastpoint;
352 
353     wxRoutePointListNode *node = pRoutePointList->GetFirst();
354     RoutePoint *prp2 = node->GetData();
355     canvas->GetDoubleCanvasPointPix( prp2->m_lat, prp2->m_lon, &lastpoint);
356 
357     if(GetnPoints() == 1 && dc) { // single point.. make sure it shows up for highlighting
358         canvas->GetDoubleCanvasPointPix( prp2->m_lat, prp2->m_lon, &r1);
359         dc->DrawLine(r1.m_x, r1.m_y, r1.m_x+2, r1.m_y+2);
360         return;
361     }
362 
363     //    Handle offscreen points
364     LLBBox bbox = vp.GetBBox();
365 
366     // dc is passed for thicker highlighted lines (performance not very important)
367 #ifndef USE_ANDROID_GLES2
368     if( !dc )
369         glBegin(GL_LINES);
370 #endif
371 
372     for(node = node->GetNext(); node; node = node->GetNext()) {
373         RoutePoint *prp1 = prp2;
374         prp2 = node->GetData();
375 
376         // Provisional, to properly set status of last point in route
377         prp2->m_pos_on_screen = false;
378         {
379 
380             wxPoint2DDouble r2;
381             canvas->GetDoubleCanvasPointPix( prp2->m_lat, prp2->m_lon, &r2);
382             if(std::isnan(r2.m_x)) {
383                 r1valid = false;
384                 continue;
385             }
386 
387             lastpoint = r2;             // For active track segment to ownship
388 
389             // don't need to perform calculations or render segment
390             // if both points are past any edge of the vp
391             // TODO: use these optimizations for dc mode
392             bool lat1l = prp1->m_lat < bbox.GetMinLat(), lat2l = prp2->m_lat < bbox.GetMinLat();
393             bool lat1r = prp1->m_lat > bbox.GetMaxLat(), lat2r = prp2->m_lat > bbox.GetMaxLat();
394             if( (lat1l && lat2l) || (lat1r && lat2r) ) {
395                 r1valid = false;
396                 prp1->m_pos_on_screen = false;
397                 continue;
398             }
399 
400             bool lon1l, lon1r, lon2l, lon2r;
401             TestLongitude(prp1->m_lon, bbox.GetMinLon(), bbox.GetMaxLon(), lon1l, lon1r);
402             TestLongitude(prp2->m_lon, bbox.GetMinLon(), bbox.GetMaxLon(), lon2l, lon2r);
403             if( (lon1l && lon2l) || (lon1r && lon2r) ) {
404                 r1valid = false;
405                 prp1->m_pos_on_screen = false;
406                 continue;
407             }
408 
409             if(!r1valid) {
410                 canvas->GetDoubleCanvasPointPix( prp1->m_lat, prp1->m_lon, &r1);
411                 if(std::isnan(r1.m_x))
412                     continue;
413             }
414 
415             //  we must decide which way to go in longitude
416             //  for projections which wrap, in this case, we will render two lines
417             //  (one may often be off screen which would be nice to fix but complicate things here
418             //  anyway, in some cases both points are on screen, but the route wraps to either side
419             //  so two lines are needed to draw this properly
420 
421             double adder = 0;
422             if( (vp.m_projection_type == PROJECTION_MERCATOR ||
423                  vp.m_projection_type == PROJECTION_EQUIRECTANGULAR) ) {
424                 float olon = vp.clon > 0 ? vp.clon - 180 : vp.clon + 180;
425 
426                 if(prp1->m_lon < prp2->m_lon) {
427                     if(prp2->m_lon - prp1->m_lon < 180) {
428                         if(olon > prp1->m_lon && olon < prp2->m_lon)
429                             adder = pix_full_circle;
430                     } else
431                         if(olon < prp1->m_lon || olon > prp2->m_lon)
432                             adder = -pix_full_circle;
433                 } else
434                     if(prp1->m_lon - prp2->m_lon < 180) {
435                         if(olon < prp1->m_lon && olon > prp2->m_lon)
436                             adder = -pix_full_circle;
437                     } else
438                         if(olon > prp1->m_lon || olon < prp2->m_lon)
439                             adder = pix_full_circle;
440             }
441 
442             if( dc )
443                 if(adder) {
444                     float adderc = cos(vp.rotation)*adder, adders = sin(vp.rotation)*adder;
445                     dc->DrawLine(r1.m_x, r1.m_y, r2.m_x + adderc, r2.m_y + adders);
446                     dc->DrawLine(r1.m_x - adderc, r1.m_y - adders, r2.m_x, r2.m_y);
447                 } else
448                     dc->DrawLine(r1.m_x, r1.m_y, r2.m_x, r2.m_y);
449             else {
450 #ifndef USE_ANDROID_GLES2
451                 glVertex2f(r1.m_x, r1.m_y);
452                 if(adder) {
453                     float adderc = cos(vp.rotation)*adder, adders = sin(vp.rotation)*adder;
454                     glVertex2f(r2.m_x+adderc, r2.m_y+adders);
455                     glVertex2f(r1.m_x-adderc, r1.m_y-adders);
456                 }
457                 glVertex2f(r2.m_x, r2.m_y);
458 
459                 // cache screen position for arrows and points
460                 if(!r1valid) {
461                     prp1->m_pos_on_screen = !lat1l && !lat1r && !lon1l && !lon1r;
462                     prp1->m_screen_pos = r1;
463                 }
464 
465                 prp2->m_pos_on_screen = !lat2l && !lat2r && !lon2l && !lon2r;
466                 prp2->m_screen_pos = r2;
467 #endif
468             }
469 
470             r1 = r2;
471             r1valid = true;
472         }
473     }
474 
475 #ifndef USE_ANDROID_GLES2
476     if( !dc )
477         glEnd();
478 #endif
479 
480 #endif
481 }
482 
ContainsSharedWP()483 bool Route::ContainsSharedWP()
484 {
485     for(wxRoutePointListNode *node = pRoutePointList->GetFirst(); node; node = node->GetNext()) {
486         RoutePoint *prp = node->GetData();
487             if ( prp->m_bKeepXRoute )
488                 return true;
489     }
490     return false;
491 }
492 
493 
DrawGL(ViewPort & vp,ChartCanvas * canvas)494 void Route::DrawGL( ViewPort &vp, ChartCanvas *canvas )
495 {
496 #ifdef ocpnUSE_GL
497     if( pRoutePointList->empty() ) return;
498 
499     if(!vp.GetBBox().IntersectOut(GetBBox()) && m_bVisible)
500         DrawGLRouteLines(vp, canvas);
501 
502     bool bVizOverride = GetSharedWPViz();
503 
504     /*  Route points  */
505     for(wxRoutePointListNode *node = pRoutePointList->GetFirst(); node; node = node->GetNext()) {
506         RoutePoint *prp = node->GetData();
507         // Inflate the bounding box a bit to ensure full drawing in accelerated pan mode.
508         // TODO this is a little extravagant, assumming a mark is always a large fixed lat/lon extent.
509         //  Maybe better to use the mark's drawn box, once it is known.
510         if(vp.GetBBox().ContainsMarge(prp->m_lat, prp->m_lon, .5)){
511             if ( !m_bVisible && prp->m_bKeepXRoute )
512                 prp->DrawGL( vp, canvas, false, bVizOverride );
513             else if (m_bVisible)
514                 prp->DrawGL( vp, canvas );
515         }
516     }
517 
518 #endif
519 }
520 
521 
DrawGLRouteLines(ViewPort & vp,ChartCanvas * canvas)522 void Route::DrawGLRouteLines( ViewPort &vp, ChartCanvas *canvas )
523 {
524 #ifdef ocpnUSE_GL
525     //  Hiliting first
526     //  Being special case to draw something for a 1 point route....
527     ocpnDC dc;
528     if(m_hiliteWidth) {
529         wxColour y = GetGlobalColor( _T ( "YELO1" ) );
530         wxColour hilt( y.Red(), y.Green(), y.Blue(), 128 );
531 
532         wxPen HiPen( hilt, m_hiliteWidth, wxPENSTYLE_SOLID );
533 
534         ocpnDC dc;
535         dc.SetPen( HiPen );
536 
537         DrawGLLines(vp, &dc, canvas);
538     }
539 
540     /* determine color and width */
541     wxColour col;
542 
543     int width = g_pRouteMan->GetRoutePen()->GetWidth(); //g_route_line_width;
544     if( m_width != wxPENSTYLE_INVALID )
545         width = m_width;
546 
547     if( m_bRtIsActive )
548     {
549         col = g_pRouteMan->GetActiveRoutePen()->GetColour();
550     } else if( m_bRtIsSelected ) {
551         col = g_pRouteMan->GetSelectedRoutePen()->GetColour();
552     } else {
553         if( m_Colour == wxEmptyString ) {
554             col = g_pRouteMan->GetRoutePen()->GetColour();
555         } else {
556             for( unsigned int i = 0; i < sizeof( ::GpxxColorNames ) / sizeof(wxString); i++ ) {
557                 if( m_Colour == ::GpxxColorNames[i] ) {
558                     col = ::GpxxColors[i];
559                     break;
560                 }
561             }
562         }
563     }
564 
565     wxPenStyle style = wxPENSTYLE_SOLID;
566     if( m_style != wxPENSTYLE_INVALID ) style = m_style;
567     dc.SetPen( *wxThePenList->FindOrCreatePen( col, width, style ) );
568     dc.SetBrush( *wxTheBrushList->FindOrCreateBrush( col, wxBRUSHSTYLE_SOLID ) );
569 
570     glLineWidth( wxMax( g_GLMinSymbolLineWidth, width ) );
571 
572     dc.SetGLStipple();
573 
574 #ifdef USE_ANDROID_GLES2
575     DrawGLLines(vp, &dc, canvas);
576 #else
577     glColor3ub(col.Red(), col.Green(), col.Blue());
578     DrawGLLines(vp, NULL, canvas);
579 #endif
580 
581     glDisable (GL_LINE_STIPPLE);
582 
583     /* direction arrows.. could probably be further optimized for opengl */
584     wxRoutePointListNode *node = pRoutePointList->GetFirst();
585     wxPoint rpt1, rpt2;
586     while(node) {
587         RoutePoint *prp = node->GetData();
588         canvas->GetCanvasPointPix( prp->m_lat, prp->m_lon, &rpt2 );
589         if(node != pRoutePointList->GetFirst())
590             RenderSegmentArrowsGL( dc, rpt1.x, rpt1.y, rpt2.x, rpt2.y, vp );
591         rpt1 = rpt2;
592         node = node->GetNext();
593     }
594     #endif
595 }
596 
597 static int s_arrow_icon[] = { 0, 0, 5, 2, 18, 6, 12, 0, 18, -6, 5, -2, 0, 0 };
598 
RenderSegment(ocpnDC & dc,int xa,int ya,int xb,int yb,ViewPort & vp,bool bdraw_arrow,int hilite_width)599 void Route::RenderSegment( ocpnDC& dc, int xa, int ya, int xb, int yb, ViewPort &vp,
600         bool bdraw_arrow, int hilite_width )
601 {
602     //    Get the dc boundary
603     int sx, sy;
604     dc.GetSize( &sx, &sy );
605 
606     //    Try to exit early if the segment is nowhere near the screen
607     wxRect r( 0, 0, sx, sy );
608     wxRect s( xa, ya, 1, 1 );
609     wxRect t( xb, yb, 1, 1 );
610     s.Union( t );
611     if( !r.Intersects( s ) ) return;
612 
613     //    Clip the line segment to the dc boundary
614     int x0 = xa;
615     int y0 = ya;
616     int x1 = xb;
617     int y1 = yb;
618 
619     //    If hilite is desired, use a Native Graphics context to render alpha colours
620     //    That is, if wxGraphicsContext is available.....
621 
622     if( hilite_width ) {
623         if( Visible == cohen_sutherland_line_clip_i( &x0, &y0, &x1, &y1, 0, sx, 0, sy ) ) {
624             wxPen psave = dc.GetPen();
625 
626             wxColour y = GetGlobalColor( _T ( "YELO1" ) );
627             wxColour hilt( y.Red(), y.Green(), y.Blue(), 128 );
628 
629             wxPen HiPen( hilt, hilite_width, wxPENSTYLE_SOLID );
630 
631             dc.SetPen( HiPen );
632             dc.StrokeLine( x0, y0, x1, y1 );
633 
634             dc.SetPen( psave );
635             dc.StrokeLine( x0, y0, x1, y1 );
636         }
637     } else {
638         if( Visible == cohen_sutherland_line_clip_i( &x0, &y0, &x1, &y1, 0, sx, 0, sy ) )
639             dc.StrokeLine( x0, y0, x1, y1 );
640     }
641 
642     if( bdraw_arrow ) {
643         //    Draw a direction arrow
644 
645         double theta = atan2( (double) ( yb - ya ), (double) ( xb - xa ) );
646         theta -= PI / 2.;
647 
648         wxPoint icon[10];
649         double icon_scale_factor = 100 * vp.view_scale_ppm;
650         icon_scale_factor = fmin ( icon_scale_factor, 1.5 );              // Sets the max size
651         icon_scale_factor = fmax ( icon_scale_factor, .10 );
652 
653         //    Get the absolute line length
654         //    and constrain the arrow to be no more than xx% of the line length
655         double nom_arrow_size = 20.;
656         double max_arrow_to_leg = .20;
657         double lpp = sqrt( pow( (double) ( xa - xb ), 2 ) + pow( (double) ( ya - yb ), 2 ) );
658 
659         double icon_size = icon_scale_factor * nom_arrow_size;
660         if( icon_size > ( lpp * max_arrow_to_leg ) ) icon_scale_factor = ( lpp * max_arrow_to_leg )
661                 / nom_arrow_size;
662 
663         for( int i = 0; i < 7; i++ ) {
664             int j = i * 2;
665             double pxa = (double) ( s_arrow_icon[j] );
666             double pya = (double) ( s_arrow_icon[j + 1] );
667 
668             pya *= icon_scale_factor;
669             pxa *= icon_scale_factor;
670 
671             double px = ( pxa * sin( theta ) ) + ( pya * cos( theta ) );
672             double py = ( pya * sin( theta ) ) - ( pxa * cos( theta ) );
673 
674             icon[i].x = (int) ( px ) + xb;
675             icon[i].y = (int) ( py ) + yb;
676         }
677         wxPen savePen = dc.GetPen();
678         dc.SetPen( *wxTRANSPARENT_PEN );
679         dc.StrokePolygon( 6, &icon[0], 0, 0 );
680         dc.SetPen( savePen );
681     }
682 }
683 
RenderSegmentArrowsGL(ocpnDC & dc,int xa,int ya,int xb,int yb,ViewPort & vp)684 void Route::RenderSegmentArrowsGL( ocpnDC &dc, int xa, int ya, int xb, int yb, ViewPort &vp)
685 {
686 #ifdef ocpnUSE_GL
687     //    Draw a direction arrow
688     float icon_scale_factor = 100 * vp.view_scale_ppm;
689     icon_scale_factor = fmin ( icon_scale_factor, 1.5 );              // Sets the max size
690     icon_scale_factor = fmax ( icon_scale_factor, .10 );
691 
692     //    Get the absolute line length
693     //    and constrain the arrow to be no more than xx% of the line length
694     float nom_arrow_size = 20.;
695     float max_arrow_to_leg = (float).20;
696     float lpp = sqrtf( powf( (float) (xa - xb), 2) + powf( (float) (ya - yb), 2) );
697 
698     float icon_size = icon_scale_factor * nom_arrow_size;
699     if( icon_size > ( lpp * max_arrow_to_leg ) )
700         icon_scale_factor = ( lpp * max_arrow_to_leg )
701             / nom_arrow_size;
702 
703     float theta = atan2f( (float)yb - ya, (float)xb - xa );
704     theta -= (float)PI;
705 
706 #ifndef USE_ANDROID_GLES2
707     glPushMatrix();
708     glTranslatef(xb, yb, 0);
709     glScalef(icon_scale_factor, icon_scale_factor, 1);
710     glRotatef(theta * 180/PI, 0, 0, 1);
711 
712     glBegin(GL_POLYGON);
713     for( int i = 0; i < 14; i+=2 )
714         glVertex2f(s_arrow_icon[i], s_arrow_icon[i+1]);
715     glEnd();
716 
717     glPopMatrix();
718 #else
719     //icon_scale_factor = 5;
720     wxPoint pts[3];
721     // 0
722     pts[0].x = s_arrow_icon[0]; pts[0].y = s_arrow_icon[1];
723     pts[1].x = s_arrow_icon[2]; pts[1].y = s_arrow_icon[3];
724     pts[2].x = s_arrow_icon[6]; pts[2].y = s_arrow_icon[7];
725     dc.DrawPolygon( 3, pts, xb, yb, icon_scale_factor, theta );
726 
727     // 1
728     pts[0].x = s_arrow_icon[2]; pts[0].y = s_arrow_icon[3];
729     pts[1].x = s_arrow_icon[4]; pts[1].y = s_arrow_icon[5];
730     pts[2].x = s_arrow_icon[6]; pts[2].y = s_arrow_icon[7];
731     dc.DrawPolygon( 3, pts, xb, yb, icon_scale_factor, theta );
732 
733     // 2
734     pts[0].x = s_arrow_icon[0]; pts[0].y = -s_arrow_icon[1];
735     pts[1].x = s_arrow_icon[2]; pts[1].y = -s_arrow_icon[3];
736     pts[2].x = s_arrow_icon[6]; pts[2].y = -s_arrow_icon[7];
737     dc.DrawPolygon( 3, pts, xb, yb, icon_scale_factor, theta );
738 
739     // 3
740     pts[0].x = s_arrow_icon[2]; pts[0].y = -s_arrow_icon[3];
741     pts[1].x = s_arrow_icon[4]; pts[1].y = -s_arrow_icon[5];
742     pts[2].x = s_arrow_icon[6]; pts[2].y = -s_arrow_icon[7];
743     dc.DrawPolygon( 3, pts, xb, yb, icon_scale_factor, theta );
744 
745 #endif
746 
747 #endif
748 }
749 
ClearHighlights(void)750 void Route::ClearHighlights( void )
751 {
752     RoutePoint *prp = NULL;
753     wxRoutePointListNode *node = pRoutePointList->GetFirst();
754 
755     while( node ) {
756         prp = node->GetData();
757         if( prp ) prp->m_bPtIsSelected = false;
758         node = node->GetNext();
759     }
760 }
761 
InsertPointBefore(RoutePoint * pRP,double rlat,double rlon,bool bRenamePoints)762 RoutePoint *Route::InsertPointBefore( RoutePoint *pRP, double rlat, double rlon,
763         bool bRenamePoints )
764 {
765     RoutePoint *newpoint = new RoutePoint( rlat, rlon, g_default_routepoint_icon,
766             GetNewMarkSequenced(), wxEmptyString );
767     newpoint->m_bIsInRoute = true;
768     newpoint->m_bDynamicName = true;
769     newpoint->SetNameShown( false );
770 
771     int nRP = pRoutePointList->IndexOf( pRP );
772     pRoutePointList->Insert( nRP, newpoint );
773 
774     if( bRenamePoints ) RenameRoutePoints();
775 
776     FinalizeForRendering();
777     UpdateSegmentDistances();
778 
779     return ( newpoint );
780 }
781 
InsertPointAfter(RoutePoint * pRP,double rlat,double rlon,bool bRenamePoints)782 RoutePoint *Route::InsertPointAfter( RoutePoint *pRP, double rlat, double rlon,
783                                       bool bRenamePoints )
784 {
785     int nRP = pRoutePointList->IndexOf( pRP );
786     if( nRP >= GetnPoints() - 1 )
787         return NULL;
788     nRP++;
789 
790     RoutePoint *newpoint = new RoutePoint( rlat, rlon, g_default_routepoint_icon,
791                                            GetNewMarkSequenced(), wxEmptyString );
792     newpoint->m_bIsInRoute = true;
793     newpoint->m_bDynamicName = true;
794     newpoint->SetNameShown( false );
795 
796     pRoutePointList->Insert( nRP, newpoint );
797 
798     if( bRenamePoints ) RenameRoutePoints();
799 
800     FinalizeForRendering();
801     UpdateSegmentDistances();
802 
803     return ( newpoint );
804 }
805 
GetNewMarkSequenced(void)806 wxString Route::GetNewMarkSequenced( void )
807 {
808     wxString ret;
809     ret.Printf( _T ( "NM%03d" ), m_nm_sequence );
810     m_nm_sequence++;
811 
812     return ret;
813 }
814 
GetLastPoint()815 RoutePoint *Route::GetLastPoint()
816 {
817     if(pRoutePointList->IsEmpty())
818         return NULL;
819 
820     return pRoutePointList->GetLast()->GetData();
821 }
822 
GetIndexOf(RoutePoint * prp)823 int Route::GetIndexOf( RoutePoint *prp )
824 {
825     int ret = pRoutePointList->IndexOf( prp ) + 1;
826     if( ret == wxNOT_FOUND ) return 0;
827     else
828         return ret;
829 
830 }
831 
DeletePoint(RoutePoint * rp,bool bRenamePoints)832 void Route::DeletePoint( RoutePoint *rp, bool bRenamePoints )
833 {
834     //    n.b. must delete Selectables  and update config before deleting the point
835     if( rp->m_bIsInLayer ) return;
836 
837     pSelect->DeleteAllSelectableRoutePoints( this );
838     pSelect->DeleteAllSelectableRouteSegments( this );
839     pConfig->DeleteWayPoint( rp );
840 
841     pRoutePointList->DeleteObject( rp );
842 
843     delete rp;
844 
845     if( bRenamePoints ) RenameRoutePoints();
846 
847     if( GetnPoints() > 1 ) {
848         pSelect->AddAllSelectableRouteSegments( this );
849         pSelect->AddAllSelectableRoutePoints( this );
850 
851         pConfig->UpdateRoute( this );
852 
853         FinalizeForRendering();
854         UpdateSegmentDistances();
855     }
856 }
857 
RemovePoint(RoutePoint * rp,bool bRenamePoints)858 void Route::RemovePoint( RoutePoint *rp, bool bRenamePoints )
859 {
860     if( rp->m_bIsActive && this->IsActive() )                  //FS#348
861     g_pRouteMan->DeactivateRoute();
862 
863     pSelect->DeleteAllSelectableRoutePoints( this );
864     pSelect->DeleteAllSelectableRouteSegments( this );
865 
866     pRoutePointList->DeleteObject( rp );
867 
868     // check all other routes to see if this point appears in any other route
869     Route *pcontainer_route = g_pRouteMan->FindRouteContainingWaypoint( rp );
870 
871     if( pcontainer_route == NULL ) {
872         rp->m_bIsInRoute = false;          // Take this point out of this (and only) route
873         rp->m_bDynamicName = false;
874         rp->m_bIsolatedMark = true;        // This has become an isolated mark
875     }
876 
877     if( bRenamePoints ) RenameRoutePoints();
878 
879 //      if ( m_nPoints > 1 )
880     {
881         pSelect->AddAllSelectableRouteSegments( this );
882         pSelect->AddAllSelectableRoutePoints( this );
883 
884         pConfig->UpdateRoute( this );
885 
886         FinalizeForRendering();
887         UpdateSegmentDistances();
888     }
889 
890 }
891 
DeSelectRoute()892 void Route::DeSelectRoute()
893 {
894     wxRoutePointListNode *node = pRoutePointList->GetFirst();
895 
896     RoutePoint *rp;
897     while( node ) {
898         rp = node->GetData();
899         rp->m_bPtIsSelected = false;
900 
901         node = node->GetNext();
902     }
903 }
904 
ReloadRoutePointIcons()905 void Route::ReloadRoutePointIcons()
906 {
907     wxRoutePointListNode *node = pRoutePointList->GetFirst();
908 
909     RoutePoint *rp;
910     while( node ) {
911         rp = node->GetData();
912         rp->ReLoadIcon();
913 
914         node = node->GetNext();
915     }
916 }
917 
FinalizeForRendering()918 void Route::FinalizeForRendering()
919 {
920     RBBox.Invalidate();
921 }
922 
GetBBox(void)923 LLBBox &Route::GetBBox( void )
924 {
925     if(RBBox.GetValid())
926         return RBBox;
927 
928     double bbox_lonmin, bbox_lonmax, bbox_latmin, bbox_latmax;
929 
930     wxRoutePointListNode *node = pRoutePointList->GetFirst();
931     RoutePoint *data = node->GetData();
932 
933     if(data->m_wpBBox.GetValid()){
934         bbox_lonmax = data->m_wpBBox.GetMaxLon();
935         bbox_lonmin = data->m_wpBBox.GetMinLon();
936         bbox_latmax = data->m_wpBBox.GetMaxLat();
937         bbox_latmin = data->m_wpBBox.GetMinLat();
938     }
939     else{
940         bbox_lonmax = bbox_lonmin = data->m_lon;
941         bbox_latmax = bbox_latmin = data->m_lat;
942     }
943 
944     double lastlon = data->m_lon, wrap = 0;
945 
946     node = node->GetNext();
947     while( node ) {
948         data = node->GetData();
949 
950         if(lastlon - data->m_lon > 180)
951             wrap += 360;
952         else if(data->m_lon - lastlon > 180)
953             wrap -= 360;
954 
955         double lon = data->m_lon + wrap;
956 
957         if( lon > bbox_lonmax )
958             bbox_lonmax = lon;
959         if( lon < bbox_lonmin )
960             bbox_lonmin = lon;
961 
962         if( data->m_lat > bbox_latmax )
963             bbox_latmax = data->m_lat;
964         if( data->m_lat < bbox_latmin )
965             bbox_latmin = data->m_lat;
966 
967         lastlon = data->m_lon;
968         node = node->GetNext();
969     }
970 
971     if(bbox_lonmin < -360)
972         bbox_lonmin += 360, bbox_lonmax += 360;
973     else if(bbox_lonmax > 360)
974         bbox_lonmin -= 360, bbox_lonmax -= 360;
975 
976     if(bbox_lonmax - bbox_lonmin > 360)
977        bbox_lonmin = -180, bbox_lonmax = 180;
978 
979     RBBox.Set(bbox_latmin, bbox_lonmin, bbox_latmax, bbox_lonmax);
980 
981     return RBBox;
982 }
983 
CalculateDCRect(wxDC & dc_route,ChartCanvas * canvas,wxRect * prect)984 void Route::CalculateDCRect( wxDC& dc_route, ChartCanvas *canvas, wxRect *prect )
985 {
986     dc_route.ResetBoundingBox();
987     dc_route.DestroyClippingRegion();
988 
989     wxRect update_rect;
990 
991     // Draw the route in skeleton form on the dc
992     // That is, draw only the route points, assuming that the segements will
993     // always be fully contained within the resulting rectangle.
994     // Can we prove this?
995     if( m_bVisible ) {
996         wxRoutePointListNode *node = pRoutePointList->GetFirst();
997         while( node ) {
998 
999             RoutePoint *prp2 = node->GetData();
1000             bool blink_save = prp2->m_bBlink;
1001             prp2->m_bBlink = false;
1002             ocpnDC odc_route( dc_route );
1003             prp2->Draw( odc_route, canvas, NULL );
1004             prp2->m_bBlink = blink_save;
1005 
1006             wxRect r =  prp2->CurrentRect_in_DC ;
1007             r.Inflate(m_hiliteWidth, m_hiliteWidth);        // allow for large hilite circles at segment ends
1008 
1009             update_rect.Union( r );
1010             node = node->GetNext();
1011         }
1012     }
1013 
1014     *prect = update_rect;
1015 }
1016 
1017 /*
1018  Update a single route segment lengths
1019  Also, compute total route length by summing segment distances.
1020  */
UpdateSegmentDistance(RoutePoint * prp0,RoutePoint * prp,double planspeed)1021 void Route::UpdateSegmentDistance( RoutePoint *prp0, RoutePoint *prp, double planspeed )
1022 {
1023     double slat1 = prp0->m_lat, slon1 = prp0->m_lon;
1024     double slat2 = prp->m_lat, slon2 = prp->m_lon;
1025 
1026 //    Calculate the absolute distance from 1->2
1027 
1028     double dd;
1029     double br;
1030     // why are we using mercator rather than great circle here?? [sean 8-11-2015]
1031     DistanceBearingMercator( slat2, slon2, slat1, slon1, &br, &dd );
1032 
1033     prp->SetCourse(br);
1034     prp->SetDistance(dd);
1035 
1036 //    And store in Point 2
1037     prp->m_seg_len = dd;
1038 
1039     m_route_length += dd;
1040 
1041 //    If Point1 Description contains VMG, store it for Properties Dialog in Point2
1042 //    If Point1 Description contains ETD, store it in Point1
1043 
1044     if( planspeed > 0. ) {
1045         wxDateTime etd;
1046 
1047         double legspeed = planspeed;
1048         if( prp->GetPlannedSpeed() > 0.1 && prp->GetPlannedSpeed() < 1000. )
1049             legspeed = prp->GetPlannedSpeed();
1050         if( legspeed > 0.1 && legspeed < 1000. ) {
1051             m_route_time += 3600. * dd / legspeed;
1052             prp->m_seg_vmg = legspeed;
1053         }
1054         wxLongLong duration = wxLongLong(3600.0 * prp->m_seg_len / prp->m_seg_vmg);
1055         prp->SetETE(duration);
1056         wxTimeSpan ts( 0, 0, duration );
1057         if( !prp0->GetManualETD().IsValid() ) {
1058             prp0->m_manual_etd = false;
1059             if( prp0->GetETA().IsValid() ) {
1060                 prp0->m_seg_etd = prp0->GetETA();
1061             } else {
1062                 prp0->m_seg_etd = m_PlannedDeparture + wxTimeSpan(0, 0, m_route_time - duration);
1063             }
1064         }
1065         prp->m_seg_eta = prp0->GetETD() + ts;
1066         if( !prp->m_manual_etd || !prp->GetETD().IsValid() ) {
1067             prp->m_seg_etd = prp->m_seg_eta;
1068             prp->m_manual_etd = false;
1069         }
1070     }
1071 }
1072 
1073 /*
1074  Update the route segment lengths, storing each segment length in <destination> point.
1075  Also, compute total route length by summing segment distances.
1076  */
UpdateSegmentDistances(double planspeed)1077 void Route::UpdateSegmentDistances( double planspeed )
1078 {
1079     wxPoint rpt, rptn;
1080 
1081     m_route_length = 0.0;
1082     m_route_time = 0.0;
1083 
1084     wxRoutePointListNode *node = pRoutePointList->GetFirst();
1085 
1086     if( node ) {
1087         RoutePoint *prp0 = node->GetData();
1088         if( !prp0->m_manual_etd ) {
1089             prp0->m_seg_etd = m_PlannedDeparture;
1090         }
1091         node = node->GetNext();
1092 
1093         while( node ) {
1094             RoutePoint *prp = node->GetData();
1095             UpdateSegmentDistance( prp0, prp, planspeed );
1096 
1097             prp0 = prp;
1098 
1099             node = node->GetNext();
1100         }
1101     }
1102 }
1103 
Reverse(bool bRenamePoints)1104 void Route::Reverse( bool bRenamePoints )
1105 {
1106     //    Reverse the GUID list
1107     wxArrayString      RoutePointGUIDList;
1108 
1109     int ncount = pRoutePointList->GetCount();
1110     for( int i = 0; i < ncount; i++ )
1111         RoutePointGUIDList.Add( GetPoint(ncount - i)->m_GUID );
1112 
1113     pRoutePointList->DeleteContents( false );
1114     pRoutePointList->Clear();
1115     m_route_length = 0.0;
1116 
1117     //    iterate over the RoutePointGUIDs
1118     for( unsigned int ip = 0; ip < RoutePointGUIDList.GetCount(); ip++ ) {
1119         wxString GUID = RoutePointGUIDList[ip];
1120 
1121         //    And on the RoutePoints themselves
1122         wxRoutePointListNode *prpnode = pWayPointMan->GetWaypointList()->GetFirst();
1123         while( prpnode ) {
1124             RoutePoint *prp = prpnode->GetData();
1125 
1126             if( prp->m_GUID == GUID ) {
1127                 AddPoint( prp );
1128                 break;
1129             }
1130             prpnode = prpnode->GetNext(); //RoutePoint
1131         }
1132     }
1133 
1134     if( bRenamePoints ) RenameRoutePoints();
1135 
1136     // Switch start/end strings. anders, 2010-01-29
1137     wxString tmp = m_RouteStartString;
1138     m_RouteStartString = m_RouteEndString;
1139     m_RouteEndString = tmp;
1140 }
1141 
SetVisible(bool visible,bool includeWpts)1142 void Route::SetVisible( bool visible, bool includeWpts )
1143 {
1144     m_bVisible = visible;
1145 
1146     if ( !includeWpts )
1147         return;
1148 
1149     wxRoutePointListNode *node = pRoutePointList->GetFirst();
1150     RoutePoint *rp;
1151     while( node ) {
1152         rp = node->GetData();
1153         if ( rp->m_bKeepXRoute )
1154         {
1155             rp->SetVisible( visible );
1156             //pConfig->UpdateWayPoint( rp );
1157         }
1158         node = node->GetNext();
1159     }
1160 }
1161 
SetListed(bool visible)1162 void Route::SetListed( bool visible )
1163 {
1164     m_bListed = visible;
1165 }
1166 
AssembleRoute(void)1167 void Route::AssembleRoute( void )
1168 {
1169 }
1170 
RenameRoutePoints(void)1171 void Route::RenameRoutePoints( void )
1172 {
1173     //    iterate on the route points.
1174     //    If dynamically named, rename according to current list position
1175 
1176     wxRoutePointListNode *node = pRoutePointList->GetFirst();
1177 
1178     int i = 1;
1179     while( node ) {
1180         RoutePoint *prp = node->GetData();
1181         if( prp->m_bDynamicName ) {
1182             wxString name;
1183             name.Printf( _T ( "%03d" ), i );
1184             prp->SetName( name );
1185         }
1186 
1187         node = node->GetNext();
1188         i++;
1189     }
1190 }
1191 
SendToGPS(const wxString & com_name,bool bsend_waypoints,wxGauge * pProgress)1192 int Route::SendToGPS(const wxString & com_name, bool bsend_waypoints, wxGauge *pProgress )
1193 {
1194     int result = 0;
1195 
1196     if( g_pMUX ) {
1197         ::wxBeginBusyCursor();
1198          result = g_pMUX->SendRouteToGPS( this, com_name, bsend_waypoints, pProgress );
1199         ::wxEndBusyCursor();
1200     }
1201 
1202     wxString msg;
1203     if( 0 == result )
1204         msg = _("Route Transmitted.");
1205     else{
1206         if( result == ERR_GARMIN_INITIALIZE )
1207             msg = _("Error on Route Upload.  Garmin GPS not connected");
1208         else
1209             msg = _("Error on Route Upload.  Please check logfiles...");
1210 
1211         OCPNMessageBox( NULL, msg, _("OpenCPN Info"), wxOK | wxICON_INFORMATION );
1212     }
1213 
1214     return (result == 0);
1215 }
1216 
1217 //    Is this route equal to another, meaning,
1218 //    Do all routepoint positions and names match?
IsEqualTo(Route * ptargetroute)1219 bool Route::IsEqualTo( Route *ptargetroute )
1220 {
1221     wxRoutePointListNode *pthisnode = ( this->pRoutePointList )->GetFirst();
1222     wxRoutePointListNode *pthatnode = ( ptargetroute->pRoutePointList )->GetFirst();
1223 
1224     if( NULL == pthisnode ) return false;
1225 
1226     if( this->m_bIsInLayer || ptargetroute->m_bIsInLayer ) return false;
1227 
1228     if( this->GetnPoints() != ptargetroute->GetnPoints() ) return false;
1229 
1230     while( pthisnode ) {
1231         if( NULL == pthatnode ) return false;
1232 
1233         RoutePoint *pthisrp = pthisnode->GetData();
1234         RoutePoint *pthatrp = pthatnode->GetData();
1235 
1236         if( ( fabs( pthisrp->m_lat - pthatrp->m_lat ) > 1.0e-6 )
1237                 || ( fabs( pthisrp->m_lon - pthatrp->m_lon ) > 1.0e-6 ) ) return false;
1238 
1239         if( !pthisrp->GetName().IsSameAs( pthatrp->GetName() ) ) return false;
1240 
1241         pthisnode = pthisnode->GetNext();
1242         pthatnode = pthatnode->GetNext();
1243     }
1244 
1245     return true;                              // success, they are the same
1246 }
1247 
1248