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_TrackStartString = start_string.c_str();
1067 new_trk->m_TrackEndString = end_string.c_str();
1068 new_trk->m_width = width;
1069 new_trk->m_style = (wxPenStyle)style;
1070 new_trk->m_Colour = color;
1071 }
1072
1073 double latitude = sqlite3_column_double(stmtp, 0);
1074 double longitude = sqlite3_column_double(stmtp, 1);
1075 std::string timestamp =
1076 reinterpret_cast<const char*>(sqlite3_column_text(stmtp, 2));
1077 int point_order = sqlite3_column_int(stmtp, 3);
1078
1079 auto point = new TrackPoint(latitude, longitude, timestamp);
1080
1081 point->m_GPXTrkSegNo = GPXTrkSeg;
1082 new_trk->AddPoint(point);
1083 }
1084 sqlite3_finalize(stmtp);
1085
1086 if (new_trk) {
1087 new_trk->SetCurrentTrackSeg(GPXTrkSeg);
1088
1089 // Add the HTML links
1090 const char* sqlh = R"(
1091 SELECT guid, html_link, html_description, html_type
1092 FROM track_html_links
1093 WHERE track_guid = ?
1094 ORDER BY html_type ASC
1095 )";
1096
1097 sqlite3_stmt* stmt;
1098
1099 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt, nullptr) == SQLITE_OK) {
1100 sqlite3_bind_text(stmt, 1, new_trk->m_GUID.ToStdString().c_str(), -1,
1101 SQLITE_TRANSIENT);
1102
1103 while (sqlite3_step(stmt) == SQLITE_ROW) {
1104 std::string link_guid =
1105 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
1106 std::string link_link =
1107 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
1108 std::string link_description =
1109 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
1110 std::string link_type =
1111 reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
1112
1113 Hyperlink* h = new Hyperlink();
1114 h->DescrText = link_description;
1115 h->Link = link_link;
1116 h->LType = link_type;
1117
1118 new_trk->m_TrackHyperlinkList->push_back(h);
1119 int yyp = 4;
1120 }
1121
1122 sqlite3_finalize(stmt);
1123
1124 } else {
1125 return false;
1126 }
1127
1128 // Insert the track into the global list
1129 g_TrackList.push_back(new_trk);
1130 // Add the selectable points and segments of the track
1131 pSelect->AddAllSelectableTrackSegments(new_trk);
1132 }
1133 }
1134 return true;
1135}
1136
1137bool NavObj_dB::DeleteTrack(Track* track) {
1138 if (!track) return false;
1139 std::string track_guid = track->m_GUID.ToStdString();
1140 const char* sql = "DELETE FROM tracks WHERE guid = ?";
1141 sqlite3_stmt* stmt;
1142
1143 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1144 sqlite3_bind_text(stmt, 1, track_guid.c_str(), -1, SQLITE_TRANSIENT);
1145 if (sqlite3_step(stmt) != SQLITE_DONE) {
1146 ReportError("DeleteTrack:step");
1147 sqlite3_finalize(stmt);
1148 return false;
1149 }
1150
1151 sqlite3_finalize(stmt);
1152 } else {
1153 return false;
1154 }
1155 return true;
1156}
1157
1158// Route support
1159
1160bool NavObj_dB::InsertRoute(Route* route) {
1161 bool rv = false;
1162 char* errMsg = 0;
1163
1164 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) {
1165 // Insert a new route
1166 wxString sql = wxString::Format("INSERT INTO routes (guid) VALUES ('%s')",
1167 route->m_GUID.ToStdString().c_str());
1168 if (!executeSQL(m_db, sql)) {
1169 return false;
1170 }
1171 UpdateDBRouteAttributes(route);
1172 }
1173
1174 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
1175 if (errMsg) {
1176 ReportError("InsertRoute:BEGIN TRANSACTION");
1177 return false;
1178 }
1179
1180 // insert routepoints
1181 for (int i = 0; i < route->GetnPoints(); i++) {
1182 auto point = route->GetPoint(i + 1);
1183 // Add the bare point
1184 if (point) {
1185 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
1186 InsertRoutePointDB(m_db, point);
1187 UpdateDBRoutePointAttributes(point);
1188 }
1189 }
1190 }
1191
1192 // insert linkages
1193 for (int i = 0; i < route->GetnPoints(); i++) {
1194 auto point = route->GetPoint(i + 1);
1195 // Add the bare point
1196 if (point) {
1197 InsertRoutePointLink(m_db, route, point, i + 1);
1198 }
1199 }
1200
1201 // Add HTML links to route
1202 int NbrOfLinks = route->m_HyperlinkList->size();
1203 if (NbrOfLinks > 0) {
1204 auto& list = *route->m_HyperlinkList;
1205 for (auto it = list.begin(); it != list.end(); ++it) {
1206 Hyperlink* link = *it;
1207 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1208 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1209 link->DescrText.ToStdString(), link->Link.ToStdString(),
1210 link->LType.ToStdString());
1211 }
1212 }
1213 }
1214
1215 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1216 rv = true;
1217 if (errMsg) {
1218 ReportError("InsertRoute:commit");
1219 rv = false;
1220 }
1221 return rv;
1222};
1223
1224bool NavObj_dB::UpdateRoute(Route* route) {
1225 bool rv = false;
1226 char* errMsg = 0;
1227
1228 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) return false;
1229
1230 sqlite3_exec(m_db, "BEGIN TRANSACTION", 0, 0, &errMsg);
1231 if (errMsg) {
1232 ReportError("UpdateRoute:BEGIN TRANSACTION");
1233 return false;
1234 }
1235
1236 UpdateDBRouteAttributes(route);
1237
1238 // update routepoints
1239 for (int i = 0; i < route->GetnPoints(); i++) {
1240 auto point = route->GetPoint(i + 1);
1241 // Add the bare point
1242 if (point) {
1243 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
1244 InsertRoutePointDB(m_db, point);
1245 }
1246 UpdateDBRoutePointAttributes(point);
1247 }
1248 }
1249
1250 // Delete and re-add point linkages
1251 const char* sql = "DELETE FROM routepoints_link WHERE route_guid = ?";
1252 sqlite3_stmt* stmt;
1253 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1254 sqlite3_bind_text(stmt, 1, route->m_GUID.ToStdString().c_str(), -1,
1255 SQLITE_TRANSIENT);
1256 } else {
1257 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1258 return false;
1259 }
1260 if (sqlite3_step(stmt) != SQLITE_DONE) {
1261 ReportError("UpdateRoute:step");
1262 sqlite3_finalize(stmt);
1263 sqlite3_exec(m_db, "COMMIT", 0, 0, &errMsg);
1264 return false;
1265 }
1266
1267 sqlite3_finalize(stmt);
1268
1269 for (int i = 0; i < route->GetnPoints(); i++) {
1270 auto point = route->GetPoint(i + 1);
1271 if (point) {
1272 InsertRoutePointLink(m_db, route, point, i + 1);
1273 }
1274 }
1275
1276 // Add HTML links to route
1277 int NbrOfLinks = route->m_HyperlinkList->size();
1278 if (NbrOfLinks > 0) {
1279 auto& list = *route->m_HyperlinkList;
1280 for (auto it = list.begin(); it != list.end(); ++it) {
1281 Hyperlink* link = *it;
1282 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1283 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1284 link->DescrText.ToStdString(), link->Link.ToStdString(),
1285 link->LType.ToStdString());
1286 }
1287 }
1288 }
1289 sqlite3_exec(m_db, "COMMIT", 0, 0, nullptr);
1290
1291 rv = true;
1292 if (errMsg) rv = false;
1293
1294 return rv;
1295};
1296
1297bool NavObj_dB::UpdateRouteViz(Route* route) {
1298 bool rv = false;
1299 char* errMsg = 0;
1300 if (!RouteExistsDB(m_db, route->m_GUID.ToStdString())) return false;
1301
1302 UpdateDBRouteAttributes(route);
1303 // update routepoints visibility
1304 for (int i = 0; i < route->GetnPoints(); i++) {
1305 auto point = route->GetPoint(i + 1);
1306 // Add the bare point
1307 if (point) {
1308 UpdateDBRoutePointViz(point);
1309 }
1310 }
1311 rv = true;
1312 if (errMsg) rv = false;
1313
1314 return rv;
1315};
1316
1317bool NavObj_dB::UpdateDBRouteAttributes(Route* route) {
1318 const char* sql =
1319 "UPDATE routes SET "
1320 "name = ?, "
1321 "description = ?, "
1322 "start_string = ?, "
1323 "end_string = ?, "
1324 "visibility = ?, "
1325 "shared_wp_viz = ?, "
1326 "planned_departure = ?, "
1327 "plan_speed = ?, "
1328 "time_format = ?, "
1329 "width = ?, "
1330 "style = ?, "
1331 "color = ? "
1332 "WHERE guid = ?";
1333
1334 sqlite3_stmt* stmt;
1335 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1336 sqlite3_bind_text(stmt, 1, route->GetName().ToStdString().c_str(), -1,
1337 SQLITE_TRANSIENT);
1338 sqlite3_bind_text(stmt, 2, route->m_RouteDescription.ToStdString().c_str(),
1339 -1, SQLITE_TRANSIENT);
1340 sqlite3_bind_text(stmt, 3, route->m_RouteStartString.ToStdString().c_str(),
1341 -1, SQLITE_TRANSIENT);
1342 sqlite3_bind_text(stmt, 4, route->m_RouteEndString.ToStdString().c_str(),
1343 -1, SQLITE_TRANSIENT);
1344 sqlite3_bind_int(stmt, 5, route->IsVisible());
1345 sqlite3_bind_int(stmt, 6, route->GetSharedWPViz());
1346 if (route->m_PlannedDeparture.IsValid())
1347 sqlite3_bind_int(stmt, 7, route->m_PlannedDeparture.GetTicks());
1348 sqlite3_bind_double(stmt, 8, route->m_PlannedSpeed);
1349 sqlite3_bind_text(stmt, 9, route->m_TimeDisplayFormat.ToStdString().c_str(),
1350 -1, SQLITE_TRANSIENT);
1351 sqlite3_bind_int(stmt, 10, route->m_width);
1352 sqlite3_bind_int(stmt, 11,
1353 (int)(route->m_style)); // track->m_style.c_str(),
1354 sqlite3_bind_text(stmt, 12, route->m_Colour.ToStdString().c_str(), -1,
1355 SQLITE_TRANSIENT);
1356 sqlite3_bind_text(stmt, 13, route->m_GUID.c_str(), route->m_GUID.size(),
1357 SQLITE_TRANSIENT);
1358 } else {
1359 return false;
1360 }
1361
1362 if (sqlite3_step(stmt) != SQLITE_DONE) {
1363 ReportError("UpdateDBRouteAttributesA:step");
1364 sqlite3_finalize(stmt);
1365 return false;
1366 }
1367
1368 sqlite3_finalize(stmt);
1369
1370 // Update the HTML links
1371 // The list of links is freshly rebuilt when this method is called
1372 // So start by deleting all existing bcomments
1373 DeleteAllCommentsForRoute(m_db, route->m_GUID.ToStdString());
1374
1375 // Now add all the links to db
1376 int NbrOfLinks = route->m_HyperlinkList->size();
1377 if (NbrOfLinks > 0) {
1378 auto& list = route->m_HyperlinkList;
1379 for (auto it = list->begin(); it != list->end(); ++it) {
1380 Hyperlink* link = *it;
1381 if (!RouteHtmlLinkExists(m_db, link->GUID)) {
1382 InsertRouteHTML(m_db, route->m_GUID.ToStdString(), link->GUID,
1383 link->DescrText.ToStdString(), link->Link.ToStdString(),
1384 link->LType.ToStdString());
1385 } else {
1386 const char* sql =
1387 "UPDATE route_html_links SET "
1388 "html_link = ?, "
1389 "html_description = ?, "
1390 "html_type = ? "
1391 "WHERE guid = ?";
1392 sqlite3_stmt* stmt;
1393 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1394 sqlite3_bind_text(stmt, 3, link->Link.ToStdString().c_str(), -1,
1395 SQLITE_TRANSIENT);
1396 sqlite3_bind_text(stmt, 4, link->DescrText.ToStdString().c_str(), -1,
1397 SQLITE_TRANSIENT);
1398 sqlite3_bind_text(stmt, 5, link->LType.ToStdString().c_str(), -1,
1399 SQLITE_TRANSIENT);
1400 }
1401 if (sqlite3_step(stmt) != SQLITE_DONE) {
1402 return false;
1403 }
1404 if (sqlite3_step(stmt) != SQLITE_DONE) {
1405 ReportError("UpdateDBRouteAttributesB:step");
1406 sqlite3_finalize(stmt);
1407 return false;
1408 }
1409 sqlite3_finalize(stmt);
1410 }
1411 }
1412 }
1413 return true;
1414}
1415
1416bool NavObj_dB::UpdateDBRoutePointAttributes(RoutePoint* point) {
1417 const char* sql =
1418 "UPDATE routepoints SET "
1419 "lat = ?, "
1420 "lon = ?, "
1421 "Symbol = ?, "
1422 "Name = ?, "
1423 "description = ?, "
1424 "TideStation = ?, "
1425 "plan_speed = ?, "
1426 "etd = ?, "
1427 "Type = ?, "
1428 "Time = ?, "
1429 "ArrivalRadius = ?, "
1430 "RangeRingsNumber = ?, "
1431 "RangeRingsStep = ?, "
1432 "RangeRingsStepUnits = ?, "
1433 "RangeRingsVisible = ?, "
1434 "RangeRingsColour = ?, "
1435 "ScaleMin = ?, "
1436 "ScaleMax = ?, "
1437 "UseScale = ?, "
1438 "visibility = ?, "
1439 "viz_name = ?, "
1440 "shared = ?, "
1441 "isolated = ? "
1442 "WHERE guid = ?";
1443
1444 sqlite3_stmt* stmt;
1445 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1446 sqlite3_bind_double(stmt, 1, point->GetLatitude());
1447 sqlite3_bind_double(stmt, 2, point->GetLongitude());
1448 sqlite3_bind_text(stmt, 3, point->GetIconName().ToStdString().c_str(), -1,
1449 SQLITE_TRANSIENT);
1450 sqlite3_bind_text(stmt, 4, point->GetName().ToStdString().c_str(), -1,
1451 SQLITE_TRANSIENT);
1452 sqlite3_bind_text(stmt, 5, point->GetDescription().ToStdString().c_str(),
1453 -1, SQLITE_TRANSIENT);
1454 sqlite3_bind_text(stmt, 6, point->m_TideStation.ToStdString().c_str(), -1,
1455 SQLITE_TRANSIENT);
1456 sqlite3_bind_double(stmt, 7, point->GetPlannedSpeed());
1457 time_t etd = -1;
1458 if (point->GetManualETD().IsValid()) etd = point->GetManualETD().GetTicks();
1459 sqlite3_bind_int(stmt, 8, etd);
1460 sqlite3_bind_text(stmt, 9, "type", -1, SQLITE_TRANSIENT);
1461 std::string timit = point->m_timestring.ToStdString().c_str();
1462 sqlite3_bind_text(stmt, 10, point->m_timestring.ToStdString().c_str(), -1,
1463 SQLITE_TRANSIENT);
1464 sqlite3_bind_double(stmt, 11, point->m_WaypointArrivalRadius);
1465
1466 sqlite3_bind_int(stmt, 12, point->m_iWaypointRangeRingsNumber);
1467 sqlite3_bind_double(stmt, 13, point->m_fWaypointRangeRingsStep);
1468 sqlite3_bind_int(stmt, 14, point->m_iWaypointRangeRingsStepUnits);
1469 sqlite3_bind_int(stmt, 15, point->m_bShowWaypointRangeRings);
1470 sqlite3_bind_text(
1471 stmt, 16,
1472 point->m_wxcWaypointRangeRingsColour.GetAsString(wxC2S_HTML_SYNTAX)
1473 .ToStdString()
1474 .c_str(),
1475 -1, SQLITE_TRANSIENT);
1476
1477 sqlite3_bind_int(stmt, 17, point->GetScaMin());
1478 sqlite3_bind_int(stmt, 18, point->GetScaMax());
1479 sqlite3_bind_int(stmt, 19, point->GetUseSca());
1480
1481 sqlite3_bind_int(stmt, 20, point->IsVisible());
1482 sqlite3_bind_int(stmt, 21, point->IsNameShown());
1483 sqlite3_bind_int(stmt, 22, point->IsShared());
1484 int iso = point->m_bIsolatedMark;
1485 sqlite3_bind_int(stmt, 23, iso); // point->m_bIsolatedMark);
1486
1487 sqlite3_bind_text(stmt, 24, point->m_GUID.ToStdString().c_str(), -1,
1488 SQLITE_TRANSIENT);
1489
1490 } else {
1491 return false;
1492 }
1493
1494 if (sqlite3_step(stmt) != SQLITE_DONE) {
1495 ReportError("UpdateDBRoutePointAttributesA:step");
1496 sqlite3_finalize(stmt);
1497 return false;
1498 }
1499
1500 sqlite3_finalize(stmt);
1501
1502 // Update the HTML links
1503 // The list of links is freshly rebuilt when this method is called
1504 // So start by deleting all existing bcomments
1505 DeleteAllCommentsForRoutePoint(m_db, point->m_GUID.ToStdString());
1506
1507 // Now add all the links to db
1508 int NbrOfLinks = point->m_HyperlinkList->size();
1509 if (NbrOfLinks > 0) {
1510 auto& list = point->m_HyperlinkList;
1511 for (auto it = list->begin(); it != list->end(); ++it) {
1512 Hyperlink* link = *it;
1513 if (!RoutePointHtmlLinkExists(m_db, link->GUID)) {
1514 InsertRoutePointHTML(m_db, point->m_GUID.ToStdString(), link->GUID,
1515 link->DescrText.ToStdString(),
1516 link->Link.ToStdString(),
1517 link->LType.ToStdString());
1518 } else {
1519 const char* sql =
1520 "UPDATE routepoint_html_links SET "
1521 "html_link = ?, "
1522 "html_description = ?, "
1523 "html_type = ? "
1524 "WHERE guid = ?";
1525 sqlite3_stmt* stmt;
1526 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1527 sqlite3_bind_text(stmt, 3, link->Link.ToStdString().c_str(), -1,
1528 SQLITE_TRANSIENT);
1529 sqlite3_bind_text(stmt, 4, link->DescrText.ToStdString().c_str(), -1,
1530 SQLITE_TRANSIENT);
1531 sqlite3_bind_text(stmt, 5, link->LType.ToStdString().c_str(), -1,
1532 SQLITE_TRANSIENT);
1533 }
1534 if (sqlite3_step(stmt) != SQLITE_DONE) {
1535 return false;
1536 }
1537 if (sqlite3_step(stmt) != SQLITE_DONE) {
1538 ReportError("UpdateDBRoutePointAttributesB:step-h");
1539 sqlite3_finalize(stmt);
1540 return false;
1541 }
1542 sqlite3_finalize(stmt);
1543 }
1544 }
1545 }
1546
1547 return true;
1548}
1549
1550bool NavObj_dB::UpdateDBRoutePointViz(RoutePoint* point) {
1551 const char* sql =
1552 "UPDATE routepoints SET "
1553 "visibility = ? "
1554 "WHERE guid = ?";
1555
1556 sqlite3_stmt* stmt;
1557 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1558 sqlite3_bind_int(stmt, 1, point->IsVisible());
1559 sqlite3_bind_text(stmt, 2, point->m_GUID.ToStdString().c_str(), -1,
1560 SQLITE_TRANSIENT);
1561
1562 } else {
1563 return false;
1564 }
1565
1566 if (sqlite3_step(stmt) != SQLITE_DONE) {
1567 ReportError("UpdateDBRoutePointVizA:step");
1568 sqlite3_finalize(stmt);
1569 return false;
1570 }
1571
1572 sqlite3_finalize(stmt);
1573
1574 return true;
1575}
1576
1577bool NavObj_dB::DeleteRoute(Route* route) {
1578 if (m_importing) return false;
1579 if (!route) return false;
1580 std::string route_guid = route->m_GUID.ToStdString();
1581 const char* sql = "DELETE FROM routes WHERE guid = ?";
1582 sqlite3_stmt* stmt;
1583
1584 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
1585 sqlite3_bind_text(stmt, 1, route_guid.c_str(), -1, SQLITE_TRANSIENT);
1586 if (sqlite3_step(stmt) != SQLITE_DONE) {
1587 ReportError("DeleteRoute:step");
1588 sqlite3_finalize(stmt);
1589 return false;
1590 }
1591 sqlite3_finalize(stmt);
1592 } else {
1593 return false;
1594 }
1595 return true;
1596}
1597
1598bool NavObj_dB::LoadAllRoutes() {
1599 const char* sql =
1600 "SELECT "
1601 "guid, "
1602 "name, "
1603 "description, "
1604 "start_string, "
1605 "end_string, "
1606 "visibility, "
1607 "shared_wp_viz, "
1608 "planned_departure, "
1609 "plan_speed, "
1610 "time_format, "
1611 "width, "
1612 "style, "
1613 "color "
1614 "FROM routes "
1615 "ORDER BY created_at ASC";
1616
1617 sqlite3_stmt* stmt_routes;
1618 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt_routes, nullptr) != SQLITE_OK) {
1619 return false;
1620 }
1621
1622 int errcode0 = SQLITE_OK;
1623 while ((errcode0 = sqlite3_step(stmt_routes)) == SQLITE_ROW) {
1624 std::string guid =
1625 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 0));
1626 std::string name =
1627 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 1));
1628 std::string description =
1629 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 2));
1630 std::string start_string =
1631 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 3));
1632 std::string end_string =
1633 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 4));
1634 int visibility = sqlite3_column_int(stmt_routes, 5);
1635 int sharewp_viz = sqlite3_column_int(stmt_routes, 6);
1636 time_t planned_departure_ticks = sqlite3_column_int(stmt_routes, 7);
1637 double plan_speed = sqlite3_column_double(stmt_routes, 8);
1638 std::string time_format =
1639 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 9));
1640
1641 int width = sqlite3_column_int(stmt_routes, 10);
1642 int style = sqlite3_column_int(stmt_routes, 11);
1643 std::string color =
1644 reinterpret_cast<const char*>(sqlite3_column_text(stmt_routes, 12));
1645
1646 Route* route = NULL;
1647
1648 // Add the route_points
1649 const char* sql = R"(
1650 SELECT latitude, longitude, timestamp, point_order
1651 FROM trk_points
1652 WHERE track_guid = ?
1653 ORDER BY point_order ASC
1654 )";
1655
1656 const char* sqlp =
1657 "SELECT p.guid, "
1658 "p.lat, "
1659 "p.lon, "
1660 "p.Symbol, "
1661 "p.Name, "
1662 "p.description, "
1663 "p.TideStation, "
1664 "p.plan_speed, "
1665 "p.etd, "
1666 "p.Type, "
1667 "p.Time, "
1668 "p.ArrivalRadius, "
1669 "p.RangeRingsNumber, "
1670 "p.RangeRingsStep, "
1671 "p.RangeRingsStepUnits, "
1672 "p.RangeRingsVisible, "
1673 "p.RangeRingsColour, "
1674 "p.ScaleMin, "
1675 "p.ScaleMax, "
1676 "p.UseScale, "
1677 "p.visibility, "
1678 "p.viz_name, "
1679 "p.shared, "
1680 "p.isolated, "
1681 "p.created_at "
1682 "FROM routepoints_link tp "
1683 "JOIN routepoints p ON p.guid = tp.point_guid "
1684 "WHERE tp.route_guid = ? "
1685 "ORDER BY tp.point_order ASC";
1686
1687 sqlite3_stmt* stmt_rp;
1688 if (sqlite3_prepare_v2(m_db, sqlp, -1, &stmt_rp, nullptr) != SQLITE_OK) {
1689 ReportError("LoadAllRoutes-B:prepare");
1690 return false;
1691 }
1692
1693 sqlite3_bind_text(stmt_rp, 1, guid.c_str(), -1, SQLITE_TRANSIENT);
1694
1695 int GPXSeg = 0;
1696 int errcode = SQLITE_OK;
1697 while ((errcode = sqlite3_step(stmt_rp)) == SQLITE_ROW) {
1698 if (!route) {
1699 route = new Route;
1700 route->m_GUID = guid;
1701
1702 // Set all the route attributes
1703 route->SetVisible(visibility == 1);
1704 route->m_RouteNameString = name.c_str();
1705 route->m_RouteDescription = description.c_str();
1706 route->m_RouteStartString = start_string.c_str();
1707 route->m_RouteEndString = end_string.c_str();
1708 route->SetVisible(visibility == 1);
1709 route->SetSharedWPViz(sharewp_viz == 1);
1710 route->m_PlannedDeparture.Set((time_t)planned_departure_ticks);
1711 route->m_PlannedSpeed = plan_speed;
1712 route->m_TimeDisplayFormat = time_format.c_str();
1713
1714 route->m_width = width;
1715 route->m_style = (wxPenStyle)style;
1716 route->m_Colour = color;
1717 }
1718
1719 // Grab all the point attributes from the SELECT statement
1720 int col = 0;
1721 std::string point_guid =
1722 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1723 double latitude = sqlite3_column_double(stmt_rp, col++);
1724 double longitude = sqlite3_column_double(stmt_rp, col++);
1725 std::string symbol =
1726 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1727 std::string name =
1728 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1729 std::string description =
1730 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1731 std::string tide_station =
1732 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1733 double plan_speed = sqlite3_column_double(stmt_rp, col++);
1734 time_t etd_epoch = sqlite3_column_int(stmt_rp, col++);
1735 std::string type =
1736 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1737 std::string time =
1738 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1739 double arrival_radius = sqlite3_column_double(stmt_rp, col++);
1740
1741 int range_ring_number = sqlite3_column_int(stmt_rp, col++);
1742 double range_ring_step = sqlite3_column_double(stmt_rp, col++);
1743 int range_ring_units = sqlite3_column_int(stmt_rp, col++);
1744 int range_ring_visible = sqlite3_column_int(stmt_rp, col++);
1745 std::string range_ring_color =
1746 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1747
1748 int scamin = sqlite3_column_int(stmt_rp, col++);
1749 int scamax = sqlite3_column_int(stmt_rp, col++);
1750 int use_scaminmax = sqlite3_column_int(stmt_rp, col++);
1751
1752 int visibility = sqlite3_column_int(stmt_rp, col++);
1753 int viz_name = sqlite3_column_int(stmt_rp, col++);
1754 int shared = sqlite3_column_int(stmt_rp, col++);
1755 int isolated = sqlite3_column_int(stmt_rp, col++);
1756 std::string point_created_at =
1757 reinterpret_cast<const char*>(sqlite3_column_text(stmt_rp, col++));
1758
1759 RoutePoint* point;
1760 // RoutePoint exists already, in another route or isolated??
1761 RoutePoint* existing_point = NULL;
1762 auto containing_route =
1763 g_pRouteMan->FindRouteContainingWaypoint(point_guid);
1764
1765 // Special case, the route may be "closed form", i.e. circular.
1766 // If so, the closing point is not really a shared point.
1767 // Detect that case, and make it so.
1768 bool b_closed_route = false;
1769 if (!containing_route) {
1770 RoutePoint* close_point = route->GetPoint(point_guid);
1771 b_closed_route = close_point != nullptr;
1772 existing_point = close_point;
1773 }
1774
1775 if (containing_route) { // In a route already?
1776 existing_point = containing_route->GetPoint(point_guid);
1777 }
1778 // Or isolated?
1779 if (!existing_point) {
1780 existing_point = pWayPointMan->FindRoutePointByGUID(point_guid.c_str());
1781 }
1782
1783 if (existing_point) {
1784 point = existing_point;
1785 if (!b_closed_route) {
1786 point->SetShared(true); // by definition, unless point is a closer.
1787 point->m_bIsolatedMark = false;
1788 }
1789 } else {
1790 point =
1791 new RoutePoint(latitude, longitude, symbol, name, point_guid, true);
1792
1793 point->m_MarkDescription = description;
1794 point->m_TideStation = tide_station;
1795 point->SetPlannedSpeed(plan_speed);
1796
1797 wxDateTime etd;
1798 etd.Set((time_t)etd_epoch);
1799 if (etd.IsValid()) point->SetETD(etd);
1800
1801 point->m_WaypointArrivalRadius = arrival_radius;
1802
1803 point->m_iWaypointRangeRingsNumber = range_ring_number;
1804 point->m_fWaypointRangeRingsStep = range_ring_step;
1805 point->m_iWaypointRangeRingsStepUnits = range_ring_units;
1806 point->SetShowWaypointRangeRings(range_ring_visible == 1);
1807 // TODO
1808 point->m_wxcWaypointRangeRingsColour.Set(range_ring_color);
1809
1810 point->SetScaMin(scamin);
1811 point->SetScaMax(scamax);
1812 point->SetUseSca(use_scaminmax == 1);
1813
1814 point->SetVisible(visibility == 1);
1815 point->SetNameShown(viz_name == 1);
1816 point->SetShared(shared == 1);
1817 point->m_bIsolatedMark = (isolated == 1);
1818
1819 if (point_created_at.size()) {
1820 // Convert from sqLite default date/time format to wxDateTime
1821 // sqLite format uses UTC, so conversion to epoch_time is clear.
1822 std::tm tm = {};
1823 std::istringstream ss(point_created_at);
1824 ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
1825 time_t epoch_time = mktime(&tm);
1826 point->m_CreateTimeX = epoch_time;
1827 }
1828
1829 // Add the point HTML links
1830 const char* sqlh = R"(
1831 SELECT guid, html_link, html_description, html_type
1832 FROM routepoint_html_links
1833 WHERE routepoint_guid = ?
1834 ORDER BY html_type ASC
1835 )";
1836
1837 sqlite3_stmt* stmt_point_link;
1838
1839 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_point_link, nullptr) ==
1840 SQLITE_OK) {
1841 sqlite3_bind_text(stmt_point_link, 1,
1842 point->m_GUID.ToStdString().c_str(), -1,
1843 SQLITE_TRANSIENT);
1844
1845 while (sqlite3_step(stmt_point_link) == SQLITE_ROW) {
1846 std::string link_guid = reinterpret_cast<const char*>(
1847 sqlite3_column_text(stmt_point_link, 0));
1848 std::string link_link = reinterpret_cast<const char*>(
1849 sqlite3_column_text(stmt_point_link, 1));
1850 std::string link_description = reinterpret_cast<const char*>(
1851 sqlite3_column_text(stmt_point_link, 2));
1852 std::string link_type = reinterpret_cast<const char*>(
1853 sqlite3_column_text(stmt_point_link, 3));
1854
1855 Hyperlink* h = new Hyperlink();
1856 h->DescrText = link_description;
1857 h->Link = link_link;
1858 h->LType = link_type;
1859
1860 point->m_HyperlinkList->push_back(h);
1861 }
1862 sqlite3_finalize(stmt_point_link);
1863 }
1864 } // new point
1865
1866 route->AddPoint(point);
1867 } // route points
1868 sqlite3_finalize(stmt_rp);
1869 if (errcode != SQLITE_DONE) {
1870 ReportError("LoadAllRoutes-A:step");
1871 return false;
1872 }
1873
1874 // Add route html links
1875 if (route) {
1876 // Add the HTML links
1877 const char* sqlh = R"(
1878 SELECT guid, html_link, html_description, html_type
1879 FROM route_html_links
1880 WHERE route_guid = ?
1881 ORDER BY html_type ASC
1882 )";
1883
1884 sqlite3_stmt* stmt_route_links;
1885
1886 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_route_links, nullptr) ==
1887 SQLITE_OK) {
1888 sqlite3_bind_text(stmt_route_links, 1,
1889 route->m_GUID.ToStdString().c_str(), -1,
1890 SQLITE_TRANSIENT);
1891
1892 int errcode2 = SQLITE_OK;
1893 while ((errcode2 = sqlite3_step(stmt_route_links)) == SQLITE_ROW) {
1894 std::string link_guid = reinterpret_cast<const char*>(
1895 sqlite3_column_text(stmt_route_links, 0));
1896 std::string link_link = reinterpret_cast<const char*>(
1897 sqlite3_column_text(stmt_route_links, 1));
1898 std::string link_description = reinterpret_cast<const char*>(
1899 sqlite3_column_text(stmt_route_links, 2));
1900 std::string link_type = reinterpret_cast<const char*>(
1901 sqlite3_column_text(stmt_route_links, 3));
1902
1903 Hyperlink* h = new Hyperlink();
1904 h->DescrText = link_description;
1905 h->Link = link_link;
1906 h->LType = link_type;
1907
1908 route->m_HyperlinkList->push_back(h);
1909 }
1910 if (errcode != SQLITE_DONE) {
1911 ReportError("LoadAllRoutes-B:step");
1912 return false;
1913 }
1914
1915 sqlite3_finalize(stmt_route_links);
1916
1917 } else {
1918 ReportError("LoadAllRoutes-B:prepare");
1919 return false;
1920 }
1921 }
1922
1923 // Insert the route into the global list
1924 InsertRouteA(route,
1925 nullptr); // NavObjectChanges::getInstance() //TODO adding
1926 // changes will force the xml file to be updated?
1927
1928 } // routes
1929 if (errcode0 != SQLITE_DONE) {
1930 ReportError("LoadAllRoutes-C:step");
1931 return false;
1932 }
1933
1934 return true;
1935}
1936
1937bool NavObj_dB::LoadAllPoints() {
1938 const char* sqlp =
1939 "SELECT "
1940 "p.guid, "
1941 "p.lat, "
1942 "p.lon, "
1943 "p.Symbol, "
1944 "p.Name, "
1945 "p.description, "
1946 "p.TideStation, "
1947 "p.plan_speed, "
1948 "p.etd, "
1949 "p.Type, "
1950 "p.Time, "
1951 "p.ArrivalRadius, "
1952 "p.RangeRingsNumber, "
1953 "p.RangeRingsStep, "
1954 "p.RangeRingsStepUnits, "
1955 "p.RangeRingsVisible, "
1956 "p.RangeRingsColour, "
1957 "p.ScaleMin, "
1958 "p.ScaleMax, "
1959 "p.UseScale, "
1960 "p.visibility, "
1961 "p.viz_name, "
1962 "p.shared, "
1963 "p.isolated, "
1964 "p.created_at "
1965 "FROM routepoints p ";
1966
1967 RoutePoint* point = nullptr;
1968
1969 sqlite3_stmt* stmt_point;
1970 if (sqlite3_prepare_v2(m_db, sqlp, -1, &stmt_point, nullptr) != SQLITE_OK) {
1971 return false;
1972 }
1973
1974 while (sqlite3_step(stmt_point) == SQLITE_ROW) {
1975 // Grab all the point attributes from the SELECT statement
1976 int col = 0;
1977 std::string point_guid =
1978 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1979 double latitude = sqlite3_column_double(stmt_point, col++);
1980 double longitude = sqlite3_column_double(stmt_point, col++);
1981 std::string symbol =
1982 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1983 std::string name =
1984 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1985 std::string description =
1986 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1987 std::string tide_station =
1988 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1989 double plan_speed = sqlite3_column_double(stmt_point, col++);
1990 time_t etd = sqlite3_column_int(stmt_point, col++);
1991 std::string type =
1992 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1993 std::string point_time_string =
1994 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
1995 double arrival_radius = sqlite3_column_double(stmt_point, col++);
1996
1997 int range_ring_number = sqlite3_column_int(stmt_point, col++);
1998 double range_ring_step = sqlite3_column_double(stmt_point, col++);
1999 int range_ring_units = sqlite3_column_int(stmt_point, col++);
2000 int range_ring_visible = sqlite3_column_int(stmt_point, col++);
2001 std::string range_ring_color =
2002 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
2003
2004 int scamin = sqlite3_column_int(stmt_point, col++);
2005 int scamax = sqlite3_column_int(stmt_point, col++);
2006 int use_scaminmax = sqlite3_column_int(stmt_point, col++);
2007
2008 int visibility = sqlite3_column_int(stmt_point, col++);
2009 int viz_name = sqlite3_column_int(stmt_point, col++);
2010 int shared = sqlite3_column_int(stmt_point, col++);
2011 int isolated = sqlite3_column_int(stmt_point, col++);
2012 std::string point_created_at =
2013 reinterpret_cast<const char*>(sqlite3_column_text(stmt_point, col++));
2014
2015 if (isolated) {
2016 point =
2017 new RoutePoint(latitude, longitude, symbol, name, point_guid, false);
2018
2019 point->m_MarkDescription = description;
2020 point->m_TideStation = tide_station;
2021 point->SetPlannedSpeed(plan_speed);
2022 point->m_WaypointArrivalRadius = arrival_radius;
2023
2024 point->m_iWaypointRangeRingsNumber = range_ring_number;
2025 point->m_fWaypointRangeRingsStep = range_ring_step;
2026 point->m_iWaypointRangeRingsStepUnits = range_ring_units;
2027 point->SetShowWaypointRangeRings(range_ring_visible == 1);
2028
2029 point->m_wxcWaypointRangeRingsColour.Set(range_ring_color);
2030
2031 point->SetScaMin(scamin);
2032 point->SetScaMax(scamax);
2033 point->SetUseSca(use_scaminmax == 1);
2034
2035 point->SetVisible(visibility == 1);
2036 point->SetNameShown(viz_name == 1);
2037 point->SetShared(shared == 1);
2038 point->m_bIsolatedMark = (isolated == 1);
2039
2040 if (point_created_at.size()) {
2041 // Convert from sqLite default date/time format to wxDateTime
2042 // sqLite format uses UTC, so conversion to epoch_time is clear.
2043 std::tm tm = {};
2044 std::istringstream ss(point_created_at);
2045 ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
2046 time_t epoch_time = mktime(&tm);
2047 point->m_CreateTimeX = epoch_time;
2048 }
2049
2050 // Add it here
2051 pWayPointMan->AddRoutePoint(point);
2052 pSelect->AddSelectableRoutePoint(point->m_lat, point->m_lon, point);
2053
2054 // Check for and process HTML links
2055 const char* sqlh = R"(
2056 SELECT guid, html_link, html_description, html_type
2057 FROM routepoint_html_links
2058 WHERE routepoint_guid = ?
2059 ORDER BY html_type ASC
2060 )";
2061
2062 sqlite3_stmt* stmt_links;
2063
2064 if (sqlite3_prepare_v2(m_db, sqlh, -1, &stmt_links, nullptr) ==
2065 SQLITE_OK) {
2066 sqlite3_bind_text(stmt_links, 1, point->m_GUID.ToStdString().c_str(),
2067 -1, SQLITE_TRANSIENT);
2068
2069 while (sqlite3_step(stmt_links) == SQLITE_ROW) {
2070 std::string link_guid =
2071 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 0));
2072 std::string link_link =
2073 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 1));
2074 std::string link_description =
2075 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 2));
2076 std::string link_type =
2077 reinterpret_cast<const char*>(sqlite3_column_text(stmt_links, 3));
2078
2079 Hyperlink* h = new Hyperlink();
2080 h->DescrText = link_description;
2081 h->Link = link_link;
2082 h->LType = link_type;
2083
2084 point->m_HyperlinkList->push_back(h);
2085 }
2086 sqlite3_finalize(stmt_links); // free the inner loop (links) staement
2087 }
2088 } // isolated
2089 } // points
2090 sqlite3_finalize(stmt_point); // free the outer loop (point) statement
2091
2092 return true;
2093}
2094bool NavObj_dB::InsertRoutePoint(RoutePoint* point) {
2095 bool rv = false;
2096 char* errMsg = 0;
2097
2098 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) {
2099 // Insert a new route point
2100 wxString sql =
2101 wxString::Format("INSERT INTO routepoints (guid) VALUES ('%s')",
2102 point->m_GUID.ToStdString().c_str());
2103 if (!executeSQL(m_db, sql)) {
2104 return false;
2105 }
2106 }
2107
2108 UpdateDBRoutePointAttributes(point);
2109
2110 // Add HTML links to routepoint
2111 int NbrOfLinks = point->m_HyperlinkList->size();
2112 if (NbrOfLinks > 0) {
2113 auto& list = point->m_HyperlinkList;
2114 for (auto it = list->begin(); it != list->end(); ++it) {
2115 Hyperlink* link = *it;
2116 if (!RoutePointHtmlLinkExists(m_db, link->GUID)) {
2117 InsertRoutePointHTML(m_db, point->m_GUID.ToStdString(), link->GUID,
2118 link->DescrText.ToStdString(),
2119 link->Link.ToStdString(),
2120 link->LType.ToStdString());
2121 }
2122 }
2123 }
2124
2125 return true;
2126}
2127
2128bool NavObj_dB::DeleteRoutePoint(RoutePoint* point) {
2129 if (m_importing) return false;
2130 if (!point) return false;
2131
2132 std::string point_guid = point->m_GUID.ToStdString();
2133
2134 // DeleteAllCommentsForRoutePoint(m_db, point_guid);
2135
2136 const char* sql = "DELETE FROM routepoints WHERE guid = ?";
2137 sqlite3_stmt* stmt;
2138
2139 if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
2140 sqlite3_bind_text(stmt, 1, point_guid.c_str(), -1, SQLITE_TRANSIENT);
2141 if (sqlite3_step(stmt) != SQLITE_DONE) {
2142 ReportError("DeleteRoutePoint:step");
2143 sqlite3_finalize(stmt);
2144 return false;
2145 }
2146
2147 sqlite3_finalize(stmt);
2148 } else {
2149 return false;
2150 }
2151 return true;
2152}
2153
2154bool NavObj_dB::UpdateRoutePoint(RoutePoint* point) {
2155 if (m_importing) return false;
2156 if (!RoutePointExists(m_db, point->m_GUID.ToStdString())) return false;
2157 UpdateDBRoutePointAttributes(point);
2158 return true;
2159}
2160
2161bool NavObj_dB::Backup(wxString fileName) {
2162 sqlite3_backup* pBackup;
2163 sqlite3* backupDatabase;
2164
2165 if (sqlite3_open(fileName.c_str(), &backupDatabase) == SQLITE_OK) {
2166 pBackup = sqlite3_backup_init(backupDatabase, "main", m_db, "main");
2167 if (pBackup) {
2168 int result = sqlite3_backup_step(pBackup, -1);
2169 if ((result == SQLITE_OK) || (result == SQLITE_DONE)) {
2170 if (sqlite3_backup_finish(pBackup) == SQLITE_OK) {
2171 sqlite3_close_v2(backupDatabase);
2172 return true;
2173 }
2174 }
2175 }
2176 }
2177 wxLogMessage("navobj database backup error: %s", sqlite3_errmsg(m_db));
2178 return false;
2179}
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