OpenCPN Partial API docs
Loading...
Searching...
No Matches
nav_object_database.cpp
1/***************************************************************************
2 * Copyright (C) 2010 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
18#include <wx/string.h>
19
20#include "model/navobj_db.h"
21#include "model/nav_object_database.h"
22#include "model/navutil_base.h"
23#include "model/route.h"
24#include "model/routeman.h"
25#include "model/select.h"
26#include "model/track.h"
27
28#ifdef __ANDROID__
29#include <QDebug>
30#endif
31
32NavObjectCollection1::NavObjectCollection1() : pugi::xml_document() {}
33
34NavObjectCollection1::~NavObjectCollection1() {}
35
36RoutePoint *GPXLoadWaypoint1(pugi::xml_node &wpt_node, wxString def_symbol_name,
37 wxString GUID, bool b_fullviz, bool b_layer,
38 bool b_layerviz, int layer_id, bool b_nameviz) {
39 bool bviz = false;
40 bool bviz_name = false;
41 bool bshared = false;
42 bool b_propvizname = false;
43 bool b_propviz = false;
44
45 wxString SymString = def_symbol_name; // default icon
46 wxString NameString;
47 wxString DescString;
48 wxString TideStation;
49 double plan_speed = 0.0;
50 wxString etd;
51 wxString TypeString;
52 wxString GuidString = GUID; // default
53 wxString TimeString;
54 wxDateTime dt;
55 RoutePoint *pWP;
56
57 HyperlinkList *linklist = NULL;
58
59 double rlat = wpt_node.attribute("lat").as_double();
60 double rlon = wpt_node.attribute("lon").as_double();
61 double ArrivalRadius = 0;
62 int l_iWaypointRangeRingsNumber = -1;
63 float l_fWaypointRangeRingsStep = -1;
64 int l_pWaypointRangeRingsStepUnits = -1;
65 bool l_bWaypointRangeRingsVisible = false;
66 long l_iWaypointScaleMin = 2147483646;
67 long l_iWaypoinScaleMax = 0;
68 bool l_bWaypointUseScale = false;
69 wxColour l_wxcWaypointRangeRingsColour;
70 l_wxcWaypointRangeRingsColour.Set("#FFFFFF");
71
72 for (pugi::xml_node child = wpt_node.first_child(); child != 0;
73 child = child.next_sibling()) {
74 const char *pcn = child.name();
75
76 if (!strcmp(pcn, "sym")) {
77 SymString = wxString::FromUTF8(child.first_child().value());
78 } else if (!strcmp(pcn, "time"))
79 TimeString = wxString::FromUTF8(child.first_child().value());
80
81 else if (!strcmp(pcn, "name")) {
82 NameString = wxString::FromUTF8(child.first_child().value());
83 if (NameString.StartsWith("@~~")) {
84 // Convert the legacy tidal event definition and change the name so
85 // that it does not kick in next time and cause overiding subsequent
86 // changes
87 TideStation = NameString.Right(NameString.length() - 3);
88 NameString.Replace("@~~", "@-~");
89 }
90 }
91
92 else if (!strcmp(pcn, "desc")) {
93 DescString = wxString::FromUTF8(child.first_child().value());
94 }
95
96 else if (!strcmp(pcn, "type")) {
97 TypeString = wxString::FromUTF8(child.first_child().value());
98 }
99
100 else // Read hyperlink
101 if (!strcmp(pcn, "link")) {
102 wxString HrefString;
103 wxString HrefTextString;
104 wxString HrefTypeString;
105 if (linklist == NULL) linklist = new HyperlinkList;
106 HrefString = wxString::FromUTF8(child.first_attribute().value());
107
108 for (pugi::xml_node child1 = child.first_child(); child1;
109 child1 = child1.next_sibling()) {
110 wxString LinkString = wxString::FromUTF8(child1.name());
111
112 if (LinkString == "text")
113 HrefTextString = wxString::FromUTF8(child1.first_child().value());
114 if (LinkString == "type")
115 HrefTypeString = wxString::FromUTF8(child1.first_child().value());
116 }
117
118 Hyperlink *link = new Hyperlink;
119 link->Link = HrefString;
120 link->DescrText = HrefTextString;
121 link->LType = HrefTypeString;
122 linklist->push_back(link);
123 }
124
125 // OpenCPN Extensions....
126 else if (!strcmp(pcn, "extensions")) {
127 for (pugi::xml_node ext_child = child.first_child(); ext_child;
128 ext_child = ext_child.next_sibling()) {
129 wxString ext_name = wxString::FromUTF8(ext_child.name());
130 if (ext_name == "opencpn:guid") {
131 GuidString = wxString::FromUTF8(ext_child.first_child().value());
132 } else if (ext_name == "opencpn:viz") {
133 b_propviz = true;
134 wxString s = wxString::FromUTF8(ext_child.first_child().value());
135 long v = 0;
136 if (s.ToLong(&v)) bviz = (v != 0);
137 } else if (ext_name == "opencpn:viz_name") {
138 b_propvizname = true;
139 wxString s = wxString::FromUTF8(ext_child.first_child().value());
140 long v = 0;
141 if (s.ToLong(&v)) bviz_name = (v != 0);
142 } else if (ext_name == "opencpn:shared") {
143 wxString s = wxString::FromUTF8(ext_child.first_child().value());
144 long v = 0;
145 if (s.ToLong(&v)) bshared = (v != 0);
146 }
147 if (ext_name == "opencpn:arrival_radius") {
148 wxString::FromUTF8(ext_child.first_child().value())
149 .ToDouble(&ArrivalRadius);
150 }
151 if (ext_name == "opencpn:waypoint_range_rings") {
152 for (pugi::xml_attribute attr = ext_child.first_attribute(); attr;
153 attr = attr.next_attribute()) {
154 if (wxString::FromUTF8(attr.name()) == "number")
155 l_iWaypointRangeRingsNumber = attr.as_int();
156 else if (wxString::FromUTF8(attr.name()) == "step")
157 l_fWaypointRangeRingsStep = attr.as_float();
158 else if (wxString::FromUTF8(attr.name()) == "units")
159 l_pWaypointRangeRingsStepUnits = attr.as_int();
160 else if (wxString::FromUTF8(attr.name()) == "visible")
161 l_bWaypointRangeRingsVisible = attr.as_bool();
162 else if (wxString::FromUTF8(attr.name()) == "colour")
163 l_wxcWaypointRangeRingsColour.Set(
164 wxString::FromUTF8(attr.as_string()));
165 }
166 }
167 if (ext_name == "opencpn:scale_min_max") {
168 for (pugi::xml_attribute attr = ext_child.first_attribute(); attr;
169 attr = attr.next_attribute()) {
170 if (wxString::FromUTF8(attr.name()) == "UseScale")
171 l_bWaypointUseScale = attr.as_bool();
172 else if (wxString::FromUTF8(attr.name()) == "ScaleMin")
173 l_iWaypointScaleMin = attr.as_int();
174 else if (wxString::FromUTF8(attr.name()) == "ScaleMax")
175 l_iWaypoinScaleMax = attr.as_float();
176 }
177 }
178 if (ext_name == "opencpn:tidestation") {
179 TideStation = wxString::FromUTF8(ext_child.first_child().value());
180 }
181 if (ext_name == "opencpn:rte_properties") {
182 for (pugi::xml_attribute attr = ext_child.first_attribute(); attr;
183 attr = attr.next_attribute()) {
184 if (!strcmp(attr.name(), "planned_speed"))
185 plan_speed = attr.as_double();
186 else if (!strcmp(attr.name(), "etd"))
187 // The timestamp is serialized without timezone information,
188 // e.g., etd="2025-04-03T20:00:27"
189 // So assume the ETD has always been saved in UTC.
190 etd = attr.as_string();
191 }
192 }
193 } // for
194 } // extensions
195 } // for
196
197 // Create waypoint
198
199 if (b_layer) {
200 if (GuidString.IsEmpty()) GuidString = pWayPointMan->CreateGUID(NULL);
201 }
202
203 pWP = new RoutePoint(rlat, rlon, SymString, NameString, GuidString,
204 false); // do not add to global WP list yet...
205 pWP->m_MarkDescription = DescString;
206 pWP->m_TideStation = TideStation;
207 pWP->m_bIsolatedMark = bshared; // This is an isolated mark
208 pWP->SetWaypointArrivalRadius(ArrivalRadius);
209 pWP->SetWaypointRangeRingsNumber(l_iWaypointRangeRingsNumber);
210 pWP->SetWaypointRangeRingsStep(l_fWaypointRangeRingsStep);
211 pWP->SetWaypointRangeRingsStepUnits(l_pWaypointRangeRingsStepUnits);
212 pWP->SetShowWaypointRangeRings(l_bWaypointRangeRingsVisible);
213
214 // Migrate from O4.x XML format.
215 // In O5, the attribute "range rings visible" is synonymous with ( "range
216 // rings number" != 0 ) So, if we see an attribute "visible"=false in
217 // importing from XML, we must set "number" = 0 to be consistent
218 if (!l_bWaypointRangeRingsVisible) pWP->SetWaypointRangeRingsNumber(0);
219
220 pWP->SetWaypointRangeRingsColour(l_wxcWaypointRangeRingsColour);
221 pWP->SetScaMin(l_iWaypointScaleMin);
222 pWP->SetScaMax(l_iWaypoinScaleMax);
223 pWP->SetUseSca(l_bWaypointUseScale);
224 pWP->SetPlannedSpeed(plan_speed);
225 pWP->SetETD(etd);
226
227 pWP->m_bShowNameData = bviz_name;
228 if (b_propvizname)
229 pWP->m_bShowName = bviz_name;
230 else if (b_fullviz)
231 pWP->m_bShowName = true;
232 else
233 pWP->m_bShowName = false;
234
235 // Special case handling of un-named points in a route
236 if (!b_nameviz && !b_propvizname) pWP->m_bShowName = false;
237
238 if (b_propviz)
239 pWP->m_bIsVisible = bviz;
240 else if (b_fullviz)
241 pWP->m_bIsVisible = true;
242
243 if (b_layer) {
244 pWP->m_bIsInLayer = true;
245 pWP->m_LayerID = layer_id;
246 pWP->m_bIsVisible = b_layerviz;
247 pWP->SetListed(false);
248 }
249
250 pWP->SetShared(bshared);
251
252 if (TimeString.Len()) {
253 pWP->m_timestring = TimeString;
254 pWP->SetCreateTime(wxInvalidDateTime); // cause deferred timestamp parsing
255 }
256
257 if (linklist) {
258 delete pWP->m_HyperlinkList; // created in RoutePoint ctor
259 pWP->m_HyperlinkList = linklist;
260 }
261
262 return pWP;
263}
264
265static TrackPoint *GPXLoadTrackPoint1(pugi::xml_node &wpt_node) {
266 wxString TimeString;
267
268 double rlat = wpt_node.attribute("lat").as_double();
269 double rlon = wpt_node.attribute("lon").as_double();
270
271 for (pugi::xml_node child = wpt_node.first_child(); child != 0;
272 child = child.next_sibling()) {
273 const char *pcn = child.name();
274 if (!strcmp(pcn, "time"))
275 TimeString = wxString::FromUTF8(child.first_child().value());
276
277 // OpenCPN Extensions....
278 else if (!strcmp(pcn, "extensions")) {
279 for (pugi::xml_node ext_child = child.first_child(); ext_child;
280 ext_child = ext_child.next_sibling()) {
281 wxString ext_name = wxString::FromUTF8(ext_child.name());
282 if (ext_name == "opencpn:action") {
283 }
284 } // for
285 } // extensions
286 } // for
287
288 // Create trackpoint
289 return new TrackPoint(rlat, rlon, TimeString);
290}
291
292Track *GPXLoadTrack1(pugi::xml_node &trk_node, bool b_fullviz, bool b_layer,
293 bool b_layerviz, int layer_id) {
294 wxString TrackName;
295 wxString DescString;
296 unsigned short int GPXSeg;
297 bool b_propviz = false;
298 bool b_viz = true;
299 Track *pTentTrack = NULL;
300 HyperlinkList *linklist = NULL;
301
302 wxString Name = wxString::FromUTF8(trk_node.name());
303 if (Name == "trk") {
304 pTentTrack = new Track();
305 GPXSeg = 0;
306
307 TrackPoint *pWp = NULL;
308
309 for (pugi::xml_node tschild = trk_node.first_child(); tschild;
310 tschild = tschild.next_sibling()) {
311 wxString ChildName = wxString::FromUTF8(tschild.name());
312 if (ChildName == "trkseg") {
313 GPXSeg += 1;
314
315 // Official GPX spec calls for trkseg to have children trkpt
316 for (pugi::xml_node tpchild = tschild.first_child(); tpchild;
317 tpchild = tpchild.next_sibling()) {
318 wxString tpChildName = wxString::FromUTF8(tpchild.name());
319 if (tpChildName == "trkpt") {
320 pWp = ::GPXLoadTrackPoint1(tpchild);
321 if (pWp) {
322 pTentTrack->AddPoint(pWp); // defer BBox calculation
323 pWp->m_GPXTrkSegNo = GPXSeg;
324 }
325 }
326 }
327 } else if (ChildName == "name")
328 TrackName = wxString::FromUTF8(tschild.first_child().value());
329 else if (ChildName == "desc")
330 DescString = wxString::FromUTF8(tschild.first_child().value());
331 else
332
333 if (ChildName == "link") {
334 wxString HrefString;
335 wxString HrefTextString;
336 wxString HrefTypeString;
337 if (linklist == NULL) linklist = new HyperlinkList;
338 HrefString = wxString::FromUTF8(tschild.first_attribute().value());
339
340 for (pugi::xml_node child1 = tschild.first_child(); child1;
341 child1 = child1.next_sibling()) {
342 wxString LinkString = wxString::FromUTF8(child1.name());
343
344 if (LinkString == "text")
345 HrefTextString = wxString::FromUTF8(child1.first_child().value());
346 if (LinkString == "type")
347 HrefTypeString = wxString::FromUTF8(child1.first_child().value());
348 }
349
350 Hyperlink *link = new Hyperlink;
351 link->Link = HrefString;
352 link->DescrText = HrefTextString;
353 link->LType = HrefTypeString;
354 linklist->push_back(link);
355 }
356
357 else if (ChildName == "extensions") {
358 for (pugi::xml_node ext_child = tschild.first_child(); ext_child;
359 ext_child = ext_child.next_sibling()) {
360 wxString ext_name = wxString::FromUTF8(ext_child.name());
361 if (ext_name == "opencpn:start") {
362 pTentTrack->m_TrackStartString =
363 wxString::FromUTF8(ext_child.first_child().value());
364 } else if (ext_name == "opencpn:end") {
365 pTentTrack->m_TrackEndString =
366 wxString::FromUTF8(ext_child.first_child().value());
367 }
368
369 else if (ext_name == "opencpn:viz") {
370 wxString viz = wxString::FromUTF8(ext_child.first_child().value());
371 b_propviz = true;
372 b_viz = (viz == "1");
373 } else if (ext_name == "opencpn:style") {
374 for (pugi::xml_attribute attr = ext_child.first_attribute(); attr;
375 attr = attr.next_attribute()) {
376 if (!strcmp(attr.name(), "style"))
377 pTentTrack->m_style = (wxPenStyle)attr.as_int();
378 else if (!strcmp(attr.name(), "width"))
379 pTentTrack->m_width = attr.as_int();
380 }
381 }
382
383 else if (ext_name == "opencpn:guid") {
384 pTentTrack->m_GUID =
385 wxString::FromUTF8(ext_child.first_child().value());
386 }
387
388 else if (ext_name.EndsWith("TrackExtension")) // Parse GPXX color
389 {
390 for (pugi::xml_node gpxx_child = ext_child.first_child();
391 gpxx_child; gpxx_child = gpxx_child.next_sibling()) {
392 wxString gpxx_name = wxString::FromUTF8(gpxx_child.name());
393 if (gpxx_name.EndsWith("DisplayColor"))
394 pTentTrack->m_Colour =
395 wxString::FromUTF8(gpxx_child.first_child().value());
396 }
397 }
398 } // extensions
399 }
400 }
401
402 pTentTrack->SetName(TrackName);
403 pTentTrack->m_TrackDescription = DescString;
404
405 if (b_propviz)
406 pTentTrack->SetVisible(b_viz);
407 else {
408 if (b_fullviz) pTentTrack->SetVisible();
409 }
410
411 if (b_layer) {
412 pTentTrack->SetVisible(b_layerviz);
413 pTentTrack->m_bIsInLayer = true;
414 pTentTrack->m_LayerID = layer_id;
415 pTentTrack->SetListed(false);
416 }
417
418 pTentTrack->SetCurrentTrackSeg(GPXSeg);
419 }
420
421 if (linklist) {
422 delete pTentTrack->m_TrackHyperlinkList; // created in TrackPoint ctor
423 pTentTrack->m_TrackHyperlinkList = linklist;
424 }
425
426 return pTentTrack;
427}
428
429Route *GPXLoadRoute1(pugi::xml_node &wpt_node, bool b_fullviz, bool b_layer,
430 bool b_layerviz, int layer_id, bool b_change,
431 bool load_points) {
432 wxString RouteName;
433 wxString DescString;
434 bool b_propviz = false;
435 bool b_propSWPviz = false;
436 bool b_viz = true;
437 bool swpViz = false;
438 Route *pTentRoute = NULL;
439
440 wxString Name = wxString::FromUTF8(wpt_node.name());
441 if (Name == "rte") {
442 pTentRoute = new Route();
443 HyperlinkList *linklist = NULL;
444
445 RoutePoint *pWp = NULL;
446 bool route_existing = false;
447 pTentRoute->m_TimeDisplayFormat = RTE_TIME_DISP_UTC;
448
449 for (pugi::xml_node tschild = wpt_node.first_child(); tschild;
450 tschild = tschild.next_sibling()) {
451 wxString ChildName = wxString::FromUTF8(tschild.name());
452
453 // load extentions first to determine if the route still exists
454 if (ChildName == "extensions") {
455 for (pugi::xml_node ext_child = tschild.first_child(); ext_child;
456 ext_child = ext_child.next_sibling()) {
457 wxString ext_name = wxString::FromUTF8(ext_child.name());
458
459 if (ext_name == "opencpn:start") {
460 pTentRoute->m_RouteStartString =
461 wxString::FromUTF8(ext_child.first_child().value());
462 } else if (ext_name == "opencpn:end") {
463 pTentRoute->m_RouteEndString =
464 wxString::FromUTF8(ext_child.first_child().value());
465 }
466
467 else if (ext_name == "opencpn:viz") {
468 wxString viz = wxString::FromUTF8(ext_child.first_child().value());
469 b_propviz = true;
470 b_viz = (viz == "1");
471 }
472
473 else if (ext_name == "opencpn:sharedWPviz") {
474 wxString viz = wxString::FromUTF8(ext_child.first_child().value());
475 b_propSWPviz = true;
476 swpViz = (viz == "1");
477 } else if (ext_name == "opencpn:style") {
478 for (pugi::xml_attribute attr = ext_child.first_attribute(); attr;
479 attr = attr.next_attribute()) {
480 if (!strcmp(attr.name(), "style"))
481 pTentRoute->m_style = (wxPenStyle)attr.as_int();
482 else if (!strcmp(attr.name(), "width"))
483 pTentRoute->m_width = attr.as_int();
484 }
485 }
486
487 else if (ext_name == "opencpn:guid") {
488 pTentRoute->m_GUID =
489 wxString::FromUTF8(ext_child.first_child().value());
490 }
491
492 else if (ext_name == "opencpn:planned_speed") {
493 pTentRoute->m_PlannedSpeed = atof(ext_child.first_child().value());
494 }
495
496 else if (ext_name == "opencpn:planned_departure") {
498 pTentRoute->m_PlannedDeparture,
499 wxString::FromUTF8(ext_child.first_child().value()));
500 }
501
502 else if (ext_name == "opencpn:time_display") {
503 pTentRoute->m_TimeDisplayFormat =
504 wxString::FromUTF8(ext_child.first_child().value());
505 } else if (ext_name.EndsWith("RouteExtension")) // Parse GPXX color
506 {
507 for (pugi::xml_node gpxx_child = ext_child.first_child();
508 gpxx_child; gpxx_child = gpxx_child.next_sibling()) {
509 wxString gpxx_name = wxString::FromUTF8(gpxx_child.name());
510 if (gpxx_name.EndsWith("DisplayColor"))
511 pTentRoute->m_Colour =
512 wxString::FromUTF8(gpxx_child.first_child().value());
513 }
514 }
515 }
516 if (!b_change) {
517 if (RouteExists(pTentRoute->m_GUID)) { // we are loading a different
518 // route with the same guid so
519 // let's generate a new guid
520 pTentRoute->m_GUID = pWayPointMan->CreateGUID(NULL);
521 route_existing = true;
522 }
523 }
524 } // extension
525 else if (load_points && ChildName == "rtept") {
526 RoutePoint *tpWp =
527 ::GPXLoadWaypoint1(tschild, "square", "", b_fullviz, b_layer,
528 b_layerviz, layer_id, false);
529 RoutePoint *erp = NULL;
530 if (!b_layer) erp = ::WaypointExists(tpWp->m_GUID);
531 // 1) if b_change is true, that means we are after crash - load the
532 // route and points as found in source file 2) if route_existing, we are
533 // loading a different route with the same guid. In this case load
534 // points as found in
535 // source file, changing the guid, but keep existing "isolated point" as
536 // found in the DB
537 // 3) in all other cases keep existing points if found and load new
538 // points if not found
539 bool new_wpt = true;
540 if (b_change) {
541 pWp = tpWp;
542 } else {
543 if (erp != NULL &&
544 (!route_existing || (route_existing && tpWp->IsShared()))) {
545 pWp = erp;
546 new_wpt = false;
547 } else {
548 if (route_existing) tpWp->m_GUID = pWayPointMan->CreateGUID(NULL);
549 pWp = tpWp;
550 }
551 }
552
553 pTentRoute->AddPoint(pWp, false, true); // defer BBox calculation
554 pWp->m_bIsInRoute = true; // Hack
555
556 if (new_wpt) {
557 if (erp == NULL) {
558 pWayPointMan->AddRoutePoint(pWp);
559 }
560 } else {
561 delete tpWp;
562 }
563 } else if (ChildName == "name") {
564 RouteName = wxString::FromUTF8(tschild.first_child().value());
565 } else if (ChildName == "desc") {
566 DescString = wxString::FromUTF8(tschild.first_child().value());
567 }
568
569 if (ChildName == "link") {
570 wxString HrefString;
571 wxString HrefTextString;
572 wxString HrefTypeString;
573 if (linklist == NULL) linklist = new HyperlinkList;
574 HrefString = wxString::FromUTF8(tschild.first_attribute().value());
575
576 for (pugi::xml_node child1 = tschild.first_child(); child1;
577 child1 = child1.next_sibling()) {
578 wxString LinkString = wxString::FromUTF8(child1.name());
579
580 if (LinkString == "text")
581 HrefTextString = wxString::FromUTF8(child1.first_child().value());
582 if (LinkString == "type")
583 HrefTypeString = wxString::FromUTF8(child1.first_child().value());
584 }
585
586 Hyperlink *link = new Hyperlink;
587 link->Link = HrefString;
588 link->DescrText = HrefTextString;
589 link->LType = HrefTypeString;
590 linklist->push_back(link);
591 }
592
593 else
594 // TODO: This is wrong, left here just to save data of the 3.3 beta
595 // series users.
596 if (ChildName.EndsWith("RouteExtension")) // Parse GPXX color
597 {
598 for (pugi::xml_node gpxx_child = tschild.first_child(); gpxx_child;
599 gpxx_child = gpxx_child.next_sibling()) {
600 wxString gpxx_name = wxString::FromUTF8(gpxx_child.name());
601 if (gpxx_name.EndsWith("DisplayColor"))
602 pTentRoute->m_Colour =
603 wxString::FromUTF8(gpxx_child.first_child().value());
604 }
605 }
606 }
607
608 pTentRoute->m_RouteNameString = RouteName;
609 pTentRoute->m_RouteDescription = DescString;
610 if (linklist) {
611 pTentRoute->m_HyperlinkList = linklist;
612 }
613
614 if (b_propviz) {
615 pTentRoute->SetVisible(b_viz);
616 } else if (b_fullviz) {
617 pTentRoute->SetVisible();
618 }
619
620 if (b_propSWPviz) pTentRoute->SetSharedWPViz(swpViz);
621
622 if (b_layer) {
623 pTentRoute->SetVisible(b_layerviz);
624 pTentRoute->m_bIsInLayer = true;
625 pTentRoute->m_LayerID = layer_id;
626 pTentRoute->SetListed(false);
627 }
628 }
629
630 return pTentRoute;
631}
632
633static bool GPXCreateWpt(pugi::xml_node node, RoutePoint *pr,
634 unsigned int flags) {
635 wxString s;
636 pugi::xml_node child;
638
639 s.Printf("%.9f", pr->m_lat);
640 node.append_attribute("lat") = s.mb_str();
641 s.Printf("%.9f", pr->m_lon);
642 node.append_attribute("lon") = s.mb_str();
643
644 if (flags & OUT_TIME) {
645 child = node.append_child("time");
646 if (pr->m_timestring.Len())
647 child.append_child(pugi::node_pcdata)
648 .set_value(pr->m_timestring.mb_str());
649 else {
650 wxDateTime dt = pr->GetCreateTime();
651 if (!dt.IsValid()) dt = wxDateTime::Now();
652
653 wxString t = dt.ToUTC()
654 .FormatISODate()
655 .Append("T")
656 .Append(dt.ToUTC().FormatISOTime())
657 .Append("Z");
658 child.append_child(pugi::node_pcdata).set_value(t.mb_str());
659 }
660 }
661
662 if ((!pr->GetName().IsEmpty() && (flags & OUT_NAME)) ||
663 (flags & OUT_NAME_FORCE)) {
664 wxCharBuffer buffer = pr->GetName().ToUTF8();
665 if (buffer.data()) {
666 child = node.append_child("name");
667 child.append_child(pugi::node_pcdata).set_value(buffer.data());
668 }
669 }
670
671 if ((!pr->GetDescription().IsEmpty() && (flags & OUT_DESC)) ||
672 (flags & OUT_DESC_FORCE)) {
673 wxCharBuffer buffer = pr->GetDescription().ToUTF8();
674 if (buffer.data()) {
675 child = node.append_child("desc");
676 child.append_child(pugi::node_pcdata).set_value(buffer.data());
677 }
678 }
679
680 // Hyperlinks
681 if (flags & OUT_HYPERLINKS) {
682 HyperlinkList *linklist = pr->m_HyperlinkList;
683 if (linklist && linklist->size()) {
684 for (Hyperlink *link : *pr->m_HyperlinkList) {
685 pugi::xml_node child_link = node.append_child("link");
686 ;
687 wxCharBuffer buffer = link->Link.ToUTF8();
688 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
689
690 buffer = link->DescrText.ToUTF8();
691 if (buffer.data()) {
692 child = child_link.append_child("text");
693 child.append_child(pugi::node_pcdata).set_value(buffer.data());
694 }
695
696 buffer = link->LType.ToUTF8();
697 if (buffer.data() && strlen(buffer.data()) > 0) {
698 child = child_link.append_child("type");
699 child.append_child(pugi::node_pcdata).set_value(buffer.data());
700 }
701 }
702 }
703 }
704
705 if (flags & OUT_SYM_FORCE) {
706 child = node.append_child("sym");
707 if (!pr->GetIconName().IsEmpty()) {
708 child.append_child(pugi::node_pcdata)
709 .set_value(pr->GetIconName().mb_str());
710 } else {
711 child.append_child("empty");
712 }
713 }
714
715 if (flags & OUT_TYPE) {
716 child = node.append_child("type");
717 child.append_child(pugi::node_pcdata).set_value("WPT");
718 }
719
720 if ((flags & OUT_GUID) || (flags & OUT_VIZ) || (flags & OUT_VIZ_NAME) ||
721 (flags & OUT_SHARED) || (flags & OUT_EXTENSION) ||
722 (flags & OUT_TIDE_STATION) || (flags & OUT_RTE_PROPERTIES)) {
723 pugi::xml_node child_ext = node.append_child("extensions");
724
725 if (!pr->m_GUID.IsEmpty() && (flags & OUT_GUID)) {
726 child = child_ext.append_child("opencpn:guid");
727 child.append_child(pugi::node_pcdata).set_value(pr->m_GUID.mb_str());
728 }
729
730 if ((flags & OUT_VIZ) && !pr->m_bIsVisible) {
731 child = child_ext.append_child("opencpn:viz");
732 child.append_child(pugi::node_pcdata).set_value("0");
733 }
734
735 if ((flags & OUT_VIZ_NAME) && pr->m_bShowName) {
736 child = child_ext.append_child("opencpn:viz_name");
737 child.append_child(pugi::node_pcdata).set_value("1");
738 }
739
740 if ((flags & OUT_SHARED) && pr->IsShared()) {
741 child = child_ext.append_child("opencpn:shared");
742 child.append_child(pugi::node_pcdata).set_value("1");
743 }
744 if (flags & OUT_ARRIVAL_RADIUS) {
745 child = child_ext.append_child("opencpn:arrival_radius");
746 s.Printf("%.3f", pr->GetWaypointArrivalRadius());
747 child.append_child(pugi::node_pcdata).set_value(s.mbc_str());
748 }
749 if (flags & OUT_WAYPOINT_RANGE_RINGS) {
750 child = child_ext.append_child("opencpn:waypoint_range_rings");
751 pugi::xml_attribute viz = child.append_attribute("visible");
752 viz.set_value(pr->m_bShowWaypointRangeRings);
753 pugi::xml_attribute number = child.append_attribute("number");
754 number.set_value(pr->m_iWaypointRangeRingsNumber);
755 pugi::xml_attribute step = child.append_attribute("step");
756 step.set_value(pr->m_fWaypointRangeRingsStep);
757 pugi::xml_attribute units = child.append_attribute("units");
758 units.set_value(pr->m_iWaypointRangeRingsStepUnits);
759
760 // Color specification in GPX file must be fully opaque
761 if (pr->m_wxcWaypointRangeRingsColour.IsOk()) {
765 pr->m_wxcWaypointRangeRingsColour.Blue(), wxALPHA_OPAQUE);
766 } else {
767 pr->m_wxcWaypointRangeRingsColour.Set(0, 0, 0, wxALPHA_OPAQUE);
768 }
769
770 pugi::xml_attribute colour = child.append_attribute("colour");
771 colour.set_value(
772 pr->m_wxcWaypointRangeRingsColour.GetAsString(wxC2S_HTML_SYNTAX)
773 .utf8_str());
774 }
775 if (flags & OUT_WAYPOINT_SCALE) {
776 child = child_ext.append_child("opencpn:scale_min_max");
777 pugi::xml_attribute use = child.append_attribute("UseScale");
778 use.set_value(pr->GetUseSca());
779 pugi::xml_attribute sca = child.append_attribute("ScaleMin");
780 sca.set_value(pr->GetScaMin());
781 pugi::xml_attribute max = child.append_attribute("ScaleMax");
782 max.set_value(pr->GetScaMax());
783 }
784 if ((flags & OUT_TIDE_STATION) && !pr->m_TideStation.IsEmpty()) {
785 child = child_ext.append_child("opencpn:tidestation");
786 child.append_child(pugi::node_pcdata)
787 .set_value(pr->m_TideStation.mb_str());
788 }
789 if ((flags & OUT_RTE_PROPERTIES) &&
790 (pr->GetPlannedSpeed() > 0.0001 || pr->m_manual_etd)) {
791 child = child_ext.append_child("opencpn:rte_properties");
792 if (pr->GetPlannedSpeed() > 0.0001) {
793 pugi::xml_attribute use = child.append_attribute("planned_speed");
794 use.set_value(
795 wxString::Format("%.1lf", pr->GetPlannedSpeed()).mb_str());
796 }
797 if (pr->m_manual_etd && pr->GetManualETD().IsValid()) {
798 pugi::xml_attribute use = child.append_attribute("etd");
799 // Currently, the serialization format is YYYY-MM-DDTHH:MM:SS
800 // without timezone information, e.g., etd="2025-04-03T20:00:27"
801 // TODO: serialize using ISO 8601 or RFC 3339 format to ensure
802 // the serialized date/time is unambiguous.
803 use.set_value(pr->GetManualETD().FormatISOCombined().mb_str());
804 }
805 }
806 }
807
808 return true;
809}
810
811static bool GPXCreateTrkpt(pugi::xml_node node, TrackPoint *pt,
812 unsigned int flags) {
813 wxString s;
814 pugi::xml_node child;
816
817 s.Printf("%.9f", pt->m_lat);
818 node.append_attribute("lat") = s.mb_str();
819 s.Printf("%.9f", pt->m_lon);
820 node.append_attribute("lon") = s.mb_str();
821
822 if (flags & OUT_TIME && pt->HasValidTimestamp()) {
823 child = node.append_child("time");
824 child.append_child(pugi::node_pcdata).set_value(pt->GetTimeString());
825 }
826
827 return true;
828}
829
830static bool GPXCreateTrk(pugi::xml_node node, Track *pTrack,
831 unsigned int flags) {
832 pugi::xml_node child;
833
834 if (pTrack->GetName().Len()) {
835 wxCharBuffer buffer = pTrack->GetName().ToUTF8();
836 if (buffer.data()) {
837 child = node.append_child("name");
838 child.append_child(pugi::node_pcdata).set_value(buffer.data());
839 }
840 }
841
842 if (pTrack->m_TrackDescription.Len()) {
843 wxCharBuffer buffer = pTrack->m_TrackDescription.ToUTF8();
844 if (buffer.data()) {
845 child = node.append_child("desc");
846 child.append_child(pugi::node_pcdata).set_value(buffer.data());
847 }
848 }
849
850 // Hyperlinks
851 HyperlinkList *linklist = pTrack->m_TrackHyperlinkList;
852 if (linklist && linklist->size()) {
853 for (Hyperlink *link : *linklist) {
854 pugi::xml_node child_link = node.append_child("link");
855 wxCharBuffer buffer = link->Link.ToUTF8();
856 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
857
858 buffer = link->DescrText.ToUTF8();
859 if (buffer.data()) {
860 child = child_link.append_child("text");
861 child.append_child(pugi::node_pcdata).set_value(buffer.data());
862 }
863
864 buffer = link->LType.ToUTF8();
865 if (buffer.data() && strlen(buffer.data()) > 0) {
866 child = child_link.append_child("type");
867 child.append_child(pugi::node_pcdata).set_value(buffer.data());
868 }
869 }
870 }
871
872 pugi::xml_node child_ext = node.append_child("extensions");
873
874 child = child_ext.append_child("opencpn:guid");
875 child.append_child(pugi::node_pcdata).set_value(pTrack->m_GUID.mb_str());
876
877 child = child_ext.append_child("opencpn:viz");
878 child.append_child(pugi::node_pcdata)
879 .set_value(pTrack->IsVisible() == true ? "1" : "0");
880
881 if (pTrack->m_TrackStartString.Len()) {
882 wxCharBuffer buffer = pTrack->m_TrackStartString.ToUTF8();
883 if (buffer.data()) {
884 child = child_ext.append_child("opencpn:start");
885 child.append_child(pugi::node_pcdata).set_value(buffer.data());
886 }
887 }
888
889 if (pTrack->m_TrackEndString.Len()) {
890 wxCharBuffer buffer = pTrack->m_TrackEndString.ToUTF8();
891 if (buffer.data()) {
892 child = child_ext.append_child("opencpn:end");
893 child.append_child(pugi::node_pcdata).set_value(buffer.data());
894 }
895 }
896
897 if (pTrack->m_width != WIDTH_UNDEFINED ||
898 pTrack->m_style != wxPENSTYLE_INVALID) {
899 child = child_ext.append_child("opencpn:style");
900
901 if (pTrack->m_width != WIDTH_UNDEFINED)
902 child.append_attribute("width") = pTrack->m_width;
903 if (pTrack->m_style != wxPENSTYLE_INVALID)
904 child.append_attribute("style") = pTrack->m_style;
905 }
906
907 if (pTrack->m_Colour != "") {
908 pugi::xml_node gpxx_ext = child_ext.append_child("gpxx:TrackExtension");
909 child = gpxx_ext.append_child("gpxx:DisplayColor");
910 child.append_child(pugi::node_pcdata).set_value(pTrack->m_Colour.mb_str());
911 }
912
913 if (flags & RT_OUT_NO_RTPTS) return true;
914
915 int node2 = 0;
916 TrackPoint *prp;
917
918 unsigned short int GPXTrkSegNo1 = 1;
919
920 do {
921 unsigned short int GPXTrkSegNo2 = GPXTrkSegNo1;
922
923 pugi::xml_node seg = node.append_child("trkseg");
924
925 while (node2 < pTrack->GetnPoints()) {
926 prp = pTrack->GetPoint(node2);
927 GPXTrkSegNo1 = prp->m_GPXTrkSegNo;
928 if (GPXTrkSegNo1 != GPXTrkSegNo2) break;
929
930 GPXCreateTrkpt(seg.append_child("trkpt"), prp, OPT_TRACKPT);
931
932 node2++;
933 }
934 } while (node2 < pTrack->GetnPoints());
935
936 return true;
937}
938
939static bool GPXCreateRoute(pugi::xml_node node, Route *pRoute) {
940 pugi::xml_node child;
941
942 if (pRoute->m_RouteNameString.Len()) {
943 wxCharBuffer buffer = pRoute->m_RouteNameString.ToUTF8();
944 if (buffer.data()) {
945 child = node.append_child("name");
946 child.append_child(pugi::node_pcdata).set_value(buffer.data());
947 }
948 }
949
950 if (pRoute->m_RouteDescription.Len()) {
951 wxCharBuffer buffer = pRoute->m_RouteDescription.ToUTF8();
952 if (buffer.data()) {
953 child = node.append_child("desc");
954 child.append_child(pugi::node_pcdata).set_value(buffer.data());
955 }
956 }
957
958 // Hyperlinks
959 HyperlinkList *linklist = pRoute->m_HyperlinkList;
960 if (linklist && linklist->size()) {
961 for (Hyperlink *link : *linklist) {
962 pugi::xml_node child_link = node.append_child("link");
963 wxCharBuffer buffer = link->Link.ToUTF8();
964 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
965
966 buffer = link->DescrText.ToUTF8();
967 if (buffer.data()) {
968 child = child_link.append_child("text");
969 child.append_child(pugi::node_pcdata).set_value(buffer.data());
970 }
971 buffer = link->LType.ToUTF8();
972 if (buffer.data() && strlen(buffer.data()) > 0) {
973 child = child_link.append_child("type");
974 child.append_child(pugi::node_pcdata).set_value(buffer.data());
975 }
976 }
977 }
978
979 pugi::xml_node child_ext = node.append_child("extensions");
980
981 child = child_ext.append_child("opencpn:guid");
982 child.append_child(pugi::node_pcdata).set_value(pRoute->m_GUID.mb_str());
983
984 child = child_ext.append_child("opencpn:viz");
985 child.append_child(pugi::node_pcdata)
986 .set_value(pRoute->IsVisible() == true ? "1" : "0");
987
988 if (pRoute->ContainsSharedWP()) {
989 child = child_ext.append_child("opencpn:sharedWPviz");
990 child.append_child(pugi::node_pcdata)
991 .set_value(pRoute->GetSharedWPViz() == true ? "1" : "0");
992 }
993
994 if (pRoute->m_RouteStartString.Len()) {
995 wxCharBuffer buffer = pRoute->m_RouteStartString.ToUTF8();
996 if (buffer.data()) {
997 child = child_ext.append_child("opencpn:start");
998 child.append_child(pugi::node_pcdata).set_value(buffer.data());
999 }
1000 }
1001
1002 if (pRoute->m_RouteEndString.Len()) {
1003 wxCharBuffer buffer = pRoute->m_RouteEndString.ToUTF8();
1004 if (buffer.data()) {
1005 child = child_ext.append_child("opencpn:end");
1006 child.append_child(pugi::node_pcdata).set_value(buffer.data());
1007 }
1008 }
1009
1010 if (pRoute->m_PlannedSpeed != ROUTE_DEFAULT_SPEED) {
1011 child = child_ext.append_child("opencpn:planned_speed");
1012 wxString s;
1013 s.Printf("%.2f", pRoute->m_PlannedSpeed);
1014 child.append_child(pugi::node_pcdata).set_value(s.mb_str());
1015 }
1016
1017 if (pRoute->m_PlannedDeparture.IsValid()) {
1018 child = child_ext.append_child("opencpn:planned_departure");
1019 wxString t = pRoute->m_PlannedDeparture.FormatISODate()
1020 .Append("T")
1021 .Append(pRoute->m_PlannedDeparture.FormatISOTime())
1022 .Append("Z");
1023 child.append_child(pugi::node_pcdata).set_value(t.mb_str());
1024 }
1025
1026 child = child_ext.append_child("opencpn:time_display");
1027 child.append_child(pugi::node_pcdata)
1028 .set_value(pRoute->m_TimeDisplayFormat.mb_str());
1029
1030 if (pRoute->m_width != WIDTH_UNDEFINED ||
1031 pRoute->m_style != wxPENSTYLE_INVALID) {
1032 child = child_ext.append_child("opencpn:style");
1033
1034 if (pRoute->m_width != WIDTH_UNDEFINED)
1035 child.append_attribute("width") = pRoute->m_width;
1036 if (pRoute->m_style != wxPENSTYLE_INVALID)
1037 child.append_attribute("style") = pRoute->m_style;
1038 }
1039
1040 pugi::xml_node gpxx_ext = child_ext.append_child("gpxx:RouteExtension");
1041 child = gpxx_ext.append_child("gpxx:IsAutoNamed");
1042 child.append_child(pugi::node_pcdata).set_value("false");
1043
1044 if (pRoute->m_Colour != "") {
1045 child = gpxx_ext.append_child("gpxx:DisplayColor");
1046 child.append_child(pugi::node_pcdata).set_value(pRoute->m_Colour.mb_str());
1047 }
1048
1049 RoutePointList *pRoutePointList = pRoute->pRoutePointList;
1050 for (RoutePoint *prp : *pRoutePointList) {
1051 GPXCreateWpt(node.append_child("rtept"), prp, OPT_ROUTEPT);
1052 }
1053 return true;
1054}
1055
1056bool InsertRouteA(Route *pTentRoute, NavObjectCollection1 *navobj) {
1057 if (!pTentRoute) return false;
1058
1059 bool bAddroute = true;
1060 // If the route has only 1 point, don't load it.
1061 if (pTentRoute->GetnPoints() < 2) bAddroute = false;
1062
1063 // TODO All this trouble for a tentative route.......Should make some
1064 // Route methods????
1065 if (bAddroute) {
1066 pRouteList->push_back(pTentRoute);
1067
1068 // Do the (deferred) calculation of BBox
1069 pTentRoute->FinalizeForRendering();
1070
1071 // Add the selectable points and segments
1072
1073 int ip = 0;
1074 float prev_rlat = 0., prev_rlon = 0.;
1075 RoutePoint *prev_pConfPoint = NULL;
1076
1077 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1078 if (ip)
1079 pSelect->AddSelectableRouteSegment(prev_rlat, prev_rlon, prp->m_lat,
1080 prp->m_lon, prev_pConfPoint, prp,
1081 pTentRoute);
1082 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, prp);
1083 prev_rlat = prp->m_lat;
1084 prev_rlon = prp->m_lon;
1085 prev_pConfPoint = prp;
1086
1087 ip++;
1088 }
1089 } else {
1090 // walk the route, deleting points used only by this route
1091 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1092 // check all other routes to see if this point appears in any other route
1093 Route *pcontainer_route = g_pRouteMan->FindRouteContainingWaypoint(prp);
1094 if (pcontainer_route == NULL) {
1095 // Take this point out of this (and only) track/route
1096 prp->m_bIsInRoute = false;
1097 if (!prp->IsShared()) {
1098 if (navobj) {
1099 delete prp;
1100 }
1101 }
1102 }
1103 }
1104
1105 delete pTentRoute;
1106 }
1107 return bAddroute;
1108}
1109
1110bool InsertTrack(Track *pTentTrack, bool bApplyChanges) {
1111 if (!pTentTrack) return false;
1112
1113 bool bAddtrack = true;
1114 // If the track has only 1 point, don't load it.
1115 // This usually occurs if some points were discarded as being co-incident.
1116 if (!bApplyChanges && pTentTrack->GetnPoints() < 2) bAddtrack = false;
1117
1118 // TODO All this trouble for a tentative track.......Should make some
1119 // Track methods????
1120 if (bAddtrack) {
1121 g_TrackList.push_back(pTentTrack);
1122
1123 // Do the (deferred) calculation of Track BBox
1124 // pTentTrack->FinalizeForRendering();
1125
1126 // Add the selectable points and segments
1127
1128 float prev_rlat = 0., prev_rlon = 0.;
1129 TrackPoint *prev_pConfPoint = NULL;
1130
1131 for (int i = 0; i < pTentTrack->GetnPoints(); i++) {
1132 TrackPoint *prp = pTentTrack->GetPoint(i);
1133
1134 if (i)
1135 pSelect->AddSelectableTrackSegment(prev_rlat, prev_rlon, prp->m_lat,
1136 prp->m_lon, prev_pConfPoint, prp,
1137 pTentTrack);
1138
1139 prev_rlat = prp->m_lat;
1140 prev_rlon = prp->m_lon;
1141 prev_pConfPoint = prp;
1142 }
1143 } else
1144 delete pTentTrack;
1145
1146 return bAddtrack;
1147}
1148
1149bool InsertWpt(RoutePoint *pWp, bool overwrite) {
1150 bool res = false;
1151 RoutePoint *pExisting =
1152 WaypointExists(pWp->GetName(), pWp->m_lat, pWp->m_lon);
1153 if (!pExisting || overwrite) {
1154 if (NULL != pWayPointMan) {
1155 if (pExisting) {
1156 pWayPointMan->DestroyWaypoint(pExisting);
1157 }
1158 pWayPointMan->AddRoutePoint(pWp);
1159 res = true;
1160 }
1161 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1162 }
1163 return res;
1164}
1165
1166static void UpdateRouteA(Route *pTentRoute, NavObjectCollection1 *navobj) {
1167 if (!pTentRoute) return;
1168 if (pTentRoute->GetnPoints() < 2) return;
1169
1170 // first delete the route to be modified if exists
1171 Route *pExisting = ::RouteExists(pTentRoute->m_GUID);
1172 if (pExisting) {
1173 g_pRouteMan->DeleteRoute(pExisting);
1174 }
1175
1176 // create a new route
1177 Route *pChangeRoute = new Route();
1178 pRouteList->push_back(pChangeRoute);
1179
1180 // update new route keeping the same gui
1181 pChangeRoute->m_GUID = pTentRoute->m_GUID;
1182 pChangeRoute->m_RouteNameString = pTentRoute->m_RouteNameString;
1183 pChangeRoute->m_RouteStartString = pTentRoute->m_RouteStartString;
1184 pChangeRoute->m_RouteEndString = pTentRoute->m_RouteEndString;
1185 pChangeRoute->SetVisible(pTentRoute->IsVisible());
1186
1187 // Add points and segments to new route
1188 int ip = 0;
1189 float prev_rlat = 0., prev_rlon = 0.;
1190 RoutePoint *prev_pConfPoint = NULL;
1191
1192 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1193 // if some wpts have been not deleted, that means they should be used in
1194 // other routes or are isolated way points so need to be updated
1195 RoutePoint *ex_rp = ::WaypointExists(prp->m_GUID);
1196 if (ex_rp) {
1197 pSelect->DeleteSelectableRoutePoint(ex_rp);
1198 ex_rp->m_lat = prp->m_lat;
1199 ex_rp->m_lon = prp->m_lon;
1200 ex_rp->SetIconName(prp->GetIconName());
1201 ex_rp->m_MarkDescription = prp->m_MarkDescription;
1202 ex_rp->SetName(prp->GetName());
1203 ex_rp->m_TideStation = prp->m_TideStation;
1204 ex_rp->SetPlannedSpeed(prp->GetPlannedSpeed());
1205 pChangeRoute->AddPoint(ex_rp);
1206 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, ex_rp);
1207
1208 } else {
1209 pChangeRoute->AddPoint(prp);
1210 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, prp);
1211 pWayPointMan->AddRoutePoint(prp);
1212 }
1213
1214 if (ip)
1215 pSelect->AddSelectableRouteSegment(prev_rlat, prev_rlon, prp->m_lat,
1216 prp->m_lon, prev_pConfPoint, prp,
1217 pChangeRoute);
1218 prev_rlat = prp->m_lat;
1219 prev_rlon = prp->m_lon;
1220 prev_pConfPoint = prp;
1221
1222 ip++;
1223 }
1224 // Do the (deferred) calculation of BBox
1225 pChangeRoute->FinalizeForRendering();
1226}
1227
1228Route *FindRouteContainingWaypoint(RoutePoint *pWP) {
1229 for (Route *proute : *pRouteList) {
1230 for (RoutePoint *prp : *proute->pRoutePointList) {
1231 if (prp == pWP) return proute;
1232 }
1233 }
1234
1235 return NULL; // not found
1236}
1237
1238bool NavObjectCollection1::CreateNavObjGPXPoints() {
1239 // Iterate over the Routepoint list, creating Nodes for
1240 // Routepoints that are not in any Route
1241 // as indicated by m_bIsolatedMark == false
1242
1243 if (!pWayPointMan) return false;
1244
1245 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1246 if ((pr->m_bIsolatedMark) && !(pr->m_bIsInLayer) && !(pr->m_btemp)) {
1247 pugi::xml_node doc = root();
1248 pugi::xml_node gpx = doc.first_child();
1249 pugi::xml_node new_node = gpx.append_child("wpt");
1250
1251 GPXCreateWpt(new_node, pr, OPT_WPT);
1252 }
1253 }
1254
1255 return true;
1256}
1257
1258bool NavObjectCollection1::CreateNavObjGPXRoutes() {
1259 // Routes
1260 if (!pRouteList) return false;
1261 for (Route *pRoute : *pRouteList) {
1262 if (!pRoute->m_bIsInLayer && !pRoute->m_btemp) {
1263 pugi::xml_node doc = root();
1264 pugi::xml_node gpx = doc.first_child();
1265 pugi::xml_node new_node = gpx.append_child("rte");
1266
1267 GPXCreateRoute(new_node, pRoute);
1268 }
1269 }
1270
1271 return true;
1272}
1273
1274bool NavObjectCollection1::CreateNavObjGPXTracks() {
1275 // Tracks
1276 for (Track *pTrack : g_TrackList) {
1277 if (pTrack->GetnPoints()) {
1278 if (!pTrack->m_bIsInLayer && !pTrack->m_btemp) {
1279 pugi::xml_node doc = root();
1280 pugi::xml_node gpx = doc.first_child();
1281 pugi::xml_node new_node = gpx.append_child("trk");
1282
1283 GPXCreateTrk(new_node, pTrack, 0);
1284 }
1285 }
1286 }
1287
1288 return true;
1289}
1290
1291bool NavObjectCollection1::CreateAllGPXObjects() {
1292 SetRootGPXNode();
1293
1294 // CreateNavObjGPXPoints();
1295 // CreateNavObjGPXRoutes();
1296 // CreateNavObjGPXTracks();
1297
1298 return true;
1299}
1300
1301bool NavObjectCollection1::AddGPXRoute(Route *pRoute) {
1302 SetRootGPXNode();
1303 pugi::xml_node doc = root();
1304 pugi::xml_node gpx = doc.first_child();
1305 pugi::xml_node new_node = gpx.append_child("rte");
1306
1307 GPXCreateRoute(new_node, pRoute);
1308 return true;
1309}
1310
1311bool NavObjectCollection1::AddGPXTrack(Track *pTrk) {
1312 SetRootGPXNode();
1313 pugi::xml_node doc = root();
1314 pugi::xml_node gpx = doc.first_child();
1315 pugi::xml_node new_node = gpx.append_child("trk");
1316
1317 GPXCreateTrk(new_node, pTrk, 0);
1318 return true;
1319}
1320
1321bool NavObjectCollection1::AddGPXWaypoint(RoutePoint *pWP) {
1322 SetRootGPXNode();
1323 pugi::xml_node doc = root();
1324 pugi::xml_node gpx = doc.first_child();
1325 pugi::xml_node new_node = gpx.append_child("wpt");
1326
1327 GPXCreateWpt(new_node, pWP, OPT_WPT);
1328 return true;
1329}
1330
1331void NavObjectCollection1::AddGPXRoutesList(RouteList *pRoutes) {
1332 SetRootGPXNode();
1333
1334 for (Route *pRData : *pRoutes) {
1335 AddGPXRoute(pRData);
1336 }
1337}
1338
1339void NavObjectCollection1::AddGPXTracksList(std::vector<Track *> *pTracks) {
1340 SetRootGPXNode();
1341
1342 for (Track *pRData : *pTracks) {
1343 AddGPXTrack(pRData);
1344 }
1345}
1346
1347bool NavObjectCollection1::AddGPXPointsList(RoutePointList *pRoutePoints) {
1348 SetRootGPXNode();
1349
1350 for (RoutePoint *pRP : *pRoutePoints) {
1351 AddGPXWaypoint(pRP);
1352 }
1353
1354 return true;
1355}
1356
1357void NavObjectCollection1::SetRootGPXNode() {
1358 if (!strlen(first_child().name())) {
1359 pugi::xml_node gpx_root = append_child("gpx");
1360 gpx_root.append_attribute("version") = "1.1";
1361 gpx_root.append_attribute("creator") = "OpenCPN";
1362 gpx_root.append_attribute("xmlns:xsi") =
1363 "http://www.w3.org/2001/XMLSchema-instance";
1364 gpx_root.append_attribute("xmlns") = "http://www.topografix.com/GPX/1/1";
1365 gpx_root.append_attribute("xmlns:gpxx") =
1366 "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
1367 gpx_root.append_attribute("xsi:schemaLocation") =
1368 "http://www.topografix.com/GPX/1/1 "
1369 "http://www.topografix.com/GPX/1/1/gpx.xsd "
1370 "http://www.garmin.com/xmlschemas/GpxExtensions/v3 "
1371 "http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
1372 gpx_root.append_attribute("xmlns:opencpn") = "http://www.opencpn.org";
1373 }
1374}
1375
1376bool NavObjectCollection1::IsOpenCPN() {
1377 for (pugi::xml_attribute attr = root().first_child().first_attribute(); attr;
1378 attr = attr.next_attribute())
1379 if (!strcmp(attr.name(), "creator") && !strcmp(attr.value(), "OpenCPN"))
1380 return true;
1381 return false;
1382}
1383
1384bool NavObjectCollection1::SaveFile(const wxString filename) {
1385 wxString tmp_filename = filename + ".tmp";
1386 if (wxFileExists(tmp_filename)) {
1387 wxRemoveFile(tmp_filename);
1388 }
1389 save_file(tmp_filename.fn_str(), " ");
1390 wxRenameFile(tmp_filename.fn_str(), filename.fn_str(), true);
1391 return true;
1392}
1393
1394bool NavObjectCollection1::LoadAllGPXObjects(bool b_full_viz,
1395 int &wpt_duplicates,
1396 bool b_compute_bbox) {
1397 wpt_duplicates = 0;
1398 pugi::xml_node objects = this->child("gpx");
1399
1400 for (pugi::xml_node object = objects.first_child(); object;
1401 object = object.next_sibling()) {
1402 if (!strcmp(object.name(), "wpt")) {
1403 RoutePoint *pWp =
1404 ::GPXLoadWaypoint1(object, "circle", "", b_full_viz, false, false, 0);
1405
1406 pWp->m_bIsolatedMark = true; // This is an isolated mark
1407 RoutePoint *pExisting =
1408 WaypointExists(pWp->GetName(), pWp->m_lat, pWp->m_lon);
1409 if (!pExisting) {
1410 if (NULL != pWayPointMan) pWayPointMan->AddRoutePoint(pWp);
1411 NavObj_dB::GetInstance().InsertRoutePoint(pWp);
1412 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1413 LLBBox wptbox;
1414 wptbox.Set(pWp->m_lat, pWp->m_lon, pWp->m_lat, pWp->m_lon);
1415 BBox.Expand(wptbox);
1416 } else {
1417 delete pWp;
1418 wpt_duplicates++;
1419 }
1420 } else if (!strcmp(object.name(), "trk")) {
1421 Track *pTrack = GPXLoadTrack1(object, b_full_viz, false, false, 0);
1422 if (InsertTrack(pTrack)) {
1423 NavObj_dB::GetInstance().InsertTrack(pTrack);
1424 }
1425 } else if (!strcmp(object.name(), "rte")) {
1426 Route *pRoute = GPXLoadRoute1(object, b_full_viz, false, false, 0, false);
1427 if (InsertRouteA(pRoute, this)) {
1428 NavObj_dB::GetInstance().InsertRoute(pRoute);
1429 if (b_compute_bbox && pRoute->IsVisible())
1430 BBox.Expand(pRoute->GetBBox());
1431 }
1432 }
1433 }
1434
1435 return true;
1436}
1437
1438int NavObjectCollection1::LoadAllGPXObjectsAsLayer(int layer_id,
1439 bool b_layerviz,
1440 wxCheckBoxState b_namesviz) {
1441 if (!pWayPointMan) return 0;
1442
1443 int n_obj = 0;
1444 pugi::xml_node objects = this->child("gpx");
1445
1446 for (pugi::xml_node object = objects.first_child(); object;
1447 object = object.next_sibling()) {
1448 if (!strcmp(object.name(), "wpt")) {
1449 RoutePoint *pWp = ::GPXLoadWaypoint1(object, "circle", "",
1450 b_namesviz != wxCHK_UNDETERMINED,
1451 true, b_layerviz, layer_id);
1452 if (b_namesviz != wxCHK_UNDETERMINED) {
1453 pWp->SetNameShown(b_namesviz == wxCHK_CHECKED);
1454 }
1455 pWp->m_bIsolatedMark = true; // This is an isolated mark
1456 pWayPointMan->AddRoutePoint(pWp);
1457 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1458 n_obj++;
1459 } else {
1460 if (!strcmp(object.name(), "trk")) {
1461 Track *pTrack =
1462 GPXLoadTrack1(object, false, true, b_layerviz, layer_id);
1463 n_obj++;
1464 InsertTrack(pTrack);
1465 } else if (!strcmp(object.name(), "rte")) {
1466 Route *pRoute =
1467 GPXLoadRoute1(object, true, true, b_layerviz, layer_id, false);
1468 n_obj++;
1469 InsertRouteA(pRoute, this);
1470 }
1471 }
1472 }
1473
1474 return n_obj;
1475}
1476
1477bool NavObjectCollection1::LoadAllGPXTrackObjects() {
1478 pugi::xml_node objects = this->child("gpx");
1479
1480 for (pugi::xml_node object = objects.first_child(); object;
1481 object = object.next_sibling()) {
1482 if (!strcmp(object.name(), "trk")) {
1483 Track *pTrack = GPXLoadTrack1(object, true, false, false, 0);
1484 InsertTrack(pTrack);
1485 }
1486 }
1487
1488 return true;
1489}
1490
1491bool NavObjectCollection1::LoadAllGPXRouteObjects() {
1492 pugi::xml_node objects = this->child("gpx");
1493
1494 for (pugi::xml_node object = objects.first_child(); object;
1495 object = object.next_sibling()) {
1496 if (!strcmp(object.name(), "rte")) {
1497 Route *route = GPXLoadRoute1(object, true, false, false, 0, false, true);
1498 InsertRouteA(route, nullptr);
1499 }
1500 }
1501
1502 return true;
1503}
1504
1505bool NavObjectCollection1::LoadAllGPXPointObjects() {
1506 pugi::xml_node objects = this->child("gpx");
1507
1508 for (pugi::xml_node object = objects.first_child(); object;
1509 object = object.next_sibling()) {
1510 if (!strcmp(object.name(), "wpt")) {
1511 RoutePoint *rp =
1512 GPXLoadWaypoint1(object, "circle", "", false, false, false, 0);
1513 rp->m_bIsolatedMark = true;
1514 pWayPointMan->AddRoutePoint(rp);
1515 }
1516 }
1517 return true;
1518}
1519
1520RoutePoint *WaypointExists(const wxString &name, double lat, double lon) {
1521 RoutePoint *pret = NULL;
1522 // if( g_bIsNewLayer ) return NULL;
1523 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1524 if (name == pr->GetName()) {
1525 if (fabs(lat - pr->m_lat) < 1.e-6 && fabs(lon - pr->m_lon) < 1.e-6) {
1526 pret = pr;
1527 break;
1528 }
1529 }
1530 }
1531
1532 return pret;
1533}
1534
1535RoutePoint *WaypointExists(const wxString &guid) {
1536 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1537 if (guid == pr->m_GUID) {
1538 return pr;
1539 }
1540 }
1541 return NULL;
1542}
1543
1544bool WptIsInRouteList(RoutePoint *pr) {
1545 bool IsInList = false;
1546
1547 for (Route *pRoute : *pRouteList) {
1548 RoutePointList *pRoutePointList = pRoute->pRoutePointList;
1549 for (RoutePoint *prp : *pRoutePointList) {
1550 if (pr->IsSame(prp)) {
1551 IsInList = true;
1552 break;
1553 }
1554 }
1555 }
1556 return IsInList;
1557}
1558
1559Route *RouteExists(const wxString &guid) {
1560 for (Route *proute : *pRouteList) {
1561 if (guid == proute->m_GUID) return proute;
1562 }
1563 return NULL;
1564}
1565
1566Route *RouteExists(Route *pTentRoute) {
1567 for (Route *proute : *pRouteList) {
1568 if (proute->IsEqualTo(pTentRoute)) return proute;
1569 }
1570 return NULL;
1571}
1572
1573Track *TrackExists(const wxString &guid) {
1574 for (Track *ptrack : g_TrackList) {
1575 if (guid == ptrack->m_GUID) return ptrack;
1576 }
1577 return NULL;
1578}
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
HyperlinkList * m_HyperlinkList
List of hyperlinks associated with this waypoint.
wxColour m_wxcWaypointRangeRingsColour
Color for the range rings display.
wxString m_MarkDescription
Description text for the waypoint.
int m_iWaypointRangeRingsNumber
Number of range rings to display around the waypoint.
void SetCreateTime(wxDateTime dt)
Sets the create time of this RoutePoint in UTC.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bShowNameData
Flag indicating if waypoint data should be shown with the name.
wxDateTime GetManualETD()
Retrieves the manually set Estimated Time of Departure for this waypoint, in UTC.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
wxString m_timestring
String representation of the waypoint creation time.
double GetPlannedSpeed()
Return the planned speed associated with this waypoint.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
bool m_btemp
Flag indicating if this is a temporary waypoint.
bool m_manual_etd
Flag indicating whether the ETD has been manually set by the user.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
int m_iWaypointRangeRingsStepUnits
Units for the range rings step (0=nm, 1=km).
wxDateTime GetCreateTime(void)
Returns the Create Time of this RoutePoint in UTC.
float m_fWaypointRangeRingsStep
Distance between consecutive range rings.
wxString m_TideStation
Associated tide station identifier.
bool m_bShowWaypointRangeRings
Flag indicating if range rings should be shown around the waypoint.
void SetETD(const wxDateTime &etd)
Sets the Estimated Time of Departure for this waypoint, in UTC.
Represents a navigational route in the navigation system.
Definition route.h:99
double m_PlannedSpeed
Default planned speed for the route in knots.
Definition route.h:321
wxString m_RouteStartString
Name or description of the route's starting point.
Definition route.h:252
wxString m_RouteDescription
Additional descriptive information about the route.
Definition route.h:262
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
wxString m_Colour
Color name for rendering the route on the chart.
Definition route.h:346
wxString m_RouteEndString
Name or description of the route's ending point.
Definition route.h:257
bool m_btemp
Flag indicating if this is a temporary route.
Definition route.h:351
wxPenStyle m_style
Style of the route line when rendered on the chart.
Definition route.h:293
wxString m_TimeDisplayFormat
Format for displaying times in the UI.
Definition route.h:331
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
wxDateTime m_PlannedDeparture
Planned departure time for the route, in UTC.
Definition route.h:326
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
HyperlinkList * m_HyperlinkList
List of hyperlinks associated with this route.
Definition route.h:361
int m_LayerID
Identifier of the layer containing this route.
Definition route.h:283
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:772
Represents a single point in a track.
Definition track.h:59
Represents a track, which is a series of connected track points.
Definition track.h:117
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
MySQL based storage for routes, tracks, etc.
const wxChar * ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime)
This function parses a string containing a GPX time representation and returns a wxDateTime containin...
Navigation Utility Functions without GUI dependencies.
Route abstraction.
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
Route Manager.
Select * pSelect
Global instance.
Definition select.cpp:36
Selected route, segment, waypoint, etc.
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.