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