OpenCPN Partial API docs
Loading...
Searching...
No Matches
navobj_db.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2025 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
24#include <cmath>
25#include <iomanip>
26#include <memory>
27#include <string>
28#include <vector>
29
30#include <wx/dir.h>
31#include <wx/filename.h>
32
33#include "model/base_platform.h"
35#include "model/navobj_db.h"
37#include "model/navutil_base.h"
38#include "model/notification.h"
40#include "model/routeman.h"
41
42static void ReportError(const std::string zmsg); // forward
43
44static bool executeSQL(sqlite3* db, const char* sql) {
45 char* errMsg = nullptr;
46 if (sqlite3_exec(db, sql, nullptr, nullptr, &errMsg) != SQLITE_OK) {
47 wxString msg =
48 wxString::Format(_("navobj database error.") + " %s", errMsg);
49 wxLogMessage(msg);
50 auto& noteman = NotificationManager::GetInstance();
51 noteman.AddNotification(NotificationSeverity::kWarning, msg.ToStdString());
52 sqlite3_free(errMsg);
53 return false;
54 }
55 return true;
56}
57
58static bool executeSQL(sqlite3* db, wxString& sql) {
59 return executeSQL(db, sql.ToStdString().c_str());
60}
61
62bool CreateTables(sqlite3* db) {
63 // Track tables
64 const char* create_tables_sql = R"(
65 CREATE TABLE IF NOT EXISTS tracks (
66 guid TEXT PRIMARY KEY NOT NULL,
67 name TEXT,
68 description TEXT,
69 visibility INTEGER,
70 start_string TEXT,
71 end_string TEXT,
72 width INTEGER,
73 style INTEGER,
74 color TEXT,
75 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
76 );
77
78 CREATE TABLE IF NOT EXISTS trk_points (
79 track_guid TEXT NOT NULL,
80 latitude REAL NOT NULL,
81 longitude REAL NOT NULL,
82 timestamp TEXT NOT NULL,
83 point_order INTEGER,
84 FOREIGN KEY (track_guid) REFERENCES tracks(guid) ON DELETE CASCADE
85 );
86
87
88 CREATE TABLE IF NOT EXISTS track_html_links (
89 guid TEXT PRIMARY KEY,
90 track_guid TEXT NOT NULL,
91 html_link TEXT,
92 html_description TEXT,
93 html_type TEXT,
94 FOREIGN KEY (track_guid) REFERENCES tracks(guid) ON DELETE CASCADE
95 );
96
97
98 CREATE TABLE IF NOT EXISTS routes (
99 guid TEXT PRIMARY KEY NOT NULL,
100 name TEXT,
101 start_string TEXT,
102 end_string TEXT,
103 description TEXT,
104 planned_departure TEXT,
105 plan_speed REAL,
106 time_format TEXT,
107 style INTEGER,
108 width INTEGER,
109 color TEXT,
110 visibility INTEGER,
111 shared_wp_viz INTEGER,
112 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
113 );
114
115
116 CREATE TABLE IF NOT EXISTS routepoints (
117 guid TEXT PRIMARY KEY NOT NULL,
118 lat REAL,
119 lon REAL,
120 Symbol TEXT,
121 Name TEXT,
122 description TEXT,
123 TideStation TEXT,
124 plan_speed REAL,
125 etd INTEGER,
126 Type TEXT,
127 Time TEXT,
128 ArrivalRadius REAL,
129 RangeRingsNumber INTEGER,
130 RangeRingsStep REAL,
131 RangeRingsStepUnits INTEGER,
132 RangeRingsVisible INTEGER,
133 RangeRingsColour TEXT,
134 ScaleMin INTEGER,
135 ScaleMax INTEGER,
136 UseScale INTEGER,
137 visibility INTEGER,
138 viz_name INTEGER,
139 shared INTEGER,
140 isolated INTEGER,
141 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
142 );
143
144 CREATE TABLE IF NOT EXISTS routepoints_link (
145 route_guid TEXT,
146 point_guid TEXT,
147 point_order INTEGER,
148 PRIMARY KEY (route_guid, point_order),
149 FOREIGN KEY (route_guid) REFERENCES routes(guid) ON DELETE CASCADE
150 );
151
152 CREATE TABLE IF NOT EXISTS route_html_links (
153 guid TEXT PRIMARY KEY,
154 route_guid TEXT NOT NULL,
155 html_link TEXT,
156 html_description TEXT,
157 html_type TEXT,
158 FOREIGN KEY (route_guid) REFERENCES routes(guid) ON DELETE CASCADE
159 );
160
161 CREATE TABLE IF NOT EXISTS routepoint_html_links (
162 guid TEXT PRIMARY KEY,
163 routepoint_guid TEXT NOT NULL,
164 html_link TEXT,
165 html_description TEXT,
166 html_type TEXT,
167 FOREIGN KEY (routepoint_guid) REFERENCES routepoints(guid) ON DELETE CASCADE
168 );
169
170 CREATE INDEX IF NOT EXISTS idx_track_points
171 ON trk_points (track_guid);
172
173 )";
174
175 if (!executeSQL(db, create_tables_sql)) return false;
176
177 return true;
178}
179
180bool TrackExists(sqlite3* db, const std::string& track_guid) {
181 const char* sql = "SELECT 1 FROM tracks WHERE guid = ? LIMIT 1";
182 sqlite3_stmt* stmt;
183 bool exists = false;
184
185 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
186 sqlite3_bind_text(stmt, 1, track_guid.c_str(), -1, SQLITE_TRANSIENT);
187
188 if (sqlite3_step(stmt) == SQLITE_ROW) {
189 exists = true; // found a match
190 }
191
192 sqlite3_finalize(stmt);
193 } else {
194 ReportError("TrackExists:prepare");
195 return false;
196 }
197 return exists;
198}
199
200bool TrackHtmlLinkExists(sqlite3* db, const std::string& link_guid) {
201 const char* sql = "SELECT 1 FROM track_html_links WHERE guid = ? LIMIT 1";
202 sqlite3_stmt* stmt;
203 bool exists = false;
204
205 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
206 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
207
208 if (sqlite3_step(stmt) == SQLITE_ROW) {
209 exists = true; // found a match
210 }
211
212 sqlite3_finalize(stmt);
213 } else {
214 ReportError("TrackHtmlLinkExists:prepare");
215 return false;
216 }
217 return exists;
218}
219
220bool DeleteAllCommentsForTrack(sqlite3* db, const std::string& track_guid) {
221 const char* sql = "DELETE FROM track_html_links WHERE track_guid = ?";
222
223 sqlite3_stmt* stmt;
224 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
225 sqlite3_bind_text(stmt, 1, track_guid.c_str(), -1, SQLITE_TRANSIENT);
226 if (sqlite3_step(stmt) != SQLITE_DONE) {
227 ReportError("DeleteAllCommentsForTrack:step");
228 return false;
229 }
230
231 sqlite3_finalize(stmt);
232 } else {
233 ReportError("DeleteAllCommentsForTrack:prepare");
234 return false;
235 }
236 return true;
237}
238
239bool InsertTrackPoint(sqlite3* db, const std::string& track_guid, double lat,
240 double lon, const std::string& timestamp, int i_point) {
241 const char* sql = R"(
242 INSERT INTO trk_points (track_guid, latitude, longitude, timestamp, point_order)
243 VALUES (?, ?, ?, ?, ?)
244 )";
245 sqlite3_stmt* stmt;
246
247 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
248 sqlite3_bind_text(stmt, 1, track_guid.c_str(), -1, SQLITE_TRANSIENT);
249 sqlite3_bind_double(stmt, 2, lat);
250 sqlite3_bind_double(stmt, 3, lon);
251 sqlite3_bind_text(stmt, 4, timestamp.c_str(), -1, SQLITE_TRANSIENT);
252 sqlite3_bind_int(stmt, 5, i_point);
253 if (sqlite3_step(stmt) != SQLITE_DONE) {
254 ReportError("InsertTrackPoint:step");
255 sqlite3_finalize(stmt);
256 return false;
257 }
258 sqlite3_finalize(stmt);
259 } else {
260 return false;
261 }
262 return true;
263}
264
265bool InsertTrackHTML(sqlite3* db, const std::string& track_guid,
266 const std::string& link_guid, const std::string& descrText,
267 const std::string& link, const std::string& ltype) {
268 const char* sql = R"(
269 INSERT INTO track_html_links (guid, track_guid, html_link, html_description, html_type)
270 VALUES (?, ?, ?, ?, ?)
271 )";
272 sqlite3_stmt* stmt;
273
274 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
275 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
276 sqlite3_bind_text(stmt, 2, track_guid.c_str(), -1, SQLITE_TRANSIENT);
277 sqlite3_bind_text(stmt, 3, link.c_str(), -1, SQLITE_TRANSIENT);
278 sqlite3_bind_text(stmt, 4, descrText.c_str(), -1, SQLITE_TRANSIENT);
279 sqlite3_bind_text(stmt, 5, ltype.c_str(), -1, SQLITE_TRANSIENT);
280 if (sqlite3_step(stmt) != SQLITE_DONE) {
281 ReportError("InsertTrackHTML:step");
282 sqlite3_finalize(stmt);
283 return false;
284 }
285 sqlite3_finalize(stmt);
286 } else {
287 return false;
288 }
289 return true;
290}
291
292//..Routes
293
294bool DeleteAllCommentsForRoute(sqlite3* db, const std::string& route_guid) {
295 const char* sql = R"(
296 DELETE FROM route_html_links WHERE route_guid = ?
297 )";
298 sqlite3_stmt* stmt;
299 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
300 sqlite3_bind_text(stmt, 1, route_guid.c_str(), -1, SQLITE_TRANSIENT);
301 if (sqlite3_step(stmt) != SQLITE_DONE) {
302 ReportError("DeleteAllCommentsForRoute:step");
303 sqlite3_finalize(stmt);
304 return false;
305 }
306 sqlite3_finalize(stmt);
307 } else {
308 return false;
309 }
310 return true;
311}
312
313bool RouteHtmlLinkExists(sqlite3* db, const std::string& link_guid) {
314 const char* sql = "SELECT 1 FROM route_html_links WHERE guid = ? LIMIT 1";
315 sqlite3_stmt* stmt;
316 bool exists = false;
317
318 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
319 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
320
321 if (sqlite3_step(stmt) == SQLITE_ROW) {
322 exists = true; // found a match
323 }
324
325 sqlite3_finalize(stmt);
326 } else {
327 return false;
328 }
329 return exists;
330}
331
332bool RoutePointHtmlLinkExists(sqlite3* db, const std::string& link_guid) {
333 const char* sql =
334 "SELECT 1 FROM routepoint_html_links WHERE guid = ? LIMIT 1";
335 sqlite3_stmt* stmt;
336 bool exists = false;
337
338 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
339 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
340
341 if (sqlite3_step(stmt) == SQLITE_ROW) {
342 exists = true; // found a match
343 }
344
345 sqlite3_finalize(stmt);
346 } else {
347 return false;
348 }
349 return exists;
350}
351
352bool RouteExistsDB(sqlite3* db, const std::string& route_guid) {
353 const char* sql = "SELECT 1 FROM routes WHERE guid = ? LIMIT 1";
354 sqlite3_stmt* stmt;
355 bool exists = false;
356
357 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
358 sqlite3_bind_text(stmt, 1, route_guid.c_str(), -1, SQLITE_TRANSIENT);
359
360 if (sqlite3_step(stmt) == SQLITE_ROW) {
361 exists = true; // found a match
362 }
363
364 sqlite3_finalize(stmt);
365 } else {
366 return false;
367 }
368 return exists;
369}
370
371bool InsertRouteHTML(sqlite3* db, const std::string& route_guid,
372 const std::string& link_guid, const std::string& descrText,
373 const std::string& link, const std::string& ltype) {
374 const char* sql = R"(
375 INSERT INTO route_html_links (guid, route_guid, html_link, html_description, html_type)
376 VALUES (?, ?, ?, ?, ?)
377 )";
378 sqlite3_stmt* stmt;
379
380 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
381 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
382 sqlite3_bind_text(stmt, 2, route_guid.c_str(), -1, SQLITE_TRANSIENT);
383 sqlite3_bind_text(stmt, 3, link.c_str(), -1, SQLITE_TRANSIENT);
384 sqlite3_bind_text(stmt, 4, descrText.c_str(), -1, SQLITE_TRANSIENT);
385 sqlite3_bind_text(stmt, 5, ltype.c_str(), -1, SQLITE_TRANSIENT);
386 if (sqlite3_step(stmt) != SQLITE_DONE) {
387 ReportError("InsertRouteHTML:step");
388 sqlite3_finalize(stmt);
389 return false;
390 }
391 sqlite3_finalize(stmt);
392 } else {
393 return false;
394 }
395 return true;
396}
397
398bool RoutePointExists(sqlite3* db, const std::string& routepoint_guid) {
399 const char* sql = "SELECT 1 FROM routepoints WHERE guid = ? LIMIT 1";
400 sqlite3_stmt* stmt;
401 bool exists = false;
402
403 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
404 sqlite3_bind_text(stmt, 1, routepoint_guid.c_str(), -1, SQLITE_TRANSIENT);
405
406 if (sqlite3_step(stmt) == SQLITE_ROW) {
407 exists = true; // found a match
408 }
409
410 sqlite3_finalize(stmt);
411 } else {
412 return false;
413 }
414 return exists;
415}
416
417bool InsertRoutePointHTML(sqlite3* db, const std::string& point_guid,
418 const std::string& link_guid,
419 const std::string& descrText, const std::string& link,
420 const std::string& ltype) {
421 const char* sql = R"(
422 INSERT INTO routepoint_html_links (guid, routepoint_guid, html_link, html_description, html_type)
423 VALUES (?, ?, ?, ?, ?)
424 )";
425 sqlite3_stmt* stmt;
426
427 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
428 sqlite3_bind_text(stmt, 1, link_guid.c_str(), -1, SQLITE_TRANSIENT);
429 sqlite3_bind_text(stmt, 2, point_guid.c_str(), -1, SQLITE_TRANSIENT);
430 sqlite3_bind_text(stmt, 3, link.c_str(), -1, SQLITE_TRANSIENT);
431 sqlite3_bind_text(stmt, 4, descrText.c_str(), -1, SQLITE_TRANSIENT);
432 sqlite3_bind_text(stmt, 5, ltype.c_str(), -1, SQLITE_TRANSIENT);
433 if (sqlite3_step(stmt) != SQLITE_DONE) {
434 ReportError("InsertRoutePointHTML:step");
435 sqlite3_finalize(stmt);
436 return false;
437 }
438 sqlite3_finalize(stmt);
439 } else {
440 return false;
441 }
442 return true;
443}
444bool DeleteAllCommentsForRoutePoint(sqlite3* db,
445 const std::string& routepoint_guid) {
446 const char* sql =
447 "DELETE FROM routepoint_html_links WHERE routepoint_guid = ?";
448
449 sqlite3_stmt* stmt;
450 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
451 sqlite3_bind_text(stmt, 1, routepoint_guid.c_str(), -1, SQLITE_TRANSIENT);
452 if (sqlite3_step(stmt) != SQLITE_DONE) {
453 ReportError("DeleteAllCommentsForRoutepoint:step");
454 return false;
455 }
456
457 sqlite3_finalize(stmt);
458 } else {
459 ReportError("DeleteAllCommentsForRoutepoint:prepare");
460 return false;
461 }
462 return true;
463}
464
465bool InsertRoutePointDB(sqlite3* db, RoutePoint* point) {
466 const char* sql = R"(
467 INSERT or REPLACE INTO routepoints(guid)
468 VALUES (?)
469 )";
470 sqlite3_stmt* stmt;
471
472 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
473 sqlite3_bind_text(stmt, 1, point->m_GUID.ToStdString().c_str(), -1,
474 SQLITE_TRANSIENT);
475 if (sqlite3_step(stmt) != SQLITE_DONE) {
476 ReportError("InsertRoutePointDB:step");
477 sqlite3_finalize(stmt);
478 return false;
479 }
480 sqlite3_finalize(stmt);
481 } else {
482 return false;
483 }
484 return true;
485}
486
487bool InsertRoutePointLink(sqlite3* db, Route* route, RoutePoint* point,
488 int point_order) {
489 const char* sql = R"(
490 INSERT or IGNORE INTO routepoints_link (route_guid, point_guid, point_order)
491 VALUES (?, ?, ?)
492 )";
493
494 sqlite3_stmt* stmt;
495
496 if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
497 sqlite3_bind_text(stmt, 1, route->m_GUID.ToStdString().c_str(), -1,
498 SQLITE_TRANSIENT);
499 sqlite3_bind_text(stmt, 2, point->m_GUID.ToStdString().c_str(), -1,
500 SQLITE_TRANSIENT);
501 sqlite3_bind_int(stmt, 3, point_order);
502 if (sqlite3_step(stmt) != SQLITE_DONE) {
503 ReportError("InsertTrackPointLink:step");
504 sqlite3_finalize(stmt);
505 return false;
506 }
507 sqlite3_finalize(stmt);
508 } else {
509 return false;
510 }
511 return true;
512}
513
514void DeleteOrphanedRoutepoint(sqlite3* db) {
515 const char* sql = R"(
516 DELETE FROM routepoints
517 WHERE guid NOT IN (SELECT point_guid FROM routepoints_link)
518 )";
519 char* errMsg = nullptr;
520
521 if (sqlite3_exec(db, sql, nullptr, nullptr, &errMsg) != SQLITE_OK) {
522 } else {
523 }
524}
525
526void errorLogCallback(void* pArg, int iErrCode, const char* zMsg) {
527 wxString msg =
528 wxString::Format(_("navobj database error.") + " %d: %s", iErrCode, zMsg);
529 wxLogMessage(msg);
530 auto& noteman = NotificationManager::GetInstance();
531 noteman.AddNotification(NotificationSeverity::kWarning, msg.ToStdString());
532}
533
534static void ReportError(const std::string zmsg) {
535 wxString msg =
536 wxString::Format(_("navobj database error.") + " %s", zmsg.c_str());
537 wxLogMessage(msg);
538 auto& noteman = NotificationManager::GetInstance();
539 noteman.AddNotification(NotificationSeverity::kWarning, msg.ToStdString());
540}
541
542NavObj_dB& NavObj_dB::GetInstance() {
543 static NavObj_dB instance;
544 return instance;
545}
546
547NavObj_dB::NavObj_dB() {
548 m_pImportProgress = nullptr;
549
550 // Set SQLite per-process config options
551 int ie = sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, nullptr);
552
553 // Does dB file exist?
554 wxString db_filename = g_BasePlatform->GetPrivateDataDir() +
555 wxFileName::GetPathSeparator() + "navobj.db";
556 if (!wxFileExists(db_filename)) {
557 // Make a safety backup of current navobj.xml
558 wxString xml_filename = g_BasePlatform->GetPrivateDataDir() +
559 wxFileName::GetPathSeparator() + "navobj.xml";
560 if (wxFileExists(xml_filename)) {
561 wxCopyFile(xml_filename, xml_filename + ".backup");
562
563 // Make another safety backup, one time
564 wxString deep_backup_filename = g_BasePlatform->GetPrivateDataDir() +
565 wxFileName::GetPathSeparator() +
566 "navobj.xml.import_backup";
567 if (!wxFileExists(deep_backup_filename)) {
568 wxCopyFile(xml_filename, deep_backup_filename);
569 }
570 }
571
572 // Create the new database file navobj.db
573
574 int create_result = sqlite3_open_v2(
575 db_filename.ToStdString().c_str(),
576 &m_db, // sqlite3 **ppDb, /* OUT: SQLite db handle */
577 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, // int flags, /* Flags */
578 NULL // char *zVfs /* Name of VFS module to use */
579 );
580 if (create_result != SQLITE_OK) {
581 wxLogMessage("Cannot create new navobj.db database file");
582 m_db = nullptr;
583 return;
584 }
585
586 // Create initial database tables
587 CreateTables(m_db);
588
589 // Save/Close the database
590 int close_result = sqlite3_close_v2(m_db);
591 if (close_result != SQLITE_OK) {
592 return;
593 }
594 }
595
596 // Open the existing database file
597 int m_open_result = sqlite3_open_v2(db_filename.ToStdString().c_str(), &m_db,
598 SQLITE_OPEN_READWRITE, NULL);
599 sqlite3_exec(m_db, "PRAGMA foreign_keys = ON;", nullptr, nullptr, nullptr);
600
601 // Add any new tables
602 CreateTables(m_db);
603 sqlite3_close_v2(m_db);
604
605 m_open_result = sqlite3_open_v2(db_filename.ToStdString().c_str(), &m_db,
606 SQLITE_OPEN_READWRITE, NULL);
607 sqlite3_exec(m_db, "PRAGMA foreign_keys = ON;", nullptr, nullptr, nullptr);
608
609 // Init class members
610 m_importing = false;
611}
612
613NavObj_dB::~NavObj_dB() { sqlite3_close_v2(m_db); }
614
615void NavObj_dB::Close() {
616 sqlite3_close_v2(m_db);
617 m_db = nullptr;
618}
619
620bool NavObj_dB::FullSchemaMigrate(wxFrame* frame) {
621 // Call successive schema updates as defined.
622 if (needsMigration_0_1(m_db)) {
623 std::string rs = SchemaUpdate_0_1(m_db, frame);
624 if (rs.size()) {
625 wxLogMessage("Error on: Schema update and migration 0->1");
626 wxLogMessage(wxString(rs.c_str()));
627 return false;
628 } else
629 wxLogMessage("Schema update and migration 0->1 successful");
630 }
631
632 try {
633 setUserVersion(m_db,
634 1); // Be sure all users get updated to version 1 at least
635 } catch (const std::runtime_error& e) {
636 // Known errors (e.g., SQLite issues)
637 wxLogMessage("Error on: Schema update and migration 0->1, setUserVersion");
638 wxLogMessage(wxString(std::string(e.what())).c_str());
639 }
640
641 // More schema updates in sequence here.
642 // ...
643
644 return true;
645}
646
647bool NavObj_dB::ImportLegacyNavobj(wxFrame* frame) {
648 wxString navobj_filename = g_BasePlatform->GetPrivateDataDir() +
649 wxFileName::GetPathSeparator() + "navobj.xml";
650 bool rv = false;
651 if (::wxFileExists(navobj_filename)) {
652 m_importing = true;
653 CountImportNavObjects();
654 m_pImportProgress = new wxProgressDialog(_("Importing Navobj database"), "",
655 m_nImportObjects, frame);
656 m_import_progesscount = 0;
657
658 rv = ImportLegacyPoints();
659 rv |= ImportLegacyRoutes();
660 rv |= ImportLegacyTracks();
661 m_importing = false;
662 m_pImportProgress->Destroy();
663 }
664
665 // Delete the imported navobj.xml
666 if (::wxFileExists(navobj_filename)) ::wxRemoveFile(navobj_filename);
667
668 return rv;
669}
670
671void NavObj_dB::CountImportNavObjects() {
672 m_nImportObjects = 0;
673 m_nimportPoints = 0;
674 m_nimportRoutes = 0;
675 m_nimportTracks = 0;
676
677 auto input_set = new NavObjectCollection1();
678 wxString navobj_filename = g_BasePlatform->GetPrivateDataDir() +
679 wxFileName::GetPathSeparator() + "navobj.xml";
680
681 if (::wxFileExists(navobj_filename) &&
682 input_set->load_file(navobj_filename.ToStdString().c_str()).status ==
683 pugi::xml_parse_status::status_ok) {
684 input_set->LoadAllGPXPointObjects();
685 auto pointlist = pWayPointMan->GetWaypointList();
686 for (RoutePoint* point : *pointlist) {
687 if (point->m_bIsolatedMark) {
688 m_nImportObjects++;
689 m_nimportPoints++;
690 }
691 }
692
693 input_set->LoadAllGPXRouteObjects();
694 for (Route* route_import : *pRouteList) {
695 m_nImportObjects++;
696 m_nimportRoutes++;
697 m_nImportObjects += route_import->GetnPoints();
698 }
699
700 input_set->LoadAllGPXTrackObjects();
701 m_nImportObjects += g_TrackList.size();
702 m_nimportTracks = g_TrackList.size();
703
704 for (Track* track_import : g_TrackList) {
705 m_nImportObjects += track_import->GetnPoints();
706 }
707 }
708 delete input_set;
709}
710
711bool NavObj_dB::ImportLegacyTracks() {
712 std::vector<Track*> tracks_added;
713 // Add all tracks to database
714 int ntrack = 0;
715 for (Track* track_import : g_TrackList) {
716 if (InsertTrack(track_import)) {
717 tracks_added.push_back(track_import);
718 }
719 ntrack++;
720 m_import_progesscount += track_import->GetnPoints() + 1;
721 wxString msg = wxString::Format("Tracks %d/%d", ntrack, m_nimportTracks);
722 m_pImportProgress->Update(m_import_progesscount, msg);
723 m_pImportProgress->Show();
724 }
725
726 // Delete all tracks that were successfully added
727 for (Track* ptrack : tracks_added) {
728 if (ptrack->m_bIsInLayer) continue;
729 g_pRouteMan->DeleteTrack(ptrack);
730 }
731
732 return true;
733}
734
735bool NavObj_dB::ImportLegacyRoutes() {
736 std::vector<Route*> routes_added;
737 // Add all routes to database
738 int nroute = 0;
739 for (Route* route_import : *pRouteList) {
740 if (InsertRoute(route_import)) {
741 routes_added.push_back(route_import);
742 }
743 nroute++;
744 m_import_progesscount += route_import->GetnPoints() + 1;
745 wxString msg = wxString::Format("Routes %d/%d", nroute, m_nimportRoutes);
746 m_pImportProgress->Update(m_import_progesscount, msg);
747 m_pImportProgress->Show();
748 }
749
750 // Delete all routes that were successfully added
751 for (Route* route : routes_added) {
752 g_pRouteMan->DeleteRoute(route);
753 }
754
755 // There may have been some points left as isolated orphans
756 // Delete them too.
757 pWayPointMan->DeleteAllWaypoints(true);
758
759 return true;
760}
761
762bool NavObj_dB::ImportLegacyPoints() {
763 std::vector<RoutePoint*> points_added;
764 // Add all isolated points to database
765 int npoint = 0;
766 int nmod = 1;
767 if (m_nimportPoints > 1000) nmod = 10;
768 if (m_nimportPoints > 10000) nmod = 100;
769
770 for (RoutePoint* point : *pWayPointMan->GetWaypointList()) {
771 if (point->m_bIsolatedMark) {
772 if (InsertRoutePointDB(m_db, point)) {
773 points_added.push_back(point);
774 }
775
776 UpdateDBRoutePointAttributes(point);
777 m_import_progesscount += 1;
778 if ((npoint % nmod) == 0) {
779 wxString msg =
780 wxString::Format("Points %d/%d", npoint, m_nimportPoints);
781 m_pImportProgress->Update(m_import_progesscount, msg);
782 m_pImportProgress->Show();
783 }
784 npoint++;
785 }
786 }
787
788 // Delete all points that were successfully added
789 for (RoutePoint* point : points_added) {
790 pWayPointMan->RemoveRoutePoint(point);
791 delete point;
792 }
793
794 return true;
795}
796
797void NavObj_dB::LoadNavObjects() {
798 LoadAllPoints();
799 LoadAllRoutes();
800 LoadAllTracks();
801}
802
803bool NavObj_dB::InsertTrack(Track* track) {
804 if (TrackExists(m_db, track->m_GUID.ToStdString())) return false;
805
806 bool rv = false;
807 char* errMsg = 0;
808 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
809 if (errMsg) {
810 ReportError("InsertTrack:BEGIN TRANSACTION");
811 return false;
812 }
813
814 // Insert a new track
815 wxString sql = wxString::Format("INSERT INTO tracks (guid) VALUES ('%s')",
816 track->m_GUID.ToStdString().c_str());
817 if (!executeSQL(m_db, sql)) {
818 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
819 return false;
820 }
821
822 UpdateDBTrackAttributes(track);
823
824 // Add any existing trkpoints
825 for (int i = 0; i < track->GetnPoints(); i++) {
826 auto point = track->GetPoint(i);
827 // Add the bare trkpoint
828 InsertTrackPoint(m_db, track->m_GUID.ToStdString(), point->m_lat,
829 point->m_lon, point->GetTimeString(), i);
830 }
831
832 // Add HTML links to track
833 int NbrOfLinks = track->m_TrackHyperlinkList->size();
834 if (NbrOfLinks > 0) {
835 auto& list = track->m_TrackHyperlinkList;
836 for (auto it = list->begin(); it != list->end(); ++it) {
837 Hyperlink* link = *it;
838 if (!TrackHtmlLinkExists(m_db, link->GUID)) {
839 InsertTrackHTML(m_db, track->m_GUID.ToStdString(), link->GUID,
840 link->DescrText.ToStdString(), link->Link.ToStdString(),
841 link->LType.ToStdString());
842 }
843 }
844 }
845 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
846 rv = true;
847 if (errMsg) rv = false;
848
849 return rv;
850};
851
852bool NavObj_dB::UpdateTrack(Track* track) {
853 bool rv = false;
854 char* errMsg = 0;
855
856 if (!TrackExists(m_db, track->m_GUID.ToStdString())) return false;
857
858 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
859 if (errMsg) {
860 ReportError("UpdateTrack:BEGIN TRANSACTION");
861 return false;
862 }
863
864 UpdateDBTrackAttributes(track);
865
866 // Delete and re-add track points
867 const char* sql = "DELETE FROM trk_points WHERE track_guid = ?";
868 sqlite3_stmt* stmt;
869 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
870 sqlite3_bind_text(stmt, 1, track->m_GUID.ToStdString().c_str(), -1,
871 SQLITE_TRANSIENT);
872 } else {
873 ReportError("UpdateTrack:prepare");
874 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
875 return false;
876 }
877 if (sqlite3_step(stmt) != SQLITE_DONE) {
878 ReportError("UpdateTrack:step");
879 sqlite3_finalize(stmt);
880 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
881 return false;
882 }
883 sqlite3_finalize(stmt);
884
885 // re-add trackpoints
886 for (int i = 0; i < track->GetnPoints(); i++) {
887 auto point = track->GetPoint(i);
888 // Add the bare point
889 if (point) {
890 InsertTrackPoint(m_db, track->m_GUID.ToStdString(), point->m_lat,
891 point->m_lon, point->GetTimeString(), i);
892 }
893 }
894
895 sqlite3_exec(m_db, "COMMIT", 0, 0, nullptr);
896
897 rv = true;
898 if (errMsg) rv = false;
899 return rv;
900};
901
902bool NavObj_dB::UpdateDBTrackAttributes(Track* track) {
903 const char* sql =
904 "UPDATE tracks SET "
905 "name = ?, "
906 "description = ?, "
907 "visibility = ?, "
908 "start_string = ?, "
909 "end_string = ?, "
910 "width = ?, "
911 "style = ?, "
912 "color = ? "
913 "WHERE guid = ?";
914
915 sqlite3_stmt* stmt;
916 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
917 sqlite3_bind_text(stmt, 1, track->GetName().ToStdString().c_str(), -1,
918 SQLITE_TRANSIENT);
919 sqlite3_bind_text(stmt, 2, track->m_TrackDescription.ToStdString().c_str(),
920 -1, SQLITE_TRANSIENT);
921 sqlite3_bind_int(stmt, 3, track->m_bVisible);
922 sqlite3_bind_text(stmt, 4, track->m_TrackStartString.ToStdString().c_str(),
923 -1, SQLITE_TRANSIENT);
924 sqlite3_bind_text(stmt, 5, track->m_TrackEndString.ToStdString().c_str(),
925 -1, SQLITE_TRANSIENT);
926 sqlite3_bind_int(stmt, 6, track->m_width);
927 sqlite3_bind_int(stmt, 7,
928 (int)(track->m_style)); // track->m_style.c_str(),
929 sqlite3_bind_text(stmt, 8, track->m_Colour.ToStdString().c_str(), -1,
930 SQLITE_TRANSIENT);
931 sqlite3_bind_text(stmt, 9, track->m_GUID.c_str(), track->m_GUID.size(),
932 SQLITE_TRANSIENT);
933 } else {
934 return false;
935 }
936
937 if (sqlite3_step(stmt) != SQLITE_DONE) {
938 ReportError("UpdateDBTrackAttributesA:step");
939 sqlite3_finalize(stmt);
940 return false;
941 }
942
943 sqlite3_finalize(stmt);
944
945 // Update the HTML links
946 // The list of links is freshly rebuilt when this method is called
947 // So start by deleting all existing bcomments
948 DeleteAllCommentsForTrack(m_db, track->m_GUID.ToStdString());
949
950 // Now add all the links to db
951 int NbrOfLinks = track->m_TrackHyperlinkList->size();
952 if (NbrOfLinks > 0) {
953 auto& list = track->m_TrackHyperlinkList;
954 for (auto it = list->begin(); it != list->end(); ++it) {
955 Hyperlink* link = *it;
956
957 if (!TrackHtmlLinkExists(m_db, link->GUID)) {
958 InsertTrackHTML(m_db, track->m_GUID.ToStdString(), link->GUID,
959 link->DescrText.ToStdString(), link->Link.ToStdString(),
960 link->LType.ToStdString());
961 } else {
962 const char* sql =
963 "UPDATE track_html_links SET "
964 "html_link = ?, "
965 "html_description = ?, "
966 "html_type = ? "
967 "WHERE guid = ?";
968 sqlite3_stmt* stmt;
969 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
970 sqlite3_bind_text(stmt, 3, link->Link.ToStdString().c_str(), -1,
971 SQLITE_TRANSIENT);
972 sqlite3_bind_text(stmt, 4, link->DescrText.ToStdString().c_str(), -1,
973 SQLITE_TRANSIENT);
974 sqlite3_bind_text(stmt, 5, link->LType.ToStdString().c_str(), -1,
975 SQLITE_TRANSIENT);
976 }
977 if (sqlite3_step(stmt) != SQLITE_DONE) {
978 ReportError("UpdateDBTRackAttributesB:step");
979 sqlite3_finalize(stmt);
980 return false;
981 }
982 sqlite3_finalize(stmt);
983 }
984 }
985 }
986
987 return true;
988}
989
990bool NavObj_dB::AddTrackPoint(Track* track, TrackPoint* point) {
991 // If track does not yet exist in dB, return
992 if (!TrackExists(m_db, track->m_GUID.ToStdString())) return false;
993
994 // Get next point order
995 int this_point_index = track->GetnPoints();
996
997 // Add the linked point to the dB
998 if (!InsertTrackPoint(m_db, track->m_GUID.ToStdString(), point->m_lat,
999 point->m_lon, point->GetTimeString(),
1000 this_point_index - 1))
1001 return false;
1002
1003 return true;
1004}
1005
1006bool NavObj_dB::LoadAllTracks() {
1007 const char* sql = R"(
1008 SELECT guid, name,
1009 description, visibility, start_string, end_string,
1010 width, style, color,
1011 created_at
1012 FROM tracks
1013 ORDER BY created_at ASC
1014 )";
1015
1016 sqlite3_stmt* stmt;
1017 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1018 return false;
1019 }
1020
1021 while (sqlite3_step(stmt) == SQLITE_ROW) {
1022 std::string guid =
1023 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
1024 std::string name =
1025 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
1026 std::string description =
1027 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
1028 int visibility = sqlite3_column_int(stmt, 3);
1029 std::string start_string =
1030 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
1031 std::string end_string =
1032 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
1033 int width = sqlite3_column_int(stmt, 6);
1034 int style = sqlite3_column_int(stmt, 7);
1035 std::string color =
1036 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 8));
1037 std::string created =
1038 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 9));
1039
1040 Track* new_trk = NULL;
1041
1042 // Add the trk_points
1043 const char* sql = R"(
1044 SELECT latitude, longitude, timestamp, point_order
1045 FROM trk_points
1046 WHERE track_guid = ?
1047 ORDER BY point_order ASC
1048 )";
1049
1050 sqlite3_stmt* stmtp;
1051 if (sqlite3_prepare_v2(m_db, sql, -1, &stmtp, nullptr) != SQLITE_OK) {
1052 return false;
1053 }
1054
1055 sqlite3_bind_text(stmtp, 1, guid.c_str(), -1, SQLITE_TRANSIENT);
1056
1057 int GPXTrkSeg = 1;
1058 while (sqlite3_step(stmtp) == SQLITE_ROW) {
1059 if (!new_trk) {
1060 new_trk = new Track;
1061 new_trk->m_GUID = guid;
1062
1063 // Set all the track attributes
1064 new_trk->SetVisible(visibility == 1);
1065 new_trk->SetName(name.c_str());
1066 new_trk->m_TrackDescription = description.c_str();
1067 new_trk->m_TrackStartString = start_string.c_str();
1068 new_trk->m_TrackEndString = end_string.c_str();
1069 new_trk->m_width = width;
1070 new_trk->m_style = (wxPenStyle)style;
1071 new_trk->m_Colour = color;
1072 }
1073
1074 double latitude = sqlite3_column_double(stmtp, 0);
1075 double longitude = sqlite3_column_double(stmtp, 1);
1076 std::string timestamp =
1077 reinterpret_cast<const char*>(sqlite3_column_text(stmtp, 2));
1078 int point_order = sqlite3_column_int(stmtp, 3);
1079
1080 auto point = new TrackPoint(latitude, longitude, timestamp);
1081
1082 point->m_GPXTrkSegNo = GPXTrkSeg;
1083 new_trk->AddPoint(point);
1084 }
1085 sqlite3_finalize(stmtp);
1086
1087 if (new_trk) {
1088 new_trk->SetCurrentTrackSeg(GPXTrkSeg);
1089
1090 // Add the HTML links
1091 const char* sqlh = R"(
1092 SELECT guid, html_link, html_description, html_type
1093 FROM track_html_links
1094 WHERE track_guid = ?
1095 ORDER BY html_type ASC
1096 )";
1097
1098 sqlite3_stmt* stmt;
1099
1100 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt, nullptr) == SQLITE_OK) {
1101 sqlite3_bind_text(stmt, 1, new_trk->m_GUID.ToStdString().c_str(), -1,
1102 SQLITE_TRANSIENT);
1103
1104 while (sqlite3_step(stmt) == SQLITE_ROW) {
1105 std::string link_guid =
1106 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
1107 std::string link_link =
1108 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
1109 std::string link_description =
1110 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
1111 std::string link_type =
1112 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
1113
1114 Hyperlink* h = new Hyperlink();
1115 h->DescrText = link_description;
1116 h->Link = link_link;
1117 h->LType = link_type;
1118
1119 new_trk->m_TrackHyperlinkList->push_back(h);
1120 int yyp = 4;
1121 }
1122
1123 sqlite3_finalize(stmt);
1124
1125 } else {
1126 return false;
1127 }
1128
1129 // Insert the track into the global list
1130 g_TrackList.push_back(new_trk);
1131 // Add the selectable points and segments of the track
1132 pSelect->AddAllSelectableTrackSegments(new_trk);
1133 }
1134 }
1135 return true;
1136}
1137
1138bool NavObj_dB::DeleteTrack(Track* track) {
1139 if (!track) return false;
1140 std::string track_guid = track->m_GUID.ToStdString();
1141 const char* sql = "DELETE FROM tracks WHERE guid = ?";
1142 sqlite3_stmt* stmt;
1143
1144 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1145 sqlite3_bind_text(stmt, 1, track_guid.c_str(), -1, SQLITE_TRANSIENT);
1146 if (sqlite3_step(stmt) != SQLITE_DONE) {
1147 ReportError("DeleteTrack:step");
1148 sqlite3_finalize(stmt);
1149 return false;
1150 }
1151
1152 sqlite3_finalize(stmt);
1153 } else {
1154 return false;
1155 }
1156 return true;
1157}
1158
1159// Route support
1160
1161bool NavObj_dB::InsertRoute(Route* route) {
1162 bool rv = false;
1163 char* errMsg = 0;
1164
1165 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) {
1166 // Insert a new route
1167 wxString sql = wxString::Format("INSERT INTO routes (guid) VALUES ('%s')",
1168 route->m_GUID.ToStdString().c_str());
1169 if (!executeSQL(m_db, sql)) {
1170 return false;
1171 }
1172 UpdateDBRouteAttributes(route);
1173 }
1174
1175 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
1176 if (errMsg) {
1177 ReportError("InsertRoute:BEGIN TRANSACTION");
1178 return false;
1179 }
1180
1181 // insert routepoints
1182 for (int i = 0; i < route->GetnPoints(); i++) {
1183 auto point = route->GetPoint(i + 1);
1184 // Add the bare point
1185 if (point) {
1186 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
1187 InsertRoutePointDB(m_db, point);
1188 UpdateDBRoutePointAttributes(point);
1189 }
1190 }
1191 }
1192
1193 // insert linkages
1194 for (int i = 0; i < route->GetnPoints(); i++) {
1195 auto point = route->GetPoint(i + 1);
1196 // Add the bare point
1197 if (point) {
1198 InsertRoutePointLink(m_db, route, point, i + 1);
1199 }
1200 }
1201
1202 // Add HTML links to route
1203 int NbrOfLinks = route->m_HyperlinkList->size();
1204 if (NbrOfLinks > 0) {
1205 auto& list = *route->m_HyperlinkList;
1206 for (auto it = list.begin(); it != list.end(); ++it) {
1207 Hyperlink* link = *it;
1208 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1209 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1210 link->DescrText.ToStdString(), link->Link.ToStdString(),
1211 link->LType.ToStdString());
1212 }
1213 }
1214 }
1215
1216 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1217 rv = true;
1218 if (errMsg) {
1219 ReportError("InsertRoute:commit");
1220 rv = false;
1221 }
1222 return rv;
1223};
1224
1225bool NavObj_dB::UpdateRoute(Route* route) {
1226 bool rv = false;
1227 char* errMsg = 0;
1228
1229 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) return false;
1230
1231 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
1232 if (errMsg) {
1233 ReportError("UpdateRoute:BEGIN TRANSACTION");
1234 return false;
1235 }
1236
1237 UpdateDBRouteAttributes(route);
1238
1239 // update routepoints
1240 for (int i = 0; i < route->GetnPoints(); i++) {
1241 auto point = route->GetPoint(i + 1);
1242 // Add the bare point
1243 if (point) {
1244 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
1245 InsertRoutePointDB(m_db, point);
1246 }
1247 UpdateDBRoutePointAttributes(point);
1248 }
1249 }
1250
1251 // Delete and re-add point linkages
1252 const char* sql = "DELETE FROM routepoints_link WHERE route_guid = ?";
1253 sqlite3_stmt* stmt;
1254 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1255 sqlite3_bind_text(stmt, 1, route->m_GUID.ToStdString().c_str(), -1,
1256 SQLITE_TRANSIENT);
1257 } else {
1258 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1259 return false;
1260 }
1261 if (sqlite3_step(stmt) != SQLITE_DONE) {
1262 ReportError("UpdateRoute:step");
1263 sqlite3_finalize(stmt);
1264 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1265 return false;
1266 }
1267
1268 sqlite3_finalize(stmt);
1269
1270 for (int i = 0; i < route->GetnPoints(); i++) {
1271 auto point = route->GetPoint(i + 1);
1272 if (point) {
1273 InsertRoutePointLink(m_db, route, point, i + 1);
1274 }
1275 }
1276
1277 // Add HTML links to route
1278 int NbrOfLinks = route->m_HyperlinkList->size();
1279 if (NbrOfLinks > 0) {
1280 auto& list = *route->m_HyperlinkList;
1281 for (auto it = list.begin(); it != list.end(); ++it) {
1282 Hyperlink* link = *it;
1283 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1284 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1285 link->DescrText.ToStdString(), link->Link.ToStdString(),
1286 link->LType.ToStdString());
1287 }
1288 }
1289 }
1290 sqlite3_exec(m_db, "COMMIT", 0, 0, nullptr);
1291
1292 rv = true;
1293 if (errMsg) rv = false;
1294
1295 return rv;
1296};
1297
1298bool NavObj_dB::UpdateRouteViz(Route* route) {
1299 bool rv = false;
1300 char* errMsg = 0;
1301 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) return false;
1302
1303 UpdateDBRouteAttributes(route);
1304 // update routepoints visibility
1305 for (int i = 0; i < route->GetnPoints(); i++) {
1306 auto point = route->GetPoint(i + 1);
1307 // Add the bare point
1308 if (point) {
1309 UpdateDBRoutePointViz(point);
1310 }
1311 }
1312 rv = true;
1313 if (errMsg) rv = false;
1314
1315 return rv;
1316};
1317
1318bool NavObj_dB::UpdateDBRouteAttributes(Route* route) {
1319 const char* sql =
1320 "UPDATE routes SET "
1321 "name = ?, "
1322 "description = ?, "
1323 "start_string = ?, "
1324 "end_string = ?, "
1325 "visibility = ?, "
1326 "shared_wp_viz = ?, "
1327 "planned_departure = ?, "
1328 "plan_speed = ?, "
1329 "time_format = ?, "
1330 "width = ?, "
1331 "style = ?, "
1332 "color = ? "
1333 "WHERE guid = ?";
1334
1335 sqlite3_stmt* stmt;
1336 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1337 sqlite3_bind_text(stmt, 1, route->GetName().ToStdString().c_str(), -1,
1338 SQLITE_TRANSIENT);
1339 sqlite3_bind_text(stmt, 2, route->m_RouteDescription.ToStdString().c_str(),
1340 -1, SQLITE_TRANSIENT);
1341 sqlite3_bind_text(stmt, 3, route->m_RouteStartString.ToStdString().c_str(),
1342 -1, SQLITE_TRANSIENT);
1343 sqlite3_bind_text(stmt, 4, route->m_RouteEndString.ToStdString().c_str(),
1344 -1, SQLITE_TRANSIENT);
1345 sqlite3_bind_int(stmt, 5, route->IsVisible());
1346 sqlite3_bind_int(stmt, 6, route->GetSharedWPViz());
1347 if (route->m_PlannedDeparture.IsValid())
1348 sqlite3_bind_int(stmt, 7, route->m_PlannedDeparture.GetTicks());
1349 sqlite3_bind_double(stmt, 8, route->m_PlannedSpeed);
1350 sqlite3_bind_text(stmt, 9, route->m_TimeDisplayFormat.ToStdString().c_str(),
1351 -1, SQLITE_TRANSIENT);
1352 sqlite3_bind_int(stmt, 10, route->m_width);
1353 sqlite3_bind_int(stmt, 11,
1354 (int)(route->m_style)); // track->m_style.c_str(),
1355 sqlite3_bind_text(stmt, 12, route->m_Colour.ToStdString().c_str(), -1,
1356 SQLITE_TRANSIENT);
1357 sqlite3_bind_text(stmt, 13, route->m_GUID.c_str(), route->m_GUID.size(),
1358 SQLITE_TRANSIENT);
1359 } else {
1360 return false;
1361 }
1362
1363 if (sqlite3_step(stmt) != SQLITE_DONE) {
1364 ReportError("UpdateDBRouteAttributesA:step");
1365 sqlite3_finalize(stmt);
1366 return false;
1367 }
1368
1369 sqlite3_finalize(stmt);
1370
1371 // Update the HTML links
1372 // The list of links is freshly rebuilt when this method is called
1373 // So start by deleting all existing bcomments
1374 DeleteAllCommentsForRoute(m_db, route->m_GUID.ToStdString());
1375
1376 // Now add all the links to db
1377 int NbrOfLinks = route->m_HyperlinkList->size();
1378 if (NbrOfLinks > 0) {
1379 auto& list = route->m_HyperlinkList;
1380 for (auto it = list->begin(); it != list->end(); ++it) {
1381 Hyperlink* link = *it;
1382 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1383 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1384 link->DescrText.ToStdString(), link->Link.ToStdString(),
1385 link->LType.ToStdString());
1386 } else {
1387 const char* sql =
1388 "UPDATE route_html_links SET "
1389 "html_link = ?, "
1390 "html_description = ?, "
1391 "html_type = ? "
1392 "WHERE guid = ?";
1393 sqlite3_stmt* stmt;
1394 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1395 sqlite3_bind_text(stmt, 3, link->Link.ToStdString().c_str(), -1,
1396 SQLITE_TRANSIENT);
1397 sqlite3_bind_text(stmt, 4, link->DescrText.ToStdString().c_str(), -1,
1398 SQLITE_TRANSIENT);
1399 sqlite3_bind_text(stmt, 5, link->LType.ToStdString().c_str(), -1,
1400 SQLITE_TRANSIENT);
1401 }
1402 if (sqlite3_step(stmt) != SQLITE_DONE) {
1403 return false;
1404 }
1405 if (sqlite3_step(stmt) != SQLITE_DONE) {
1406 ReportError("UpdateDBRouteAttributesB:step");
1407 sqlite3_finalize(stmt);
1408 return false;
1409 }
1410 sqlite3_finalize(stmt);
1411 }
1412 }
1413 }
1414 return true;
1415}
1416
1417bool NavObj_dB::UpdateDBRoutePointAttributes(RoutePoint* point) {
1418 const char* sql =
1419 "UPDATE routepoints SET "
1420 "lat = ?, "
1421 "lon = ?, "
1422 "Symbol = ?, "
1423 "Name = ?, "
1424 "description = ?, "
1425 "TideStation = ?, "
1426 "plan_speed = ?, "
1427 "etd = ?, "
1428 "Type = ?, "
1429 "Time = ?, "
1430 "ArrivalRadius = ?, "
1431 "RangeRingsNumber = ?, "
1432 "RangeRingsStep = ?, "
1433 "RangeRingsStepUnits = ?, "
1434 "RangeRingsVisible = ?, "
1435 "RangeRingsColour = ?, "
1436 "ScaleMin = ?, "
1437 "ScaleMax = ?, "
1438 "UseScale = ?, "
1439 "visibility = ?, "
1440 "viz_name = ?, "
1441 "shared = ?, "
1442 "isolated = ? "
1443 "WHERE guid = ?";
1444
1445 sqlite3_stmt* stmt;
1446 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1447 sqlite3_bind_double(stmt, 1, point->GetLatitude());
1448 sqlite3_bind_double(stmt, 2, point->GetLongitude());
1449 sqlite3_bind_text(stmt, 3, point->GetIconName().ToStdString().c_str(), -1,
1450 SQLITE_TRANSIENT);
1451 sqlite3_bind_text(stmt, 4, point->GetName().ToStdString().c_str(), -1,
1452 SQLITE_TRANSIENT);
1453 sqlite3_bind_text(stmt, 5, point->GetDescription().ToStdString().c_str(),
1454 -1, SQLITE_TRANSIENT);
1455 sqlite3_bind_text(stmt, 6, point->m_TideStation.ToStdString().c_str(), -1,
1456 SQLITE_TRANSIENT);
1457 sqlite3_bind_double(stmt, 7, point->GetPlannedSpeed());
1458 time_t etd = -1;
1459 if (point->GetManualETD().IsValid()) etd = point->GetManualETD().GetTicks();
1460 sqlite3_bind_int(stmt, 8, etd);
1461 sqlite3_bind_text(stmt, 9, "type", -1, SQLITE_TRANSIENT);
1462 std::string timit = point->m_timestring.ToStdString().c_str();
1463 sqlite3_bind_text(stmt, 10, point->m_timestring.ToStdString().c_str(), -1,
1464 SQLITE_TRANSIENT);
1465 sqlite3_bind_double(stmt, 11, point->m_WaypointArrivalRadius);
1466
1467 sqlite3_bind_int(stmt, 12, point->m_iWaypointRangeRingsNumber);
1468 sqlite3_bind_double(stmt, 13, point->m_fWaypointRangeRingsStep);
1469 sqlite3_bind_int(stmt, 14, point->m_iWaypointRangeRingsStepUnits);
1470 sqlite3_bind_int(stmt, 15, point->m_bShowWaypointRangeRings);
1471 sqlite3_bind_text(
1472 stmt, 16,
1473 point->m_wxcWaypointRangeRingsColour.GetAsString(wxC2S_HTML_SYNTAX)
1474 .ToStdString()
1475 .c_str(),
1476 -1, SQLITE_TRANSIENT);
1477
1478 sqlite3_bind_int(stmt, 17, point->GetScaMin());
1479 sqlite3_bind_int(stmt, 18, point->GetScaMax());
1480 sqlite3_bind_int(stmt, 19, point->GetUseSca());
1481
1482 sqlite3_bind_int(stmt, 20, point->IsVisible());
1483 sqlite3_bind_int(stmt, 21, point->IsNameShown());
1484 sqlite3_bind_int(stmt, 22, point->IsShared());
1485 int iso = point->m_bIsolatedMark;
1486 sqlite3_bind_int(stmt, 23, iso); // point->m_bIsolatedMark);
1487
1488 sqlite3_bind_text(stmt, 24, point->m_GUID.ToStdString().c_str(), -1,
1489 SQLITE_TRANSIENT);
1490
1491 } else {
1492 return false;
1493 }
1494
1495 if (sqlite3_step(stmt) != SQLITE_DONE) {
1496 ReportError("UpdateDBRoutePointAttributesA:step");
1497 sqlite3_finalize(stmt);
1498 return false;
1499 }
1500
1501 sqlite3_finalize(stmt);
1502
1503 // Update the HTML links
1504 // The list of links is freshly rebuilt when this method is called
1505 // So start by deleting all existing bcomments
1506 DeleteAllCommentsForRoutePoint(m_db, point->m_GUID.ToStdString());
1507
1508 // Now add all the links to db
1509 int NbrOfLinks = point->m_HyperlinkList->size();
1510 if (NbrOfLinks > 0) {
1511 auto& list = point->m_HyperlinkList;
1512 for (auto it = list->begin(); it != list->end(); ++it) {
1513 Hyperlink* link = *it;
1514 if (!RoutePointHtmlLinkExists(m_db, link->GUID)) {
1515 InsertRoutePointHTML(m_db, point->m_GUID.ToStdString(), link->GUID,
1516 link->DescrText.ToStdString(),
1517 link->Link.ToStdString(),
1518 link->LType.ToStdString());
1519 } else {
1520 const char* sql =
1521 "UPDATE routepoint_html_links SET "
1522 "html_link = ?, "
1523 "html_description = ?, "
1524 "html_type = ? "
1525 "WHERE guid = ?";
1526 sqlite3_stmt* stmt;
1527 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1528 sqlite3_bind_text(stmt, 3, link->Link.ToStdString().c_str(), -1,
1529 SQLITE_TRANSIENT);
1530 sqlite3_bind_text(stmt, 4, link->DescrText.ToStdString().c_str(), -1,
1531 SQLITE_TRANSIENT);
1532 sqlite3_bind_text(stmt, 5, link->LType.ToStdString().c_str(), -1,
1533 SQLITE_TRANSIENT);
1534 }
1535 if (sqlite3_step(stmt) != SQLITE_DONE) {
1536 return false;
1537 }
1538 if (sqlite3_step(stmt) != SQLITE_DONE) {
1539 ReportError("UpdateDBRoutePointAttributesB:step-h");
1540 sqlite3_finalize(stmt);
1541 return false;
1542 }
1543 sqlite3_finalize(stmt);
1544 }
1545 }
1546 }
1547
1548 return true;
1549}
1550
1551bool NavObj_dB::UpdateDBRoutePointViz(RoutePoint* point) {
1552 const char* sql =
1553 "UPDATE routepoints SET "
1554 "visibility = ? "
1555 "WHERE guid = ?";
1556
1557 sqlite3_stmt* stmt;
1558 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1559 sqlite3_bind_int(stmt, 1, point->IsVisible());
1560 sqlite3_bind_text(stmt, 2, point->m_GUID.ToStdString().c_str(), -1,
1561 SQLITE_TRANSIENT);
1562
1563 } else {
1564 return false;
1565 }
1566
1567 if (sqlite3_step(stmt) != SQLITE_DONE) {
1568 ReportError("UpdateDBRoutePointVizA:step");
1569 sqlite3_finalize(stmt);
1570 return false;
1571 }
1572
1573 sqlite3_finalize(stmt);
1574
1575 return true;
1576}
1577
1578bool NavObj_dB::DeleteRoute(Route* route) {
1579 if (m_importing) return false;
1580 if (!route) return false;
1581 std::string route_guid = route->m_GUID.ToStdString();
1582 const char* sql = "DELETE FROM routes WHERE guid = ?";
1583 sqlite3_stmt* stmt;
1584
1585 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1586 sqlite3_bind_text(stmt, 1, route_guid.c_str(), -1, SQLITE_TRANSIENT);
1587 if (sqlite3_step(stmt) != SQLITE_DONE) {
1588 ReportError("DeleteRoute:step");
1589 sqlite3_finalize(stmt);
1590 return false;
1591 }
1592 sqlite3_finalize(stmt);
1593 } else {
1594 return false;
1595 }
1596 return true;
1597}
1598
1599bool NavObj_dB::LoadAllRoutes() {
1600 const char* sql =
1601 "SELECT "
1602 "guid, "
1603 "name, "
1604 "description, "
1605 "start_string, "
1606 "end_string, "
1607 "visibility, "
1608 "shared_wp_viz, "
1609 "planned_departure, "
1610 "plan_speed, "
1611 "time_format, "
1612 "width, "
1613 "style, "
1614 "color "
1615 "FROM routes "
1616 "ORDER BY created_at ASC";
1617
1618 sqlite3_stmt* stmt_routes;
1619 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt_routes, nullptr) != SQLITE_OK) {
1620 return false;
1621 }
1622
1623 int errcode0 = SQLITE_OK;
1624 while ((errcode0 = sqlite3_step(stmt_routes)) == SQLITE_ROW) {
1625 std::string guid =
1626 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 0));
1627 std::string name =
1628 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 1));
1629 std::string description =
1630 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 2));
1631 std::string start_string =
1632 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 3));
1633 std::string end_string =
1634 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 4));
1635 int visibility = sqlite3_column_int(stmt_routes, 5);
1636 int sharewp_viz = sqlite3_column_int(stmt_routes, 6);
1637 time_t planned_departure_ticks = sqlite3_column_int(stmt_routes, 7);
1638 double plan_speed = sqlite3_column_double(stmt_routes, 8);
1639 std::string time_format =
1640 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 9));
1641
1642 int width = sqlite3_column_int(stmt_routes, 10);
1643 int style = sqlite3_column_int(stmt_routes, 11);
1644 std::string color =
1645 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 12));
1646
1647 Route* route = NULL;
1648
1649 // Add the route_points
1650 const char* sql = R"(
1651 SELECT latitude, longitude, timestamp, point_order
1652 FROM trk_points
1653 WHERE track_guid = ?
1654 ORDER BY point_order ASC
1655 )";
1656
1657 const char* sqlp =
1658 "SELECT p.guid, "
1659 "p.lat, "
1660 "p.lon, "
1661 "p.Symbol, "
1662 "p.Name, "
1663 "p.description, "
1664 "p.TideStation, "
1665 "p.plan_speed, "
1666 "p.etd, "
1667 "p.Type, "
1668 "p.Time, "
1669 "p.ArrivalRadius, "
1670 "p.RangeRingsNumber, "
1671 "p.RangeRingsStep, "
1672 "p.RangeRingsStepUnits, "
1673 "p.RangeRingsVisible, "
1674 "p.RangeRingsColour, "
1675 "p.ScaleMin, "
1676 "p.ScaleMax, "
1677 "p.UseScale, "
1678 "p.visibility, "
1679 "p.viz_name, "
1680 "p.shared, "
1681 "p.isolated, "
1682 "p.created_at "
1683 "FROM routepoints_link tp "
1684 "JOIN routepoints p ON p.guid = tp.point_guid "
1685 "WHERE tp.route_guid = ? "
1686 "ORDER BY tp.point_order ASC";
1687
1688 sqlite3_stmt* stmt_rp;
1689 if (sqlite3_prepare_v2(m_db, sqlp, -1, &stmt_rp, nullptr) != SQLITE_OK) {
1690 ReportError("LoadAllRoutes-B:prepare");
1691 return false;
1692 }
1693
1694 sqlite3_bind_text(stmt_rp, 1, guid.c_str(), -1, SQLITE_TRANSIENT);
1695
1696 int GPXSeg = 0;
1697 int errcode = SQLITE_OK;
1698 while ((errcode = sqlite3_step(stmt_rp)) == SQLITE_ROW) {
1699 if (!route) {
1700 route = new Route;
1701 route->m_GUID = guid;
1702
1703 // Set all the route attributes
1704 route->SetVisible(visibility == 1);
1705 route->m_RouteNameString = name.c_str();
1706 route->m_RouteDescription = description.c_str();
1707 route->m_RouteStartString = start_string.c_str();
1708 route->m_RouteEndString = end_string.c_str();
1709 route->SetVisible(visibility == 1);
1710 route->SetSharedWPViz(sharewp_viz == 1);
1711 route->m_PlannedDeparture.Set((time_t)planned_departure_ticks);
1712 route->m_PlannedSpeed = plan_speed;
1713 route->m_TimeDisplayFormat = time_format.c_str();
1714
1715 route->m_width = width;
1716 route->m_style = (wxPenStyle)style;
1717 route->m_Colour = color;
1718 }
1719
1720 // Grab all the point attributes from the SELECT statement
1721 int col = 0;
1722 std::string point_guid =
1723 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1724 double latitude = sqlite3_column_double(stmt_rp, col++);
1725 double longitude = sqlite3_column_double(stmt_rp, col++);
1726 std::string symbol =
1727 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1728 std::string name =
1729 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1730 std::string description =
1731 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1732 std::string tide_station =
1733 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1734 double plan_speed = sqlite3_column_double(stmt_rp, col++);
1735 time_t etd_epoch = sqlite3_column_int(stmt_rp, col++);
1736 std::string type =
1737 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1738 std::string time =
1739 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1740 double arrival_radius = sqlite3_column_double(stmt_rp, col++);
1741
1742 int range_ring_number = sqlite3_column_int(stmt_rp, col++);
1743 double range_ring_step = sqlite3_column_double(stmt_rp, col++);
1744 int range_ring_units = sqlite3_column_int(stmt_rp, col++);
1745 int range_ring_visible = sqlite3_column_int(stmt_rp, col++);
1746 std::string range_ring_color =
1747 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1748
1749 int scamin = sqlite3_column_int(stmt_rp, col++);
1750 int scamax = sqlite3_column_int(stmt_rp, col++);
1751 int use_scaminmax = sqlite3_column_int(stmt_rp, col++);
1752
1753 int visibility = sqlite3_column_int(stmt_rp, col++);
1754 int viz_name = sqlite3_column_int(stmt_rp, col++);
1755 int shared = sqlite3_column_int(stmt_rp, col++);
1756 int isolated = sqlite3_column_int(stmt_rp, col++);
1757 std::string point_created_at =
1758 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1759
1760 RoutePoint* point;
1761 // RoutePoint exists already, in another route or isolated??
1762 RoutePoint* existing_point = NULL;
1763 auto containing_route =
1764 g_pRouteMan->FindRouteContainingWaypoint(point_guid);
1765
1766 // Special case, the route may be "closed form", i.e. circular.
1767 // If so, the closing point is not really a shared point.
1768 // Detect that case, and make it so.
1769 bool b_closed_route = false;
1770 if (!containing_route) {
1771 RoutePoint* close_point = route->GetPoint(point_guid);
1772 b_closed_route = close_point != nullptr;
1773 existing_point = close_point;
1774 }
1775
1776 if (containing_route) { // In a route already?
1777 existing_point = containing_route->GetPoint(point_guid);
1778 }
1779 // Or isolated?
1780 if (!existing_point) {
1781 existing_point = pWayPointMan->FindRoutePointByGUID(point_guid.c_str());
1782 }
1783
1784 if (existing_point) {
1785 point = existing_point;
1786 if (!b_closed_route) {
1787 point->SetShared(true); // by definition, unless point is a closer.
1788 point->m_bIsolatedMark = false;
1789 }
1790 } else {
1791 point =
1792 new RoutePoint(latitude, longitude, symbol, name, point_guid, true);
1793
1794 point->m_MarkDescription = description;
1795 point->m_TideStation = tide_station;
1796 point->SetPlannedSpeed(plan_speed);
1797
1798 wxDateTime etd;
1799 etd.Set((time_t)etd_epoch);
1800 if (etd.IsValid()) point->SetETD(etd);
1801
1802 point->m_WaypointArrivalRadius = arrival_radius;
1803
1804 point->m_iWaypointRangeRingsNumber = range_ring_number;
1805 point->m_fWaypointRangeRingsStep = range_ring_step;
1806 point->m_iWaypointRangeRingsStepUnits = range_ring_units;
1807 point->SetShowWaypointRangeRings(range_ring_visible == 1);
1808 // TODO
1809 point->m_wxcWaypointRangeRingsColour.Set(range_ring_color);
1810
1811 point->SetScaMin(scamin);
1812 point->SetScaMax(scamax);
1813 point->SetUseSca(use_scaminmax == 1);
1814
1815 point->SetVisible(visibility == 1);
1816 point->SetNameShown(viz_name == 1);
1817 point->SetShared(shared == 1);
1818 point->m_bIsolatedMark = (isolated == 1);
1819
1820 if (point_created_at.size()) {
1821 // Convert from sqLite default date/time format to wxDateTime
1822 // sqLite format uses UTC, so conversion to epoch_time is clear.
1823 std::tm tm = {};
1824 std::istringstream ss(point_created_at);
1825 ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
1826 time_t epoch_time = mktime(&tm);
1827 point->m_CreateTimeX = epoch_time;
1828 }
1829
1830 // Add the point HTML links
1831 const char* sqlh = R"(
1832 SELECT guid, html_link, html_description, html_type
1833 FROM routepoint_html_links
1834 WHERE routepoint_guid = ?
1835 ORDER BY html_type ASC
1836 )";
1837
1838 sqlite3_stmt* stmt_point_link;
1839
1840 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_point_link, nullptr) ==
1841 SQLITE_OK) {
1842 sqlite3_bind_text(stmt_point_link, 1,
1843 point->m_GUID.ToStdString().c_str(), -1,
1844 SQLITE_TRANSIENT);
1845
1846 while (sqlite3_step(stmt_point_link) == SQLITE_ROW) {
1847 std::string link_guid = reinterpret_cast<const char*>(
1848 sqlite3_column_text(stmt_point_link, 0));
1849 std::string link_link = reinterpret_cast<const char*>(
1850 sqlite3_column_text(stmt_point_link, 1));
1851 std::string link_description = reinterpret_cast<const char*>(
1852 sqlite3_column_text(stmt_point_link, 2));
1853 std::string link_type = reinterpret_cast<const char*>(
1854 sqlite3_column_text(stmt_point_link, 3));
1855
1856 Hyperlink* h = new Hyperlink();
1857 h->DescrText = link_description;
1858 h->Link = link_link;
1859 h->LType = link_type;
1860
1861 point->m_HyperlinkList->push_back(h);
1862 }
1863 sqlite3_finalize(stmt_point_link);
1864 }
1865 } // new point
1866
1867 route->AddPoint(point);
1868 } // route points
1869 sqlite3_finalize(stmt_rp);
1870 if (errcode != SQLITE_DONE) {
1871 ReportError("LoadAllRoutes-A:step");
1872 return false;
1873 }
1874
1875 // Add route html links
1876 if (route) {
1877 // Add the HTML links
1878 const char* sqlh = R"(
1879 SELECT guid, html_link, html_description, html_type
1880 FROM route_html_links
1881 WHERE route_guid = ?
1882 ORDER BY html_type ASC
1883 )";
1884
1885 sqlite3_stmt* stmt_route_links;
1886
1887 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_route_links, nullptr) ==
1888 SQLITE_OK) {
1889 sqlite3_bind_text(stmt_route_links, 1,
1890 route->m_GUID.ToStdString().c_str(), -1,
1891 SQLITE_TRANSIENT);
1892
1893 int errcode2 = SQLITE_OK;
1894 while ((errcode2 = sqlite3_step(stmt_route_links)) == SQLITE_ROW) {
1895 std::string link_guid = reinterpret_cast<const char*>(
1896 sqlite3_column_text(stmt_route_links, 0));
1897 std::string link_link = reinterpret_cast<const char*>(
1898 sqlite3_column_text(stmt_route_links, 1));
1899 std::string link_description = reinterpret_cast<const char*>(
1900 sqlite3_column_text(stmt_route_links, 2));
1901 std::string link_type = reinterpret_cast<const char*>(
1902 sqlite3_column_text(stmt_route_links, 3));
1903
1904 Hyperlink* h = new Hyperlink();
1905 h->DescrText = link_description;
1906 h->Link = link_link;
1907 h->LType = link_type;
1908
1909 route->m_HyperlinkList->push_back(h);
1910 }
1911 if (errcode != SQLITE_DONE) {
1912 ReportError("LoadAllRoutes-B:step");
1913 return false;
1914 }
1915
1916 sqlite3_finalize(stmt_route_links);
1917
1918 } else {
1919 ReportError("LoadAllRoutes-B:prepare");
1920 return false;
1921 }
1922 }
1923
1924 // Insert the route into the global list
1925 InsertRouteA(route,
1926 nullptr); // NavObjectChanges::getInstance() //TODO adding
1927 // changes will force the xml file to be updated?
1928
1929 } // routes
1930 if (errcode0 != SQLITE_DONE) {
1931 ReportError("LoadAllRoutes-C:step");
1932 return false;
1933 }
1934
1935 return true;
1936}
1937
1938bool NavObj_dB::LoadAllPoints() {
1939 const char* sqlp =
1940 "SELECT "
1941 "p.guid, "
1942 "p.lat, "
1943 "p.lon, "
1944 "p.Symbol, "
1945 "p.Name, "
1946 "p.description, "
1947 "p.TideStation, "
1948 "p.plan_speed, "
1949 "p.etd, "
1950 "p.Type, "
1951 "p.Time, "
1952 "p.ArrivalRadius, "
1953 "p.RangeRingsNumber, "
1954 "p.RangeRingsStep, "
1955 "p.RangeRingsStepUnits, "
1956 "p.RangeRingsVisible, "
1957 "p.RangeRingsColour, "
1958 "p.ScaleMin, "
1959 "p.ScaleMax, "
1960 "p.UseScale, "
1961 "p.visibility, "
1962 "p.viz_name, "
1963 "p.shared, "
1964 "p.isolated, "
1965 "p.created_at "
1966 "FROM routepoints p ";
1967
1968 RoutePoint* point = nullptr;
1969
1970 sqlite3_stmt* stmt_point;
1971 if (sqlite3_prepare_v2(m_db, sqlp, -1, &stmt_point, nullptr) != SQLITE_OK) {
1972 return false;
1973 }
1974
1975 while (sqlite3_step(stmt_point) == SQLITE_ROW) {
1976 // Grab all the point attributes from the SELECT statement
1977 int col = 0;
1978 std::string point_guid =
1979 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1980 double latitude = sqlite3_column_double(stmt_point, col++);
1981 double longitude = sqlite3_column_double(stmt_point, col++);
1982 std::string symbol =
1983 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1984 std::string name =
1985 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1986 std::string description =
1987 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1988 std::string tide_station =
1989 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1990 double plan_speed = sqlite3_column_double(stmt_point, col++);
1991 time_t etd = sqlite3_column_int(stmt_point, col++);
1992 std::string type =
1993 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1994 std::string point_time_string =
1995 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1996 double arrival_radius = sqlite3_column_double(stmt_point, col++);
1997
1998 int range_ring_number = sqlite3_column_int(stmt_point, col++);
1999 double range_ring_step = sqlite3_column_double(stmt_point, col++);
2000 int range_ring_units = sqlite3_column_int(stmt_point, col++);
2001 int range_ring_visible = sqlite3_column_int(stmt_point, col++);
2002 std::string range_ring_color =
2003 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
2004
2005 int scamin = sqlite3_column_int(stmt_point, col++);
2006 int scamax = sqlite3_column_int(stmt_point, col++);
2007 int use_scaminmax = sqlite3_column_int(stmt_point, col++);
2008
2009 int visibility = sqlite3_column_int(stmt_point, col++);
2010 int viz_name = sqlite3_column_int(stmt_point, col++);
2011 int shared = sqlite3_column_int(stmt_point, col++);
2012 int isolated = sqlite3_column_int(stmt_point, col++);
2013 std::string point_created_at =
2014 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
2015
2016 if (isolated) {
2017 point =
2018 new RoutePoint(latitude, longitude, symbol, name, point_guid, false);
2019
2020 point->m_MarkDescription = description;
2021 point->m_TideStation = tide_station;
2022 point->SetPlannedSpeed(plan_speed);
2023 point->m_WaypointArrivalRadius = arrival_radius;
2024
2025 point->m_iWaypointRangeRingsNumber = range_ring_number;
2026 point->m_fWaypointRangeRingsStep = range_ring_step;
2027 point->m_iWaypointRangeRingsStepUnits = range_ring_units;
2028 point->SetShowWaypointRangeRings(range_ring_visible == 1);
2029
2030 point->m_wxcWaypointRangeRingsColour.Set(range_ring_color);
2031
2032 point->SetScaMin(scamin);
2033 point->SetScaMax(scamax);
2034 point->SetUseSca(use_scaminmax == 1);
2035
2036 point->SetVisible(visibility == 1);
2037 point->SetNameShown(viz_name == 1);
2038 point->SetShared(shared == 1);
2039 point->m_bIsolatedMark = (isolated == 1);
2040
2041 if (point_created_at.size()) {
2042 // Convert from sqLite default date/time format to wxDateTime
2043 // sqLite format uses UTC, so conversion to epoch_time is clear.
2044 std::tm tm = {};
2045 std::istringstream ss(point_created_at);
2046 ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
2047 time_t epoch_time = mktime(&tm);
2048 point->m_CreateTimeX = epoch_time;
2049 }
2050
2051 // Add it here
2052 pWayPointMan->AddRoutePoint(point);
2053 pSelect->AddSelectableRoutePoint(point->m_lat, point->m_lon, point);
2054
2055 // Check for and process HTML links
2056 const char* sqlh = R"(
2057 SELECT guid, html_link, html_description, html_type
2058 FROM routepoint_html_links
2059 WHERE routepoint_guid = ?
2060 ORDER BY html_type ASC
2061 )";
2062
2063 sqlite3_stmt* stmt_links;
2064
2065 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_links, nullptr) ==
2066 SQLITE_OK) {
2067 sqlite3_bind_text(stmt_links, 1, point->m_GUID.ToStdString().c_str(),
2068 -1, SQLITE_TRANSIENT);
2069
2070 while (sqlite3_step(stmt_links) == SQLITE_ROW) {
2071 std::string link_guid =
2072 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 0));
2073 std::string link_link =
2074 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 1));
2075 std::string link_description =
2076 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 2));
2077 std::string link_type =
2078 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 3));
2079
2080 Hyperlink* h = new Hyperlink();
2081 h->DescrText = link_description;
2082 h->Link = link_link;
2083 h->LType = link_type;
2084
2085 point->m_HyperlinkList->push_back(h);
2086 }
2087 sqlite3_finalize(stmt_links); // free the inner loop (links) staement
2088 }
2089 } // isolated
2090 } // points
2091 sqlite3_finalize(stmt_point); // free the outer loop (point) statement
2092
2093 return true;
2094}
2095bool NavObj_dB::InsertRoutePoint(RoutePoint* point) {
2096 bool rv = false;
2097 char* errMsg = 0;
2098
2099 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
2100 // Insert a new route point
2101 wxString sql =
2102 wxString::Format("INSERT INTO routepoints (guid) VALUES ('%s')",
2103 point->m_GUID.ToStdString().c_str());
2104 if (!executeSQL(m_db, sql)) {
2105 return false;
2106 }
2107 }
2108
2109 UpdateDBRoutePointAttributes(point);
2110
2111 // Add HTML links to routepoint
2112 int NbrOfLinks = point->m_HyperlinkList->size();
2113 if (NbrOfLinks > 0) {
2114 auto& list = point->m_HyperlinkList;
2115 for (auto it = list->begin(); it != list->end(); ++it) {
2116 Hyperlink* link = *it;
2117 if (!RoutePointHtmlLinkExists(m_db, link->GUID)) {
2118 InsertRoutePointHTML(m_db, point->m_GUID.ToStdString(), link->GUID,
2119 link->DescrText.ToStdString(),
2120 link->Link.ToStdString(),
2121 link->LType.ToStdString());
2122 }
2123 }
2124 }
2125
2126 return true;
2127}
2128
2129bool NavObj_dB::DeleteRoutePoint(RoutePoint* point) {
2130 if (m_importing) return false;
2131 if (!point) return false;
2132
2133 std::string point_guid = point->m_GUID.ToStdString();
2134
2135 // DeleteAllCommentsForRoutePoint(m_db, point_guid);
2136
2137 const char* sql = "DELETE FROM routepoints WHERE guid = ?";
2138 sqlite3_stmt* stmt;
2139
2140 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
2141 sqlite3_bind_text(stmt, 1, point_guid.c_str(), -1, SQLITE_TRANSIENT);
2142 if (sqlite3_step(stmt) != SQLITE_DONE) {
2143 ReportError("DeleteRoutePoint:step");
2144 sqlite3_finalize(stmt);
2145 return false;
2146 }
2147
2148 sqlite3_finalize(stmt);
2149 } else {
2150 return false;
2151 }
2152 return true;
2153}
2154
2155bool NavObj_dB::UpdateRoutePoint(RoutePoint* point) {
2156 if (m_importing) return false;
2157 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) return false;
2158 UpdateDBRoutePointAttributes(point);
2159 return true;
2160}
2161
2162bool NavObj_dB::Backup(wxString fileName) {
2163 sqlite3_backup* pBackup;
2164 sqlite3* backupDatabase;
2165
2166 if (sqlite3_open(fileName.c_str(), &backupDatabase) == SQLITE_OK) {
2167 pBackup = sqlite3_backup_init(backupDatabase, "main", m_db, "main");
2168 if (pBackup) {
2169 int result = sqlite3_backup_step(pBackup, -1);
2170 if ((result == SQLITE_OK) || (result == SQLITE_DONE)) {
2171 if (sqlite3_backup_finish(pBackup) == SQLITE_OK) {
2172 sqlite3_close_v2(backupDatabase);
2173 return true;
2174 }
2175 }
2176 }
2177 }
2178 wxLogMessage("navobj database backup error: %s", sqlite3_errmsg(m_db));
2179 return false;
2180}
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Basic platform specific support utilities without GUI deps.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
The navobj SQLite container object, a singleton.
Definition navobj_db.h:35
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.
wxString m_GUID
Globally Unique Identifier for the waypoint.
wxDateTime m_CreateTimeX
Creation timestamp for the waypoint, in UTC.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
wxDateTime GetManualETD()
Retrieves the manually set Estimated Time of Departure for this waypoint, in UTC.
wxString m_timestring
String representation of the waypoint creation time.
double GetPlannedSpeed()
Return the planned speed associated with this waypoint.
double m_WaypointArrivalRadius
Arrival radius in nautical miles.
int m_iWaypointRangeRingsStepUnits
Units for the range rings step (0=nm, 1=km).
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
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
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
HyperlinkList * m_HyperlinkList
List of hyperlinks associated with this route.
Definition route.h:361
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:765
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.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Decoded messages send/receive support.
bool exists(const std::string &name)
MySQL based storage for routes, tracks, etc.
navobj_db_util.h – MySQL support utilities
Navigation Utility Functions without GUI dependencies.
User notification container.
User notifications manager.
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
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96