1 /***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Route Manager
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2010 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26 #include "wx/wxprec.h"
27
28 #ifndef WX_PRECOMP
29 #include "wx/wx.h"
30 #endif //precompiled headers
31
32 #include "wx/image.h"
33 #include "wx/tokenzr.h"
34 #include <wx/progdlg.h>
35
36 #include "dychart.h"
37
38 #include <stdlib.h>
39 #include <math.h>
40 #include <time.h>
41
42 #include <wx/listimpl.cpp>
43
44 #include "styles.h"
45 #include "routeman.h"
46 #include "concanv.h"
47 #include "navutil.h"
48 #include "georef.h"
49 #include "RoutePropDlgImpl.h"
50 #include "TrackPropDlg.h"
51 #include "routemanagerdialog.h"
52 #include "pluginmanager.h"
53 #include "multiplexer.h"
54 #include "MarkIcon.h"
55 #include "cutil.h"
56 #include "AIS_Decoder.h"
57 #include "wx28compat.h"
58 #include "Route.h"
59
60 #include <wx/dir.h>
61 #include <wx/filename.h>
62 #include <wx/stdpaths.h>
63 #include <wx/apptrait.h>
64 #include "OCPNPlatform.h"
65 #include "Track.h"
66 #include "chcanv.h"
67
68 #ifdef ocpnUSE_SVG
69 #include "wxSVG/svg.h"
70 #endif // ocpnUSE_SVG
71
72 #ifdef __OCPN__ANDROID__
73 #include "androidUTIL.h"
74 #endif
75
76 void appendOSDirSlash(wxString* pString);
77
78 extern MyFrame *gFrame;
79 extern OCPNPlatform *g_Platform;
80 extern ConsoleCanvas *console;
81
82 extern RouteList *pRouteList;
83 extern TrackList *pTrackList;
84 extern Select *pSelect;
85 extern MyConfig *pConfig;
86 extern Routeman *g_pRouteMan;
87
88 extern wxRect g_blink_rect;
89
90 extern double gLat, gLon, gSog, gCog;
91 extern double gVar;
92 extern wxString gRmcDate, gRmcTime;
93 extern bool g_bMagneticAPB;
94
95 extern RoutePoint *pAnchorWatchPoint1;
96 extern RoutePoint *pAnchorWatchPoint2;
97
98 extern TrackPropDlg *pTrackPropDialog;
99 extern ActiveTrack *g_pActiveTrack;
100 extern int g_track_line_width;
101
102 extern RoutePropDlgImpl *pRoutePropDialog;
103 extern RouteManagerDialog *pRouteManagerDialog;
104 extern RoutePoint *pAnchorWatchPoint1;
105 extern RoutePoint *pAnchorWatchPoint2;
106 extern int g_route_line_width;
107 extern Multiplexer *g_pMUX;
108 extern AIS_Decoder *g_pAIS;
109
110 extern PlugInManager *g_pi_manager;
111 extern ocpnStyle::StyleManager* g_StyleManager;
112 extern bool g_bAdvanceRouteWaypointOnArrivalOnly;
113 extern Route *pAISMOBRoute;
114 extern bool g_btouch;
115 extern float g_ChartScaleFactorExp;
116
117 bool g_bPluginHandleAutopilotRoute;
118
119 // List definitions for Waypoint Manager Icons
120 WX_DECLARE_LIST(wxBitmap, markicon_bitmap_list_type);
121 WX_DECLARE_LIST(wxString, markicon_key_list_type);
122 WX_DECLARE_LIST(wxString, markicon_description_list_type);
123
124 // List implementation for Waypoint Manager Icons
125 #include <wx/listimpl.cpp>
126 WX_DEFINE_LIST(markicon_bitmap_list_type);
127 WX_DEFINE_LIST(markicon_key_list_type);
128 WX_DEFINE_LIST(markicon_description_list_type);
129
130 //Helper conditional file name dir slash
131 void appendOSDirSlash(wxString* pString);
132
LoadSVGIcon(wxString filename,int width,int height)133 wxImage LoadSVGIcon( wxString filename, int width, int height )
134 {
135 #ifdef ocpnUSE_SVG
136
137 #ifndef USE_ANDROID_GLES2
138 wxSVGDocument svgDoc;
139 if( svgDoc.Load(filename) )
140 return svgDoc.Render( width, height, NULL, true, true ) ;
141 else{
142 wxLogMessage(filename);
143 return wxImage(32, 32);
144 }
145 #else
146 wxBitmap bmp = loadAndroidSVG( filename, width, height );
147 if(bmp.IsOk())
148 return bmp.ConvertToImage();
149 else
150 return wxImage(32, 32);
151
152 #endif
153
154 #else
155 return wxImage(32, 32);
156 #endif // ocpnUSE_SVG
157 }
158
159
CompareMarkIcons(MarkIcon * mi1,MarkIcon * mi2)160 static int CompareMarkIcons( MarkIcon *mi1, MarkIcon *mi2 )
161 {
162 return (mi1->icon_name.CmpNoCase( mi2->icon_name));
163 }
164
165 //--------------------------------------------------------------------------------
166 // Routeman "Route Manager"
167 //--------------------------------------------------------------------------------
168
Routeman(MyApp * parent)169 Routeman::Routeman( MyApp *parent )
170 {
171 m_pparent_app = parent;
172 pActiveRoute = NULL;
173 pActivePoint = NULL;
174 pRouteActivatePoint = NULL;
175 }
176
~Routeman()177 Routeman::~Routeman()
178 {
179 if( pRouteActivatePoint ) delete pRouteActivatePoint;
180 }
181
IsRouteValid(Route * pRoute)182 bool Routeman::IsRouteValid( Route *pRoute )
183 {
184 wxRouteListNode *node = pRouteList->GetFirst();
185 while( node ) {
186 if( pRoute == node->GetData() ) return true;
187 node = node->GetNext();
188 }
189 return false;
190 }
191
192 // Make a 2-D search to find the route containing a given waypoint
FindRouteContainingWaypoint(RoutePoint * pWP)193 Route *Routeman::FindRouteContainingWaypoint( RoutePoint *pWP )
194 {
195 wxRouteListNode *node = pRouteList->GetFirst();
196 while( node ) {
197 Route *proute = node->GetData();
198
199 wxRoutePointListNode *pnode = ( proute->pRoutePointList )->GetFirst();
200 while( pnode ) {
201 RoutePoint *prp = pnode->GetData();
202 if( prp == pWP ) return proute;
203 pnode = pnode->GetNext();
204 }
205
206 node = node->GetNext();
207 }
208
209 return NULL; // not found
210 }
211
GetRouteArrayContaining(RoutePoint * pWP)212 wxArrayPtrVoid *Routeman::GetRouteArrayContaining( RoutePoint *pWP )
213 {
214 wxArrayPtrVoid *pArray = new wxArrayPtrVoid;
215
216 wxRouteListNode *route_node = pRouteList->GetFirst();
217 while( route_node ) {
218 Route *proute = route_node->GetData();
219
220 wxRoutePointListNode *waypoint_node = ( proute->pRoutePointList )->GetFirst();
221 while( waypoint_node ) {
222 RoutePoint *prp = waypoint_node->GetData();
223 if( prp == pWP ){ // success
224 pArray->Add( (void *) proute );
225 break; // only add a route to the array once, even if there are duplicate points
226 // in the route...See FS#1743
227 }
228
229 waypoint_node = waypoint_node->GetNext(); // next waypoint
230 }
231
232 route_node = route_node->GetNext(); // next route
233 }
234
235 if( pArray->GetCount() ) return pArray;
236
237 else {
238 delete pArray;
239 return NULL;
240 }
241 }
242
RemovePointFromRoute(RoutePoint * point,Route * route,ChartCanvas * cc)243 void Routeman::RemovePointFromRoute( RoutePoint* point, Route* route, ChartCanvas *cc )
244 {
245 // Rebuild the route selectables
246 pSelect->DeleteAllSelectableRoutePoints( route );
247 pSelect->DeleteAllSelectableRouteSegments( route );
248
249 route->RemovePoint( point );
250
251 // Check for 1 point routes. If we are creating a route, this is an undo, so keep the 1 point.
252 if( cc && (route->GetnPoints() <= 1) && (cc->m_routeState == 0) ) {
253 pConfig->DeleteConfigRoute( route );
254 g_pRouteMan->DeleteRoute( route );
255 route = NULL;
256 }
257 // Add this point back into the selectables
258 pSelect->AddSelectableRoutePoint( point->m_lat, point->m_lon, point );
259
260 if( pRoutePropDialog && ( pRoutePropDialog->IsShown() ) ) {
261 pRoutePropDialog->SetRouteAndUpdate( route, true );
262 }
263
264 gFrame->InvalidateAllGL();
265 }
266
FindBestActivatePoint(Route * pR,double lat,double lon,double cog,double sog)267 RoutePoint *Routeman::FindBestActivatePoint( Route *pR, double lat, double lon, double cog,
268 double sog )
269 {
270 if( !pR ) return NULL;
271
272 // Walk thru all the points to find the "best"
273 RoutePoint *best_point = NULL;
274 double min_time_found = 1e6;
275
276 wxRoutePointListNode *node = ( pR->pRoutePointList )->GetFirst();
277 while( node ) {
278 RoutePoint *pn = node->GetData();
279
280 double brg, dist;
281 DistanceBearingMercator( pn->m_lat, pn->m_lon, lat, lon, &brg, &dist );
282
283 double angle = brg - cog;
284 double soa = cos( angle * PI / 180. );
285
286 double time_to_wp = dist / soa;
287
288 if( time_to_wp > 0 ) {
289 if( time_to_wp < min_time_found ) {
290 min_time_found = time_to_wp;
291 best_point = pn;
292 }
293 }
294 node = node->GetNext();
295 }
296 return best_point;
297 }
298
ActivateRoute(Route * pRouteToActivate,RoutePoint * pStartPoint)299 bool Routeman::ActivateRoute( Route *pRouteToActivate, RoutePoint *pStartPoint )
300 {
301 wxJSONValue v;
302 v[_T("Route_activated")] = pRouteToActivate->m_RouteNameString;
303 v[_T("GUID")] = pRouteToActivate->m_GUID;
304 wxString msg_id( _T("OCPN_RTE_ACTIVATED") );
305 g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
306 if(g_bPluginHandleAutopilotRoute)
307 return true;
308
309 pActiveRoute = pRouteToActivate;
310
311 if( pStartPoint ) {
312 pActivePoint = pStartPoint;
313 } else {
314 wxRoutePointListNode *node = ( pActiveRoute->pRoutePointList )->GetFirst();
315 pActivePoint = node->GetData(); // start at beginning
316 }
317
318 ActivateRoutePoint( pRouteToActivate, pActivePoint );
319
320 m_bArrival = false;
321 m_arrival_min = 1e6;
322 m_arrival_test = 0;
323
324 pRouteToActivate->m_bRtIsActive = true;
325
326 m_bDataValid = false;
327
328 console->ShowWithFreshFonts();
329
330 return true;
331 }
332
ActivateRoutePoint(Route * pA,RoutePoint * pRP_target)333 bool Routeman::ActivateRoutePoint( Route *pA, RoutePoint *pRP_target )
334 {
335 wxJSONValue v;
336 v[_T("GUID")] = pRP_target->m_GUID;
337 v[_T("WP_activated")] = pRP_target->GetName();
338
339 wxString msg_id( _T("OCPN_WPT_ACTIVATED") );
340 g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
341
342 if(g_bPluginHandleAutopilotRoute)
343 return true;
344
345 pActiveRoute = pA;
346
347 pActivePoint = pRP_target;
348 pActiveRoute->m_pRouteActivePoint = pRP_target;
349
350 wxRoutePointListNode *node = ( pActiveRoute->pRoutePointList )->GetFirst();
351 while( node ) {
352 RoutePoint *pn = node->GetData();
353 pn->m_bBlink = false; // turn off all blinking points
354 pn->m_bIsActive = false;
355
356 node = node->GetNext();
357 }
358
359 node = ( pActiveRoute->pRoutePointList )->GetFirst();
360 RoutePoint *prp_first = node->GetData();
361
362 // If activating first point in route, create a "virtual" waypoint at present position
363 if( pRP_target == prp_first ) {
364 if( pRouteActivatePoint ) delete pRouteActivatePoint;
365
366 pRouteActivatePoint = new RoutePoint( gLat, gLon, wxString( _T("") ), wxString( _T("") ),
367 wxEmptyString, false ); // Current location
368 pRouteActivatePoint->m_bShowName = false;
369
370 pActiveRouteSegmentBeginPoint = pRouteActivatePoint;
371 }
372
373 else {
374 prp_first->m_bBlink = false;
375 node = node->GetNext();
376 RoutePoint *np_prev = prp_first;
377 while( node ) {
378 RoutePoint *pnext = node->GetData();
379 if( pnext == pRP_target ) {
380 pActiveRouteSegmentBeginPoint = np_prev;
381 break;
382 }
383
384 np_prev = pnext;
385 node = node->GetNext();
386 }
387 }
388
389 pRP_target->m_bBlink = true; // blink the active point
390 pRP_target->m_bIsActive = true; // and active
391
392 g_blink_rect = pRP_target->CurrentRect_in_DC; // set up global blinker
393
394 m_bArrival = false;
395 m_arrival_min = 1e6;
396 m_arrival_test = 0;
397
398
399 // Update the RouteProperties Dialog, if currently shown
400 if( pRoutePropDialog && pRoutePropDialog->IsShown() ) {
401 if( pRoutePropDialog->GetRoute() == pA ) {
402 pRoutePropDialog->SetEnroutePoint(pActivePoint);
403
404 }
405 }
406 return true;
407 }
408
ActivateNextPoint(Route * pr,bool skipped)409 bool Routeman::ActivateNextPoint( Route *pr, bool skipped )
410 {
411 wxJSONValue v;
412 if( pActivePoint ) {
413 pActivePoint->m_bBlink = false;
414 pActivePoint->m_bIsActive = false;
415
416 v[_T("isSkipped")] = skipped;
417 v[_T("GUID")] = pActivePoint->m_GUID;
418 v[_T("WP_arrived")] = pActivePoint->GetName();
419 }
420 int n_index_active = pActiveRoute->GetIndexOf( pActivePoint );
421 if( ( n_index_active + 1 ) <= pActiveRoute->GetnPoints() ) {
422 pActiveRouteSegmentBeginPoint = pActivePoint;
423
424 pActiveRoute->m_pRouteActivePoint = pActiveRoute->GetPoint( n_index_active + 1 );
425
426 pActivePoint = pActiveRoute->GetPoint( n_index_active + 1 );
427 v[_T("Next_WP")] = pActivePoint->GetName();
428 v[_T("GUID")] = pActivePoint->m_GUID;
429
430 pActivePoint->m_bBlink = true;
431 pActivePoint->m_bIsActive = true;
432 g_blink_rect = pActivePoint->CurrentRect_in_DC; // set up global blinker
433
434 m_bArrival = false;
435 m_arrival_min = 1e6;
436 m_arrival_test = 0;
437
438 // Update the RouteProperties Dialog, if currently shown
439 if( pRoutePropDialog && pRoutePropDialog->IsShown() ) {
440 if( pRoutePropDialog->GetRoute() == pr ) {
441 pRoutePropDialog->SetEnroutePoint(pActivePoint);
442 }
443 }
444
445 wxString msg_id( _T("OCPN_WPT_ARRIVED") );
446 g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
447
448 return true;
449 }
450
451 return false;
452 }
453
UpdateProgress()454 bool Routeman::UpdateProgress()
455 {
456 bool bret_val = false;
457
458 if( pActiveRoute ) {
459 // Update bearing, range, and crosstrack error
460
461 // Bearing is calculated as Mercator Sailing, i.e. a cartographic "bearing"
462 double north, east;
463 toSM( pActivePoint->m_lat, pActivePoint->m_lon, gLat, gLon, &east, &north );
464 double a = atan( north / east );
465 if( fabs( pActivePoint->m_lon - gLon ) < 180. ) {
466 if( pActivePoint->m_lon > gLon ) CurrentBrgToActivePoint = 90. - ( a * 180 / PI );
467 else
468 CurrentBrgToActivePoint = 270. - ( a * 180 / PI );
469 } else {
470 if( pActivePoint->m_lon > gLon ) CurrentBrgToActivePoint = 270. - ( a * 180 / PI );
471 else
472 CurrentBrgToActivePoint = 90. - ( a * 180 / PI );
473 }
474
475 // Calculate range using Great Circle Formula
476
477 double d5 = DistGreatCircle( gLat, gLon, pActivePoint->m_lat, pActivePoint->m_lon );
478 CurrentRngToActivePoint = d5;
479
480 // Get the XTE vector, normal to current segment
481 vector2D va, vb, vn;
482
483 double brg1, dist1, brg2, dist2;
484 DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon,
485 pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &brg1,
486 &dist1 );
487 vb.x = dist1 * sin( brg1 * PI / 180. );
488 vb.y = dist1 * cos( brg1 * PI / 180. );
489
490 DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon, gLat, gLon, &brg2,
491 &dist2 );
492 va.x = dist2 * sin( brg2 * PI / 180. );
493 va.y = dist2 * cos( brg2 * PI / 180. );
494
495 double sdelta = vGetLengthOfNormal( &va, &vb, &vn ); // NM
496 CurrentXTEToActivePoint = sdelta;
497
498 // Calculate the distance to the arrival line, which is perpendicular to the current route segment
499 // Taking advantage of the calculated normal from current position to route segment vn
500 vector2D vToArriveNormal;
501 vSubtractVectors( &va, &vn, &vToArriveNormal );
502
503 CurrentRangeToActiveNormalCrossing = vVectorMagnitude( &vToArriveNormal );
504
505 // Compute current segment course
506 // Using simple Mercater projection
507 double x1, y1, x2, y2;
508 toSM( pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon,
509 pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon, &x1,
510 &y1 );
511
512 toSM( pActivePoint->m_lat, pActivePoint->m_lon, pActiveRouteSegmentBeginPoint->m_lat,
513 pActiveRouteSegmentBeginPoint->m_lon, &x2, &y2 );
514
515 double e1 = atan2( ( x2 - x1 ), ( y2 - y1 ) );
516 CurrentSegmentCourse = e1 * 180 / PI;
517 if( CurrentSegmentCourse < 0 ) CurrentSegmentCourse += 360;
518
519 // Compute XTE direction
520 double h = atan( vn.y / vn.x );
521 if( vn.x > 0 ) CourseToRouteSegment = 90. - ( h * 180 / PI );
522 else
523 CourseToRouteSegment = 270. - ( h * 180 / PI );
524
525 h = CurrentBrgToActivePoint - CourseToRouteSegment;
526 if( h < 0 ) h = h + 360;
527
528 if( h > 180 ) XTEDir = 1;
529 else
530 XTEDir = -1;
531
532 // Determine Arrival
533
534 bool bDidArrival = false;
535
536 // Special signal: if ArrivalRadius < 0, NEVER arrive...
537 // Used for MOB auto-created routes.
538 if( pActivePoint->GetWaypointArrivalRadius() > 0){
539 if( CurrentRangeToActiveNormalCrossing <= pActivePoint->GetWaypointArrivalRadius() ) {
540 m_bArrival = true;
541 UpdateAutopilot();
542
543 bDidArrival = true;
544 DoAdvance();
545
546 }
547 else {
548 // Test to see if we are moving away from the arrival point, and
549 // have been moving away for 2 seconds.
550 // If so, we should declare "Arrival"
551 if( (CurrentRangeToActiveNormalCrossing - m_arrival_min) > pActivePoint->GetWaypointArrivalRadius() ){
552 if(++m_arrival_test > 2 && !g_bAdvanceRouteWaypointOnArrivalOnly) {
553 m_bArrival = true;
554 UpdateAutopilot();
555
556 bDidArrival = true;
557 DoAdvance();
558 }
559 }
560 else
561 m_arrival_test = 0;
562
563 }
564 }
565
566 if( !bDidArrival )
567 m_arrival_min = wxMin( m_arrival_min, CurrentRangeToActiveNormalCrossing );
568
569 if( !bDidArrival ) // Only once on arrival
570 UpdateAutopilot();
571
572 bret_val = true; // a route is active
573 }
574
575 m_bDataValid = true;
576
577 return bret_val;
578 }
579
DoAdvance(void)580 void Routeman::DoAdvance(void)
581 {
582 if( !ActivateNextPoint( pActiveRoute, false ) ) // at the end?
583 {
584 Route *pthis_route = pActiveRoute;
585 DeactivateRoute( true ); // this is an arrival
586
587 if( pthis_route->m_bDeleteOnArrival && !pthis_route->m_bIsBeingEdited) {
588 pConfig->DeleteConfigRoute( pthis_route );
589 DeleteRoute( pthis_route );
590 }
591
592 if( pRouteManagerDialog )
593 pRouteManagerDialog->UpdateRouteListCtrl();
594
595 }
596 }
597
598
599
DeactivateRoute(bool b_arrival)600 bool Routeman::DeactivateRoute( bool b_arrival )
601 {
602 if( pActivePoint ) {
603 pActivePoint->m_bBlink = false;
604 pActivePoint->m_bIsActive = false;
605 }
606
607 if( pActiveRoute ) {
608 pActiveRoute->m_bRtIsActive = false;
609 pActiveRoute->m_pRouteActivePoint = NULL;
610
611 wxJSONValue v;
612 if( !b_arrival ) {
613 v[_T("Route_deactivated")] = pActiveRoute->m_RouteNameString;
614 v[_T("GUID")] = pActiveRoute->m_GUID;
615 wxString msg_id( _T("OCPN_RTE_DEACTIVATED") );
616 g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
617 } else {
618 v[_T("GUID")] = pActiveRoute->m_GUID;
619 v[_T("Route_ended")] = pActiveRoute->m_RouteNameString;
620 wxString msg_id( _T("OCPN_RTE_ENDED") );
621 g_pi_manager->SendJSONMessageToAllPlugins( msg_id, v );
622 }
623 }
624
625 pActiveRoute = NULL;
626
627 if( pRouteActivatePoint ) delete pRouteActivatePoint;
628 pRouteActivatePoint = NULL;
629
630 console->pCDI->ClearBackground();
631
632 console->Show( false );
633
634 m_bDataValid = false;
635
636 return true;
637 }
638
UpdateAutopilot()639 bool Routeman::UpdateAutopilot()
640 {
641 //Send all known Autopilot messages upstream
642
643 //Avoid a possible not initiated SOG/COG. APs can be confused if in NAV mode wo valid GPS
644 double r_Sog(0.0), r_Cog(0.0);
645 if (!std::isnan(gSog)) r_Sog = gSog;
646 if (!std::isnan(gCog)) r_Cog = gCog;
647
648 // Send active leg info directly to plugins
649
650 ActiveLegDat leg_info;
651 leg_info.Btw = CurrentBrgToActivePoint;
652 leg_info.Dtw = CurrentRngToActivePoint;
653 leg_info.Xte = CurrentXTEToActivePoint;
654 if (XTEDir < 0) {
655 leg_info.Xte = -leg_info.Xte; // Left side of the track -> negative XTE
656 }
657 leg_info.wp_name = pActivePoint->GetName().Truncate(6);
658 leg_info.arrival = m_bArrival;
659 g_pi_manager->SendActiveLegInfoToAllPlugIns(&leg_info);
660
661 //RMB
662 {
663
664 m_NMEA0183.TalkerID = _T("EC");
665
666 SENTENCE snt;
667 m_NMEA0183.Rmb.IsDataValid = NTrue;
668 m_NMEA0183.Rmb.CrossTrackError = CurrentXTEToActivePoint;
669
670 if( XTEDir < 0 ) m_NMEA0183.Rmb.DirectionToSteer = Left;
671 else
672 m_NMEA0183.Rmb.DirectionToSteer = Right;
673
674 m_NMEA0183.Rmb.To = pActivePoint->GetName().Truncate( 6 );
675 m_NMEA0183.Rmb.From = pActiveRouteSegmentBeginPoint->GetName().Truncate( 6 );
676
677 if( pActivePoint->m_lat < 0. ) m_NMEA0183.Rmb.DestinationPosition.Latitude.Set(
678 -pActivePoint->m_lat, _T("S") );
679 else
680 m_NMEA0183.Rmb.DestinationPosition.Latitude.Set( pActivePoint->m_lat, _T("N") );
681
682 if( pActivePoint->m_lon < 0. ) m_NMEA0183.Rmb.DestinationPosition.Longitude.Set(
683 -pActivePoint->m_lon, _T("W") );
684 else
685 m_NMEA0183.Rmb.DestinationPosition.Longitude.Set( pActivePoint->m_lon, _T("E") );
686
687 m_NMEA0183.Rmb.RangeToDestinationNauticalMiles = CurrentRngToActivePoint;
688 m_NMEA0183.Rmb.BearingToDestinationDegreesTrue = CurrentBrgToActivePoint;
689 m_NMEA0183.Rmb.DestinationClosingVelocityKnots = r_Sog * cos( (r_Cog - CurrentBrgToActivePoint) * PI / 180.0 );
690
691 if( m_bArrival ) m_NMEA0183.Rmb.IsArrivalCircleEntered = NTrue;
692 else
693 m_NMEA0183.Rmb.IsArrivalCircleEntered = NFalse;
694
695 m_NMEA0183.Rmb.FAAModeIndicator = "A";
696 m_NMEA0183.Rmb.Write( snt );
697
698 g_pMUX->SendNMEAMessage( snt.Sentence );
699 }
700
701 // RMC
702 {
703
704 m_NMEA0183.TalkerID = _T("EC");
705
706 SENTENCE snt;
707 m_NMEA0183.Rmc.IsDataValid = NTrue;
708
709 if( gLat < 0. ) m_NMEA0183.Rmc.Position.Latitude.Set( -gLat, _T("S") );
710 else
711 m_NMEA0183.Rmc.Position.Latitude.Set( gLat, _T("N") );
712
713 if( gLon < 0. ) m_NMEA0183.Rmc.Position.Longitude.Set( -gLon, _T("W") );
714 else
715 m_NMEA0183.Rmc.Position.Longitude.Set( gLon, _T("E") );
716
717 m_NMEA0183.Rmc.SpeedOverGroundKnots = r_Sog;
718 m_NMEA0183.Rmc.TrackMadeGoodDegreesTrue = r_Cog;
719
720 if( !std::isnan(gVar) ) {
721 if( gVar < 0. ) {
722 m_NMEA0183.Rmc.MagneticVariation = -gVar;
723 m_NMEA0183.Rmc.MagneticVariationDirection = West;
724 } else {
725 m_NMEA0183.Rmc.MagneticVariation = gVar;
726 m_NMEA0183.Rmc.MagneticVariationDirection = East;
727 }
728 } else
729 m_NMEA0183.Rmc.MagneticVariation = 361.; // A signal to NMEA converter, gVAR is unknown
730
731 // Send GPS time to autopilot if available else send local system time
732 if ( !gRmcTime.IsEmpty() && !gRmcDate.IsEmpty() ) {
733 m_NMEA0183.Rmc.UTCTime = gRmcTime;
734 m_NMEA0183.Rmc.Date = gRmcDate;
735 }
736 else {
737 wxDateTime now = wxDateTime::Now();
738 wxDateTime utc = now.ToUTC();
739 wxString time = utc.Format( _T("%H%M%S") );
740 m_NMEA0183.Rmc.UTCTime = time;
741 wxString date = utc.Format( _T("%d%m%y") );
742 m_NMEA0183.Rmc.Date = date;
743 }
744
745 m_NMEA0183.Rmc.FAAModeIndicator = "A";
746 m_NMEA0183.Rmc.Write( snt );
747
748 g_pMUX->SendNMEAMessage( snt.Sentence );
749 }
750
751 // APB
752 {
753 m_NMEA0183.TalkerID = _T("EC");
754
755 SENTENCE snt;
756
757 m_NMEA0183.Apb.IsLoranBlinkOK = NTrue;
758 m_NMEA0183.Apb.IsLoranCCycleLockOK = NTrue;
759
760 m_NMEA0183.Apb.CrossTrackErrorMagnitude = CurrentXTEToActivePoint;
761
762 if( XTEDir < 0 ) m_NMEA0183.Apb.DirectionToSteer = Left;
763 else
764 m_NMEA0183.Apb.DirectionToSteer = Right;
765
766 m_NMEA0183.Apb.CrossTrackUnits = _T("N");
767
768 if( m_bArrival )
769 m_NMEA0183.Apb.IsArrivalCircleEntered = NTrue;
770 else
771 m_NMEA0183.Apb.IsArrivalCircleEntered = NFalse;
772
773 // We never pass the perpendicular, since we declare arrival before reaching this point
774 m_NMEA0183.Apb.IsPerpendicular = NFalse;
775
776 m_NMEA0183.Apb.To = pActivePoint->GetName().Truncate( 6 );
777
778 double brg1, dist1;
779 DistanceBearingMercator( pActivePoint->m_lat, pActivePoint->m_lon,
780 pActiveRouteSegmentBeginPoint->m_lat, pActiveRouteSegmentBeginPoint->m_lon,
781 &brg1,
782 &dist1 );
783
784 if( g_bMagneticAPB && !std::isnan(gVar) ) {
785
786 double brg1m = ((brg1 - gVar) >= 0.) ? (brg1 - gVar) : (brg1 - gVar + 360.);
787 double bapm = ((CurrentBrgToActivePoint - gVar) >= 0.) ? (CurrentBrgToActivePoint - gVar) : (CurrentBrgToActivePoint - gVar + 360.);
788
789 m_NMEA0183.Apb.BearingOriginToDestination = brg1m;
790 m_NMEA0183.Apb.BearingOriginToDestinationUnits = _T("M");
791
792 m_NMEA0183.Apb.BearingPresentPositionToDestination = bapm;
793 m_NMEA0183.Apb.BearingPresentPositionToDestinationUnits = _T("M");
794
795 m_NMEA0183.Apb.HeadingToSteer = bapm;
796 m_NMEA0183.Apb.HeadingToSteerUnits = _T("M");
797 }
798 else {
799 m_NMEA0183.Apb.BearingOriginToDestination = brg1;
800 m_NMEA0183.Apb.BearingOriginToDestinationUnits = _T("T");
801
802 m_NMEA0183.Apb.BearingPresentPositionToDestination = CurrentBrgToActivePoint;
803 m_NMEA0183.Apb.BearingPresentPositionToDestinationUnits = _T("T");
804
805
806 m_NMEA0183.Apb.HeadingToSteer = CurrentBrgToActivePoint;
807 m_NMEA0183.Apb.HeadingToSteerUnits = _T("T");
808 }
809
810 m_NMEA0183.Apb.Write( snt );
811 g_pMUX->SendNMEAMessage( snt.Sentence );
812 }
813
814 // XTE
815 {
816 m_NMEA0183.TalkerID = _T("EC");
817
818 SENTENCE snt;
819
820 m_NMEA0183.Xte.IsLoranBlinkOK = NTrue;
821 m_NMEA0183.Xte.IsLoranCCycleLockOK = NTrue;
822
823 m_NMEA0183.Xte.CrossTrackErrorDistance = CurrentXTEToActivePoint;
824
825 if( XTEDir < 0 ) m_NMEA0183.Xte.DirectionToSteer = Left;
826 else
827 m_NMEA0183.Xte.DirectionToSteer = Right;
828
829 m_NMEA0183.Xte.CrossTrackUnits = _T("N");
830
831 m_NMEA0183.Xte.Write( snt );
832 g_pMUX->SendNMEAMessage( snt.Sentence );
833 }
834
835
836 return true;
837 }
838
DoesRouteContainSharedPoints(Route * pRoute)839 bool Routeman::DoesRouteContainSharedPoints( Route *pRoute )
840 {
841 if( pRoute ) {
842 // walk the route, looking at each point to see if it is used by another route
843 // or is isolated
844 wxRoutePointListNode *pnode = ( pRoute->pRoutePointList )->GetFirst();
845 while( pnode ) {
846 RoutePoint *prp = pnode->GetData();
847
848 // check all other routes to see if this point appears in any other route
849 wxArrayPtrVoid *pRA = GetRouteArrayContaining( prp );
850
851 if( pRA ) {
852 for( unsigned int ir = 0; ir < pRA->GetCount(); ir++ ) {
853 Route *pr = (Route *) pRA->Item( ir );
854 if( pr == pRoute)
855 continue; // self
856 else
857 return true;
858 }
859 }
860
861 if( pnode ) pnode = pnode->GetNext();
862 }
863
864 // Now walk the route again, looking for isolated type shared waypoints
865 pnode = ( pRoute->pRoutePointList )->GetFirst();
866 while( pnode ) {
867 RoutePoint *prp = pnode->GetData();
868 if( prp->m_bKeepXRoute == true )
869 return true;
870
871 if( pnode ) pnode = pnode->GetNext();
872 }
873 }
874
875 return false;
876 }
877
878
879
DeleteRoute(Route * pRoute)880 bool Routeman::DeleteRoute( Route *pRoute )
881 {
882 if( pRoute ) {
883 if( pRoute == pAISMOBRoute )
884 {
885 int ret = OCPNMessageBox( NULL, _("You are trying to delete an active AIS MOB route, are you REALLY sure?"), _("OpenCPN Warning"), wxYES_NO );
886
887 if( ret == wxID_NO )
888 return false;
889 else
890 pAISMOBRoute = NULL;
891 }
892 ::wxBeginBusyCursor();
893
894 if( GetpActiveRoute() == pRoute ) DeactivateRoute();
895
896 if (pRoute->m_bIsInLayer) {
897 ::wxEndBusyCursor();
898 return false;
899 }
900 if( pRoutePropDialog && ( pRoutePropDialog->IsShown()) && (pRoute == pRoutePropDialog->GetRoute()) ) {
901 pRoutePropDialog->Hide();
902 }
903
904 pConfig->DeleteConfigRoute( pRoute );
905
906 // Remove the route from associated lists
907 pSelect->DeleteAllSelectableRouteSegments( pRoute );
908 pRouteList->DeleteObject( pRoute );
909
910 // walk the route, tentatively deleting/marking points used only by this route
911 wxRoutePointListNode *pnode = ( pRoute->pRoutePointList )->GetFirst();
912 while( pnode ) {
913 RoutePoint *prp = pnode->GetData();
914
915 // check all other routes to see if this point appears in any other route
916 Route *pcontainer_route = FindRouteContainingWaypoint( prp );
917
918 if( pcontainer_route == NULL && prp->m_bIsInRoute ) {
919 prp->m_bIsInRoute = false; // Take this point out of this (and only) route
920 if( !prp->m_bKeepXRoute ) {
921 // This does not need to be done with navobj.xml storage, since the waypoints are stored with the route
922 // pConfig->DeleteWayPoint(prp);
923
924 pSelect->DeleteSelectablePoint( prp, SELTYPE_ROUTEPOINT );
925
926 // Remove all instances of this point from the list.
927 wxRoutePointListNode *pdnode = pnode;
928 while( pdnode ) {
929 pRoute->pRoutePointList->DeleteNode( pdnode );
930 pdnode = pRoute->pRoutePointList->Find( prp );
931 }
932
933 pnode = NULL;
934 delete prp;
935 } else {
936 prp->m_bDynamicName = false;
937 prp->m_bIsolatedMark = true; // This has become an isolated mark
938 prp->m_bKeepXRoute = false; // and is no longer part of a route
939 }
940
941 }
942 if( pnode ) pnode = pnode->GetNext();
943 else
944 pnode = pRoute->pRoutePointList->GetFirst(); // restart the list
945 }
946
947 delete pRoute;
948
949 ::wxEndBusyCursor();
950
951 }
952 return true;
953 }
954
DeleteAllRoutes(void)955 void Routeman::DeleteAllRoutes( void )
956 {
957 ::wxBeginBusyCursor();
958
959 // Iterate on the RouteList
960 wxRouteListNode *node = pRouteList->GetFirst();
961 while( node ) {
962 Route *proute = node->GetData();
963 if( proute == pAISMOBRoute )
964 {
965 ::wxEndBusyCursor();
966 int ret = OCPNMessageBox( NULL, _("You are trying to delete an active AIS MOB route, are you REALLY sure?"), _("OpenCPN Warning"), wxYES_NO );
967 if( ret == wxID_NO )
968 return;
969 else
970 pAISMOBRoute = NULL;
971 ::wxBeginBusyCursor();
972 }
973
974 node = node->GetNext();
975 if( proute->m_bIsInLayer )
976 continue;
977
978 pConfig->m_bSkipChangeSetUpdate = true;
979 pConfig->DeleteConfigRoute( proute );
980 DeleteRoute( proute );
981 pConfig->m_bSkipChangeSetUpdate = false;
982 }
983
984 ::wxEndBusyCursor();
985
986 }
987
DeleteAllTracks(void)988 void Routeman::DeleteAllTracks( void )
989 {
990 ::wxBeginBusyCursor();
991
992 // Iterate on the RouteList
993 wxTrackListNode *node = pTrackList->GetFirst();
994 while( node ) {
995 Track *ptrack = node->GetData();
996 node = node->GetNext();
997
998 if( ptrack->m_bIsInLayer )
999 continue;
1000
1001 g_pAIS->DeletePersistentTrack( ptrack );
1002 pConfig->m_bSkipChangeSetUpdate = true;
1003 pConfig->DeleteConfigTrack( ptrack );
1004 DeleteTrack( ptrack );
1005 pConfig->m_bSkipChangeSetUpdate = false;
1006 }
1007
1008 ::wxEndBusyCursor();
1009
1010 }
1011
DeleteTrack(Track * pTrack)1012 void Routeman::DeleteTrack( Track *pTrack )
1013 {
1014 if( pTrack ) {
1015 if( pTrack->m_bIsInLayer ) return;
1016
1017 ::wxBeginBusyCursor();
1018
1019 wxGenericProgressDialog *pprog = nullptr;
1020
1021 int count = pTrack->GetnPoints();
1022 if( count > 10000) {
1023 pprog = new wxGenericProgressDialog( _("OpenCPN Track Delete"), _T("0/0"), count, NULL,
1024 wxPD_APP_MODAL | wxPD_SMOOTH |
1025 wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME );
1026 pprog->SetSize( 400, wxDefaultCoord );
1027 pprog->Centre();
1028
1029 }
1030 if( pTrackPropDialog && ( pTrackPropDialog->IsShown()) && (pTrack == pTrackPropDialog->GetTrack()) ) {
1031 pTrackPropDialog->Hide();
1032 }
1033
1034 // Remove the track from associated lists
1035 pSelect->DeleteAllSelectableTrackSegments( pTrack );
1036 pTrackList->DeleteObject( pTrack );
1037
1038 #if 0
1039 // walk the track, deleting points used by this track
1040 int ic = 0;
1041 wxTrackPointListNode *pnode = ( pTrack->pTrackPointList )->GetFirst();
1042 while( pnode )
1043 {
1044 if(pprog)
1045 {
1046 wxString msg;
1047 msg.Printf(_T("%d/%d"), ic, count);
1048 if(ic % 100 == 0)
1049 pprog->Update( ic, msg );
1050 ic++;
1051 }
1052
1053 TrackPoint *prp = pnode->GetData();
1054 delete prp;
1055
1056 pnode = pnode->GetNext();
1057 }
1058 #endif
1059 if( pTrack == g_pActiveTrack ) {
1060 g_pActiveTrack = NULL;
1061 m_pparent_app->TrackOff();
1062 }
1063
1064 delete pTrack;
1065
1066 ::wxEndBusyCursor();
1067
1068 delete pprog;
1069 }
1070 }
1071
SetColorScheme(ColorScheme cs)1072 void Routeman::SetColorScheme( ColorScheme cs )
1073 {
1074 // Re-Create the pens and colors
1075
1076 int scaled_line_width = g_route_line_width;
1077 int track_scaled_line_width = g_track_line_width;
1078 if(g_btouch){
1079 double nominal_line_width_pix = wxMax(1.5, floor(g_Platform->GetDisplayDPmm() / 5.0)); //0.2 mm nominal, but not less than 1 pixel
1080
1081 double sline_width = wxMax(nominal_line_width_pix, g_route_line_width);
1082 sline_width *= g_ChartScaleFactorExp;
1083 scaled_line_width = wxMax( sline_width, 2);
1084
1085 double tsline_width = wxMax(nominal_line_width_pix, g_track_line_width);
1086 tsline_width *= g_ChartScaleFactorExp;
1087 track_scaled_line_width = wxMax( tsline_width, 2);
1088 }
1089
1090 m_pActiveRoutePointPen = wxThePenList->FindOrCreatePen( wxColour( 0, 0, 255 ),
1091 scaled_line_width, wxPENSTYLE_SOLID );
1092 m_pRoutePointPen = wxThePenList->FindOrCreatePen( wxColour( 0, 0, 255 ), scaled_line_width,
1093 wxPENSTYLE_SOLID );
1094
1095 // Or in something like S-52 compliance
1096
1097 m_pRoutePen = wxThePenList->FindOrCreatePen( GetGlobalColor( _T("UINFB") ), scaled_line_width,
1098 wxPENSTYLE_SOLID );
1099 m_pSelectedRoutePen = wxThePenList->FindOrCreatePen( GetGlobalColor( _T("UINFO") ),
1100 scaled_line_width, wxPENSTYLE_SOLID );
1101 m_pActiveRoutePen = wxThePenList->FindOrCreatePen( GetGlobalColor( _T("UARTE") ),
1102 scaled_line_width, wxPENSTYLE_SOLID );
1103 m_pTrackPen = wxThePenList->FindOrCreatePen( GetGlobalColor( _T("CHMGD") ), track_scaled_line_width,
1104 wxPENSTYLE_SOLID );
1105
1106 m_pRouteBrush = wxTheBrushList->FindOrCreateBrush( GetGlobalColor( _T("UINFB") ), wxBRUSHSTYLE_SOLID );
1107 m_pSelectedRouteBrush = wxTheBrushList->FindOrCreateBrush( GetGlobalColor( _T("UINFO") ),
1108 wxBRUSHSTYLE_SOLID );
1109 m_pActiveRouteBrush = wxTheBrushList->FindOrCreateBrush( GetGlobalColor( _T("PLRTE") ),
1110 wxBRUSHSTYLE_SOLID );
1111
1112 }
1113
GetRouteReverseMessage(void)1114 wxString Routeman::GetRouteReverseMessage( void )
1115 {
1116 return wxString(
1117 _("Waypoints can be renamed to reflect the new order, the names will be '001', '002' etc.\n\nDo you want to rename the waypoints?") );
1118 }
1119
FindRouteByGUID(const wxString & guid)1120 Route *Routeman::FindRouteByGUID(const wxString &guid)
1121 {
1122 wxRouteListNode *node1 = pRouteList->GetFirst();
1123 while( node1 ) {
1124 Route *pRoute = node1->GetData();
1125
1126 if( pRoute->m_GUID == guid )
1127 return pRoute;
1128 node1 = node1->GetNext();
1129 }
1130
1131 return NULL;
1132 }
1133
FindTrackByGUID(const wxString & guid)1134 Track *Routeman::FindTrackByGUID(const wxString &guid)
1135 {
1136 wxTrackListNode *node1 = pTrackList->GetFirst();
1137 while( node1 ) {
1138 Track *pTrack = node1->GetData();
1139
1140 if( pTrack->m_GUID == guid )
1141 return pTrack;
1142 node1 = node1->GetNext();
1143 }
1144
1145 return NULL;
1146 }
1147
ZeroCurrentXTEToActivePoint()1148 void Routeman::ZeroCurrentXTEToActivePoint()
1149 {
1150 // When zeroing XTE create a "virtual" waypoint at present position
1151 if( pRouteActivatePoint ) delete pRouteActivatePoint;
1152 pRouteActivatePoint = new RoutePoint( gLat, gLon, wxString( _T("") ), wxString( _T("") ),
1153 wxEmptyString, false ); // Current location
1154 pRouteActivatePoint->m_bShowName = false;
1155
1156 pActiveRouteSegmentBeginPoint = pRouteActivatePoint;
1157 m_arrival_min = 1e6;
1158 }
1159
1160 //--------------------------------------------------------------------------------
1161 // WayPointman Implementation
1162 //--------------------------------------------------------------------------------
1163
WayPointman()1164 WayPointman::WayPointman()
1165 {
1166
1167 m_pWayPointList = new RoutePointList;
1168
1169 pmarkicon_image_list = NULL;
1170
1171 ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
1172 m_pIconArray = new ArrayOfMarkIcon;
1173 m_pLegacyIconArray = NULL;
1174 m_pExtendedIconArray = NULL;
1175
1176 m_cs = (ColorScheme)-1;
1177
1178 m_nGUID = 0;
1179 m_iconListScale = -999.0;
1180 m_iconListHeight = -1;
1181 }
1182
~WayPointman()1183 WayPointman::~WayPointman()
1184 {
1185 // Two step here, since the RoutePoint dtor also touches the
1186 // RoutePoint list.
1187 // Copy the master RoutePoint list to a temporary list,
1188 // then clear and delete objects from the temp list
1189
1190 RoutePointList temp_list;
1191
1192 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
1193 while( node ) {
1194 RoutePoint *pr = node->GetData();
1195
1196 temp_list.Append( pr );
1197 node = node->GetNext();
1198 }
1199
1200 temp_list.DeleteContents( true );
1201 temp_list.Clear();
1202
1203 m_pWayPointList->Clear();
1204 delete m_pWayPointList;
1205
1206 for( unsigned int i = 0; i < m_pIconArray->GetCount(); i++ ) {
1207 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( i );
1208 delete pmi->piconBitmap;
1209 delete pmi;
1210 }
1211
1212 m_pIconArray->Clear();
1213 delete m_pIconArray;
1214
1215 if( pmarkicon_image_list ) pmarkicon_image_list->RemoveAll();
1216 delete pmarkicon_image_list;
1217 }
1218
AddRoutePoint(RoutePoint * prp)1219 bool WayPointman::AddRoutePoint(RoutePoint *prp)
1220 {
1221 if(!prp)
1222 return false;
1223
1224 wxRoutePointListNode *prpnode = m_pWayPointList->Append(prp);
1225 prp->SetManagerListNode( prpnode );
1226
1227 return true;
1228 }
1229
RemoveRoutePoint(RoutePoint * prp)1230 bool WayPointman::RemoveRoutePoint(RoutePoint *prp)
1231 {
1232 if(!prp)
1233 return false;
1234
1235 wxRoutePointListNode *prpnode = (wxRoutePointListNode *)prp->GetManagerListNode();
1236
1237 if(prpnode)
1238 delete prpnode;
1239 else
1240 m_pWayPointList->DeleteObject(prp);
1241
1242 prp->SetManagerListNode( NULL );
1243
1244 return true;
1245 }
1246
ProcessUserIcons(ocpnStyle::Style * style)1247 void WayPointman::ProcessUserIcons( ocpnStyle::Style* style )
1248 {
1249 wxString msg;
1250 msg.Printf(_T("DPMM: %g ScaleFactorExp: %g"), g_Platform->GetDisplayDPmm(), g_ChartScaleFactorExp);
1251 wxLogMessage(msg);
1252
1253 wxString UserIconPath = g_Platform->GetPrivateDataDir();
1254 wxChar sep = wxFileName::GetPathSeparator();
1255 if( UserIconPath.Last() != sep ) UserIconPath.Append( sep );
1256 UserIconPath.Append( _T("UserIcons/") );
1257
1258 wxLogMessage(_T("Looking for UserIcons at ") + UserIconPath );
1259
1260 if( wxDir::Exists( UserIconPath ) ) {
1261 wxLogMessage(_T("Loading UserIcons from ") + UserIconPath );
1262 wxArrayString FileList;
1263
1264 int n_files = wxDir::GetAllFiles( UserIconPath, &FileList, _T(""), wxDIR_FILES );
1265
1266 for( int ifile = 0; ifile < n_files; ifile++ ) {
1267 wxString name = FileList[ifile];
1268
1269 wxFileName fn( name );
1270 wxString iconname = fn.GetName();
1271 wxBitmap icon1;
1272
1273 if( fn.GetExt().Lower() == _T("xpm") ) {
1274 if( icon1.LoadFile( name, wxBITMAP_TYPE_XPM ) ) {
1275 wxLogMessage(_T("Adding icon: ") + iconname);
1276 ProcessIcon( icon1, iconname, iconname );
1277 }
1278 }
1279 if( fn.GetExt().Lower() == _T("png") ) {
1280 if( icon1.LoadFile( name, wxBITMAP_TYPE_PNG ) ) {
1281 wxLogMessage(_T("Adding icon: ") + iconname);
1282 ProcessIcon( icon1, iconname, iconname );
1283 }
1284 }
1285 if( fn.GetExt().Lower() == _T("svg") ) {
1286 //double bm_size = 16.0 * g_Platform->GetDisplayDPmm() * g_ChartScaleFactorExp;
1287 double bm_size = 62 * g_ChartScaleFactorExp;
1288 wxBitmap iconSVG = LoadSVGIcon( name, bm_size, bm_size );
1289 MarkIcon * pmi = ProcessIcon( iconSVG, iconname, iconname );
1290 if(pmi)
1291 pmi->preScaled = true;
1292 }
1293
1294 }
1295 }
1296 }
1297
1298
ProcessIcons(ocpnStyle::Style * style)1299 void WayPointman::ProcessIcons( ocpnStyle::Style* style )
1300 {
1301 m_pIconArray->Clear();
1302
1303 ProcessDefaultIcons();
1304
1305 // Load user defined icons.
1306 // Done after default icons are initialized,
1307 // so that user may substitute an icon by using the same name in the Usericons file.
1308 ProcessUserIcons( style );
1309
1310 if( NULL != pmarkicon_image_list ) {
1311 pmarkicon_image_list->RemoveAll();
1312 delete pmarkicon_image_list;
1313 pmarkicon_image_list = NULL;
1314 }
1315
1316 // First find the largest bitmap size, to use as the base size for lists of icons
1317 int w = 0;
1318 int h = 0;
1319
1320 for( unsigned int i = 0; i < m_pIconArray->GetCount(); i++ ) {
1321 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( i );
1322 w = wxMax(w, pmi->iconImage.GetWidth());
1323 h = wxMax(h, pmi->iconImage.GetHeight());
1324 }
1325
1326 m_bitmapSizeForList = wxMax(w,h);
1327 m_bitmapSizeForList = wxMin(100, m_bitmapSizeForList);
1328
1329
1330 }
1331
ProcessDefaultIcons()1332 void WayPointman::ProcessDefaultIcons()
1333 {
1334 wxString iconDir = g_Platform->GetSharedDataDir();
1335 appendOSDirSlash(&iconDir);
1336 iconDir.append(_T("uidata"));
1337 appendOSDirSlash(&iconDir);
1338 iconDir.append(_T("markicons"));
1339 appendOSDirSlash(&iconDir);
1340
1341 MarkIcon *pmi = 0;
1342
1343 // Add the legacy icons to their own sorted array
1344 if(m_pLegacyIconArray)
1345 m_pLegacyIconArray->Clear();
1346 else
1347 m_pLegacyIconArray = new SortedArrayOfMarkIcon(CompareMarkIcons);
1348
1349 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-Empty.svg"), _T("empty"), _T("Empty") ); if(pmi)pmi->preScaled = true;
1350 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-Triangle.svg"), _T("triangle"), _T("Triangle") ); if(pmi)pmi->preScaled = true;
1351 pmi = ProcessLegacyIcon( iconDir + _T("1st-Active-Waypoint.svg"), _T("activepoint"), _T("Active WP") ); if(pmi)pmi->preScaled = true;
1352 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Boarding-Location.svg"), _T("boarding"), _T("Boarding Location") ); if(pmi)pmi->preScaled = true;
1353 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Airplane.svg"), _T("airplane"), _T("Airplane") ); if(pmi)pmi->preScaled = true;
1354 pmi = ProcessLegacyIcon( iconDir + _T("1st-Anchorage.svg"), _T("anchorage"), _T("Anchorage") ); if(pmi)pmi->preScaled = true;
1355 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-Anchor2.svg"), _T("anchor"), _T("Anchor") ); if(pmi)pmi->preScaled = true;
1356 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Boundary.svg"), _T("boundary"), _T("Boundary Mark") ); if(pmi)pmi->preScaled = true;
1357 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Buoy-TypeA.svg"), _T("bouy1"), _T("Bouy Type A") ); if(pmi)pmi->preScaled = true;
1358 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Buoy-TypeB.svg"), _T("bouy2"), _T("Bouy Type B") ); if(pmi)pmi->preScaled = true;
1359 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Campfire.svg"), _T("campfire"), _T("Campfire") ); if(pmi)pmi->preScaled = true;
1360 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Camping.svg"), _T("camping"), _T("Camping Spot") ); if(pmi)pmi->preScaled = true;
1361 pmi = ProcessLegacyIcon( iconDir + _T("Sea-Floor-Coral.svg"), _T("coral"), _T("Coral") ); if(pmi)pmi->preScaled = true;
1362 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Fishing.svg"), _T("fishhaven"), _T("Fish Haven") ); if(pmi)pmi->preScaled = true;
1363 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Fishing.svg"), _T("fishing"), _T("Fishing Spot") ); if(pmi)pmi->preScaled = true;
1364 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Fishing.svg"), _T("fish"), _T("Fish") ); if(pmi)pmi->preScaled = true;
1365 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Mooring-Buoy.svg"), _T("float"), _T("Float") ); if(pmi)pmi->preScaled = true;
1366 pmi = ProcessLegacyIcon( iconDir + _T("Service-Food.svg"), _T("food"), _T("Food") ); if(pmi)pmi->preScaled = true;
1367 pmi = ProcessLegacyIcon( iconDir + _T("Service-Fuel-Pump-Diesel-Petrol.svg"), _T("fuel"), _T("Fuel") ); if(pmi)pmi->preScaled = true;
1368 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-Green.svg"), _T("greenlite"), _T("Green Light") ); if(pmi)pmi->preScaled = true;
1369 pmi = ProcessLegacyIcon( iconDir + _T("Sea-Floor-Sea-Weed.svg"), _T("kelp"), _T("Kelp") ); if(pmi)pmi->preScaled = true;
1370 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-TypeA.svg"), _T("light"), _T("Light Type A") ); if(pmi)pmi->preScaled = true;
1371 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-TypeB.svg"), _T("light1"), _T("Light Type B") ); if(pmi)pmi->preScaled = true;
1372 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-Vessel.svg"), _T("litevessel"), _T("litevessel") ); if(pmi)pmi->preScaled = true;
1373 pmi = ProcessLegacyIcon( iconDir + _T("1st-Man-Overboard.svg"), _T("mob"), _T("MOB") ); if(pmi)pmi->preScaled = true;
1374 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Mooring-Buoy.svg"), _T("mooring"), _T("Mooring Bouy") ); if(pmi)pmi->preScaled = true;
1375 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Mooring-Buoy-Super.svg"), _T("oilbouy"), _T("Oil Bouy") ); if(pmi)pmi->preScaled = true;
1376 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Oil-Platform.svg"), _T("platform"), _T("Platform") ); if(pmi)pmi->preScaled = true;
1377 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-Red-Green.svg"), _T("redgreenlite"), _T("Red/Green Light") ); if(pmi)pmi->preScaled = true;
1378 pmi = ProcessLegacyIcon( iconDir + _T("Marks-Light-Red.svg"), _T("redlite"), _T("Red Light") ); if(pmi)pmi->preScaled = true;
1379 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Rock-Exposed.svg"), _T("rock1"), _T("Rock (exposed)") ); if(pmi)pmi->preScaled = true;
1380 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Rock-Awash.svg"), _T("rock2"), _T("Rock, (awash)") ); if(pmi)pmi->preScaled = true;
1381 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Sandbar.svg"), _T("sand"), _T("Sand") ); if(pmi)pmi->preScaled = true;
1382 pmi = ProcessLegacyIcon( iconDir + _T("Activity-Diving-Scuba-Flag.svg"), _T("scuba"), _T("Scuba") ); if(pmi)pmi->preScaled = true;
1383 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Sandbar.svg"), _T("shoal"), _T("Shoal") ); if(pmi)pmi->preScaled = true;
1384 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Snag.svg"), _T("snag"), _T("Snag") ); if(pmi)pmi->preScaled = true;
1385 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-Square.svg"), _T("square"), _T("Square") ); if(pmi)pmi->preScaled = true;
1386 pmi = ProcessLegacyIcon( iconDir + _T("1st-Diamond.svg"), _T("diamond"), _T("Diamond") ); if(pmi)pmi->preScaled = true;
1387 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-Circle.svg"), _T("circle"), _T("Circle") ); if(pmi)pmi->preScaled = true;
1388 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Wreck1.svg"), _T("wreck1"), _T("Wreck A") ); if(pmi)pmi->preScaled = true;
1389 pmi = ProcessLegacyIcon( iconDir + _T("Hazard-Wreck2.svg"), _T("wreck2"), _T("Wreck B") ); if(pmi)pmi->preScaled = true;
1390 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-X-Small-Blue.svg"), _T("xmblue"), _T("Blue X") ); if(pmi)pmi->preScaled = true;
1391 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-X-Small-Green.svg"), _T("xmgreen"), _T("Green X") ); if(pmi)pmi->preScaled = true;
1392 pmi = ProcessLegacyIcon( iconDir + _T("Symbol-X-Small-Red.svg"), _T("xmred"), _T("Red X") ); if(pmi)pmi->preScaled = true;
1393
1394
1395 // Add the extended icons to their own sorted array
1396 if(m_pExtendedIconArray)
1397 m_pExtendedIconArray->Clear();
1398 else
1399 m_pExtendedIconArray = new SortedArrayOfMarkIcon(CompareMarkIcons);
1400
1401 #if 0
1402 wxArrayString FileList;
1403 double bm_size = -1;
1404
1405 int n_files = wxDir::GetAllFiles( iconDir, &FileList );
1406
1407 // If the scale factor is not unity, measure the first icon in the list
1408 // So that we may apply the scale factor exactly to all
1409 if( fabs(g_ChartScaleFactorExp - 1.0) > 0.1){
1410
1411 for( int ifile = 0; ifile < n_files; ifile++ ) {
1412 wxString name = FileList[ifile];
1413
1414 wxFileName fn( name );
1415
1416 if( fn.GetExt().Lower() == _T("svg") ) {
1417 wxBitmap bmt = LoadSVGIcon(name, -1, -1 );
1418 bm_size = bmt.GetWidth() * g_ChartScaleFactorExp;
1419 break;
1420 }
1421 }
1422 }
1423
1424 for( int ifile = 0; ifile < n_files; ifile++ ) {
1425 wxString name = FileList[ifile];
1426
1427 wxFileName fn( name );
1428 wxString iconname = fn.GetName();
1429 wxBitmap icon1;
1430 if( fn.GetExt().Lower() == _T("svg") ) {
1431 wxImage iconSVG = LoadSVGIcon( name, (int)bm_size, (int)bm_size );
1432 MarkIcon * pmi = ProcessExtendedIcon( iconSVG, iconname, iconname );
1433 if(pmi)
1434 pmi->preScaled = true;
1435 }
1436 }
1437 #else
1438 // Look for cached icons
1439
1440 wxString iconCacheDir = g_Platform->GetPrivateDataDir();
1441 appendOSDirSlash(&iconCacheDir);
1442 iconCacheDir.append(_T("iconCache"));
1443 appendOSDirSlash(&iconCacheDir);
1444
1445 // Create the cache dir here if necessary
1446 if(!wxDir::Exists(iconCacheDir))
1447 wxFileName::Mkdir(iconCacheDir);
1448
1449
1450 wxArrayString FileList;
1451 double bm_size = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * 12.0)); // nominal size, but not less than 4 pixel
1452 bm_size *= g_ChartScaleFactorExp;
1453
1454 bool bcacheLoaded = false;
1455
1456 int n_files = wxDir::GetAllFiles( iconDir, &FileList );
1457
1458 // To expedite icon loading, look in the iconCache first
1459 wxArrayString cacheFileList;
1460 int n_cache_files = wxDir::GetAllFiles( iconCacheDir, &cacheFileList );
1461 if(n_cache_files){
1462
1463 bool bReload = false;
1464 // Load a cached icon file and get it's size for comparison
1465 for( int ifile = 0; ifile < n_cache_files; ifile++ ) {
1466 wxString name = cacheFileList[ifile];
1467
1468 wxImage imagePNG;
1469 if(imagePNG.LoadFile(name)){
1470 int w = imagePNG.GetWidth();
1471 if(fabs(w - bm_size) > 1)
1472 bReload = true;
1473 break;
1474 }
1475 }
1476
1477 // If cached files are the proper size(scale)...
1478 if(!bReload){
1479 for( int ifile = 0; ifile < n_cache_files; ifile++ ) {
1480 wxString name = cacheFileList[ifile];
1481
1482 wxFileName fn( name );
1483 wxString iconname = fn.GetName();
1484
1485 wxImage imagePNG;
1486 if(imagePNG.LoadFile(name)){
1487 MarkIcon * pmi = ProcessExtendedIcon( imagePNG, iconname, iconname );
1488 if(pmi)
1489 pmi->preScaled = true;
1490 bcacheLoaded = true; //At least one icon was loaded
1491 }
1492 }
1493 }
1494 }
1495
1496 // Possibly an upgrade of App, with more icons available
1497 // So, reload them all
1498 if(n_cache_files < 50)
1499 bcacheLoaded = false;
1500
1501 // Cache was unusable, so load from original
1502 if(!bcacheLoaded)
1503 {
1504 g_Platform->ShowBusySpinner();
1505
1506 for( int ifile = 0; ifile < n_files; ifile++ ) {
1507 wxString name = FileList[ifile];
1508
1509 wxFileName fn( name );
1510 wxString iconname = fn.GetName();
1511 wxBitmap icon1;
1512
1513 if( fn.GetExt().Lower() == _T("svg") ) {
1514 wxImage iconSVG = LoadSVGIcon( name, (int)bm_size, (int)bm_size );
1515
1516 // Cache the icon as .png file
1517 wxString filePNG = iconCacheDir + iconname + _T(".png");
1518 iconSVG.SaveFile(filePNG, wxBITMAP_TYPE_PNG);
1519 MarkIcon * pmi = ProcessExtendedIcon( iconSVG, iconname, iconname );
1520 if(pmi)
1521 pmi->preScaled = true;
1522 }
1523 }
1524 g_Platform->HideBusySpinner();
1525
1526 }
1527 #endif
1528
1529
1530 // Walk the two sorted lists, adding icons to the un-sorted master list
1531
1532 for( unsigned int i = 0; i < m_pLegacyIconArray->GetCount(); i++ ) {
1533 pmi = (MarkIcon *) m_pLegacyIconArray->Item( i );
1534 m_pIconArray->Add( pmi );
1535 }
1536
1537 for( unsigned int i = 0; i < m_pExtendedIconArray->GetCount(); i++ ) {
1538 pmi = (MarkIcon *) m_pExtendedIconArray->Item( i );
1539
1540 // Do not add any icons from the extended array if they have already been used as legacy substitutes
1541 bool noAdd = false;
1542 for( unsigned int j = 0; j < m_pLegacyIconArray->GetCount(); j++ ) {
1543 MarkIcon *pmiLegacy = (MarkIcon *) m_pLegacyIconArray->Item( j );
1544 if(pmiLegacy->icon_name.IsSameAs(pmi->icon_name)){
1545 noAdd = true;
1546 break;
1547 }
1548 }
1549 if(!noAdd)
1550 m_pIconArray->Add( pmi );
1551
1552 }
1553 }
1554
1555
1556
ProcessIcon(wxBitmap pimage,const wxString & key,const wxString & description)1557 MarkIcon *WayPointman::ProcessIcon(wxBitmap pimage, const wxString & key, const wxString & description)
1558 {
1559 MarkIcon *pmi = 0;
1560
1561 bool newIcon = true;
1562
1563 // avoid adding duplicates
1564 for( unsigned int i = 0; i < m_pIconArray->GetCount(); i++ ) {
1565 pmi = (MarkIcon *) m_pIconArray->Item( i );
1566 if( pmi->icon_name.IsSameAs( key ) ) {
1567 newIcon = false;
1568 delete pmi->piconBitmap;
1569 break;
1570 }
1571 }
1572
1573 if( newIcon ) {
1574 pmi = new MarkIcon;
1575 pmi->icon_name = key; // Used for sorting
1576 m_pIconArray->Add( pmi );
1577 }
1578
1579 wxBitmap *pbm = new wxBitmap( pimage );
1580 pmi->icon_name = key;
1581 pmi->icon_description = description;
1582 pmi->piconBitmap = NULL;
1583 pmi->icon_texture = 0; /* invalidate */
1584 pmi->preScaled = false;
1585 pmi->iconImage = pbm->ConvertToImage();
1586 pmi->m_blistImageOK = false;
1587 delete pbm;
1588
1589 return pmi;
1590 }
1591
ProcessExtendedIcon(wxImage & image,const wxString & key,const wxString & description)1592 MarkIcon *WayPointman::ProcessExtendedIcon(wxImage &image, const wxString & key, const wxString & description)
1593 {
1594 MarkIcon *pmi = 0;
1595
1596 bool newIcon = true;
1597
1598 // avoid adding duplicates
1599 for( unsigned int i = 0; i < m_pExtendedIconArray->GetCount(); i++ ) {
1600 pmi = (MarkIcon *) m_pExtendedIconArray->Item( i );
1601 if( pmi->icon_name.IsSameAs( key ) ) {
1602 newIcon = false;
1603 delete pmi->piconBitmap;
1604 break;
1605 }
1606 }
1607
1608 if( newIcon ) {
1609 pmi = new MarkIcon;
1610 pmi->icon_name = key; // Used for sorting
1611 m_pExtendedIconArray->Add( pmi );
1612 }
1613
1614 wxRect rClip = CropImageOnAlpha(image);
1615 wxImage imageClip = image.GetSubImage(rClip);
1616
1617 pmi->icon_name = key;
1618 pmi->icon_description = description;
1619 pmi->piconBitmap = NULL;
1620 pmi->icon_texture = 0; /* invalidate */
1621 pmi->preScaled = false;
1622 pmi->iconImage = imageClip;
1623 pmi->m_blistImageOK = false;
1624
1625 return pmi;
1626 }
1627
ProcessLegacyIcon(wxString fileName,const wxString & key,const wxString & description)1628 MarkIcon *WayPointman::ProcessLegacyIcon( wxString fileName, const wxString & key, const wxString & description)
1629 {
1630 double bm_size = -1.0;
1631
1632 #ifndef __OCPN__ANDROID__
1633 if( fabs(g_ChartScaleFactorExp - 1.0) > 0.1){
1634 wxImage img = LoadSVGIcon(fileName, -1, -1 );
1635 bm_size = img.GetWidth() * g_ChartScaleFactorExp;
1636 }
1637 #else
1638 // Set the onscreen size of the symbol
1639 // Compensate for various display resolutions
1640 // Develop empirically, making a "diamond" symbol about 4 mm square
1641 // Android uses "density buckets", so simpple math produces poor results.
1642 // Thus, these factors have been empirically tweaked to provide good results on a variety of devices
1643 float nominal_legacy_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * 12.0));
1644 float pix_factor = nominal_legacy_icon_size_pixels / 68.0; // legacy icon size
1645
1646 wxImage img = LoadSVGIcon(fileName, -1, -1 );
1647 bm_size = img.GetWidth() * pix_factor * g_ChartScaleFactorExp;
1648 #endif
1649
1650 wxImage image = LoadSVGIcon(fileName, (int)bm_size, (int)bm_size );
1651 wxRect rClip = CropImageOnAlpha(image);
1652 wxImage imageClip = image.GetSubImage(rClip);
1653
1654 MarkIcon *pmi = 0;
1655
1656 bool newIcon = true;
1657
1658 // avoid adding duplicates
1659 for( unsigned int i = 0; i < m_pLegacyIconArray->GetCount(); i++ ) {
1660 pmi = (MarkIcon *) m_pLegacyIconArray->Item( i );
1661 if( pmi->icon_name.IsSameAs( key ) ) {
1662 newIcon = false;
1663 delete pmi->piconBitmap;
1664 break;
1665 }
1666 }
1667
1668 if( newIcon ) {
1669 pmi = new MarkIcon;
1670 pmi->icon_name = key; // Used for sorting
1671 m_pLegacyIconArray->Add( pmi );
1672 }
1673
1674 pmi->icon_name = key;
1675 pmi->icon_description = description;
1676 pmi->piconBitmap = NULL;
1677 pmi->icon_texture = 0; /* invalidate */
1678 pmi->preScaled = false;
1679 pmi->iconImage = imageClip;
1680 pmi->m_blistImageOK = false;
1681
1682 return pmi;
1683 }
1684
CropImageOnAlpha(wxImage & image)1685 wxRect WayPointman::CropImageOnAlpha(wxImage &image)
1686 {
1687 const int w = image.GetWidth();
1688 const int h = image.GetHeight();
1689
1690 wxRect rv = wxRect(0,0, w, h);
1691 if(!image.HasAlpha())
1692 return rv;
1693
1694 unsigned char *pAlpha = image.GetAlpha();
1695
1696 int leftCrop = w;
1697 int topCrop = h;
1698 int rightCrop = w;
1699 int bottomCrop = h;
1700
1701 // Horizontal
1702 for(int i=0 ; i < h ; i++){
1703 int lineStartIndex = i *w;
1704
1705 int j = 0;
1706 while((j < w) && (pAlpha[lineStartIndex+j] == 0) )
1707 j++;
1708 leftCrop = wxMin(leftCrop, j);
1709
1710 int k = w - 1;
1711 while( k && (pAlpha[lineStartIndex+k] == 0) )
1712 k--;
1713 rightCrop = wxMin(rightCrop, image.GetWidth() - k - 2);
1714 }
1715
1716 // Vertical
1717 for(int i=0 ; i < w ; i++){
1718 int columnStartIndex = i;
1719
1720 int j = 0;
1721 while((j < h) && (pAlpha[columnStartIndex+ (j * w)] == 0) )
1722 j++;
1723 topCrop = wxMin(topCrop, j);
1724
1725 int k = h - 1;
1726 while( k && (pAlpha[columnStartIndex+(k * w)] == 0) )
1727 k--;
1728 bottomCrop = wxMin(bottomCrop, h - k - 2);
1729 }
1730
1731 int xcrop = wxMin(rightCrop, leftCrop);
1732 int ycrop = wxMin(topCrop, bottomCrop);
1733 int crop = wxMin(xcrop, ycrop);
1734
1735 rv.x = wxMax(crop, 0);
1736 rv.width = wxMax(1, w - (2 * crop));
1737 rv.width = wxMin(rv.width, w);
1738 rv.y = rv.x;
1739 rv.height = rv.width;
1740
1741 return rv;
1742
1743 }
1744
Getpmarkicon_image_list(int nominal_height)1745 wxImageList *WayPointman::Getpmarkicon_image_list( int nominal_height )
1746 {
1747 // Cached version available?
1748 if( pmarkicon_image_list && (nominal_height == m_iconListHeight)){
1749 return pmarkicon_image_list;
1750 }
1751
1752 // Build an image list large enough
1753 if( NULL != pmarkicon_image_list ) {
1754 pmarkicon_image_list->RemoveAll();
1755 delete pmarkicon_image_list;
1756 }
1757 pmarkicon_image_list = new wxImageList( nominal_height, nominal_height );
1758
1759 m_iconListHeight = nominal_height;
1760 m_bitmapSizeForList = nominal_height;
1761
1762 return pmarkicon_image_list;
1763 }
1764
CreateDimBitmap(wxBitmap * pBitmap,double factor)1765 wxBitmap *WayPointman::CreateDimBitmap( wxBitmap *pBitmap, double factor )
1766 {
1767 wxImage img = pBitmap->ConvertToImage();
1768 int sx = img.GetWidth();
1769 int sy = img.GetHeight();
1770
1771 wxImage new_img( img );
1772
1773 for( int i = 0; i < sx; i++ ) {
1774 for( int j = 0; j < sy; j++ ) {
1775 if( !img.IsTransparent( i, j ) ) {
1776 new_img.SetRGB( i, j, (unsigned char) ( img.GetRed( i, j ) * factor ),
1777 (unsigned char) ( img.GetGreen( i, j ) * factor ),
1778 (unsigned char) ( img.GetBlue( i, j ) * factor ) );
1779 }
1780 }
1781 }
1782
1783 wxBitmap *pret = new wxBitmap( new_img );
1784
1785 return pret;
1786
1787 }
1788
CreateDimImage(wxImage & image,double factor)1789 wxImage WayPointman::CreateDimImage( wxImage &image, double factor )
1790 {
1791 int sx = image.GetWidth();
1792 int sy = image.GetHeight();
1793
1794 wxImage new_img( image );
1795
1796 for( int i = 0; i < sx; i++ ) {
1797 for( int j = 0; j < sy; j++ ) {
1798 if( !image.IsTransparent( i, j ) ) {
1799 new_img.SetRGB( i, j, (unsigned char) ( image.GetRed( i, j ) * factor ),
1800 (unsigned char) ( image.GetGreen( i, j ) * factor ),
1801 (unsigned char) ( image.GetBlue( i, j ) * factor ) );
1802 }
1803 }
1804 }
1805
1806
1807 return wxImage(new_img);
1808
1809 }
1810
SetColorScheme(ColorScheme cs)1811 void WayPointman::SetColorScheme( ColorScheme cs )
1812 {
1813 m_cs = cs;
1814 ReloadAllIcons();
1815 }
1816
ReloadAllIcons()1817 void WayPointman::ReloadAllIcons( )
1818 {
1819 ProcessIcons( g_StyleManager->GetCurrentStyle() );
1820
1821 for( unsigned int i = 0; i < m_pIconArray->GetCount(); i++ ) {
1822 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( i );
1823 wxImage dim_image;
1824 if(m_cs == GLOBAL_COLOR_SCHEME_DUSK){
1825 dim_image = CreateDimImage(pmi->iconImage, .50);
1826 pmi->iconImage = dim_image;
1827 }
1828 else if(m_cs == GLOBAL_COLOR_SCHEME_NIGHT){
1829 dim_image = CreateDimImage(pmi->iconImage, .20);
1830 pmi->iconImage = dim_image;
1831 }
1832 }
1833
1834 ReloadRoutepointIcons();
1835 }
1836
ReloadRoutepointIcons()1837 void WayPointman::ReloadRoutepointIcons()
1838 {
1839 // Iterate on the RoutePoint list, requiring each to reload icon
1840
1841 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
1842 while( node ) {
1843 RoutePoint *pr = node->GetData();
1844 pr->ReLoadIcon();
1845 node = node->GetNext();
1846 }
1847 }
1848
DoesIconExist(const wxString & icon_key) const1849 bool WayPointman::DoesIconExist(const wxString & icon_key) const
1850 {
1851 MarkIcon *pmi;
1852 unsigned int i;
1853
1854 for( i = 0; i < m_pIconArray->GetCount(); i++ ) {
1855 pmi = (MarkIcon *) m_pIconArray->Item( i );
1856 if( pmi->icon_name.IsSameAs( icon_key ) ) return true;
1857 }
1858
1859 return false;
1860 }
1861
GetIconBitmap(const wxString & icon_key)1862 wxBitmap *WayPointman::GetIconBitmap( const wxString& icon_key )
1863 {
1864 wxBitmap *pret = NULL;
1865 MarkIcon *pmi = NULL;
1866 unsigned int i;
1867
1868 for( i = 0; i < m_pIconArray->GetCount(); i++ ) {
1869 pmi = (MarkIcon *) m_pIconArray->Item( i );
1870 if( pmi->icon_name.IsSameAs( icon_key ) )
1871 break;
1872 }
1873
1874 if( i == m_pIconArray->GetCount() ) // key not found
1875 {
1876 // find and return bitmap for "circle"
1877 for( i = 0; i < m_pIconArray->GetCount(); i++ ) {
1878 pmi = (MarkIcon *) m_pIconArray->Item( i );
1879 // if( pmi->icon_name.IsSameAs( _T("circle") ) )
1880 // break;
1881 }
1882 }
1883
1884 if( i == m_pIconArray->GetCount() ) // "circle" not found
1885 pmi = (MarkIcon *) m_pIconArray->Item( 0 ); // use item 0
1886
1887 if( pmi ){
1888 if(pmi->piconBitmap)
1889 pret = pmi->piconBitmap;
1890 else{
1891 if(pmi->iconImage.IsOk()){
1892 pmi->piconBitmap = new wxBitmap(pmi->iconImage);
1893 pret = pmi->piconBitmap;
1894 }
1895 }
1896 }
1897 return pret;
1898 }
1899
GetIconPrescaled(const wxString & icon_key)1900 bool WayPointman::GetIconPrescaled( const wxString& icon_key )
1901 {
1902 MarkIcon *pmi = NULL;
1903 unsigned int i;
1904
1905 for( i = 0; i < m_pIconArray->GetCount(); i++ ) {
1906 pmi = (MarkIcon *) m_pIconArray->Item( i );
1907 if( pmi->icon_name.IsSameAs( icon_key ) )
1908 break;
1909 }
1910
1911 if( i == m_pIconArray->GetCount() ) // key not found
1912 {
1913 // find and return bitmap for "circle"
1914 for( i = 0; i < m_pIconArray->GetCount(); i++ ) {
1915 pmi = (MarkIcon *) m_pIconArray->Item( i );
1916 // if( pmi->icon_name.IsSameAs( _T("circle") ) )
1917 // break;
1918 }
1919 }
1920
1921 if( i == m_pIconArray->GetCount() ) // "circle" not found
1922 pmi = (MarkIcon *) m_pIconArray->Item( 0 ); // use item 0
1923
1924 if( pmi )
1925 return pmi->preScaled;
1926 else
1927 return false;
1928 }
1929
GetIconTexture(const wxBitmap * pbm,int & glw,int & glh)1930 unsigned int WayPointman::GetIconTexture( const wxBitmap *pbm, int &glw, int &glh )
1931 {
1932 #ifdef ocpnUSE_GL
1933 int index = GetIconIndex( pbm );
1934 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( index );
1935
1936 if(!pmi->icon_texture) {
1937 /* make rgba texture */
1938 wxImage image = pbm->ConvertToImage();
1939 unsigned char *d = image.GetData();
1940 if (d == 0) {
1941 // don't create a texture with junk
1942 return 0;
1943 }
1944
1945 glGenTextures(1, &pmi->icon_texture);
1946 glBindTexture(GL_TEXTURE_2D, pmi->icon_texture);
1947
1948 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
1949 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
1950 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
1951
1952
1953 int w = image.GetWidth(), h = image.GetHeight();
1954
1955 pmi->tex_w = NextPow2(w);
1956 pmi->tex_h = NextPow2(h);
1957
1958 unsigned char *a = image.GetAlpha();
1959
1960 unsigned char mr, mg, mb;
1961 if (!a)
1962 image.GetOrFindMaskColour( &mr, &mg, &mb );
1963
1964 unsigned char *e = new unsigned char[4 * w * h];
1965 for( int y = 0; y < h; y++ ) {
1966 for( int x = 0; x < w; x++ ) {
1967 unsigned char r, g, b;
1968 int off = ( y * w + x );
1969 r = d[off * 3 + 0];
1970 g = d[off * 3 + 1];
1971 b = d[off * 3 + 2];
1972 e[off * 4 + 0] = r;
1973 e[off * 4 + 1] = g;
1974 e[off * 4 + 2] = b;
1975
1976 e[off * 4 + 3] = a ? a[off] : ( ( r == mr ) && ( g == mg ) && ( b == mb ) ? 0 : 255 );
1977 }
1978 }
1979
1980 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pmi->tex_w, pmi->tex_h,
1981 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
1982 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h,
1983 GL_RGBA, GL_UNSIGNED_BYTE, e);
1984
1985 delete [] e;
1986 }
1987
1988 glw = pmi->tex_w;
1989 glh = pmi->tex_h;
1990
1991 return pmi->icon_texture;
1992 #else
1993 return 0;
1994 #endif
1995 }
1996
1997
GetIconBitmapForList(int index,int height)1998 wxBitmap WayPointman::GetIconBitmapForList( int index, int height )
1999 {
2000 wxBitmap pret;
2001 MarkIcon *pmi;
2002
2003 if( index >= 0 ) {
2004 pmi = (MarkIcon *) m_pIconArray->Item( index );
2005 // Scale the icon to "list size" if necessary
2006 if(pmi->iconImage.GetHeight() != height){
2007 int w = height;
2008 int h = height;
2009 int w0 = pmi->iconImage.GetWidth();
2010 int h0 = pmi->iconImage.GetHeight();
2011
2012 wxImage icon_resized = pmi->iconImage; // make a copy
2013 if( h0 <= h && w0 <= w ) {
2014 icon_resized = pmi->iconImage.Resize( wxSize( w, h ), wxPoint( w/2 -w0/2, h/2-h0/2 ) );
2015 } else {
2016 // rescale in one or two directions to avoid cropping, then resize to fit to cell
2017 int h1 = h;
2018 int w1 = w;
2019 if( h0 > h ) w1 = wxRound( (double) w0 * ( (double) h / (double) h0 ) );
2020
2021 else if( w0 > w ) h1 = wxRound( (double) h0 * ( (double) w / (double) w0 ) );
2022
2023 icon_resized = pmi->iconImage.Rescale( w1, h1 );
2024 icon_resized = pmi->iconImage.Resize( wxSize( w, h ), wxPoint( w/2 -w1/2, h/2-h1/2 ) );
2025 }
2026
2027 pret = wxBitmap(icon_resized);
2028
2029 }
2030 else
2031 pret = wxBitmap(pmi->iconImage);
2032
2033
2034 }
2035
2036
2037 return pret;
2038 }
2039
2040
2041
2042
2043
GetIconDescription(int index)2044 wxString *WayPointman::GetIconDescription( int index )
2045 {
2046 wxString *pret = NULL;
2047
2048 if( index >= 0 ) {
2049 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( index );
2050 pret = &pmi->icon_description;
2051 }
2052 return pret;
2053 }
2054
GetIconKey(int index)2055 wxString *WayPointman::GetIconKey( int index )
2056 {
2057 wxString *pret = NULL;
2058
2059 if( (index >= 0) && ((unsigned int)index < m_pIconArray->GetCount()) ) {
2060 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( index );
2061 pret = &pmi->icon_name;
2062 }
2063 return pret;
2064 }
2065
GetIconIndex(const wxBitmap * pbm)2066 int WayPointman::GetIconIndex( const wxBitmap *pbm )
2067 {
2068 unsigned int ret = 0;
2069 MarkIcon *pmi;
2070
2071 wxASSERT(m_pIconArray->GetCount() >= 1);
2072 for( unsigned int i = 0; i < m_pIconArray->GetCount(); i++ ) {
2073 pmi = (MarkIcon *) m_pIconArray->Item( i );
2074 if( pmi->piconBitmap == pbm ){
2075 ret = i;
2076 break;
2077 }
2078 }
2079
2080 return ret;
2081 }
2082
2083
GetIconImageListIndex(const wxBitmap * pbm)2084 int WayPointman::GetIconImageListIndex( const wxBitmap *pbm )
2085 {
2086 MarkIcon *pmi = (MarkIcon *) m_pIconArray->Item( GetIconIndex (pbm) );
2087
2088 // Build a "list - sized" image
2089 if(pmarkicon_image_list && !pmi->m_blistImageOK){
2090 int h0 = pmi->iconImage.GetHeight();
2091 int w0 = pmi->iconImage.GetWidth();
2092 int h = m_bitmapSizeForList;
2093 int w = m_bitmapSizeForList;
2094
2095 wxImage icon_larger = pmi->iconImage; // make a copy
2096 if( h0 <= h && w0 <= w ) {
2097 icon_larger = pmi->iconImage.Resize( wxSize( w, h ), wxPoint( w/2 -w0/2, h/2-h0/2 ) );
2098 } else {
2099 // rescale in one or two directions to avoid cropping, then resize to fit to cell
2100 int h1 = h;
2101 int w1 = w;
2102 if( h0 > h ) w1 = wxRound( (double) w0 * ( (double) h / (double) h0 ) );
2103
2104 else if( w0 > w ) h1 = wxRound( (double) h0 * ( (double) w / (double) w0 ) );
2105
2106 icon_larger = pmi->iconImage.Rescale( w1, h1 );
2107 icon_larger = icon_larger.Resize( wxSize( w, h ), wxPoint( w/2 -w1/2, h/2-h1/2 ) );
2108 }
2109
2110 int index = pmarkicon_image_list->Add( wxBitmap(icon_larger));
2111
2112 // Create and replace "x-ed out" icon,
2113 // Being careful to preserve (some) transparency
2114
2115 icon_larger.ConvertAlphaToMask( 128 );
2116
2117 unsigned char r,g,b;
2118 icon_larger.GetOrFindMaskColour(&r, &g, &b);
2119 wxColour unused_color(r,g,b);
2120
2121 wxBitmap bmp0( icon_larger );
2122
2123 wxBitmap bmp(w, h, -1 );
2124 wxMemoryDC mdc( bmp );
2125 mdc.SetBackground( wxBrush( unused_color) );
2126 mdc.Clear();
2127 mdc.DrawBitmap( bmp0, 0, 0 );
2128 int xm = bmp.GetWidth() / 2;
2129 int ym = bmp.GetHeight() / 2;
2130 int dp = xm / 2;
2131 int width = wxMax(xm / 10, 2);
2132 wxPen red(GetGlobalColor(_T( "URED" )), width );
2133 mdc.SetPen( red );
2134 mdc.DrawLine( xm-dp, ym-dp, xm+dp, ym+dp );
2135 mdc.DrawLine( xm-dp, ym+dp, xm+dp, ym-dp );
2136 mdc.SelectObject( wxNullBitmap );
2137
2138 wxMask *pmask = new wxMask(bmp, unused_color);
2139 bmp.SetMask( pmask );
2140
2141 pmarkicon_image_list->Add( bmp );
2142
2143 pmi->m_blistImageOK = true;
2144 pmi->listIndex = index;
2145
2146 }
2147
2148 return pmi->listIndex;
2149
2150 }
2151
2152
GetXIconImageListIndex(const wxBitmap * pbm)2153 int WayPointman::GetXIconImageListIndex( const wxBitmap *pbm )
2154 {
2155 return GetIconImageListIndex( pbm ) +1; // index of "X-ed out" icon in the image list
2156 }
2157
2158 // Create the unique identifier
CreateGUID(RoutePoint * pRP)2159 wxString WayPointman::CreateGUID( RoutePoint *pRP )
2160 {
2161 //FIXME: this method is not needed at all (if GetUUID works...)
2162 /*wxDateTime now = wxDateTime::Now();
2163 time_t ticks = now.GetTicks();
2164 wxString GUID;
2165 GUID.Printf(_T("%d-%d-%d-%d"), ((int)fabs(pRP->m_lat * 1e4)), ((int)fabs(pRP->m_lon * 1e4)), (int)ticks, m_nGUID);
2166
2167 m_nGUID++;
2168
2169 return GUID;*/
2170 return GpxDocument::GetUUID();
2171 }
2172
FindRoutePointByGUID(const wxString & guid)2173 RoutePoint *WayPointman::FindRoutePointByGUID(const wxString &guid)
2174 {
2175 wxRoutePointListNode *prpnode = m_pWayPointList->GetFirst();
2176 while( prpnode ) {
2177 RoutePoint *prp = prpnode->GetData();
2178
2179 if( prp->m_GUID == guid ) return ( prp );
2180
2181 prpnode = prpnode->GetNext(); //RoutePoint
2182 }
2183
2184 return NULL;
2185 }
2186
GetNearbyWaypoint(double lat,double lon,double radius_meters)2187 RoutePoint *WayPointman::GetNearbyWaypoint( double lat, double lon, double radius_meters )
2188 {
2189 // Iterate on the RoutePoint list, checking distance
2190
2191 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
2192 while( node ) {
2193 RoutePoint *pr = node->GetData();
2194
2195 double a = lat - pr->m_lat;
2196 double b = lon - pr->m_lon;
2197 double l = sqrt( ( a * a ) + ( b * b ) );
2198
2199 if( ( l * 60. * 1852. ) < radius_meters ) return pr;
2200
2201 node = node->GetNext();
2202 }
2203 return NULL;
2204
2205 }
2206
GetOtherNearbyWaypoint(double lat,double lon,double radius_meters,const wxString & guid)2207 RoutePoint *WayPointman::GetOtherNearbyWaypoint( double lat, double lon, double radius_meters,
2208 const wxString &guid )
2209 {
2210 // Iterate on the RoutePoint list, checking distance
2211
2212 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
2213 while( node ) {
2214 RoutePoint *pr = node->GetData();
2215
2216 double a = lat - pr->m_lat;
2217 double b = lon - pr->m_lon;
2218 double l = sqrt( ( a * a ) + ( b * b ) );
2219
2220 if( ( l * 60. * 1852. ) < radius_meters ) if( pr->m_GUID != guid ) return pr;
2221
2222 node = node->GetNext();
2223 }
2224 return NULL;
2225
2226 }
2227
ClearRoutePointFonts(void)2228 void WayPointman::ClearRoutePointFonts( void )
2229 {
2230 // Iterate on the RoutePoint list, clearing Font pointers
2231 // This is typically done globally after a font switch
2232
2233 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
2234 while( node ) {
2235 RoutePoint *pr = node->GetData();
2236
2237 pr->m_pMarkFont = NULL;
2238 node = node->GetNext();
2239 }
2240 }
2241
SharedWptsExist()2242 bool WayPointman::SharedWptsExist()
2243 {
2244 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
2245 while( node ) {
2246 RoutePoint *prp = node->GetData();
2247 if (prp->m_bKeepXRoute && ( prp->m_bIsInRoute || prp == pAnchorWatchPoint1 || prp == pAnchorWatchPoint2))
2248 return true;
2249 node = node->GetNext();
2250 }
2251 return false;
2252 }
2253
DeleteAllWaypoints(bool b_delete_used)2254 void WayPointman::DeleteAllWaypoints( bool b_delete_used )
2255 {
2256 // Iterate on the RoutePoint list, deleting all
2257 wxRoutePointListNode *node = m_pWayPointList->GetFirst();
2258 while( node ) {
2259 RoutePoint *prp = node->GetData();
2260 // if argument is false, then only delete non-route waypoints
2261 if( !prp->m_bIsInLayer && ( prp->GetIconName() != _T("mob") )
2262 && ( ( b_delete_used && prp->m_bKeepXRoute )
2263 || ( ( !prp->m_bIsInRoute )
2264 && !( prp == pAnchorWatchPoint1 ) && !( prp == pAnchorWatchPoint2 ) ) ) ) {
2265 DestroyWaypoint(prp);
2266 delete prp;
2267 node = m_pWayPointList->GetFirst();
2268 } else
2269 node = node->GetNext();
2270 }
2271 return;
2272
2273 }
2274
DestroyWaypoint(RoutePoint * pRp,bool b_update_changeset)2275 void WayPointman::DestroyWaypoint( RoutePoint *pRp, bool b_update_changeset )
2276 {
2277 if( ! b_update_changeset )
2278 pConfig->m_bSkipChangeSetUpdate = true; // turn OFF change-set updating if requested
2279
2280 if( pRp ) {
2281 // Get a list of all routes containing this point
2282 // and remove the point from them all
2283 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining( pRp );
2284 if( proute_array ) {
2285 for( unsigned int ir = 0; ir < proute_array->GetCount(); ir++ ) {
2286 Route *pr = (Route *) proute_array->Item( ir );
2287
2288 /* FS#348
2289 if ( g_pRouteMan->GetpActiveRoute() == pr ) // Deactivate any route containing this point
2290 g_pRouteMan->DeactivateRoute();
2291 */
2292 pr->RemovePoint( pRp );
2293
2294 }
2295
2296 // Scrub the routes, looking for one-point routes
2297 for( unsigned int ir = 0; ir < proute_array->GetCount(); ir++ ) {
2298 Route *pr = (Route *) proute_array->Item( ir );
2299 if( pr->GetnPoints() < 2 ) {
2300 bool prev_bskip = pConfig->m_bSkipChangeSetUpdate;
2301 pConfig->m_bSkipChangeSetUpdate = true;
2302 pConfig->DeleteConfigRoute( pr );
2303 g_pRouteMan->DeleteRoute( pr );
2304 pConfig->m_bSkipChangeSetUpdate = prev_bskip;
2305 }
2306 }
2307
2308 delete proute_array;
2309 }
2310
2311 // Now it is safe to delete the point
2312 pConfig->DeleteWayPoint( pRp );
2313 pConfig->m_bSkipChangeSetUpdate = false;
2314
2315 pSelect->DeleteSelectableRoutePoint( pRp );
2316
2317 // The RoutePoint might be currently in use as an anchor watch point
2318 if( pRp == pAnchorWatchPoint1 ) pAnchorWatchPoint1 = NULL;
2319 if( pRp == pAnchorWatchPoint2 ) pAnchorWatchPoint2 = NULL;
2320
2321 RemoveRoutePoint( pRp);
2322
2323 }
2324 }
2325