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 // HACK FOR TESTING NAVOBJ_DB
521 return nullptr;
522
523 pTentRoute->m_GUID = pWayPointMan->CreateGUID(NULL);
524 route_existing = true;
525 }
526 }
527 } // extension
528 else if (load_points && ChildName == "rtept") {
529 RoutePoint *tpWp =
530 ::GPXLoadWaypoint1(tschild, "square", "", b_fullviz, b_layer,
531 b_layerviz, layer_id, false);
532 RoutePoint *erp = NULL;
533 if (!b_layer) erp = ::WaypointExists(tpWp->m_GUID);
534 // 1) if b_change is true, that means we are after crash - load the
535 // route and points as found in source file 2) if route_existing, we are
536 // loading a different route with the same guid. In this case load
537 // points as found in
538 // source file, changing the guid, but keep existing "isolated point" as
539 // found in the DB
540 // 3) in all other cases keep existing points if found and load new
541 // points if not found
542 bool new_wpt = true;
543 if (b_change) {
544 pWp = tpWp;
545 } else {
546 if (erp != NULL &&
547 (!route_existing || (route_existing && tpWp->IsShared()))) {
548 pWp = erp;
549 new_wpt = false;
550 } else {
551 if (route_existing) tpWp->m_GUID = pWayPointMan->CreateGUID(NULL);
552 pWp = tpWp;
553 }
554 }
555
556 pTentRoute->AddPoint(pWp, false, true); // defer BBox calculation
557 pWp->m_bIsInRoute = true; // Hack
558
559 if (new_wpt) {
560 if (erp == NULL) {
561 pWayPointMan->AddRoutePoint(pWp);
562 }
563 } else {
564 delete tpWp;
565 }
566 } else if (ChildName == "name") {
567 RouteName = wxString::FromUTF8(tschild.first_child().value());
568 } else if (ChildName == "desc") {
569 DescString = wxString::FromUTF8(tschild.first_child().value());
570 }
571
572 if (ChildName == "link") {
573 wxString HrefString;
574 wxString HrefTextString;
575 wxString HrefTypeString;
576 if (linklist == NULL) linklist = new HyperlinkList;
577 HrefString = wxString::FromUTF8(tschild.first_attribute().value());
578
579 for (pugi::xml_node child1 = tschild.first_child(); child1;
580 child1 = child1.next_sibling()) {
581 wxString LinkString = wxString::FromUTF8(child1.name());
582
583 if (LinkString == "text")
584 HrefTextString = wxString::FromUTF8(child1.first_child().value());
585 if (LinkString == "type")
586 HrefTypeString = wxString::FromUTF8(child1.first_child().value());
587 }
588
589 Hyperlink *link = new Hyperlink;
590 link->Link = HrefString;
591 link->DescrText = HrefTextString;
592 link->LType = HrefTypeString;
593 linklist->push_back(link);
594 }
595
596 else
597 // TODO: This is wrong, left here just to save data of the 3.3 beta
598 // series users.
599 if (ChildName.EndsWith("RouteExtension")) // Parse GPXX color
600 {
601 for (pugi::xml_node gpxx_child = tschild.first_child(); gpxx_child;
602 gpxx_child = gpxx_child.next_sibling()) {
603 wxString gpxx_name = wxString::FromUTF8(gpxx_child.name());
604 if (gpxx_name.EndsWith("DisplayColor"))
605 pTentRoute->m_Colour =
606 wxString::FromUTF8(gpxx_child.first_child().value());
607 }
608 }
609 }
610
611 pTentRoute->m_RouteNameString = RouteName;
612 pTentRoute->m_RouteDescription = DescString;
613 if (linklist) {
614 pTentRoute->m_HyperlinkList = linklist;
615 }
616
617 if (b_propviz) {
618 pTentRoute->SetVisible(b_viz);
619 } else if (b_fullviz) {
620 pTentRoute->SetVisible();
621 }
622
623 if (b_propSWPviz) pTentRoute->SetSharedWPViz(swpViz);
624
625 if (b_layer) {
626 pTentRoute->SetVisible(b_layerviz);
627 pTentRoute->m_bIsInLayer = true;
628 pTentRoute->m_LayerID = layer_id;
629 pTentRoute->SetListed(false);
630 }
631 }
632
633 return pTentRoute;
634}
635
636static bool GPXCreateWpt(pugi::xml_node node, RoutePoint *pr,
637 unsigned int flags) {
638 wxString s;
639 pugi::xml_node child;
641
642 s.Printf("%.9f", pr->m_lat);
643 node.append_attribute("lat") = s.mb_str();
644 s.Printf("%.9f", pr->m_lon);
645 node.append_attribute("lon") = s.mb_str();
646
647 if (flags & OUT_TIME) {
648 child = node.append_child("time");
649 if (pr->m_timestring.Len())
650 child.append_child(pugi::node_pcdata)
651 .set_value(pr->m_timestring.mb_str());
652 else {
653 wxDateTime dt = pr->GetCreateTime();
654 if (!dt.IsValid()) dt = wxDateTime::Now();
655
656 wxString t = dt.ToUTC()
657 .FormatISODate()
658 .Append("T")
659 .Append(dt.ToUTC().FormatISOTime())
660 .Append("Z");
661 child.append_child(pugi::node_pcdata).set_value(t.mb_str());
662 }
663 }
664
665 if ((!pr->GetName().IsEmpty() && (flags & OUT_NAME)) ||
666 (flags & OUT_NAME_FORCE)) {
667 wxCharBuffer buffer = pr->GetName().ToUTF8();
668 if (buffer.data()) {
669 child = node.append_child("name");
670 child.append_child(pugi::node_pcdata).set_value(buffer.data());
671 }
672 }
673
674 if ((!pr->GetDescription().IsEmpty() && (flags & OUT_DESC)) ||
675 (flags & OUT_DESC_FORCE)) {
676 wxCharBuffer buffer = pr->GetDescription().ToUTF8();
677 if (buffer.data()) {
678 child = node.append_child("desc");
679 child.append_child(pugi::node_pcdata).set_value(buffer.data());
680 }
681 }
682
683 // Hyperlinks
684 if (flags & OUT_HYPERLINKS) {
685 HyperlinkList *linklist = pr->m_HyperlinkList;
686 if (linklist && linklist->size()) {
687 for (Hyperlink *link : *pr->m_HyperlinkList) {
688 pugi::xml_node child_link = node.append_child("link");
689 ;
690 wxCharBuffer buffer = link->Link.ToUTF8();
691 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
692
693 buffer = link->DescrText.ToUTF8();
694 if (buffer.data()) {
695 child = child_link.append_child("text");
696 child.append_child(pugi::node_pcdata).set_value(buffer.data());
697 }
698
699 buffer = link->LType.ToUTF8();
700 if (buffer.data() && strlen(buffer.data()) > 0) {
701 child = child_link.append_child("type");
702 child.append_child(pugi::node_pcdata).set_value(buffer.data());
703 }
704 }
705 }
706 }
707
708 if (flags & OUT_SYM_FORCE) {
709 child = node.append_child("sym");
710 if (!pr->GetIconName().IsEmpty()) {
711 child.append_child(pugi::node_pcdata)
712 .set_value(pr->GetIconName().mb_str());
713 } else {
714 child.append_child("empty");
715 }
716 }
717
718 if (flags & OUT_TYPE) {
719 child = node.append_child("type");
720 child.append_child(pugi::node_pcdata).set_value("WPT");
721 }
722
723 if ((flags & OUT_GUID) || (flags & OUT_VIZ) || (flags & OUT_VIZ_NAME) ||
724 (flags & OUT_SHARED) || (flags & OUT_EXTENSION) ||
725 (flags & OUT_TIDE_STATION) || (flags & OUT_RTE_PROPERTIES)) {
726 pugi::xml_node child_ext = node.append_child("extensions");
727
728 if (!pr->m_GUID.IsEmpty() && (flags & OUT_GUID)) {
729 child = child_ext.append_child("opencpn:guid");
730 child.append_child(pugi::node_pcdata).set_value(pr->m_GUID.mb_str());
731 }
732
733 if ((flags & OUT_VIZ) && !pr->m_bIsVisible) {
734 child = child_ext.append_child("opencpn:viz");
735 child.append_child(pugi::node_pcdata).set_value("0");
736 }
737
738 if ((flags & OUT_VIZ_NAME) && pr->m_bShowName) {
739 child = child_ext.append_child("opencpn:viz_name");
740 child.append_child(pugi::node_pcdata).set_value("1");
741 }
742
743 if ((flags & OUT_SHARED) && pr->IsShared()) {
744 child = child_ext.append_child("opencpn:shared");
745 child.append_child(pugi::node_pcdata).set_value("1");
746 }
747 if (flags & OUT_ARRIVAL_RADIUS) {
748 child = child_ext.append_child("opencpn:arrival_radius");
749 s.Printf("%.3f", pr->GetWaypointArrivalRadius());
750 child.append_child(pugi::node_pcdata).set_value(s.mbc_str());
751 }
752 if (flags & OUT_WAYPOINT_RANGE_RINGS) {
753 child = child_ext.append_child("opencpn:waypoint_range_rings");
754 pugi::xml_attribute viz = child.append_attribute("visible");
755 viz.set_value(pr->m_bShowWaypointRangeRings);
756 pugi::xml_attribute number = child.append_attribute("number");
757 number.set_value(pr->m_iWaypointRangeRingsNumber);
758 pugi::xml_attribute step = child.append_attribute("step");
759 step.set_value(pr->m_fWaypointRangeRingsStep);
760 pugi::xml_attribute units = child.append_attribute("units");
761 units.set_value(pr->m_iWaypointRangeRingsStepUnits);
762
763 // Color specification in GPX file must be fully opaque
764 if (pr->m_wxcWaypointRangeRingsColour.IsOk()) {
768 pr->m_wxcWaypointRangeRingsColour.Blue(), wxALPHA_OPAQUE);
769 } else {
770 pr->m_wxcWaypointRangeRingsColour.Set(0, 0, 0, wxALPHA_OPAQUE);
771 }
772
773 pugi::xml_attribute colour = child.append_attribute("colour");
774 colour.set_value(
775 pr->m_wxcWaypointRangeRingsColour.GetAsString(wxC2S_HTML_SYNTAX)
776 .utf8_str());
777 }
778 if (flags & OUT_WAYPOINT_SCALE) {
779 child = child_ext.append_child("opencpn:scale_min_max");
780 pugi::xml_attribute use = child.append_attribute("UseScale");
781 use.set_value(pr->GetUseSca());
782 pugi::xml_attribute sca = child.append_attribute("ScaleMin");
783 sca.set_value(pr->GetScaMin());
784 pugi::xml_attribute max = child.append_attribute("ScaleMax");
785 max.set_value(pr->GetScaMax());
786 }
787 if ((flags & OUT_TIDE_STATION) && !pr->m_TideStation.IsEmpty()) {
788 child = child_ext.append_child("opencpn:tidestation");
789 child.append_child(pugi::node_pcdata)
790 .set_value(pr->m_TideStation.mb_str());
791 }
792 if ((flags & OUT_RTE_PROPERTIES) &&
793 (pr->GetPlannedSpeed() > 0.0001 || pr->m_manual_etd)) {
794 child = child_ext.append_child("opencpn:rte_properties");
795 if (pr->GetPlannedSpeed() > 0.0001) {
796 pugi::xml_attribute use = child.append_attribute("planned_speed");
797 use.set_value(
798 wxString::Format("%.1lf", pr->GetPlannedSpeed()).mb_str());
799 }
800 if (pr->m_manual_etd && pr->GetManualETD().IsValid()) {
801 pugi::xml_attribute use = child.append_attribute("etd");
802 // Currently, the serialization format is YYYY-MM-DDTHH:MM:SS
803 // without timezone information, e.g., etd="2025-04-03T20:00:27"
804 // TODO: serialize using ISO 8601 or RFC 3339 format to ensure
805 // the serialized date/time is unambiguous.
806 use.set_value(pr->GetManualETD().FormatISOCombined().mb_str());
807 }
808 }
809 }
810
811 return true;
812}
813
814static bool GPXCreateTrkpt(pugi::xml_node node, TrackPoint *pt,
815 unsigned int flags) {
816 wxString s;
817 pugi::xml_node child;
819
820 s.Printf("%.9f", pt->m_lat);
821 node.append_attribute("lat") = s.mb_str();
822 s.Printf("%.9f", pt->m_lon);
823 node.append_attribute("lon") = s.mb_str();
824
825 if (flags & OUT_TIME && pt->HasValidTimestamp()) {
826 child = node.append_child("time");
827 child.append_child(pugi::node_pcdata).set_value(pt->GetTimeString());
828 }
829
830 return true;
831}
832
833static bool GPXCreateTrk(pugi::xml_node node, Track *pTrack,
834 unsigned int flags) {
835 pugi::xml_node child;
836
837 if (pTrack->GetName().Len()) {
838 wxCharBuffer buffer = pTrack->GetName().ToUTF8();
839 if (buffer.data()) {
840 child = node.append_child("name");
841 child.append_child(pugi::node_pcdata).set_value(buffer.data());
842 }
843 }
844
845 if (pTrack->m_TrackDescription.Len()) {
846 wxCharBuffer buffer = pTrack->m_TrackDescription.ToUTF8();
847 if (buffer.data()) {
848 child = node.append_child("desc");
849 child.append_child(pugi::node_pcdata).set_value(buffer.data());
850 }
851 }
852
853 // Hyperlinks
854 HyperlinkList *linklist = pTrack->m_TrackHyperlinkList;
855 if (linklist && linklist->size()) {
856 for (Hyperlink *link : *linklist) {
857 pugi::xml_node child_link = node.append_child("link");
858 wxCharBuffer buffer = link->Link.ToUTF8();
859 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
860
861 buffer = link->DescrText.ToUTF8();
862 if (buffer.data()) {
863 child = child_link.append_child("text");
864 child.append_child(pugi::node_pcdata).set_value(buffer.data());
865 }
866
867 buffer = link->LType.ToUTF8();
868 if (buffer.data() && strlen(buffer.data()) > 0) {
869 child = child_link.append_child("type");
870 child.append_child(pugi::node_pcdata).set_value(buffer.data());
871 }
872 }
873 }
874
875 pugi::xml_node child_ext = node.append_child("extensions");
876
877 child = child_ext.append_child("opencpn:guid");
878 child.append_child(pugi::node_pcdata).set_value(pTrack->m_GUID.mb_str());
879
880 child = child_ext.append_child("opencpn:viz");
881 child.append_child(pugi::node_pcdata)
882 .set_value(pTrack->IsVisible() == true ? "1" : "0");
883
884 if (pTrack->m_TrackStartString.Len()) {
885 wxCharBuffer buffer = pTrack->m_TrackStartString.ToUTF8();
886 if (buffer.data()) {
887 child = child_ext.append_child("opencpn:start");
888 child.append_child(pugi::node_pcdata).set_value(buffer.data());
889 }
890 }
891
892 if (pTrack->m_TrackEndString.Len()) {
893 wxCharBuffer buffer = pTrack->m_TrackEndString.ToUTF8();
894 if (buffer.data()) {
895 child = child_ext.append_child("opencpn:end");
896 child.append_child(pugi::node_pcdata).set_value(buffer.data());
897 }
898 }
899
900 if (pTrack->m_width != WIDTH_UNDEFINED ||
901 pTrack->m_style != wxPENSTYLE_INVALID) {
902 child = child_ext.append_child("opencpn:style");
903
904 if (pTrack->m_width != WIDTH_UNDEFINED)
905 child.append_attribute("width") = pTrack->m_width;
906 if (pTrack->m_style != wxPENSTYLE_INVALID)
907 child.append_attribute("style") = pTrack->m_style;
908 }
909
910 if (pTrack->m_Colour != "") {
911 pugi::xml_node gpxx_ext = child_ext.append_child("gpxx:TrackExtension");
912 child = gpxx_ext.append_child("gpxx:DisplayColor");
913 child.append_child(pugi::node_pcdata).set_value(pTrack->m_Colour.mb_str());
914 }
915
916 if (flags & RT_OUT_NO_RTPTS) return true;
917
918 int node2 = 0;
919 TrackPoint *prp;
920
921 unsigned short int GPXTrkSegNo1 = 1;
922
923 do {
924 unsigned short int GPXTrkSegNo2 = GPXTrkSegNo1;
925
926 pugi::xml_node seg = node.append_child("trkseg");
927
928 while (node2 < pTrack->GetnPoints()) {
929 prp = pTrack->GetPoint(node2);
930 GPXTrkSegNo1 = prp->m_GPXTrkSegNo;
931 if (GPXTrkSegNo1 != GPXTrkSegNo2) break;
932
933 GPXCreateTrkpt(seg.append_child("trkpt"), prp, OPT_TRACKPT);
934
935 node2++;
936 }
937 } while (node2 < pTrack->GetnPoints());
938
939 return true;
940}
941
942static bool GPXCreateRoute(pugi::xml_node node, Route *pRoute) {
943 pugi::xml_node child;
944
945 if (pRoute->m_RouteNameString.Len()) {
946 wxCharBuffer buffer = pRoute->m_RouteNameString.ToUTF8();
947 if (buffer.data()) {
948 child = node.append_child("name");
949 child.append_child(pugi::node_pcdata).set_value(buffer.data());
950 }
951 }
952
953 if (pRoute->m_RouteDescription.Len()) {
954 wxCharBuffer buffer = pRoute->m_RouteDescription.ToUTF8();
955 if (buffer.data()) {
956 child = node.append_child("desc");
957 child.append_child(pugi::node_pcdata).set_value(buffer.data());
958 }
959 }
960
961 // Hyperlinks
962 HyperlinkList *linklist = pRoute->m_HyperlinkList;
963 if (linklist && linklist->size()) {
964 for (Hyperlink *link : *linklist) {
965 pugi::xml_node child_link = node.append_child("link");
966 wxCharBuffer buffer = link->Link.ToUTF8();
967 if (buffer.data()) child_link.append_attribute("href") = buffer.data();
968
969 buffer = link->DescrText.ToUTF8();
970 if (buffer.data()) {
971 child = child_link.append_child("text");
972 child.append_child(pugi::node_pcdata).set_value(buffer.data());
973 }
974 buffer = link->LType.ToUTF8();
975 if (buffer.data() && strlen(buffer.data()) > 0) {
976 child = child_link.append_child("type");
977 child.append_child(pugi::node_pcdata).set_value(buffer.data());
978 }
979 }
980 }
981
982 pugi::xml_node child_ext = node.append_child("extensions");
983
984 child = child_ext.append_child("opencpn:guid");
985 child.append_child(pugi::node_pcdata).set_value(pRoute->m_GUID.mb_str());
986
987 child = child_ext.append_child("opencpn:viz");
988 child.append_child(pugi::node_pcdata)
989 .set_value(pRoute->IsVisible() == true ? "1" : "0");
990
991 if (pRoute->ContainsSharedWP()) {
992 child = child_ext.append_child("opencpn:sharedWPviz");
993 child.append_child(pugi::node_pcdata)
994 .set_value(pRoute->GetSharedWPViz() == true ? "1" : "0");
995 }
996
997 if (pRoute->m_RouteStartString.Len()) {
998 wxCharBuffer buffer = pRoute->m_RouteStartString.ToUTF8();
999 if (buffer.data()) {
1000 child = child_ext.append_child("opencpn:start");
1001 child.append_child(pugi::node_pcdata).set_value(buffer.data());
1002 }
1003 }
1004
1005 if (pRoute->m_RouteEndString.Len()) {
1006 wxCharBuffer buffer = pRoute->m_RouteEndString.ToUTF8();
1007 if (buffer.data()) {
1008 child = child_ext.append_child("opencpn:end");
1009 child.append_child(pugi::node_pcdata).set_value(buffer.data());
1010 }
1011 }
1012
1013 if (pRoute->m_PlannedSpeed != ROUTE_DEFAULT_SPEED) {
1014 child = child_ext.append_child("opencpn:planned_speed");
1015 wxString s;
1016 s.Printf("%.2f", pRoute->m_PlannedSpeed);
1017 child.append_child(pugi::node_pcdata).set_value(s.mb_str());
1018 }
1019
1020 if (pRoute->m_PlannedDeparture.IsValid()) {
1021 child = child_ext.append_child("opencpn:planned_departure");
1022 wxString t = pRoute->m_PlannedDeparture.FormatISODate()
1023 .Append("T")
1024 .Append(pRoute->m_PlannedDeparture.FormatISOTime())
1025 .Append("Z");
1026 child.append_child(pugi::node_pcdata).set_value(t.mb_str());
1027 }
1028
1029 child = child_ext.append_child("opencpn:time_display");
1030 child.append_child(pugi::node_pcdata)
1031 .set_value(pRoute->m_TimeDisplayFormat.mb_str());
1032
1033 if (pRoute->m_width != WIDTH_UNDEFINED ||
1034 pRoute->m_style != wxPENSTYLE_INVALID) {
1035 child = child_ext.append_child("opencpn:style");
1036
1037 if (pRoute->m_width != WIDTH_UNDEFINED)
1038 child.append_attribute("width") = pRoute->m_width;
1039 if (pRoute->m_style != wxPENSTYLE_INVALID)
1040 child.append_attribute("style") = pRoute->m_style;
1041 }
1042
1043 pugi::xml_node gpxx_ext = child_ext.append_child("gpxx:RouteExtension");
1044 child = gpxx_ext.append_child("gpxx:IsAutoNamed");
1045 child.append_child(pugi::node_pcdata).set_value("false");
1046
1047 if (pRoute->m_Colour != "") {
1048 child = gpxx_ext.append_child("gpxx:DisplayColor");
1049 child.append_child(pugi::node_pcdata).set_value(pRoute->m_Colour.mb_str());
1050 }
1051
1052 RoutePointList *pRoutePointList = pRoute->pRoutePointList;
1053 for (RoutePoint *prp : *pRoutePointList) {
1054 GPXCreateWpt(node.append_child("rtept"), prp, OPT_ROUTEPT);
1055 }
1056 return true;
1057}
1058
1059bool InsertRouteA(Route *pTentRoute, NavObjectCollection1 *navobj) {
1060 if (!pTentRoute) return false;
1061
1062 bool bAddroute = true;
1063 // If the route has only 1 point, don't load it.
1064 if (pTentRoute->GetnPoints() < 2) bAddroute = false;
1065
1066 // TODO All this trouble for a tentative route.......Should make some
1067 // Route methods????
1068 if (bAddroute) {
1069 pRouteList->push_back(pTentRoute);
1070
1071 // Do the (deferred) calculation of BBox
1072 pTentRoute->FinalizeForRendering();
1073
1074 // Add the selectable points and segments
1075
1076 int ip = 0;
1077 float prev_rlat = 0., prev_rlon = 0.;
1078 RoutePoint *prev_pConfPoint = NULL;
1079
1080 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1081 if (ip)
1082 pSelect->AddSelectableRouteSegment(prev_rlat, prev_rlon, prp->m_lat,
1083 prp->m_lon, prev_pConfPoint, prp,
1084 pTentRoute);
1085 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, prp);
1086 prev_rlat = prp->m_lat;
1087 prev_rlon = prp->m_lon;
1088 prev_pConfPoint = prp;
1089
1090 ip++;
1091 }
1092 } else {
1093 // walk the route, deleting points used only by this route
1094 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1095 // check all other routes to see if this point appears in any other route
1096 Route *pcontainer_route = g_pRouteMan->FindRouteContainingWaypoint(prp);
1097 if (pcontainer_route == NULL) {
1098 // Take this point out of this (and only) track/route
1099 prp->m_bIsInRoute = false;
1100 if (!prp->IsShared()) {
1101 if (navobj) {
1102 delete prp;
1103 }
1104 }
1105 }
1106 }
1107
1108 delete pTentRoute;
1109 }
1110 return bAddroute;
1111}
1112
1113bool InsertTrack(Track *pTentTrack, bool bApplyChanges) {
1114 if (!pTentTrack) return false;
1115
1116 bool bAddtrack = true;
1117 // If the track has only 1 point, don't load it.
1118 // This usually occurs if some points were discarded as being co-incident.
1119 if (!bApplyChanges && pTentTrack->GetnPoints() < 2) bAddtrack = false;
1120
1121 // TODO All this trouble for a tentative track.......Should make some
1122 // Track methods????
1123 if (bAddtrack) {
1124 g_TrackList.push_back(pTentTrack);
1125
1126 // Do the (deferred) calculation of Track BBox
1127 // pTentTrack->FinalizeForRendering();
1128
1129 // Add the selectable points and segments
1130
1131 float prev_rlat = 0., prev_rlon = 0.;
1132 TrackPoint *prev_pConfPoint = NULL;
1133
1134 for (int i = 0; i < pTentTrack->GetnPoints(); i++) {
1135 TrackPoint *prp = pTentTrack->GetPoint(i);
1136
1137 if (i)
1138 pSelect->AddSelectableTrackSegment(prev_rlat, prev_rlon, prp->m_lat,
1139 prp->m_lon, prev_pConfPoint, prp,
1140 pTentTrack);
1141
1142 prev_rlat = prp->m_lat;
1143 prev_rlon = prp->m_lon;
1144 prev_pConfPoint = prp;
1145 }
1146 } else
1147 delete pTentTrack;
1148
1149 return bAddtrack;
1150}
1151
1152bool InsertWpt(RoutePoint *pWp, bool overwrite) {
1153 bool res = false;
1154 RoutePoint *pExisting =
1155 WaypointExists(pWp->GetName(), pWp->m_lat, pWp->m_lon);
1156 if (!pExisting || overwrite) {
1157 if (NULL != pWayPointMan) {
1158 if (pExisting) {
1159 pWayPointMan->DestroyWaypoint(pExisting);
1160 }
1161 pWayPointMan->AddRoutePoint(pWp);
1162 res = true;
1163 }
1164 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1165 }
1166 return res;
1167}
1168
1169static void UpdateRouteA(Route *pTentRoute, NavObjectCollection1 *navobj) {
1170 if (!pTentRoute) return;
1171 if (pTentRoute->GetnPoints() < 2) return;
1172
1173 // first delete the route to be modified if exists
1174 Route *pExisting = ::RouteExists(pTentRoute->m_GUID);
1175 if (pExisting) {
1176 g_pRouteMan->DeleteRoute(pExisting);
1177 }
1178
1179 // create a new route
1180 Route *pChangeRoute = new Route();
1181 pRouteList->push_back(pChangeRoute);
1182
1183 // update new route keeping the same gui
1184 pChangeRoute->m_GUID = pTentRoute->m_GUID;
1185 pChangeRoute->m_RouteNameString = pTentRoute->m_RouteNameString;
1186 pChangeRoute->m_RouteStartString = pTentRoute->m_RouteStartString;
1187 pChangeRoute->m_RouteEndString = pTentRoute->m_RouteEndString;
1188 pChangeRoute->SetVisible(pTentRoute->IsVisible());
1189
1190 // Add points and segments to new route
1191 int ip = 0;
1192 float prev_rlat = 0., prev_rlon = 0.;
1193 RoutePoint *prev_pConfPoint = NULL;
1194
1195 for (RoutePoint *prp : *pTentRoute->pRoutePointList) {
1196 // if some wpts have been not deleted, that means they should be used in
1197 // other routes or are isolated way points so need to be updated
1198 RoutePoint *ex_rp = ::WaypointExists(prp->m_GUID);
1199 if (ex_rp) {
1200 pSelect->DeleteSelectableRoutePoint(ex_rp);
1201 ex_rp->m_lat = prp->m_lat;
1202 ex_rp->m_lon = prp->m_lon;
1203 ex_rp->SetIconName(prp->GetIconName());
1204 ex_rp->m_MarkDescription = prp->m_MarkDescription;
1205 ex_rp->SetName(prp->GetName());
1206 ex_rp->m_TideStation = prp->m_TideStation;
1207 ex_rp->SetPlannedSpeed(prp->GetPlannedSpeed());
1208 pChangeRoute->AddPoint(ex_rp);
1209 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, ex_rp);
1210
1211 } else {
1212 pChangeRoute->AddPoint(prp);
1213 pSelect->AddSelectableRoutePoint(prp->m_lat, prp->m_lon, prp);
1214 pWayPointMan->AddRoutePoint(prp);
1215 }
1216
1217 if (ip)
1218 pSelect->AddSelectableRouteSegment(prev_rlat, prev_rlon, prp->m_lat,
1219 prp->m_lon, prev_pConfPoint, prp,
1220 pChangeRoute);
1221 prev_rlat = prp->m_lat;
1222 prev_rlon = prp->m_lon;
1223 prev_pConfPoint = prp;
1224
1225 ip++;
1226 }
1227 // Do the (deferred) calculation of BBox
1228 pChangeRoute->FinalizeForRendering();
1229}
1230
1231Route *FindRouteContainingWaypoint(RoutePoint *pWP) {
1232 for (Route *proute : *pRouteList) {
1233 for (RoutePoint *prp : *proute->pRoutePointList) {
1234 if (prp == pWP) return proute;
1235 }
1236 }
1237
1238 return NULL; // not found
1239}
1240
1241bool NavObjectCollection1::CreateNavObjGPXPoints() {
1242 // Iterate over the Routepoint list, creating Nodes for
1243 // Routepoints that are not in any Route
1244 // as indicated by m_bIsolatedMark == false
1245
1246 if (!pWayPointMan) return false;
1247
1248 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1249 if ((pr->m_bIsolatedMark) && !(pr->m_bIsInLayer) && !(pr->m_btemp)) {
1250 pugi::xml_node doc = root();
1251 pugi::xml_node gpx = doc.first_child();
1252 pugi::xml_node new_node = gpx.append_child("wpt");
1253
1254 GPXCreateWpt(new_node, pr, OPT_WPT);
1255 }
1256 }
1257
1258 return true;
1259}
1260
1261bool NavObjectCollection1::CreateNavObjGPXRoutes() {
1262 // Routes
1263 if (!pRouteList) return false;
1264 for (Route *pRoute : *pRouteList) {
1265 if (!pRoute->m_bIsInLayer && !pRoute->m_btemp) {
1266 pugi::xml_node doc = root();
1267 pugi::xml_node gpx = doc.first_child();
1268 pugi::xml_node new_node = gpx.append_child("rte");
1269
1270 GPXCreateRoute(new_node, pRoute);
1271 }
1272 }
1273
1274 return true;
1275}
1276
1277bool NavObjectCollection1::CreateNavObjGPXTracks() {
1278 // Tracks
1279 for (Track *pTrack : g_TrackList) {
1280 if (pTrack->GetnPoints()) {
1281 if (!pTrack->m_bIsInLayer && !pTrack->m_btemp) {
1282 pugi::xml_node doc = root();
1283 pugi::xml_node gpx = doc.first_child();
1284 pugi::xml_node new_node = gpx.append_child("trk");
1285
1286 GPXCreateTrk(new_node, pTrack, 0);
1287 }
1288 }
1289 }
1290
1291 return true;
1292}
1293
1294bool NavObjectCollection1::CreateAllGPXObjects() {
1295 SetRootGPXNode();
1296
1297 // CreateNavObjGPXPoints();
1298 // CreateNavObjGPXRoutes();
1299 // CreateNavObjGPXTracks();
1300
1301 return true;
1302}
1303
1304bool NavObjectCollection1::AddGPXRoute(Route *pRoute) {
1305 SetRootGPXNode();
1306 pugi::xml_node doc = root();
1307 pugi::xml_node gpx = doc.first_child();
1308 pugi::xml_node new_node = gpx.append_child("rte");
1309
1310 GPXCreateRoute(new_node, pRoute);
1311 return true;
1312}
1313
1314bool NavObjectCollection1::AddGPXTrack(Track *pTrk) {
1315 SetRootGPXNode();
1316 pugi::xml_node doc = root();
1317 pugi::xml_node gpx = doc.first_child();
1318 pugi::xml_node new_node = gpx.append_child("trk");
1319
1320 GPXCreateTrk(new_node, pTrk, 0);
1321 return true;
1322}
1323
1324bool NavObjectCollection1::AddGPXWaypoint(RoutePoint *pWP) {
1325 SetRootGPXNode();
1326 pugi::xml_node doc = root();
1327 pugi::xml_node gpx = doc.first_child();
1328 pugi::xml_node new_node = gpx.append_child("wpt");
1329
1330 GPXCreateWpt(new_node, pWP, OPT_WPT);
1331 return true;
1332}
1333
1334void NavObjectCollection1::AddGPXRoutesList(RouteList *pRoutes) {
1335 SetRootGPXNode();
1336
1337 for (Route *pRData : *pRoutes) {
1338 AddGPXRoute(pRData);
1339 }
1340}
1341
1342void NavObjectCollection1::AddGPXTracksList(std::vector<Track *> *pTracks) {
1343 SetRootGPXNode();
1344
1345 for (Track *pRData : *pTracks) {
1346 AddGPXTrack(pRData);
1347 }
1348}
1349
1350bool NavObjectCollection1::AddGPXPointsList(RoutePointList *pRoutePoints) {
1351 SetRootGPXNode();
1352
1353 for (RoutePoint *pRP : *pRoutePoints) {
1354 AddGPXWaypoint(pRP);
1355 }
1356
1357 return true;
1358}
1359
1360void NavObjectCollection1::SetRootGPXNode() {
1361 if (!strlen(first_child().name())) {
1362 pugi::xml_node gpx_root = append_child("gpx");
1363 gpx_root.append_attribute("version") = "1.1";
1364 gpx_root.append_attribute("creator") = "OpenCPN";
1365 gpx_root.append_attribute("xmlns:xsi") =
1366 "http://www.w3.org/2001/XMLSchema-instance";
1367 gpx_root.append_attribute("xmlns") = "http://www.topografix.com/GPX/1/1";
1368 gpx_root.append_attribute("xmlns:gpxx") =
1369 "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
1370 gpx_root.append_attribute("xsi:schemaLocation") =
1371 "http://www.topografix.com/GPX/1/1 "
1372 "http://www.topografix.com/GPX/1/1/gpx.xsd "
1373 "http://www.garmin.com/xmlschemas/GpxExtensions/v3 "
1374 "http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
1375 gpx_root.append_attribute("xmlns:opencpn") = "http://www.opencpn.org";
1376 }
1377}
1378
1379bool NavObjectCollection1::IsOpenCPN() {
1380 for (pugi::xml_attribute attr = root().first_child().first_attribute(); attr;
1381 attr = attr.next_attribute())
1382 if (!strcmp(attr.name(), "creator") && !strcmp(attr.value(), "OpenCPN"))
1383 return true;
1384 return false;
1385}
1386
1387bool NavObjectCollection1::SaveFile(const wxString filename) {
1388 wxString tmp_filename = filename + ".tmp";
1389 if (wxFileExists(tmp_filename)) {
1390 wxRemoveFile(tmp_filename);
1391 }
1392 save_file(tmp_filename.fn_str(), " ");
1393 wxRenameFile(tmp_filename.fn_str(), filename.fn_str(), true);
1394 return true;
1395}
1396
1397bool NavObjectCollection1::LoadAllGPXObjects(bool b_full_viz,
1398 int &wpt_duplicates,
1399 bool b_compute_bbox) {
1400 wpt_duplicates = 0;
1401 pugi::xml_node objects = this->child("gpx");
1402
1403 for (pugi::xml_node object = objects.first_child(); object;
1404 object = object.next_sibling()) {
1405 if (!strcmp(object.name(), "wpt")) {
1406 RoutePoint *pWp =
1407 ::GPXLoadWaypoint1(object, "circle", "", b_full_viz, false, false, 0);
1408
1409 pWp->m_bIsolatedMark = true; // This is an isolated mark
1410 RoutePoint *pExisting =
1411 WaypointExists(pWp->GetName(), pWp->m_lat, pWp->m_lon);
1412 if (!pExisting) {
1413 if (NULL != pWayPointMan) pWayPointMan->AddRoutePoint(pWp);
1414 NavObj_dB::GetInstance().InsertRoutePoint(pWp);
1415 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1416 LLBBox wptbox;
1417 wptbox.Set(pWp->m_lat, pWp->m_lon, pWp->m_lat, pWp->m_lon);
1418 BBox.Expand(wptbox);
1419 } else {
1420 delete pWp;
1421 wpt_duplicates++;
1422 }
1423 } else if (!strcmp(object.name(), "trk")) {
1424 Track *pTrack = GPXLoadTrack1(object, b_full_viz, false, false, 0);
1425 if (InsertTrack(pTrack)) {
1426 NavObj_dB::GetInstance().InsertTrack(pTrack);
1427 }
1428 } else if (!strcmp(object.name(), "rte")) {
1429 Route *pRoute = GPXLoadRoute1(object, b_full_viz, false, false, 0, false);
1430 if (InsertRouteA(pRoute, this)) {
1431 NavObj_dB::GetInstance().InsertRoute(pRoute);
1432 if (b_compute_bbox && pRoute->IsVisible())
1433 BBox.Expand(pRoute->GetBBox());
1434 }
1435 }
1436 }
1437
1438 return true;
1439}
1440
1441int NavObjectCollection1::LoadAllGPXObjectsAsLayer(int layer_id,
1442 bool b_layerviz,
1443 wxCheckBoxState b_namesviz) {
1444 if (!pWayPointMan) return 0;
1445
1446 int n_obj = 0;
1447 pugi::xml_node objects = this->child("gpx");
1448
1449 for (pugi::xml_node object = objects.first_child(); object;
1450 object = object.next_sibling()) {
1451 if (!strcmp(object.name(), "wpt")) {
1452 RoutePoint *pWp = ::GPXLoadWaypoint1(object, "circle", "",
1453 b_namesviz != wxCHK_UNDETERMINED,
1454 true, b_layerviz, layer_id);
1455 if (b_namesviz != wxCHK_UNDETERMINED) {
1456 pWp->SetNameShown(b_namesviz == wxCHK_CHECKED);
1457 }
1458 pWp->m_bIsolatedMark = true; // This is an isolated mark
1459 pWayPointMan->AddRoutePoint(pWp);
1460 pSelect->AddSelectableRoutePoint(pWp->m_lat, pWp->m_lon, pWp);
1461 n_obj++;
1462 } else {
1463 if (!strcmp(object.name(), "trk")) {
1464 Track *pTrack =
1465 GPXLoadTrack1(object, false, true, b_layerviz, layer_id);
1466 n_obj++;
1467 InsertTrack(pTrack);
1468 } else if (!strcmp(object.name(), "rte")) {
1469 Route *pRoute =
1470 GPXLoadRoute1(object, true, true, b_layerviz, layer_id, false);
1471 n_obj++;
1472 InsertRouteA(pRoute, this);
1473 }
1474 }
1475 }
1476
1477 return n_obj;
1478}
1479
1480bool NavObjectCollection1::LoadAllGPXTrackObjects() {
1481 pugi::xml_node objects = this->child("gpx");
1482
1483 for (pugi::xml_node object = objects.first_child(); object;
1484 object = object.next_sibling()) {
1485 if (!strcmp(object.name(), "trk")) {
1486 Track *pTrack = GPXLoadTrack1(object, true, false, false, 0);
1487 InsertTrack(pTrack);
1488 }
1489 }
1490
1491 return true;
1492}
1493
1494bool NavObjectCollection1::LoadAllGPXRouteObjects() {
1495 pugi::xml_node objects = this->child("gpx");
1496
1497 for (pugi::xml_node object = objects.first_child(); object;
1498 object = object.next_sibling()) {
1499 if (!strcmp(object.name(), "rte")) {
1500 Route *route = GPXLoadRoute1(object, true, false, false, 0, false, true);
1501 InsertRouteA(route, nullptr);
1502 }
1503 }
1504
1505 return true;
1506}
1507
1508bool NavObjectCollection1::LoadAllGPXPointObjects() {
1509 pugi::xml_node objects = this->child("gpx");
1510
1511 for (pugi::xml_node object = objects.first_child(); object;
1512 object = object.next_sibling()) {
1513 if (!strcmp(object.name(), "wpt")) {
1514 RoutePoint *rp =
1515 GPXLoadWaypoint1(object, "circle", "", false, false, false, 0);
1516 rp->m_bIsolatedMark = true;
1517 pWayPointMan->AddRoutePoint(rp);
1518 }
1519 }
1520 return true;
1521}
1522
1523RoutePoint *WaypointExists(const wxString &name, double lat, double lon) {
1524 RoutePoint *pret = NULL;
1525 // if( g_bIsNewLayer ) return NULL;
1526 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1527 if (name == pr->GetName()) {
1528 if (fabs(lat - pr->m_lat) < 1.e-6 && fabs(lon - pr->m_lon) < 1.e-6) {
1529 pret = pr;
1530 break;
1531 }
1532 }
1533 }
1534
1535 return pret;
1536}
1537
1538RoutePoint *WaypointExists(const wxString &guid) {
1539 for (RoutePoint *pr : *pWayPointMan->GetWaypointList()) {
1540 if (guid == pr->m_GUID) {
1541 return pr;
1542 }
1543 }
1544 return NULL;
1545}
1546
1547bool WptIsInRouteList(RoutePoint *pr) {
1548 bool IsInList = false;
1549
1550 for (Route *pRoute : *pRouteList) {
1551 RoutePointList *pRoutePointList = pRoute->pRoutePointList;
1552 for (RoutePoint *prp : *pRoutePointList) {
1553 if (pr->IsSame(prp)) {
1554 IsInList = true;
1555 break;
1556 }
1557 }
1558 }
1559 return IsInList;
1560}
1561
1562Route *RouteExists(const wxString &guid) {
1563 for (Route *proute : *pRouteList) {
1564 if (guid == proute->m_GUID) return proute;
1565 }
1566 return NULL;
1567}
1568
1569Route *RouteExists(Route *pTentRoute) {
1570 for (Route *proute : *pRouteList) {
1571 if (proute->IsEqualTo(pTentRoute)) return proute;
1572 }
1573 return NULL;
1574}
1575
1576Track *TrackExists(const wxString &guid) {
1577 for (Track *ptrack : g_TrackList) {
1578 if (guid == ptrack->m_GUID) return ptrack;
1579 }
1580 return NULL;
1581}
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:762
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.
Definition routeman.cpp:998
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.