FlightGear next
NavDataCache.cxx
Go to the documentation of this file.
1
2/*
3 * SPDX-FileCopyrightText: (C) 2012 James Turner <james@flightgear.org>
4 * SPDX_FileComment: Defins a unified binary cache for navigation data, parsed from text/XMl sources
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "config.h"
9
10#include "NavDataCache.hxx"
11
12// std
13#include <cstddef> // for std::size_t
14#include <map>
15#include <cstring> // for memcoy
16#include <cassert>
17#include <stdint.h> // for int64_t
18#include <sstream> // for std::ostringstream
19#include <mutex>
20#include <utility>
21#include <vector>
22
23#ifdef SYSTEM_SQLITE
24// the standard sqlite3.h doesn't give a way to set SQLITE_UINT64_TYPE,
25// so we have to hope sizeof(int64_t) matches sizeof(sqlite3_int64).
26// otherwise things will go bad quickly.
27 #include "sqlite3.h"
28#else
29// to ensure compatability between sqlite3_int64 and PositionedID,
30// force the type used by sqlite to match PositionedID explicitly
31#define SQLITE_INT64_TYPE int64_t
32#define SQLITE_UINT64_TYPE uint64_t
33
34 #include "fg_sqlite3.h"
35#endif
36
37#if defined(SG_WINDOWS)
38#define WIN32_LEAN_AND_MEAN // less crap :)
39#include <Windows.h>
40#else
41# include <sys/file.h>
42#include <unistd.h>
43#endif
44
45// SimGear
46#include <simgear/bucket/newbucket.hxx>
47#include <simgear/debug/logstream.hxx>
48#include <simgear/io/sg_file.hxx>
49#include <simgear/misc/sg_dir.hxx>
50#include <simgear/misc/sg_path.hxx>
51#include <simgear/misc/strutils.hxx>
52#include <simgear/sg_inlines.h>
53#include <simgear/structure/exception.hxx>
54#include <simgear/threads/SGThread.hxx>
55
56#include "CacheSchema.h"
57#include "PositionedOctree.hxx"
58#include "fix.hxx"
59#include "markerbeacon.hxx"
60#include "navrecord.hxx"
61#include "poidb.hxx"
62#include <ATC/CommStation.hxx>
63#include <Airports/airport.hxx>
65#include <Airports/gnnode.hxx>
66#include <Airports/parking.hxx>
67#include <Airports/runways.hxx>
68#include <GUI/MessageBox.hxx>
69#include <Main/fg_props.hxx>
70#include <Main/globals.hxx>
71#include <Main/options.hxx>
73#include <Navaids/airways.hxx>
74#include <Navaids/fixlist.hxx>
75#include <Navaids/navdb.hxx>
76
77using std::string;
78
79#define SG_NAVCACHE SG_NAVAID
80//#define LAZY_OCTREE_UPDATES 1
81
82namespace {
83
84const int MAX_RETRIES = 10;
85
86const int CACHE_SIZE_KBYTES= 32 * 1024;
87
88// bind a std::string to a sqlite statement. The std::string must live the
89// entire duration of the statement execution - do not pass a temporary
90// std::string, or the compiler may delete it, freeing the C-string storage,
91// and causing subtle memory corruption bugs!
92void sqlite_bind_stdstring(sqlite3_stmt* stmt, int value, const std::string& s)
93{
94 sqlite3_bind_text(stmt, value, s.c_str(), s.length(), SQLITE_STATIC);
95}
96
97// variant of the above, which does not care about the lifetime of the
98// passed std::string
99void sqlite_bind_temp_stdstring(sqlite3_stmt* stmt, int value, const std::string& s)
100{
101 sqlite3_bind_text(stmt, value, s.c_str(), s.length(), SQLITE_TRANSIENT);
102}
103
104typedef sqlite3_stmt* sqlite3_stmt_ptr;
105
106void f_distanceCartSqrFunction(sqlite3_context* ctx, int argc, sqlite3_value* argv[])
107{
108 if (argc != 6) {
109 return;
110 }
111
112 SGVec3d posA(sqlite3_value_double(argv[0]),
113 sqlite3_value_double(argv[1]),
114 sqlite3_value_double(argv[2]));
115
116 SGVec3d posB(sqlite3_value_double(argv[3]),
117 sqlite3_value_double(argv[4]),
118 sqlite3_value_double(argv[5]));
119 sqlite3_result_double(ctx, distSqr(posA, posB));
120}
121
122
123static string cleanRunwayNo(const string& aRwyNo)
124{
125 if (aRwyNo[0] == 'x') {
126 return string(); // no ident for taxiways
127 }
128
129 string result(aRwyNo);
130 // canonicalise runway ident to two digits and one optional uppercase letter
131 if ((aRwyNo.size() == 1) || !isdigit(aRwyNo[1])) {
132 result = "0" + aRwyNo;
133 }
134
135 // trim off trailing garbage
136 if (result.size() > 2) {
137 char suffix = toupper(result[2]);
138 if (suffix == 'X') {
139 result = result.substr(0, 2);
140 }
141 }
142
143 return result;
144}
145
146class AbandonCacheException : public sg_exception
147{
148public:
149 AbandonCacheException() : sg_exception("Cache build cancelled", {}, {}, false)
150 {
151 }
152};
153
154} // anonymous namespace
155
156namespace flightgear
157{
158
166class RebuildThread : public SGThread
167{
168public:
170 _cache(cache),
171 _phase(NavDataCache::REBUILD_UNKNOWN),
172 _completionPercent(0),
173 _isFinished(false)
174 {
175
176 }
177
179 {
180 join();
181 }
182
183 bool isFinished() const
184 {
185 std::lock_guard<std::mutex> g(_lock);
186 return _isFinished;
187 }
188
189 virtual void run()
190 {
191 SGTimeStamp st;
192 st.stamp();
193 _cache->doRebuild();
194 SG_LOG(SG_NAVCACHE, SG_INFO, "cache rebuild took:" << st.elapsedMSec() << "msec");
195
196 std::lock_guard<std::mutex> g(_lock);
197 _isFinished = true;
199 }
200
202 {
204 std::lock_guard<std::mutex> g(_lock);
205 ph = _phase;
206 return ph;
207 }
208
209 unsigned int completionPercent() const
210 {
211 unsigned int perc = 0;
212 std::lock_guard<std::mutex> g(_lock);
213 perc = _completionPercent;
214 return perc;
215 }
216
217 void setProgress(NavDataCache::RebuildPhase ph, unsigned int percent)
218 {
219 std::lock_guard<std::mutex> g(_lock);
220 _phase = ph;
221 _completionPercent = percent;
222 }
223
224private:
225 NavDataCache* _cache;
227 unsigned int _completionPercent;
228 mutable std::mutex _lock;
229 bool _isFinished;
230};
231
233
234typedef std::map<PositionedID, FGPositionedRef> PositionedCache;
235
237{
238public:
240 const string& ident, const SGGeod& pos) :
242 {
243 SG_UNUSED(airport);
244 }
245};
246
247// useful for debugging 'hanging' queries: look for a statement
248// which starts but never completes
249#if 0
250int traceCallback(unsigned int traceCode, void* ctx, void* p, void* x)
251{
252 if (traceCode == SQLITE_TRACE_STMT) {
253 SG_LOG(SG_NAVCACHE, SG_WARN, "step:" << p << " text=" << (char*)x);
254 }
255 else if (traceCode == SQLITE_TRACE_PROFILE) {
256 int64_t* nanoSecs = (int64_t*)x;
257 SG_LOG(SG_NAVCACHE, SG_WARN, "profile:" << p << " took " << *nanoSecs);
258 }
259
260 return 0;
261}
262#endif
263
265{
266public:
267 NavDataCachePrivate(const SGPath& p, NavDataCache* o) :
268 outer(o),
269 db(nullptr),
270 path(p),
271 readOnly(false),
272 cacheHits(0),
273 cacheMisses(0),
275 transactionAborted(false)
276 {
277 }
278
280 {
281 close();
282 }
283
284 void init()
285 {
286 SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path);
287
288 readOnly = fgGetBool("/sim/fghome-readonly", false);
289 SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache read-only flags is:" << readOnly);
290
291 if (!readOnly && !path.canWrite()) {
292 throw sg_exception("Nav-cache file is not writeable");
293 }
294
295 int openFlags = readOnly ? SQLITE_OPEN_READONLY :
296 (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
297 std::string pathUtf8 = path.utf8Str();
298 int result = sqlite3_open_v2(pathUtf8.c_str(), &db, openFlags, NULL);
299 if (result == SQLITE_MISUSE) {
300 // docs state sqlite3_errmsg may not work in this case
301 SG_LOG(SG_NAVCACHE, SG_WARN, "Failed to open DB at " << path << ": misuse of Sqlite API");
302 throw sg_exception("Navcache failed to open: Sqlite API misuse");
303 } else if (result != SQLITE_OK) {
304 std::string errMsg = sqlite3_errmsg(db);
305 SG_LOG(SG_NAVCACHE, SG_WARN, "Failed to open DB at " << path << " with error:\n\t" << errMsg);
306 throw sg_exception("Navcache failed to open:" + errMsg);
307 }
308
309 if (!readOnly && (sqlite3_db_readonly(db, nullptr) != 0)) {
310 throw sg_exception("Nav-cache file opened but is not writeable");
311 }
312
313 // enable tracing for debugging stuck queries
314 // sqlite3_trace_v2(db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE, traceCallback, this);
315
316 sqlite3_stmt_ptr checkTables =
317 prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'");
318
319 sqlite3_create_function(db, "distanceCartSqr", 6, SQLITE_ANY, NULL,
320 f_distanceCartSqrFunction, NULL, NULL);
321
322 execSelect(checkTables);
323 bool didCreate = false;
324 if (!readOnly && (sqlite3_column_int(checkTables, 0) == 0)) {
325 SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables");
326 initTables();
327 didCreate = true;
328 }
329
330 reset(checkTables);
331
333
334 readPropertyQuery = prepare("SELECT value FROM properties WHERE key=?");
335 writePropertyQuery = prepare("INSERT INTO properties (key, value) VALUES (?,?)");
336 clearProperty = prepare("DELETE FROM properties WHERE key=?1");
337
338 if (didCreate) {
339 writeIntProperty("schema-version", SCHEMA_VERSION);
340 } else {
341 int schemaVersion = outer->readIntProperty("schema-version");
342 if (schemaVersion != SCHEMA_VERSION) {
343 SG_LOG(SG_NAVCACHE, SG_INFO, "Navcache schema mismatch, will rebuild");
344 throw sg_exception("Navcache schema has changed", {}, {}, false);
345 }
346 }
347
348 // see http://www.sqlite.org/pragma.html#pragma_cache_size
349 // for the details, small cache would cause thrashing.
350 std::ostringstream q;
351 q << "PRAGMA cache_size=-" << CACHE_SIZE_KBYTES << ";";
352 runSQL(q.str());
354 }
355
356 void close()
357 {
358 for (sqlite3_stmt_ptr stmt : prepared) {
359 sqlite3_finalize(stmt);
360 }
361 prepared.clear();
362 sqlite3_close(db);
363 }
364
366 {
367 SG_LOG(SG_NAVCACHE, SG_INFO, "running DB integrity check");
368 SGTimeStamp st;
369 st.stamp();
370
371 sqlite3_stmt_ptr stmt = prepare("PRAGMA quick_check(1)");
372 if (!execSelect(stmt)) {
373 throw sg_exception("DB integrity check failed to run");
374 }
375
376 string v = (char*) sqlite3_column_text(stmt, 0);
377 if (v != "ok") {
378 throw sg_exception("DB integrity check returned:" + v);
379 }
380
381 SG_LOG(SG_NAVCACHE, SG_INFO, "NavDataCache integrity check took:" << st.elapsedMSec());
382 finalize(stmt);
383 }
384
385 bool isCachedFileModified(const SGPath& path, bool verbose);
386 void findDatFiles(NavDataCache::DatFileType datFileType);
388 NavDataCache::DatFileType datFileType,
389 bool verbose);
390
391
392 sqlite3_stmt_ptr prepareSQL(const std::string& sql)
393 {
394 sqlite3_stmt_ptr stmt;
395 int result = sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, nullptr);
396 int retries = 0;
397 int retryMSec = 1;
398
399 while (result == SQLITE_BUSY) {
400 if (retries > MAX_RETRIES) {
401 break;
402 }
403
404 ++retries;
405 SGTimeStamp::sleepForMSec(retryMSec);
406 retryMSec = retryMSec << 1; // double each time
407 // try again
408 result = sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, nullptr);
409 }
410
411 if (result == SQLITE_OK) {
412 return stmt; // common case, all good
413 }
414
415 string errMsg;
416 if (result == SQLITE_MISUSE) {
417 errMsg = "Sqlite API abuse";
418 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite API abuse");
419 } else {
420 errMsg = sqlite3_errmsg(db);
421 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " running:\n\t" << sql);
422 }
423
424 throw sg_exception("Sqlite error:" + errMsg, sql);
425 }
426
427 void runSQL(const string& sql)
428 {
429 sqlite3_stmt_ptr stmt = prepareSQL(sql);
430 try {
431 execSelect(stmt);
432 } catch (sg_exception&) {
433 sqlite3_finalize(stmt);
434 throw; // re-throw
435 }
436
437 sqlite3_finalize(stmt);
438 }
439
440 sqlite3_stmt_ptr prepare(const string& sql)
441 {
442 sqlite3_stmt_ptr stmt = prepareSQL(sql);
443 prepared.push_back(stmt);
444 return stmt;
445 }
446
447 void finalize(sqlite3_stmt_ptr s)
448 {
449 StmtVec::iterator it = std::find(prepared.begin(), prepared.end(), s);
450 if (it == prepared.end()) {
451 throw sg_exception("Finalising statement that was not prepared");
452 }
453
454 prepared.erase(it);
455 sqlite3_finalize(s);
456 }
457
458 void reset(sqlite3_stmt_ptr stmt)
459 {
460 assert(stmt);
461 if (sqlite3_reset(stmt) != SQLITE_OK) {
462 string errMsg = sqlite3_errmsg(db);
463 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error resetting:" << errMsg);
464 throw sg_exception("Sqlite error resetting:" + errMsg, sqlite3_sql(stmt));
465 }
466 }
467
468 bool execSelect(sqlite3_stmt_ptr stmt)
469 {
470 return stepSelect(stmt);
471 }
472
473 bool stepSelect(sqlite3_stmt_ptr stmt)
474 {
475 if (abandonCache)
476 throw AbandonCacheException{};
477
478 int retries = 0;
479 int result;
480 int retryMSec = 10;
481
482 while (retries < MAX_RETRIES) {
483 result = sqlite3_step(stmt);
484 if (result == SQLITE_ROW) {
485 return true; // at least one result row
486 }
487
488 if (result == SQLITE_DONE) {
489 return false; // no result rows
490 }
491
492 if (result != SQLITE_BUSY) {
493 break;
494 }
495
496 SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on select, will retry:" << retries);
497 SGTimeStamp::sleepForMSec(retryMSec);
498 retryMSec *= 2; // double the back-off time, each time
499 } // of retry loop for DB locked
500
501 if (retries >= MAX_RETRIES) {
502 SG_LOG(SG_NAVCACHE, SG_ALERT, "exceeded maximum number of SQLITE_BUSY retries");
503 return false;
504 }
505
506 string errMsg;
507 if (result == SQLITE_MISUSE) {
508 errMsg = "Sqlite API abuse";
509 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite API abuse");
510 } else {
511 errMsg = sqlite3_errmsg(db);
512 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " (" << result
513 << ") while running:\n\t" << sqlite3_sql(stmt));
514 }
515
516 throw sg_exception("Sqlite error:" + errMsg, sqlite3_sql(stmt));
517 }
518
519 void execSelect1(sqlite3_stmt_ptr stmt)
520 {
521 if (!execSelect(stmt)) {
522 SG_LOG(SG_NAVCACHE, SG_WARN, "empty SELECT running:\n\t" << sqlite3_sql(stmt));
523 flightgear::sentryReportException("empty SELECT running:" + std::string{sqlite3_sql(stmt)});
524 throw sg_exception("no results returned for select", sqlite3_sql(stmt));
525 }
526 }
527
528 sqlite3_int64 execInsert(sqlite3_stmt_ptr stmt)
529 {
530 execSelect(stmt);
531 sqlite3_int64 rowid = sqlite3_last_insert_rowid(db);
532 reset(stmt);
533 return rowid;
534 }
535
536 void execUpdate(sqlite3_stmt_ptr stmt)
537 {
538 execSelect(stmt);
539 reset(stmt);
540 }
541
543 {
544 string_list commands = simgear::strutils::split(SCHEMA_SQL, ";");
545 for (std::string sql : commands) {
546 if (sql.empty()) {
547 continue;
548 }
549
550 runSQL(sql);
551 } // of commands in scheme loop
552 }
553
555 {
556 const auto commands = simgear::strutils::split(TEMPORARY_SCHEMA_SQL, ";");
557 for (auto sql : commands) {
558 if (sql.empty()) {
559 continue;
560 }
561
562 runSQL(sql);
563 } // of commands in scheme loop
564 }
565
567 {
568 writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)");
569
570 beginTransactionStmt = prepare("BEGIN");
571 commitTransactionStmt = prepare("COMMIT");
572 rollbackTransactionStmt = prepare("ROLLBACK");
573
574
575#define POSITIONED_COLS "guid, type, ident, name, airport, lon, lat, elev_m, octree_node"
576#define AND_TYPED "AND type>=?2 AND type <=?3"
577 statCacheCheck = prepare("SELECT stamp, sha FROM stat_cache WHERE path=?");
578 stampFileCache = prepare("INSERT OR REPLACE INTO stat_cache "
579 "(path, stamp, sha) VALUES (?,?, ?)");
580
581 loadPositioned = prepare("SELECT " POSITIONED_COLS " FROM all_positioned WHERE guid=?");
582 loadAirportStmt = prepare("SELECT scenery_path, has_metar FROM airport WHERE rowid=?");
583 loadNavaid = prepare("SELECT range_nm, freq, multiuse, runway, colocated FROM navaid WHERE rowid=?");
584 loadCommStation = prepare("SELECT freq_khz, range_nm FROM comm WHERE rowid=?");
585 loadRunwayStmt = prepare("SELECT heading, length_ft, width_m, surface, displaced_threshold,"
586 "stopway, reciprocal, ils FROM runway WHERE rowid=?1");
587
588 getAirportItems = prepare("SELECT guid FROM all_positioned WHERE airport=?1 " AND_TYPED);
589
590
591 setAirportMetar = prepare("UPDATE airport SET has_metar=?2 WHERE rowid="
592 "(SELECT rowid FROM positioned WHERE ident=?1 AND type>=?3 AND type <=?4)");
593 sqlite3_bind_int(setAirportMetar, 3, FGPositioned::AIRPORT);
594 sqlite3_bind_int(setAirportMetar, 4, FGPositioned::SEAPORT);
595
596 setRunwayReciprocal = prepare("UPDATE runway SET reciprocal=?2 WHERE rowid=?1");
597 setRunwayILS = prepare("UPDATE runway SET ils=?2 WHERE rowid=?1");
598 setNavaidColocated = prepare("UPDATE navaid SET colocated=?2 WHERE rowid=?1");
599
600 insertPositionedQuery = prepare("INSERT INTO positioned "
601 "(type, ident, name, airport, lon, lat, elev_m, octree_node, "
602 "cart_x, cart_y, cart_z)"
603 " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)");
604
605 insertTempPosQuery = prepare("INSERT INTO temp_positioned "
606 "(rowid, type, ident, name, lon, lat, elev_m, "
607 "cart_x, cart_y, cart_z)"
608 " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)");
609
610 updatePosition = prepare("UPDATE positioned SET lon=?2, lat=?3, elev_m=?4, octree_node=?5, "
611 "cart_x=?6, cart_y=?7, cart_z=?8 WHERE rowid=?1");
612 updateTempPos = prepare("UPDATE temp_positioned SET lon=?2, lat=?3, elev_m=?4, octree_node=?5, "
613 "cart_x=?6, cart_y=?7, cart_z=?8 WHERE rowid=?1");
614
615 insertAirport = prepare("INSERT INTO airport (rowid, scenery_path, has_metar) VALUES (?, ?, ?)");
616 insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)"
617 " VALUES (?1, ?2, ?3, ?4, ?5, ?6)");
618
619 insertCommStation = prepare("INSERT INTO comm (rowid, freq_khz, range_nm)"
620 " VALUES (?, ?, ?)");
621 insertRunway = prepare("INSERT INTO runway "
622 "(rowid, heading, length_ft, width_m, surface, displaced_threshold, stopway, reciprocal)"
623 " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)");
624 runwayLengthFtQuery = prepare("SELECT length_ft FROM runway WHERE rowid=?1");
625
626 removePositionedQuery = prepare("DELETE FROM positioned WHERE rowid=?1");
627 removeTempPosQuery = prepare("DELETE FROM temp_positioned WHERE rowid=?1");
628
629 // query statement
630 findClosestWithIdent = prepare("SELECT guid FROM all_positioned WHERE ident=?1 " AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)");
631
632 findCommByFreq = prepare("SELECT positioned.rowid FROM positioned, comm WHERE "
633 "positioned.rowid=comm.rowid AND freq_khz=?1 "
634 AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)");
635
636 findNavsByFreq = prepare("SELECT positioned.rowid FROM positioned, navaid WHERE "
637 "positioned.rowid=navaid.rowid "
638 "AND navaid.freq=?1 " AND_TYPED
639 " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)");
640
641 findNavsByFreqNoPos = prepare("SELECT positioned.rowid FROM positioned, navaid WHERE "
642 "positioned.rowid=navaid.rowid AND freq=?1 " AND_TYPED);
643
644 findNavaidForRunway = prepare("SELECT positioned.rowid FROM positioned, navaid WHERE "
645 "positioned.rowid=navaid.rowid AND runway=?1 AND type=?2");
646
647 // for an octree branch, return the child octree nodes which exist,
648 // described as a bit-mask
649 getOctreeChildren = prepare("SELECT children FROM octree WHERE rowid=?1");
650
651#ifdef LAZY_OCTREE_UPDATES
652 updateOctreeChildren = prepare("UPDATE octree SET children=?2 WHERE rowid=?1");
653#else
654 // mask the new child value into the existing one
655 updateOctreeChildren = prepare("UPDATE octree SET children=(?2 | children) WHERE rowid=?1");
656#endif
657
658 // define a new octree node (with no children)
659 insertOctree = prepare("INSERT INTO octree (rowid, children) VALUES (?1, 0)");
660
661 getOctreeLeafChildren = prepare("SELECT rowid, type FROM positioned WHERE octree_node=?1");
662
663 searchAirports = prepare("SELECT ident, name FROM positioned WHERE (name LIKE ?1 OR ident LIKE ?1) " AND_TYPED
664 // prioritize entries with matching ICAO
665 " ORDER BY (ident LIKE ?1) DESC");
666 sqlite3_bind_int(searchAirports, 2, FGPositioned::AIRPORT);
667 sqlite3_bind_int(searchAirports, 3, FGPositioned::SEAPORT);
668
669 getAllAirports = prepare("SELECT ident, name FROM positioned WHERE type>=?1 AND type <=?2");
670 sqlite3_bind_int(getAllAirports, 1, FGPositioned::AIRPORT);
671 sqlite3_bind_int(getAllAirports, 2, FGPositioned::SEAPORT);
672
673
674 getAirportItemByIdent = prepare("SELECT guid FROM all_positioned WHERE airport=?1 AND ident=?2 AND type=?3");
675
676 findAirportRunway = prepare("SELECT airport, rowid FROM positioned WHERE ident=?2 AND type=?3 AND airport="
677 "(SELECT rowid FROM positioned WHERE type=?4 AND ident=?1)");
678 sqlite3_bind_int(findAirportRunway, 3, FGPositioned::RUNWAY);
679 sqlite3_bind_int(findAirportRunway, 4, FGPositioned::AIRPORT);
680
681 // three-way join to get the navaid ident and runway ident in a single select.
682 // we're joining positioned to itself by the navaid runway, with the complication
683 // that we need to join the navaids table to get the runway ID.
684 // we also need to filter by type to excluse glideslope (GS) matches
685 findILS = prepare("SELECT nav.rowid FROM positioned AS nav, positioned AS rwy, navaid WHERE "
686 "nav.ident=?1 AND nav.airport=?2 AND rwy.ident=?3 "
687 "AND rwy.rowid = navaid.runway AND navaid.rowid=nav.rowid "
688 "AND (nav.type=?4 OR nav.type=?5)");
689
690 sqlite3_bind_int(findILS, 4, FGPositioned::ILS);
691 sqlite3_bind_int(findILS, 5, FGPositioned::LOC);
692
693 // airways
694 findAirwayNet = prepare("SELECT rowid FROM airway WHERE network=?1 AND ident=?2");
695 findAirway = prepare("SELECT rowid FROM airway WHERE ident=?1");
696 loadAirway = prepare("SELECT ident, network FROM airway WHERE rowid=?1");
697 insertAirway = prepare("INSERT INTO airway (ident, network) "
698 "VALUES (?1, ?2)");
699
700 insertAirwayEdge = prepare("INSERT INTO airway_edge (network, airway, a, b) "
701 "VALUES (?1, ?2, ?3, ?4)");
702
703 isPosInAirway = prepare("SELECT rowid FROM airway_edge WHERE network=?1 AND (a=?2 OR b=?2)");
704
705 airwayEdgesFrom = prepare("SELECT airway, b FROM airway_edge WHERE network=?1 AND a=?2");
706 airwayEdgesTo = prepare("SELECT airway, a FROM airway_edge WHERE network=?1 AND b=?2");
707 airwayEdges = prepare("SELECT a, b FROM airway_edge WHERE airway=?1");
708 }
709
710 void writeIntProperty(const string& key, int value)
711 {
712 if (!readOnly){
713 sqlite_bind_stdstring(clearProperty, 1, key);
715
716 sqlite_bind_stdstring(writePropertyQuery, 1, key);
717 sqlite3_bind_int(writePropertyQuery, 2, value);
719 }
720 }
721
722
723 FGPositioned* loadById(sqlite_int64 rowId, sqlite3_int64& aptId);
724
725 FGAirport* loadAirport(sqlite_int64 rowId,
727 const string& id, const string& name, const SGGeod& pos)
728 {
729 sqlite3_bind_int64(loadAirportStmt, 1, rowId);
731 SGPath sceneryPath{(char *) sqlite3_column_text(loadAirportStmt, 0)};
732 bool hasMetar = (sqlite3_column_int(loadAirportStmt, 1) > 0);
734
735 return new FGAirport(rowId, id, pos, name, hasMetar, ty,
736 std::move(sceneryPath));
737 }
738
740 const string& id, const SGGeod& pos, PositionedID apt)
741 {
742 sqlite3_bind_int(loadRunwayStmt, 1, rowId);
744
745 double heading = sqlite3_column_double(loadRunwayStmt, 0);
746 double lengthM = sqlite3_column_int(loadRunwayStmt, 1);
747 double widthM = sqlite3_column_double(loadRunwayStmt, 2);
748 int surface = sqlite3_column_int(loadRunwayStmt, 3);
749
750 if (ty == FGPositioned::TAXIWAY) {
752 return new FGTaxiway(rowId, id, pos, heading, lengthM, widthM, surface, apt);
753 } else if (ty == FGPositioned::HELIPAD) {
755 return new FGHelipad(rowId, apt, id, pos, heading, lengthM, widthM, surface);
756 } else {
757 double displacedThreshold = sqlite3_column_double(loadRunwayStmt, 4);
758 double stopway = sqlite3_column_double(loadRunwayStmt, 5);
759 PositionedID reciprocal = sqlite3_column_int64(loadRunwayStmt, 6);
760 PositionedID ils = sqlite3_column_int64(loadRunwayStmt, 7);
761 FGRunway* r = new FGRunway(rowId, apt, id, pos, heading, lengthM, widthM,
762 displacedThreshold, stopway, surface);
763
764 if (reciprocal > 0) {
765 r->setReciprocalRunway(reciprocal);
766 }
767
768 if (ils > 0) {
769 r->setILS(ils);
770 }
771
773 return r;
774 }
775 }
776
777 CommStation* loadComm(sqlite3_int64 rowId, FGPositioned::Type ty,
778 const string& id, const string& name,
779 const SGGeod& pos,
780 PositionedID airport)
781 {
782 SG_UNUSED(id);
783
784 sqlite3_bind_int64(loadCommStation, 1, rowId);
786
787 int range = sqlite3_column_int(loadCommStation, 0);
788 int freqKhz = sqlite3_column_int(loadCommStation, 1);
790
791 CommStation* c = new CommStation(rowId, name, ty, pos, freqKhz, range);
792 c->setAirport(airport);
793 return c;
794 }
795
796 FGPositioned* loadNav(sqlite3_int64 rowId,
797 FGPositioned::Type ty, const string& id,
798 const string& name, const SGGeod& pos)
799 {
800 sqlite3_bind_int64(loadNavaid, 1, rowId);
802
803 PositionedID runway = sqlite3_column_int64(loadNavaid, 3);
804 // marker beacons are light-weight
805 if ((ty == FGPositioned::OM) || (ty == FGPositioned::IM) ||
806 (ty == FGPositioned::MM))
807 {
809 return new FGMarkerBeaconRecord(rowId, ty, runway, pos);
810 }
811
812 int rangeNm = sqlite3_column_int(loadNavaid, 0),
813 freq = sqlite3_column_int(loadNavaid, 1);
814 double mulituse = sqlite3_column_double(loadNavaid, 2);
815 PositionedID colocated = sqlite3_column_int64(loadNavaid, 4);
817
818 FGNavRecord* n =
821 (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway)
822 : new FGNavRecord
823 (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway);
824
825 if (colocated)
826 n->setColocatedDME(colocated);
827
828 return n;
829 }
830
832 const string& name, const SGGeod& pos, PositionedID apt,
833 bool spatialIndex)
834 {
835 if (abandonCache)
836 throw AbandonCacheException{};
837
838 SGVec3d cartPos(SGVec3d::fromGeod(pos));
839
840 sqlite3_bind_int(insertPositionedQuery, 1, ty);
841 sqlite_bind_stdstring(insertPositionedQuery, 2, ident);
842 sqlite_bind_stdstring(insertPositionedQuery, 3, name);
843 sqlite3_bind_int64(insertPositionedQuery, 4, apt);
844 sqlite3_bind_double(insertPositionedQuery, 5, pos.getLongitudeDeg());
845 sqlite3_bind_double(insertPositionedQuery, 6, pos.getLatitudeDeg());
846 sqlite3_bind_double(insertPositionedQuery, 7, pos.getElevationM());
847
848 if (spatialIndex) {
850 assert(intersects(octreeLeaf->bbox(), cartPos));
851 sqlite3_bind_int64(insertPositionedQuery, 8, octreeLeaf->guid());
852 } else {
853 sqlite3_bind_null(insertPositionedQuery, 8);
854 }
855
856 sqlite3_bind_double(insertPositionedQuery, 9, cartPos.x());
857 sqlite3_bind_double(insertPositionedQuery, 10, cartPos.y());
858 sqlite3_bind_double(insertPositionedQuery, 11, cartPos.z());
859
861 return r;
862 }
863
864
866 const string& name, const SGGeod& pos,
867 bool spatialIndex)
868 {
869 if (abandonCache)
870 throw AbandonCacheException{};
871
872 SGVec3d cartPos(SGVec3d::fromGeod(pos));
873
874 sqlite3_bind_int64(insertTempPosQuery, 1, guid);
875 sqlite3_bind_int(insertTempPosQuery, 2, ty);
876 sqlite_bind_stdstring(insertTempPosQuery, 3, ident);
877 sqlite_bind_stdstring(insertTempPosQuery, 4, name);
878 sqlite3_bind_double(insertTempPosQuery, 5, pos.getLongitudeDeg());
879 sqlite3_bind_double(insertTempPosQuery, 6, pos.getLatitudeDeg());
880 sqlite3_bind_double(insertTempPosQuery, 7, pos.getElevationM());
881 sqlite3_bind_double(insertTempPosQuery, 8, cartPos.x());
882 sqlite3_bind_double(insertTempPosQuery, 9, cartPos.y());
883 sqlite3_bind_double(insertTempPosQuery, 10, cartPos.z());
884
886
887 // insert into the transient octree
889 octreeLeaf->insertChild(ty, guid);
890
891 return guid;
892 }
893
894 FGPositionedList findAllByString(const string& s, const string& column,
895 FGPositioned::Filter* filter, bool exact)
896 {
897 string query = s;
898 if (!exact) query += "%";
899
900 // build up SQL query text
901 string matchTerm = exact ? "=?1" : " LIKE ?1";
902 string sql = "SELECT guid FROM all_positioned WHERE " + column + matchTerm;
903 if (filter) {
904 sql += " " AND_TYPED;
905 }
906
907 // find or prepare a suitable statement frrm the SQL
908 sqlite3_stmt_ptr stmt = findByStringDict[sql];
909 if (!stmt) {
910 stmt = prepare(sql);
911 findByStringDict[sql] = stmt;
912 }
913
914 sqlite_bind_stdstring(stmt, 1, query);
915 if (filter) {
916 sqlite3_bind_int(stmt, 2, filter->minType());
917 sqlite3_bind_int(stmt, 3, filter->maxType());
918 }
919
920 std::vector<PositionedID> guids;
921 // Run the prepared SQL statement
922 while (stepSelect(stmt)) {
923 guids.push_back(sqlite3_column_int64(stmt, 0));
924 }
925
926 // This must be done *before* outer->loadById(), otherwise the same
927 // prepared SQL statement could be executed reentrantly from there.
928 reset(stmt);
929
930 FGPositionedList result;
931 for (const auto guid: guids) {
932 FGPositioned* pos = outer->loadById(guid);
933 if (!filter || filter->pass(pos)) {
934 result.push_back(pos);
935 }
936 }
937
938 return result;
939 }
940
941 PositionedIDVec selectIds(sqlite3_stmt_ptr query)
942 {
943 PositionedIDVec result;
944 while (stepSelect(query)) {
945 result.push_back(sqlite3_column_int64(query, 0));
946 }
947 reset(query);
948 return result;
949 }
950
952 {
953 sqlite3_bind_int64(runwayLengthFtQuery, 1, rwy);
955 double length = sqlite3_column_double(runwayLengthFtQuery, 0);
957 return length;
958 }
959
961 {
963 sqlite3_bind_int64(updateOctreeChildren, 1, nd->guid());
964 sqlite3_bind_int(updateOctreeChildren, 2, nd->childMask());
966 }
967
968 deferredOctreeUpdates.clear();
969 }
970
972 {
973 auto stmt = rowid < 0 ? removeTempPosQuery : removePositionedQuery;
974 sqlite3_bind_int64(stmt, 1, rowid);
975 execUpdate(stmt);
976 reset(stmt);
977 }
978
979 NavDataCache* outer;
980 sqlite3* db;
981 SGPath path;
983
984 // flag set during shutdown: allows us to abandon queries, etc
985 // if exit is requested during a rebuild.
986 bool abandonCache = false;
987
988
993 unsigned int cacheHits, cacheMisses;
994
998 unsigned int transactionLevel;
1001
1002 std::map<DatFileType, NavDataCache::DatFilesGroupInfo> datFilesInfo;
1005
1011
1014 sqlite3_stmt_ptr insertTempPosQuery;
1018
1019 sqlite3_stmt_ptr findClosestWithIdent;
1020 // octree (spatial index) related queries
1023
1028 sqlite3_stmt_ptr findAirportRunway,
1030
1031 sqlite3_stmt_ptr runwayLengthFtQuery;
1032
1033 // airways
1037 sqlite3_stmt_ptr loadAirway;
1038
1039 // since there's many permutations of ident/name queries, we create
1040 // them programtically, but cache the exact query by its raw SQL once
1041 // used.
1042 std::map<string, sqlite3_stmt_ptr> findByStringDict;
1043
1044 typedef std::vector<sqlite3_stmt_ptr> StmtVec;
1046
1047 std::set<Octree::Branch*> deferredOctreeUpdates;
1048
1049 // if we're performing a rebuild, the thread that is doing the work.
1050 // otherwise, NULL
1051 std::unique_ptr<RebuildThread> rebuilder;
1052
1053 // transient rowIDs (not actually present in the on-disk DB, only in our
1054 // in-memory cache / temporary table) start at this value and count down
1056};
1057
1059
1061 sqlite3_int64& aptId)
1062{
1063 if (abandonCache)
1064 throw AbandonCacheException{};
1065
1066 sqlite3_bind_int64(loadPositioned, 1, rowid);
1068
1069 assert(rowid == sqlite3_column_int64(loadPositioned, 0));
1070 FGPositioned::Type ty = (FGPositioned::Type)sqlite3_column_int(loadPositioned, 1);
1071
1072 PositionedID prowid = static_cast<PositionedID>(rowid);
1073 string ident = (char*)sqlite3_column_text(loadPositioned, 2);
1074 string name = (char*)sqlite3_column_text(loadPositioned, 3);
1075 aptId = sqlite3_column_int64(loadPositioned, 4);
1076 double lon = sqlite3_column_double(loadPositioned, 5);
1077 double lat = sqlite3_column_double(loadPositioned, 6);
1078 double elev = sqlite3_column_double(loadPositioned, 7);
1079 SGGeod pos = SGGeod::fromDegM(lon, lat, elev);
1080
1082
1083 switch (ty) {
1087 return loadAirport(rowid, ty, ident, name, pos);
1088
1090 return new AirportTower(prowid, aptId, ident, pos);
1091
1095 return loadRunway(rowid, ty, ident, pos, aptId);
1096
1097 case FGPositioned::LOC:
1098 case FGPositioned::VOR:
1099 case FGPositioned::GS:
1100 case FGPositioned::ILS:
1101 case FGPositioned::NDB:
1102 case FGPositioned::OM:
1103 case FGPositioned::MM:
1104 case FGPositioned::IM:
1105 case FGPositioned::DME:
1108 return loadNav(rowid, ty, ident, name, pos);
1109
1110 case FGPositioned::FIX:
1111 return new FGFix(rowid, ident, pos);
1112
1115 case FGPositioned::CITY:
1116 case FGPositioned::TOWN:
1119 FGPositioned* wpt = new POI(rowid, ty, ident, pos, name);
1120 return wpt;
1121 }
1122
1131 return loadComm(rowid, ty, ident, name, pos, aptId);
1132
1133 default:
1134 return NULL;
1135 }
1136}
1137
1139{
1140 if (!path.exists()) {
1141 throw sg_exception(
1142 "NavCache: missing file '" + path.utf8Str() + "'",
1143 string("NavDataCache::NavDataCachePrivate::isCachedFileModified()"));
1144 }
1145
1146 sqlite_bind_temp_stdstring(statCacheCheck, 1, path.realpath().utf8Str());
1147 sgDebugPriority logLevel = verbose ? SG_WARN : SG_DEBUG;
1148 if (!execSelect(statCacheCheck)) {
1149 SG_LOG(SG_NAVCACHE, logLevel, "NavCache: (re-)build required because '" <<
1150 path.utf8Str() << "' is not in the cache");
1152 return true;
1153 }
1154
1155 time_t modtime = sqlite3_column_int64(statCacheCheck, 0);
1156 time_t delta = std::labs(modtime - path.modTime());
1157 if (delta == 0) {
1158 SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: modtime matches, no rebuild required for " << path);
1160 return false;
1161 }
1162
1163 const std::string shaHash{(char*)sqlite3_column_text(statCacheCheck, 1)};
1164 SGFile f(path);
1165 const auto fileHash = f.computeHash();
1166 const auto isModified = (fileHash != shaHash);
1168
1169 if (!isModified) {
1170 // the mode time check failed, but the hashes matched. Let's update our modtime so we
1171 // don't compute the hash until the mod-time changes again.
1172 SG_LOG(SG_NAVCACHE, logLevel, "NavCache: " << path << " has changed modtime but contents are unchanged, re-setting cahced mod-time");
1173 outer->stampCacheFile(path, fileHash);
1174 }
1175
1176 return isModified;
1177}
1178
1179// Find the list of $scenery_path/NavData/<type>/*.dat[.gz] files found
1180// inside scenery paths, plus the default file for the given type (e.g.,
1181// $FG_ROOT/Airports/apt.dat.gz for the 'apt' type).
1182// Also compute the total size of all these files (in bytes), which is useful
1183// for progress information.
1184// The result is stored in the datFilesInfo field.
1186 NavDataCache::DatFileType datFileType)
1187{
1189 result.datFileType = datFileType;
1190 result.totalSize = 0;
1191
1192 // we don't always add FG_ROOT/Scenery to the path list. But if it exists,
1193 // we want to pick up NavData files from it, since we have shipped
1194 // scenery (for BIKF) which uses newer data than the default files
1195 // in Airports/
1196 auto paths = globals->get_fg_scenery();
1197 const auto fgrootScenery = globals->get_fg_root() / "Scenery";
1198 if (fgrootScenery.exists()) {
1199 auto it = std::find(paths.begin(), paths.end(), fgrootScenery);
1200 if (it == paths.end()) {
1201 paths.push_back(fgrootScenery);
1202 }
1203 }
1204
1205 for (const auto& path: paths) {
1206 if (! path.isDir()) {
1207 SG_LOG(SG_NAVCACHE, SG_WARN, path <<
1208 ": given as a scenery path, but is not a directory");
1209 continue;
1210 }
1211
1212 const SGPath datFilesDir =
1213 path / "NavData" / NavDataCache::datTypeStr[datFileType];
1214
1215 if (datFilesDir.isDir()) {
1216 // Deterministic because the return value of simgear::Dir::children() is
1217 // already sorted
1218 const PathList files = simgear::Dir(datFilesDir).children(
1219 simgear::Dir::TYPE_FILE | simgear::Dir::NO_DOT_OR_DOTDOT);
1220
1221 for (const auto& f : files) {
1222 const std::string name = f.file();
1223 if (simgear::strutils::ends_with(name, ".dat") ||
1224 simgear::strutils::ends_with(name, ".dat.gz")) {
1225 result.paths.push_back({f, path});
1226 result.totalSize += f.sizeInBytes();
1227 }
1228 }
1229 }
1230 } // of loop over the list of scenery paths
1231
1232 // Add the default file (e.g., $FG_ROOT/Airports/apt.dat.gz), at least for
1233 // now
1234 SGPath defaultDatFile(globals->get_fg_root());
1235 defaultDatFile.append(NavDataCache::defaultDatFile[datFileType]);
1236 if ((result.paths.empty() || result.paths.back().datPath != defaultDatFile) &&
1237 defaultDatFile.isFile()) {
1238 result.paths.push_back({defaultDatFile, globals->get_fg_root()});
1239 result.totalSize += defaultDatFile.sizeInBytes();
1240 }
1241
1242 datFilesInfo[datFileType] = result;
1243}
1244
1245// Compare:
1246// - the list of dat files given by 'datFilesGroupInfo';
1247// - the list obtained from the record with key '<type>.dat files' of the
1248// NavDataCache 'properties' table, where <type> is one of 'apt', 'metar',
1249// 'fix', 'poi', etc..
1250//
1251// This comparison is sensitive to the number and order of the files,
1252// their respective SGPath::realpath() and SGPath::modTime().
1254 NavDataCache::DatFileType datFileType,
1255 bool verbose)
1256{
1257 // 'apt' or 'metar' or 'fix' or...
1258 const NavDataCache::DatFilesGroupInfo& datFilesGroupInfo =
1259 datFilesInfo[datFileType];
1260 const string datTypeStr =
1261 NavDataCache::datTypeStr[datFileType];
1262 const string_list cachedFiles = outer->readOrderedStringListProperty(
1263 datTypeStr + ".dat files", SGPath::pathListSep);
1264 const auto& datFiles = datFilesGroupInfo.paths;
1265 auto datFilesIt = datFiles.cbegin();
1266 string_list::const_iterator cachedFilesIt = cachedFiles.begin();
1267 // Same logic as in NavDataCachePrivate::isCachedFileModified()
1268 sgDebugPriority logLevel = verbose ? SG_WARN : SG_DEBUG;
1269
1270 if (cachedFilesIt == cachedFiles.end() && datFilesIt != datFiles.end()) {
1271 SG_LOG(SG_NAVCACHE, logLevel,
1272 "NavCache: rebuild required for " << datTypeStr << ".dat files "
1273 "(no file in cache, but " << datFiles.size() << " such file" <<
1274 (datFiles.size() > 1 ? "s" : "") << " found in scenery paths)");
1275 return true;
1276 }
1277
1278 while (datFilesIt != datFiles.end()) {
1279 const SGPath& path = (datFilesIt++)->datPath;
1280
1281 if (!path.exists()) {
1282 throw sg_exception(
1283 "NavCache: non-existent file '" + path.utf8Str() + "'",
1284 string("NavDataCache::NavDataCachePrivate::areDatFilesModified()"));
1285 }
1286
1287 if (cachedFilesIt == cachedFiles.end()) {
1288 SG_LOG(SG_NAVCACHE, logLevel,
1289 "NavCache: rebuild required for " << datTypeStr << ".dat files "
1290 "(less files in cache than in scenery paths)");
1291 return true;
1292 } else {
1293 string cachedFile = *(cachedFilesIt++);
1294 string fileOnDisk = path.realpath().utf8Str();
1295
1296 if (cachedFile != fileOnDisk || isCachedFileModified(path, verbose)) {
1297 // isCachedFileModified() does all the logging, so we only have to
1298 // tell what is happening in case that method was not called.
1299 if (cachedFile != fileOnDisk) {
1300 SG_LOG(SG_NAVCACHE, logLevel,
1301 "NavCache: rebuild required because '" << cachedFile <<
1302 "' (in cache) != '" << fileOnDisk << "' (on disk)");
1303 }
1304 return true;
1305 }
1306 }
1307 } // of loop over the elements of 'datFiles'
1308
1309 if (cachedFilesIt != cachedFiles.end()) {
1310 SG_LOG(SG_NAVCACHE, logLevel,
1311 "NavCache: rebuild required for " << datTypeStr << ".dat files "
1312 "(more files in cache than in scenery paths)");
1313 return true;
1314 }
1315
1316 SG_LOG(SG_NAVCACHE, SG_DEBUG,
1317 "NavCache: no rebuild required for " << datTypeStr << ".dat files");
1318 return false;
1319}
1320
1321
1322// NavDataCache's static member variables
1324
1325const string NavDataCache::datTypeStr[] = {
1326 string("apt"),
1327 string("metar"),
1328 string("awy"),
1329 string("nav"),
1330 string("fix"),
1331 string("poi"),
1332 string("carrier"),
1333 string("TACAN_freq")
1334};
1335
1336const string NavDataCache::defaultDatFile[] = {
1337 string("Airports/apt.dat.gz"),
1338 string("Airports/metar.dat.gz"),
1339 string("Navaids/awy.dat.gz"),
1340 string("Navaids/nav.dat.gz"),
1341 string("Navaids/fix.dat.gz"),
1342 string("Navaids/poi.dat.gz"),
1343 string("Navaids/carrier.dat.gz"),
1344 string("Navaids/TACAN_freq.dat.gz")
1345};
1346
1347NavDataCache::NavDataCache()
1348{
1349 const int MAX_TRIES = 3;
1350 SGPath homePath(globals->get_fg_home());
1351
1352 std::ostringstream os;
1353 string_list versionParts = simgear::strutils::split(VERSION, ".");
1354 if (versionParts.size() < 2) {
1355 os << "navdata.cache";
1356 } else {
1357 os << "navdata_" << versionParts[0] << "_" << versionParts[1] << ".cache";
1358 }
1359
1360 // permit additional DB connections from the same process
1361 sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
1362
1363 for (int t=0; t < MAX_TRIES; ++t) {
1364 SGPath cachePath = homePath / os.str();
1365 try {
1366 d.reset(new NavDataCachePrivate(cachePath, this));
1367 d->init();
1368 //d->checkCacheFile();
1369 // reached this point with no exception, success
1370 break;
1371 } catch (sg_exception& e) {
1372 SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what()
1373 << " (attempt " << t << ")");
1374
1375 if (t == (MAX_TRIES - 1)) {
1376 // final attempt still failed, we are busted
1378 "Unable to open navigation cache",
1379 std::string("The navigation data cache could not be opened:")
1380 + e.getMessage(), e.getOrigin());
1381 }
1382
1383 d.reset();
1384
1385 // only wipe the existing if not readonly
1386 if (cachePath.exists() && !fgGetBool("/sim/fghome-readonly", false)) {
1387 bool ok = cachePath.remove();
1388 if (!ok) {
1389 SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache: failed to remove previous cache file");
1391 "Unable to re-create navigation cache",
1392 "Attempting to remove the old cache failed.",
1393 "Location: " + cachePath.utf8Str());
1394 }
1395 }
1396 }
1397 } // of retry loop
1398
1399 // Update d->aptDatFilesInfo, d->metarDatPath, d->navDatPath, etc.
1401}
1402
1404{
1405 assert(static_instance == this);
1406
1407 if (d->rebuilder) {
1408 addSentryBreadcrumb("shutting down cache with rebuild active", "info");
1409 // setting thsi will cause DB operations to throw the special
1410 // AbandonCache exception, and hence cause the rebuild thread to
1411 // exit pretty quickly, so the join() won't take too long.
1412 d->abandonCache = true;
1413 d->rebuilder.reset(); // will the destructor which does a join()
1414 addSentryBreadcrumb("abandoned rebuild scuessfully", "info");
1415 }
1416
1417
1418// ensure we wip the airports cache too, or we'll get out
1419// of sync during tests
1421
1422 static_instance = nullptr;
1423 d.reset();
1424}
1425
1427{
1428 assert(static_instance == nullptr);
1429 static_instance = new NavDataCache;
1430 return static_instance;
1431}
1432
1434{
1435 return static_instance;
1436}
1437
1439{
1440 if (static_instance) {
1441 delete static_instance;
1442 }
1443}
1444
1445// Update the lists of dat files used for NavCache freshness checking and
1446// rebuilding.
1448 // All $scenery_path/NavData/apt/*.dat[.gz] files found inside scenery
1449 // paths, plus $FG_ROOT/Airports/apt.dat.gz (order matters).
1450 d->findDatFiles(DATFILETYPE_APT);
1451
1452 // All $scenery_path/NavData/{nav,fix}/*.dat[.gz] files found inside scenery
1453 // paths, plus $FG_ROOT/Navaids/{nav,fix}.dat.gz (order matters).
1454 d->findDatFiles(DATFILETYPE_NAV);
1455 d->findDatFiles(DATFILETYPE_FIX);
1456 d->findDatFiles(DATFILETYPE_POI);
1457
1458 // These ones are still managed the "old" way (no search through scenery
1459 // paths).
1460 d->metarDatPath = SGPath(globals->get_fg_root());
1461 d->metarDatPath.append("Airports/metar.dat.gz");
1462
1463 // d->poiDatPath = SGPath(globals->get_fg_root());
1464 // d->poiDatPath.append("Navaids/poi.dat.gz");
1465
1466 d->carrierDatPath = SGPath(globals->get_fg_root());
1467 d->carrierDatPath.append("Navaids/carrier_nav.dat.gz");
1468
1469 d->airwayDatPath = SGPath(globals->get_fg_root());
1470 d->airwayDatPath.append("Navaids/awy.dat.gz");
1471}
1472
1475{
1476 auto iter = d->datFilesInfo.find(datFileType);
1477 if (iter == d->datFilesInfo.end()) {
1478 throw sg_error("NavCache: requesting info about the list of " +
1479 datTypeStr[datFileType] + " dat files, however this is not "
1480 "implemented yet!");
1481 }
1482 return iter->second;
1483}
1484
1486{
1487 if (d->readOnly) {
1488 return false;
1489 }
1490
1491 const auto environmentOverride = std::getenv("FG_NAVCACHE_REBUILD");
1492 bool dontRebuildFlag = false;
1493 if (environmentOverride) {
1494 if (!strcmp(environmentOverride, "0")) {
1495 dontRebuildFlag = true;
1496 }
1497
1498 if (!strcmp(environmentOverride, "1")) {
1499 SG_LOG(SG_NAVCACHE, SG_MANDATORY_INFO, "NavCache: forcing rebuild becuase FG_NAVCACHE_REBUILD=1");
1500 return true;
1501 }
1502 }
1503
1504 if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
1505 SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache");
1506 return true;
1507 }
1508
1509 if (d->areDatFilesModified(DATFILETYPE_APT, true) ||
1510 d->isCachedFileModified(d->metarDatPath, true) ||
1511 d->areDatFilesModified(DATFILETYPE_NAV, true) ||
1512 d->areDatFilesModified(DATFILETYPE_FIX, true) ||
1513 d->isCachedFileModified(d->carrierDatPath, true) ||
1514 d->areDatFilesModified(DATFILETYPE_POI, true) ||
1515 d->isCachedFileModified(d->airwayDatPath, true))
1516 {
1517 if (dontRebuildFlag) {
1518 SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache: skipping rebuild because FG_NAVCACHE_REBUILD=0");
1519 SG_LOG(SG_NAVCACHE, SG_ALERT, "!! Navigation and airport data will be incorrect !!");
1520 return false;
1521 } else {
1522 SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: main cache rebuild required");
1523 return true;
1524 }
1525 }
1526
1527 SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required");
1528 return false;
1529}
1530
1531
1532#if defined(SG_WINDOWS)
1533static HANDLE static_fgNavCacheRebuildMutex = nullptr;
1534#else
1535const std::string static_rebuildLockFile = "fgfs_cache_rebuild.lock";
1537#endif
1538
1544
1546{
1547#if defined(SG_WINDOWS)
1548 if (static_fgNavCacheRebuildMutex == nullptr) {
1549 // avoid multiple copies racing on the nav-cache build
1550 static_fgNavCacheRebuildMutex = CreateMutexA(nullptr, FALSE, "org.flightgear.fgfs.rebuild-navcache");
1551 if (static_fgNavCacheRebuildMutex == nullptr) {
1552 SG_LOG(SG_IO, SG_ALERT, "Failed to create NavCache rebuild mutex");
1553 return RebuildLockFailed;
1554 }
1555
1556 if (GetLastError() == ERROR_ALREADY_EXISTS) {
1558 }
1559
1560 // accquire the mutex, so that other processes can check the status.
1561 const int result = WaitForSingleObject(static_fgNavCacheRebuildMutex, 100);
1562 if (result != WAIT_OBJECT_0) {
1563 SG_LOG(SG_IO, SG_ALERT, "Failed to lock NavCache rebuild mutex:" << GetLastError());
1564 return RebuildLockFailed;
1565 }
1566 }
1567#else
1568 SGPath lockPath(globals->get_fg_home(), static_rebuildLockFile);
1569 std::string ps = lockPath.utf8Str();
1570 static_rebuildLockFileFd = ::open(ps.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
1571 if (static_rebuildLockFileFd < 0) {
1572 SG_LOG(SG_IO, SG_ALERT, "Failed to create rebuild lock file (" << lockPath << "):" << simgear::strutils::error_string(errno));
1573 return RebuildLockFailed;
1574 }
1575 int err = ::flock(static_rebuildLockFileFd, LOCK_EX | LOCK_NB);
1576 if (err < 0) {
1577 if (errno == EWOULDBLOCK) {
1579 }
1580
1581 SG_LOG(SG_IO, SG_ALERT, "Failed to lock file (" << lockPath << "):" << simgear::strutils::error_string(errno));
1582 return RebuildLockFailed;
1583 }
1584
1585#endif
1586 return RebuildLockOk;
1587}
1588
1590{
1591#if defined(SG_WINDOWS)
1592 ReleaseMutex(static_fgNavCacheRebuildMutex);
1593#else
1594 int err = ::flock(static_rebuildLockFileFd, LOCK_UN);
1595 if (err < 0) {
1596 SG_LOG(SG_IO, SG_ALERT, "Failed to unlock rebuild file:" << simgear::strutils::error_string(errno));
1597 }
1598
1599 ::close(static_rebuildLockFileFd);
1600 SGPath lockPath(globals->get_fg_home(), static_rebuildLockFile);
1601 lockPath.remove();
1602#endif
1603}
1604
1605
1607{
1608 if (!d->rebuilder.get()) {
1609 auto r = accquireRebuildLock();
1610 if (r == RebuildLockAlreadyLocked) {
1611 flightgear::fatalMessageBoxThenExit("Multiple copies of Flightgear initializing",
1612 "Multiple copies of FlightGear are trying to initialise the same navigation database. "
1613 "This means something has gone badly wrong: please report this error.");
1614 } else if (r == RebuildLockFailed) {
1615 flightgear::fatalMessageBoxThenExit("Multiple copies of Flightgear initializing",
1616 "Failed to initialise NavCache rebuild protection");
1617 }
1618
1619 d->rebuilder.reset(new RebuildThread(this));
1620 d->rebuilder->start();
1621 }
1622
1623 // poll the rebuild thread
1624 RebuildPhase phase = d->rebuilder->currentPhase();
1625 if (phase == REBUILD_DONE) {
1626 d->rebuilder.reset(); // all done!
1628 }
1629 return phase;
1630}
1631
1633{
1634#if defined(SG_WINDOWS)
1635 if (!static_fgNavCacheRebuildMutex) {
1636 static_fgNavCacheRebuildMutex = OpenMutexA(SYNCHRONIZE, FALSE, "org.flightgear.fgfs.rebuild-navcache");
1637 if (!static_fgNavCacheRebuildMutex) {
1638 // this is the common case: no other fgfs.exe is doing a rebuild, so
1639 // the mutex does not exist. Simple, we are done
1640 if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1641 return false;
1642 }
1643
1644 flightgear::fatalMessageBoxThenExit("Multiple copies of Flightgear initializing",
1645 "Unable to check if other copies of FlightGear are initializing. "
1646 "Please report this error.");
1647 }
1648 }
1649
1650 // poll the named mutex
1651 auto result = WaitForSingleObject(static_fgNavCacheRebuildMutex, 0);
1652 if (result == WAIT_OBJECT_0) {
1653 // we accquired it, release it and we're done
1654 // (there could be multiple read-only copies in this situation)
1655 ReleaseMutex(static_fgNavCacheRebuildMutex);
1656 CloseHandle(static_fgNavCacheRebuildMutex);
1657 static_fgNavCacheRebuildMutex = nullptr;
1658 return false;
1659 }
1660
1661 // failed to accquire the mutex, so assume another FGFS.exe is rebuilding,
1662 // the GU should wait.
1663 return true;
1664#else
1665 SGPath lockPath(globals->get_fg_home(), static_rebuildLockFile);
1666 std::string ps = lockPath.utf8Str();
1667 static_rebuildLockFileFd = ::open(ps.c_str(), O_RDONLY, 0644);
1668 if (static_rebuildLockFileFd < 0) {
1669 if (errno == ENOENT) {
1670 return false; // no such file, easy
1671 }
1672
1673 SG_LOG(SG_IO, SG_ALERT, "Error opening lock file:" << simgear::strutils::error_string(errno));
1674 return false;
1675 }
1676
1677 int err = ::flock(static_rebuildLockFileFd, LOCK_EX | LOCK_NB);
1678 if (err < 0) {
1679 if (errno == EWOULDBLOCK) {
1680 return true;
1681 }
1682
1683 SG_LOG(SG_IO, SG_ALERT, "Error querying lock file:" << simgear::strutils::error_string(errno));
1684 return false;
1685 }
1686
1687 // release it again, so any *other* waiting copies can also succeed
1688 ::flock(static_rebuildLockFileFd, LOCK_UN);
1689 return false;
1690#endif
1691}
1692
1694{
1695 if (!d->rebuilder.get()) {
1696 return 0;
1697 }
1698
1699 return d->rebuilder->completionPercent();
1700}
1701
1703{
1704 if (!d->rebuilder.get()) {
1705 return;
1706 }
1707
1708 d->rebuilder->setProgress(ph, percent);
1709}
1710
1711void NavDataCache::loadDatFiles(
1712 DatFileType type,
1713 std::function<void(const SceneryLocation&, std::size_t, std::size_t)> loader)
1714{
1715 SGTimeStamp st;
1716 const string typeStr = datTypeStr[type];
1717 string_list datFiles;
1718 const NavDataCache::DatFilesGroupInfo datFilesInfo = getDatFilesInfo(type);
1719 const SceneryLocationList sceneryLocations = datFilesInfo.paths;
1720 std::size_t bytesReadSoFar = 0;
1721
1722 st.stamp();
1723 for (const auto& scLoc : sceneryLocations) {
1724 const string path = scLoc.datPath.realpath().utf8Str();
1725 datFiles.push_back(path);
1726 SG_LOG(SG_GENERAL, SG_INFO,
1727 "Loading " + typeStr + ".dat file: '" << std::move(path) << "'");
1728 loader(scLoc, bytesReadSoFar, datFilesInfo.totalSize);
1729 bytesReadSoFar += scLoc.datPath.sizeInBytes();
1730 stampCacheFile(scLoc.datPath); // this uses the realpath() of the file
1731 }
1732
1733 // Store the list of .dat files we have loaded
1734 writeOrderedStringListProperty(typeStr + ".dat files", datFiles,
1735 SGPath::pathListSep);
1736 SG_LOG(SG_NAVCACHE, SG_INFO,
1737 typeStr + ".dat files load took: " <<
1738 st.elapsedMSec());
1739}
1740
1741void NavDataCache::doRebuild()
1742{
1743 rebuildInProgress = true;
1744
1745 try {
1746 d->close(); // completely close the sqlite object
1747 d->path.remove(); // remove the file on disk
1748 d->init(); // start again from scratch
1749
1750 // initialise the root octree node
1751 d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)");
1752
1753 SGTimeStamp st;
1754 {
1755 Transaction txn(this);
1756 APTLoader aptLoader;
1757 FixesLoader fixesLoader;
1758 NavLoader navLoader;
1759
1760 using namespace std::placeholders; // for _1, _2, _3...
1761
1762 loadDatFiles(DATFILETYPE_APT,
1763 std::bind(&APTLoader::readAptDatFile, &aptLoader, _1, _2, _3));
1764
1765 st.stamp();
1767 SG_LOG(SG_NAVCACHE, SG_DEBUG, "Processing airports");
1768 aptLoader.loadAirports(); // load airport data into the NavCache
1769 SG_LOG(SG_NAVCACHE, SG_INFO,
1770 "processing airports took:" <<
1771 st.elapsedMSec());
1772
1774 metarDataLoad(d->metarDatPath);
1775 stampCacheFile(d->metarDatPath);
1776
1777 loadDatFiles(DATFILETYPE_FIX,
1778 std::bind(&FixesLoader::loadFixes, &fixesLoader, _1, _2, _3));
1779 loadDatFiles(DATFILETYPE_NAV,
1780 std::bind(&NavLoader::loadNav, &navLoader, _1, _2, _3));
1781
1783 st.stamp();
1784 txn.commit();
1785 SG_LOG(SG_NAVCACHE, SG_INFO, "stage 1 commit took:" << st.elapsedMSec());
1786 }
1787
1788 {
1789 Transaction txn(this);
1790 POILoader poisLoader;
1791
1792 using namespace std::placeholders; // for _1, _2, _3...
1793
1794 st.stamp();
1795 loadDatFiles(DATFILETYPE_POI,
1796 std::bind(&POILoader::loadPOIs, &poisLoader, _1, _2, _3));
1797
1798 SG_LOG(SG_NAVCACHE, SG_INFO, "poi.dat load took:" << st.elapsedMSec());
1799
1801 st.stamp();
1802 txn.commit();
1803 SG_LOG(SG_NAVCACHE, SG_INFO, "POI commit took:" << st.elapsedMSec());
1804 }
1805
1806 {
1807 Transaction txn(this);
1808 NavLoader navLoader;
1809 navLoader.loadCarrierNav(d->carrierDatPath);
1810 stampCacheFile(d->carrierDatPath);
1811
1812 st.stamp();
1813 Airway::loadAWYDat(d->airwayDatPath);
1814 stampCacheFile(d->airwayDatPath);
1815 SG_LOG(SG_NAVCACHE, SG_INFO, "awy.dat load took:" << st.elapsedMSec());
1816
1817 d->flushDeferredOctreeUpdates();
1818
1819 string sceneryPaths = SGPath::join(globals->get_fg_scenery(), ";");
1820 writeStringProperty("scenery_paths", sceneryPaths);
1821
1822 st.stamp();
1823 txn.commit();
1824 SG_LOG(SG_NAVCACHE, SG_INFO, "final commit took:" << st.elapsedMSec());
1825
1826 }
1827
1828 } catch (sg_exception& e) {
1829 SG_LOG(SG_NAVCACHE, SG_ALERT, "caught exception rebuilding navCache:" << e.what());
1830 }
1831
1832 rebuildInProgress = false;
1833}
1834
1835int NavDataCache::readIntProperty(const string& key)
1836{
1837 sqlite_bind_stdstring(d->readPropertyQuery, 1, key);
1838 int result = 0;
1839
1840 if (d->execSelect(d->readPropertyQuery)) {
1841 result = sqlite3_column_int(d->readPropertyQuery, 0);
1842 } else {
1843 SG_LOG(SG_NAVCACHE, SG_WARN, "readIntProperty: unknown:" << key);
1844 }
1845
1846 d->reset(d->readPropertyQuery);
1847 return result;
1848}
1849
1850double NavDataCache::readDoubleProperty(const string& key)
1851{
1852 sqlite_bind_stdstring(d->readPropertyQuery, 1, key);
1853 double result = 0.0;
1854 if (d->execSelect(d->readPropertyQuery)) {
1855 result = sqlite3_column_double(d->readPropertyQuery, 0);
1856 } else {
1857 SG_LOG(SG_NAVCACHE, SG_WARN, "readDoubleProperty: unknown:" << key);
1858 }
1859
1860 d->reset(d->readPropertyQuery);
1861 return result;
1862}
1863
1864string NavDataCache::readStringProperty(const string& key)
1865{
1866 sqlite_bind_stdstring(d->readPropertyQuery, 1, key);
1867 string result;
1868 if (d->execSelect(d->readPropertyQuery)) {
1869 result = (char*) sqlite3_column_text(d->readPropertyQuery, 0);
1870 } else {
1871 SG_LOG(SG_NAVCACHE, SG_WARN, "readStringProperty: unknown:" << key);
1872 }
1873
1874 d->reset(d->readPropertyQuery);
1875 return result;
1876}
1877
1878void NavDataCache::writeIntProperty(const string& key, int value)
1879{
1880 d->writeIntProperty(key, value);
1881}
1882
1883void NavDataCache::writeStringProperty(const string& key, const string& value)
1884{
1885 if (!isReadOnly()) {
1886 sqlite_bind_stdstring(d->clearProperty, 1, key);
1887 d->execUpdate(d->clearProperty);
1888
1889 sqlite_bind_stdstring(d->writePropertyQuery, 1, key);
1890 sqlite_bind_stdstring(d->writePropertyQuery, 2, value);
1891 d->execUpdate(d->writePropertyQuery);
1892 }
1893}
1894
1895void NavDataCache::writeDoubleProperty(const string& key, const double& value)
1896{
1897 if (!isReadOnly()) {
1898 sqlite_bind_stdstring(d->clearProperty, 1, key);
1899 d->execUpdate(d->clearProperty);
1900
1901 sqlite_bind_stdstring(d->writePropertyQuery, 1, key);
1902 sqlite3_bind_double(d->writePropertyQuery, 2, value);
1903 d->execUpdate(d->writePropertyQuery);
1904 }
1905}
1906
1908{
1909 sqlite_bind_stdstring(d->readPropertyQuery, 1, key);
1910 string_list result;
1911 while (d->stepSelect(d->readPropertyQuery)) {
1912 result.push_back((char*) sqlite3_column_text(d->readPropertyQuery, 0));
1913 }
1914 d->reset(d->readPropertyQuery);
1915
1916 return result;
1917}
1918
1919void NavDataCache::writeStringListProperty(const string& key, const string_list& values)
1920{
1921 if (!isReadOnly()) {
1922 sqlite_bind_stdstring(d->clearProperty, 1, key);
1923 d->execUpdate(d->clearProperty);
1924
1925 for (string value : values) {
1926 sqlite_bind_stdstring(d->writePropertyMulti, 1, key);
1927 sqlite_bind_stdstring(d->writePropertyMulti, 2, value);
1928 d->execInsert(d->writePropertyMulti);
1929 }
1930 }
1931}
1932
1934 const char* separator)
1935{
1936 string_list l = simgear::strutils::split(readStringProperty(key), separator);
1937 assert(!l.empty());
1938 // See comment in writeOrderedStringListProperty()
1939 l.pop_back();
1940
1941 return l;
1942}
1943
1945 const string_list& values,
1946 const char* separator)
1947{
1948 string s;
1949
1950 // If values == {a, b, c}, we'll write this string to the property:
1951 //
1952 // a + separator + b + separator + c + separator
1953 //
1954 // When this is split around 'separator' after reading back the property
1955 // value, we'll obtain an extraneous empty element after 'c' that should be
1956 // discarded. This last separator allows correct write-read round-trip when
1957 // 'values' is empty.
1958 if (!values.empty()) {
1959 s = simgear::strutils::join(values, separator) + string(separator);
1960 }
1961
1962 writeStringProperty(key, s);
1963}
1964
1966{
1967 return d->isCachedFileModified(path, false);
1968}
1969
1970void NavDataCache::stampCacheFile(const SGPath& path, const std::string& sha)
1971{
1972 if (!isReadOnly()){
1973 sqlite_bind_temp_stdstring(d->stampFileCache, 1, path.realpath().utf8Str());
1974 sqlite3_bind_int64(d->stampFileCache, 2, path.modTime());
1975
1976 if (sha.empty()) {
1977 SGFile f(path);
1978 sqlite_bind_temp_stdstring(d->stampFileCache, 3, f.computeHash());
1979 } else {
1980 sqlite_bind_temp_stdstring(d->stampFileCache, 3, sha);
1981 }
1982 d->execInsert(d->stampFileCache);
1983 }
1984}
1985
1986void NavDataCache::beginTransaction()
1987{
1988 if (d->transactionLevel == 0) {
1989 d->transactionAborted = false;
1990 d->stepSelect(d->beginTransactionStmt);
1991 sqlite3_reset(d->beginTransactionStmt);
1992 }
1993
1994 ++d->transactionLevel;
1995}
1996
1997void NavDataCache::commitTransaction()
1998{
1999 assert(d->transactionLevel > 0);
2000 if (--d->transactionLevel == 0) {
2001 // if a nested transaction aborted, we might end up here, but must
2002 // still abort the entire transaction. That's bad, but safer than
2003 // committing.
2004 sqlite3_stmt_ptr q = d->transactionAborted ? d->rollbackTransactionStmt : d->commitTransactionStmt;
2005
2006 int retries = 0;
2007 int result;
2008 while (retries < MAX_RETRIES) {
2009 result = sqlite3_step(q);
2010 if (result == SQLITE_DONE) {
2011 break;
2012 }
2013
2014 // see http://www.sqlite.org/c3ref/get_autocommit.html for a hint
2015 // what's going on here: autocommit in inactive inside BEGIN, so if
2016 // it's active, the DB was rolled-back
2017 if (sqlite3_get_autocommit(d->db)) {
2018 SG_LOG(SG_NAVCACHE, SG_ALERT, "commit: was rolled back!" << retries);
2019 flightgear::sentryReportException("DB commit was rolled back");
2020 d->transactionAborted = true;
2021 break;
2022 }
2023
2024 if (result != SQLITE_BUSY) {
2025 break;
2026 }
2027
2028 SGTimeStamp::sleepForMSec(++retries * 100);
2029 SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on commit, will retry:" << retries);
2030 } // of retry loop for DB busy
2031
2032 string errMsg;
2033 if (result != SQLITE_DONE) {
2034 errMsg = sqlite3_errmsg(d->db);
2035 flightgear::sentryReportException("DB error:" + errMsg + " running " + std::string{sqlite3_sql(q)});
2036 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " for " << result
2037 << " while running:\n\t" << sqlite3_sql(q));
2038 }
2039
2040 sqlite3_reset(q);
2041 }
2042}
2043
2044void NavDataCache::abortTransaction()
2045{
2046 SG_LOG(SG_NAVCACHE, SG_WARN, "NavCache: aborting transaction");
2047 flightgear::sentryReportException("DB aborting transactino");
2048
2049 assert(d->transactionLevel > 0);
2050 if (--d->transactionLevel == 0) {
2051 d->stepSelect(d->rollbackTransactionStmt);
2052 sqlite3_reset(d->rollbackTransactionStmt);
2053 }
2054
2055 d->transactionAborted = true;
2056}
2057
2059{
2060 std::for_each(d->cache.begin(), d->cache.end(), [](PositionedCache::value_type& v) {
2061 if (v.second->type() == FGPositioned::MOBILE_TACAN) {
2062 auto mobile = fgpositioned_cast<FGMobileNavRecord>(v.second);
2063 mobile->clearVehicle();
2064 }
2065 });
2066}
2067
2069{
2070 if (!d || (rowid == 0)) {
2071 return {};
2072 };
2073
2074 PositionedCache::iterator it = d->cache.find(rowid);
2075 if (it != d->cache.end()) {
2076 d->cacheHits++;
2077 return it->second; // cache hit
2078 }
2079
2080 sqlite3_int64 aptId;
2081 FGPositionedRef pos = d->loadById(rowid, aptId);
2082 if (rebuildInProgress) {
2083 // Do not cache and apply ILS adjustment while rebuilding the cache.
2084 // The adjustment process requires all ILS navaids to be present,
2085 // which is not true during the cache rebuild.
2086 return pos;
2087 }
2088 d->cache.insert(it, PositionedCache::value_type(rowid, pos));
2089 d->cacheMisses++;
2090
2091 // when we loaded an ILS, we must apply per-airport changes
2092 if ((pos->type() == FGPositioned::ILS) && (aptId > 0)) {
2094 apt->validateILSData();
2095 }
2096
2097 return pos;
2098}
2099
2101 const string& name,
2102 const SGPath& sceneryPath)
2103{
2104 const string sceneryPath_str = sceneryPath.utf8Str();
2105 // airports have their pos computed based on the avergae runway centres
2106 // so the pos isn't available immediately. Pass a dummy pos and avoid
2107 // doing spatial indexing until later
2108 sqlite3_int64 rowId = d->insertPositioned(ty, ident, name, SGGeod(),
2109 0 /* airport */,
2110 false /* spatial index */);
2111
2112 sqlite3_bind_int64(d->insertAirport, 1, rowId);
2113 sqlite_bind_stdstring(d->insertAirport, 2, sceneryPath_str);
2114 d->execInsert(d->insertAirport);
2115
2116 return rowId;
2117}
2118
2119void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos)
2120{
2121 const bool isTemporary = (item < 0);
2122 SGVec3d cartPos(SGVec3d::fromGeod(pos));
2123 auto it = d->cache.find(item);
2124
2125 // transient item, update the transient octree : this is much easier than the
2126 // persistent octree case (see logic below) becuase we know the tree is fully
2127 // loaded, and there's no DB table to keep in sync; we just update the in-memory
2128 // leaves once we found them.
2129 if (isTemporary) {
2130 assert(it != d->cache.end()); // transient item must existing in the in-memory cache
2131
2132 SGVec3d oldCartPos(SGVec3d::fromGeod(it->second->geod()));
2133 const auto ty = it->second->type();
2134
2135 Octree::Leaf* oldOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(oldCartPos);
2136 Octree::Leaf* newOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(cartPos);
2137
2138 // commonly the octree leaf won't change, so check for that
2139 if (oldOctreeLeaf != newOctreeLeaf) {
2140 oldOctreeLeaf->removeChild(item);
2141 newOctreeLeaf->insertChild(ty, item);
2142 }
2143
2144 it->second->modifyPosition(pos);
2145 }
2146
2147
2148 if (it != d->cache.end()) {
2149 d->cache[item]->modifyPosition(pos);
2150 }
2151
2152 auto stmt = isTemporary ? d->updateTempPos : d->updatePosition;
2153 sqlite3_bind_int(stmt, 1, item);
2154 sqlite3_bind_double(stmt, 2, pos.getLongitudeDeg());
2155 sqlite3_bind_double(stmt, 3, pos.getLatitudeDeg());
2156 sqlite3_bind_double(stmt, 4, pos.getElevationM());
2157
2158 if (!isTemporary) {
2159 // bug 905; the octree leaf may change here, but the leaf may already be
2160 // loaded, and caching its children. (Either the old or new leaf!). Worse,
2161 // we may be called here as a result of loading one of those leaf's children.
2162 // instead of dealing with all those possibilites, such as modifying
2163 // the in-memory leaf's STL child container, we simply leave the runtime
2164 // structures alone. This is fine providing items do no move very far, since
2165 // all the spatial searches ultimately use the items' real cartesian position,
2166 // which was updated above.
2168 sqlite3_bind_int64(stmt, 5, octreeLeaf->guid());
2169 }
2170
2171 sqlite3_bind_double(stmt, 6, cartPos.x());
2172 sqlite3_bind_double(stmt, 7, cartPos.y());
2173 sqlite3_bind_double(stmt, 8, cartPos.z());
2174
2175
2176 d->execUpdate(stmt);
2177}
2178
2179void NavDataCache::insertTower(PositionedID airportId, const SGGeod& pos)
2180{
2181 d->insertPositioned(FGPositioned::TOWER, string(), string(),
2182 pos, airportId, true /* spatial index */);
2183}
2184
2186NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident,
2187 const SGGeod& pos, PositionedID apt,
2188 double heading, double length, double width, double displacedThreshold,
2189 double stopway, int markings, int approach, int tdz, int reil,
2190 int surfaceCode, int shoulder_code, float smoothness, int center_lights,
2191 int edge_lights, int distance_remaining)
2192{
2193 // only runways are spatially indexed; don't bother indexing taxiways
2194 // or pavements
2195 bool spatialIndex = ( ty == FGPositioned::RUNWAY || ty == FGPositioned::HELIPAD);
2196
2197 sqlite3_int64 rowId = d->insertPositioned(ty, cleanRunwayNo(ident), "", pos, apt,
2198 spatialIndex);
2199 sqlite3_bind_int64(d->insertRunway, 1, rowId);
2200 sqlite3_bind_double(d->insertRunway, 2, heading);
2201 sqlite3_bind_double(d->insertRunway, 3, length);
2202 sqlite3_bind_double(d->insertRunway, 4, width);
2203 sqlite3_bind_int(d->insertRunway, 5, surfaceCode);
2204 sqlite3_bind_double(d->insertRunway, 6, displacedThreshold);
2205 sqlite3_bind_double(d->insertRunway, 7, stopway);
2206 sqlite3_bind_int(d->insertRunway, 8, markings);
2207 sqlite3_bind_int(d->insertRunway, 9, approach);
2208 sqlite3_bind_int(d->insertRunway, 10, tdz);
2209 sqlite3_bind_int(d->insertRunway, 11, reil);
2210 sqlite3_bind_int(d->insertRunway, 12, shoulder_code);
2211 sqlite3_bind_double(d->insertRunway, 13, smoothness);
2212 sqlite3_bind_int(d->insertRunway, 14, center_lights);
2213 sqlite3_bind_int(d->insertRunway, 15, edge_lights);
2214 sqlite3_bind_int(d->insertRunway, 16, distance_remaining);
2215
2216 return d->execInsert(d->insertRunway);
2217}
2218
2220NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident,
2221 const SGGeod& pos, PositionedID apt,
2222 double heading, double length, double width, double displacedThreshold,
2223 double stopway, int surfaceCode)
2224{
2225 return insertRunway(ty, ident, pos, apt,
2226 heading, length, width, displacedThreshold,
2227 stopway, 0, 0, 0, 0,
2228 surfaceCode, 0, 0.0f, 0, 0, 0);
2229}
2230
2232{
2233 sqlite3_bind_int64(d->setRunwayReciprocal, 1, runway);
2234 sqlite3_bind_int64(d->setRunwayReciprocal, 2, recip);
2235 d->execUpdate(d->setRunwayReciprocal);
2236
2237// and the opposite direction too!
2238 sqlite3_bind_int64(d->setRunwayReciprocal, 2, runway);
2239 sqlite3_bind_int64(d->setRunwayReciprocal, 1, recip);
2240 d->execUpdate(d->setRunwayReciprocal);
2241}
2242
2244{
2245 sqlite3_bind_int64(d->setRunwayILS, 1, runway);
2246 sqlite3_bind_int64(d->setRunwayILS, 2, ils);
2247 d->execUpdate(d->setRunwayILS);
2248
2249 // and the in-memory one
2250 if (d->cache.find(runway) != d->cache.end()) {
2251 FGRunway* instance = (FGRunway*) d->cache[runway].ptr();
2252 instance->setILS(ils);
2253 }
2254}
2255
2258 const string& name, const SGGeod& pos,
2259 int freq, int range, double multiuse,
2260 PositionedID apt, PositionedID runway)
2261{
2262 bool spatialIndex = true;
2263 if (ty == FGPositioned::MOBILE_TACAN) {
2264 spatialIndex = false;
2265 }
2266
2267 sqlite3_int64 rowId = d->insertPositioned(ty, ident, name, pos, apt,
2268 spatialIndex);
2269 sqlite3_bind_int64(d->insertNavaid, 1, rowId);
2270 sqlite3_bind_int(d->insertNavaid, 2, freq);
2271 sqlite3_bind_int(d->insertNavaid, 3, range);
2272 sqlite3_bind_double(d->insertNavaid, 4, multiuse);
2273 sqlite3_bind_int64(d->insertNavaid, 5, runway);
2274 sqlite3_bind_int64(d->insertNavaid, 6, 0);
2275 return d->execInsert(d->insertNavaid);
2276}
2277
2279{
2280 // Update DB entries...
2281 sqlite3_bind_int64(d->setNavaidColocated, 1, navaid);
2282 sqlite3_bind_int64(d->setNavaidColocated, 2, colocatedDME);
2283 d->execUpdate(d->setNavaidColocated);
2284
2285 // ...and the in-memory copy of the navrecord
2286 if (d->cache.find(navaid) != d->cache.end()) {
2287 FGNavRecord* rec = (FGNavRecord*) d->cache[navaid].get();
2288 rec->setColocatedDME(colocatedDME);
2289 }
2290}
2291
2293 const string& name, const SGGeod& pos, int freq, int range,
2294 PositionedID apt)
2295{
2296 sqlite3_int64 rowId = d->insertPositioned(ty, "", name, pos, apt, true);
2297 sqlite3_bind_int64(d->insertCommStation, 1, rowId);
2298 sqlite3_bind_int(d->insertCommStation, 2, freq);
2299 sqlite3_bind_int(d->insertCommStation, 3, range);
2300 return d->execInsert(d->insertCommStation);
2301}
2302
2303PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos,
2304 const std::string& name, bool isTransient)
2305{
2306 if (isTransient) {
2307 return d->insertTemporaryPositioned(createTransientID(), ty, ident, name, aPos,
2308 true /* spatial index */);
2309 } else {
2310 return d->insertPositioned(ty, ident, name, aPos, 0,
2311 true /* spatial index */);
2312 }
2313
2314}
2315
2317{
2318 const bool isTemporary = ref->guid() < 0;
2319 // remove from the octree if temporary.
2320 if (isTemporary) {
2321 auto octreeLeaf = Octree::globalTransientOctree()->findLeafForPos(ref->cart());
2322 octreeLeaf->removeChild(ref->guid());
2323 }
2324
2325 d->removePositioned(ref->guid());
2326
2327 auto it = d->cache.find(ref->guid());
2328 d->cache.erase(it);
2329
2330 return true;
2331}
2332
2333void NavDataCache::setAirportMetar(const string& icao, bool hasMetar)
2334{
2335 sqlite_bind_stdstring(d->setAirportMetar, 1, icao);
2336 sqlite3_bind_int(d->setAirportMetar, 2, hasMetar);
2337 d->execUpdate(d->setAirportMetar);
2338}
2339
2340//------------------------------------------------------------------------------
2342 FGPositioned::Filter* filter,
2343 bool exact )
2344{
2345 return d->findAllByString(s, "ident", filter, exact);
2346}
2347
2348//------------------------------------------------------------------------------
2350 FGPositioned::Filter* filter,
2351 bool exact )
2352{
2353 return d->findAllByString(s, "name", filter, exact);
2354}
2355
2356//------------------------------------------------------------------------------
2358 const SGGeod& aPos,
2359 FGPositioned::Filter* aFilter )
2360{
2361 sqlite_bind_stdstring(d->findClosestWithIdent, 1, aIdent);
2362 if (aFilter) {
2363 sqlite3_bind_int(d->findClosestWithIdent, 2, aFilter->minType());
2364 sqlite3_bind_int(d->findClosestWithIdent, 3, aFilter->maxType());
2365 } else { // full type range
2366 sqlite3_bind_int(d->findClosestWithIdent, 2, FGPositioned::INVALID);
2367 sqlite3_bind_int(d->findClosestWithIdent, 3, FGPositioned::LAST_TYPE);
2368 }
2369
2370 SGVec3d cartPos(SGVec3d::fromGeod(aPos));
2371 sqlite3_bind_double(d->findClosestWithIdent, 4, cartPos.x());
2372 sqlite3_bind_double(d->findClosestWithIdent, 5, cartPos.y());
2373 sqlite3_bind_double(d->findClosestWithIdent, 6, cartPos.z());
2374
2375 FGPositionedRef result;
2376
2377 while (d->stepSelect(d->findClosestWithIdent)) {
2378 FGPositionedRef pos = loadById(sqlite3_column_int64(d->findClosestWithIdent, 0));
2379 if (aFilter && !aFilter->pass(pos)) {
2380 continue;
2381 }
2382
2383 result = pos;
2384 break;
2385 }
2386
2387 d->reset(d->findClosestWithIdent);
2388 return result;
2389}
2390
2391
2393{
2394 sqlite3_bind_int64(d->getOctreeChildren, 1, octreeNodeId);
2395 if (!d->execSelect(d->getOctreeChildren)) {
2396 // this can occur when in read-only mode: we don't add
2397 // new Octree nodes to the real DB (only in memory),
2398 // but will still call this code speculatively.
2399 // see the early-return just below in defineOctreeNode
2400 return 0;
2401 }
2402
2403 int children = sqlite3_column_int(d->getOctreeChildren, 0);
2404 d->reset(d->getOctreeChildren);
2405 return children;
2406}
2407
2409{
2410 if (isReadOnly()) {
2411 return;
2412 }
2413
2414 sqlite3_bind_int64(d->insertOctree, 1, nd->guid());
2415 d->execInsert(d->insertOctree);
2416
2417#ifdef LAZY_OCTREE_UPDATES
2418 d->deferredOctreeUpdates.insert(pr);
2419#else
2420 // lowest three bits of node ID are 0..7 index of the child in the parent
2421 int childIndex = nd->guid() & 0x07;
2422
2423 sqlite3_bind_int64(d->updateOctreeChildren, 1, pr->guid());
2424// mask has bit N set where child N exists
2425 int childMask = 1 << childIndex;
2426 sqlite3_bind_int(d->updateOctreeChildren, 2, childMask);
2427 d->execUpdate(d->updateOctreeChildren);
2428#endif
2429}
2430
2433{
2434 sqlite3_bind_int64(d->getOctreeLeafChildren, 1, octreeNodeId);
2435
2437 while (d->stepSelect(d->getOctreeLeafChildren)) {
2438 FGPositioned::Type ty = static_cast<FGPositioned::Type>
2439 (sqlite3_column_int(d->getOctreeLeafChildren, 1));
2440 r.push_back(std::make_pair(ty,
2441 sqlite3_column_int64(d->getOctreeLeafChildren, 0)));
2442 }
2443
2444 d->reset(d->getOctreeLeafChildren);
2445 return r;
2446}
2447
2448
2454char** NavDataCache::searchAirportNamesAndIdents(const std::string& searchInput)
2455{
2456 sqlite3_stmt_ptr stmt;
2457 unsigned int numMatches = 0, numAllocated = 16;
2458 string heliport("HELIPORT");
2459 bool heli_p = searchInput.substr(0, heliport.length()) == heliport;
2460 auto pos = searchInput.find(":");
2461 string aFilter((pos != string::npos) ? searchInput.substr(pos+1) : searchInput);
2462 string searchTerm("%" + aFilter + "%");
2463
2464 if (aFilter.empty() && !heli_p) {
2465 stmt = d->getAllAirports;
2466 numAllocated = 4096; // start much larger for all airports
2467 } else {
2468 stmt = d->searchAirports;
2469 sqlite_bind_stdstring(stmt, 1, searchTerm);
2470 if (heli_p) {
2471 sqlite3_bind_int(stmt, 2, FGPositioned::HELIPORT);
2472 sqlite3_bind_int(stmt, 3, FGPositioned::HELIPORT);
2473 }
2474 else {
2475 sqlite3_bind_int(stmt, 2, FGPositioned::AIRPORT);
2476 sqlite3_bind_int(stmt, 3, FGPositioned::SEAPORT);
2477 }
2478 }
2479
2480 char** result = (char**) malloc(sizeof(char*) * numAllocated);
2481 while (d->stepSelect(stmt)) {
2482 if ((numMatches + 1) >= numAllocated) {
2483 numAllocated <<= 1; // double in size!
2484 // reallocate results array
2485 char** nresult = (char**) malloc(sizeof(char*) * numAllocated);
2486 memcpy(nresult, result, sizeof(char*) * numMatches);
2487 free(result);
2488 result = nresult;
2489 }
2490
2491 // nasty code to avoid excessive string copying and allocations.
2492 // We format results as follows (note whitespace!):
2493 // ' name-of-airport-chars (ident)'
2494 // so the total length is:
2495 // 1 + strlen(name) + 4 + strlen(icao) + 1 + 1 (for the null)
2496 // which gives a grand total of 7 + name-length + icao-length.
2497 // note the ident can be three letters (non-ICAO local strip), four
2498 // (default ICAO) or more (extended format ICAO)
2499 int nameLength = sqlite3_column_bytes(stmt, 1);
2500 int icaoLength = sqlite3_column_bytes(stmt, 0);
2501 char* entry = (char*) malloc(7 + nameLength + icaoLength);
2502 char* dst = entry;
2503 *dst++ = ' ';
2504 memcpy(dst, sqlite3_column_text(stmt, 1), nameLength);
2505 dst += nameLength;
2506 *dst++ = ' ';
2507 *dst++ = ' ';
2508 *dst++ = ' ';
2509 *dst++ = '(';
2510 memcpy(dst, sqlite3_column_text(stmt, 0), icaoLength);
2511 dst += icaoLength;
2512 *dst++ = ')';
2513 *dst++ = 0;
2514
2515 result[numMatches++] = entry;
2516 }
2517
2518 result[numMatches] = NULL; // end of list marker
2519 d->reset(stmt);
2520 return result;
2521}
2522
2524NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter)
2525{
2526 auto matches = findCommsByFreq(freqKhz, aPos, aFilter);
2527 if (matches.empty()) {
2528 return {};
2529 }
2530
2531 return loadById(matches.front());
2532}
2533
2535NavDataCache::findCommsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter)
2536{
2537 PositionedIDVec matches;
2538
2539 sqlite3_bind_int(d->findCommByFreq, 1, freqKhz);
2540 if (aFilter) {
2541 sqlite3_bind_int(d->findCommByFreq, 2, aFilter->minType());
2542 sqlite3_bind_int(d->findCommByFreq, 3, aFilter->maxType());
2543 } else { // full type range
2544 sqlite3_bind_int(d->findCommByFreq, 2, FGPositioned::FREQ_GROUND);
2545 sqlite3_bind_int(d->findCommByFreq, 3, FGPositioned::FREQ_UNICOM);
2546 }
2547
2548 SGVec3d cartPos(SGVec3d::fromGeod(aPos));
2549 sqlite3_bind_double(d->findCommByFreq, 4, cartPos.x());
2550 sqlite3_bind_double(d->findCommByFreq, 5, cartPos.y());
2551 sqlite3_bind_double(d->findCommByFreq, 6, cartPos.z());
2552 FGPositionedRef result;
2553
2554 while (d->execSelect(d->findCommByFreq)) {
2555 const auto id = sqlite3_column_int64(d->findCommByFreq, 0);
2556 if (aFilter) {
2558 if (aFilter && !aFilter->pass(p)) {
2559 continue;
2560 }
2561 }
2562
2563
2564 matches.push_back(id);
2565 }
2566
2567 d->reset(d->findCommByFreq);
2568 return matches;
2569}
2570
2572NavDataCache::findNavaidsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter)
2573{
2574 sqlite3_bind_int(d->findNavsByFreq, 1, freqKhz);
2575 if (aFilter) {
2576 sqlite3_bind_int(d->findNavsByFreq, 2, aFilter->minType());
2577 sqlite3_bind_int(d->findNavsByFreq, 3, aFilter->maxType());
2578 } else { // full type range
2579 sqlite3_bind_int(d->findNavsByFreq, 2, FGPositioned::NDB);
2580 sqlite3_bind_int(d->findNavsByFreq, 3, FGPositioned::GS);
2581 }
2582
2583 SGVec3d cartPos(SGVec3d::fromGeod(aPos));
2584 sqlite3_bind_double(d->findNavsByFreq, 4, cartPos.x());
2585 sqlite3_bind_double(d->findNavsByFreq, 5, cartPos.y());
2586 sqlite3_bind_double(d->findNavsByFreq, 6, cartPos.z());
2587
2588 return d->selectIds(d->findNavsByFreq);
2589}
2590
2593{
2594 sqlite3_bind_int(d->findNavsByFreqNoPos, 1, freqKhz);
2595 if (aFilter) {
2596 sqlite3_bind_int(d->findNavsByFreqNoPos, 2, aFilter->minType());
2597 sqlite3_bind_int(d->findNavsByFreqNoPos, 3, aFilter->maxType());
2598 } else { // full type range
2599 sqlite3_bind_int(d->findNavsByFreqNoPos, 2, FGPositioned::NDB);
2600 sqlite3_bind_int(d->findNavsByFreqNoPos, 3, FGPositioned::GS);
2601 }
2602
2603 return d->selectIds(d->findNavsByFreqNoPos);
2604}
2605
2608 FGPositioned::Type maxTy)
2609{
2610 if (maxTy == FGPositioned::INVALID) {
2611 maxTy = ty; // single-type range
2612 }
2613
2614 sqlite3_bind_int64(d->getAirportItems, 1, apt);
2615 sqlite3_bind_int(d->getAirportItems, 2, ty);
2616 sqlite3_bind_int(d->getAirportItems, 3, maxTy);
2617
2618 return d->selectIds(d->getAirportItems);
2619}
2620
2623 const std::string& ident)
2624{
2625 sqlite3_bind_int64(d->getAirportItemByIdent, 1, apt);
2626 sqlite_bind_stdstring(d->getAirportItemByIdent, 2, ident);
2627 sqlite3_bind_int(d->getAirportItemByIdent, 3, ty);
2628 PositionedID result = 0;
2629
2630 if (d->execSelect(d->getAirportItemByIdent)) {
2631 result = sqlite3_column_int64(d->getAirportItemByIdent, 0);
2632 }
2633
2634 d->reset(d->getAirportItemByIdent);
2635 return result;
2636}
2637
2639NavDataCache::findAirportRunway(const std::string& aName)
2640{
2641 if (aName.empty()) {
2642 return AirportRunwayPair();
2643 }
2644
2645 string_list parts = simgear::strutils::split(aName);
2646 if (parts.size() < 2) {
2647 SG_LOG(SG_NAVCACHE, SG_WARN, "findAirportRunway: malformed name:" << aName);
2648 return AirportRunwayPair();
2649 }
2650
2651 AirportRunwayPair result;
2652 sqlite_bind_stdstring(d->findAirportRunway, 1, parts[0]);
2653 const auto cleanedRunway = cleanRunwayNo(parts[1]);
2654 sqlite_bind_stdstring(d->findAirportRunway, 2, cleanedRunway);
2655
2656 if (d->execSelect(d->findAirportRunway)) {
2657 result = AirportRunwayPair(sqlite3_column_int64(d->findAirportRunway, 0),
2658 sqlite3_column_int64(d->findAirportRunway, 1));
2659
2660 } else {
2661 SG_LOG(SG_NAVCACHE, SG_WARN, "findAirportRunway: unknown airport/runway:" << aName);
2662 }
2663
2664 d->reset(d->findAirportRunway);
2665 return result;
2666}
2667
2669NavDataCache::findILS(PositionedID airport, const string& aRunway, const string& navIdent)
2670{
2671 string runway(cleanRunwayNo(aRunway));
2672
2673 sqlite_bind_stdstring(d->findILS, 1, navIdent);
2674 sqlite3_bind_int64(d->findILS, 2, airport);
2675 sqlite_bind_stdstring(d->findILS, 3, runway);
2676 PositionedID result = 0;
2677 if (d->execSelect(d->findILS)) {
2678 result = sqlite3_column_int64(d->findILS, 0);
2679 }
2680
2681 d->reset(d->findILS);
2682 return result;
2683}
2684
2685int NavDataCache::findAirway(int network, const string& aName, bool create)
2686{
2687 assert((network == 1) || (network == 2));
2688
2689 sqlite3_bind_int(d->findAirwayNet, 1, network);
2690 sqlite_bind_stdstring(d->findAirwayNet, 2, aName);
2691
2692 int airway = 0;
2693 if (d->execSelect(d->findAirwayNet)) {
2694 // already exists
2695 airway = sqlite3_column_int(d->findAirwayNet, 0);
2696 } else if (create) {
2697 d->reset(d->insertAirway);
2698 sqlite_bind_stdstring(d->insertAirway, 1, aName);
2699 sqlite3_bind_int(d->insertAirway, 2, network);
2700 airway = d->execInsert(d->insertAirway);
2701 } else {
2702 // doesn't exist but don't create
2703 }
2704
2705 d->reset(d->findAirwayNet);
2706 return airway;
2707}
2708
2709int NavDataCache::findAirway(const string& aName)
2710{
2711 sqlite_bind_stdstring(d->findAirway, 1, aName);
2712
2713 int airway = 0;
2714 if (d->execSelect(d->findAirway)) {
2715 // already exists
2716 airway = sqlite3_column_int(d->findAirway, 0);
2717 }
2718
2719 d->reset(d->findAirway);
2720 return airway;
2721}
2722
2723
2724void NavDataCache::insertEdge(int network, int airwayID, PositionedID from, PositionedID to)
2725{
2726 sqlite3_bind_int(d->insertAirwayEdge, 1, network);
2727 sqlite3_bind_int(d->insertAirwayEdge, 2, airwayID);
2728 sqlite3_bind_int64(d->insertAirwayEdge, 3, from);
2729 sqlite3_bind_int64(d->insertAirwayEdge, 4, to);
2730 d->execInsert(d->insertAirwayEdge);
2731}
2732
2734{
2735 sqlite3_bind_int(d->isPosInAirway, 1, network);
2736 sqlite3_bind_int64(d->isPosInAirway, 2, pos);
2737 bool ok = d->execSelect(d->isPosInAirway);
2738 d->reset(d->isPosInAirway);
2739
2740 return ok;
2741}
2742
2744{
2745 sqlite3_bind_int(d->airwayEdgesFrom, 1, network);
2746 sqlite3_bind_int64(d->airwayEdgesFrom, 2, pos);
2747
2748 AirwayEdgeVec result;
2749 while (d->stepSelect(d->airwayEdgesFrom)) {
2750 result.push_back(AirwayEdge(
2751 sqlite3_column_int(d->airwayEdgesFrom, 0),
2752 sqlite3_column_int64(d->airwayEdgesFrom, 1)
2753 ));
2754 }
2755
2756 d->reset(d->airwayEdgesFrom);
2757
2758// find bidirectional / backwsards edges
2759 // at present all edges are bidirectional
2760 sqlite3_bind_int(d->airwayEdgesTo, 1, network);
2761 sqlite3_bind_int64(d->airwayEdgesTo, 2, pos);
2762
2763 while (d->stepSelect(d->airwayEdgesTo)) {
2764 result.push_back(AirwayEdge(
2765 sqlite3_column_int(d->airwayEdgesTo, 0),
2766 sqlite3_column_int64(d->airwayEdgesTo, 1)
2767 ));
2768 }
2769
2770 d->reset(d->airwayEdgesTo);
2771
2772 return result;
2773}
2774
2776{
2777 sqlite3_bind_int(d->loadAirway, 1, airwayID);
2778 bool ok = d->execSelect(d->loadAirway);
2779 AirwayRef result;
2780 if (ok) {
2781 string ident = (char*) sqlite3_column_text(d->loadAirway, 0);
2782 Airway::Level network = static_cast<Airway::Level>(sqlite3_column_int(d->loadAirway, 1));
2783 result = new Airway(ident, network, airwayID, 0, 0);
2784 }
2785 d->reset(d->loadAirway);
2786 return result;
2787}
2788
2790{
2791 d->reset(d->airwayEdges);
2792 sqlite3_bind_int(d->airwayEdges, 1, id);
2793
2794 typedef std::pair<PositionedID, PositionedID> Edge;
2795 typedef std::deque<Edge> EdgeVec;
2796 typedef std::deque<PositionedID> PositionedIDDeque;
2797
2798// build up the EdgeVec, order is arbitrary
2799 EdgeVec rawEdges;
2800 while (d->stepSelect(d->airwayEdges)) {
2801 rawEdges.push_back(Edge(sqlite3_column_int64(d->airwayEdges, 0),
2802 sqlite3_column_int64(d->airwayEdges, 1)
2803 ));
2804 }
2805
2806 d->reset(d->airwayEdges);
2807 if (rawEdges.empty()) {
2808 return {};
2809 }
2810
2811// linearize
2812 PositionedIDVec result;
2813
2814 while (!rawEdges.empty()) {
2815 bool didAddEdge = false;
2816 std::set<PositionedID> seen;
2817 EdgeVec nextDeque;
2818 PositionedIDDeque linearAirway;
2819 PositionedID firstId = rawEdges.front().first,
2820 lastId = rawEdges.front().second;
2821
2822 // first edge is trivial
2823 linearAirway.push_back(firstId);
2824 linearAirway.push_back(lastId);
2825 seen.insert(firstId);
2826 seen.insert(lastId);
2827 rawEdges.pop_front();
2828
2829 while (!rawEdges.empty()) {
2830 Edge e = rawEdges.front();
2831 rawEdges.pop_front();
2832
2833 bool seenFirst = (seen.find(e.first) != seen.end());
2834 bool seenSecond = (seen.find(e.second) != seen.end());
2835
2836 // duplicated segment, should be impossible
2837 assert(!(seenFirst && seenSecond));
2838
2839 if (!seenFirst && !seenSecond) {
2840 // push back to try later on
2841 nextDeque.push_back(e);
2842 if (rawEdges.empty()) {
2843 rawEdges = nextDeque;
2844 nextDeque.clear();
2845
2846 if (!didAddEdge) {
2847 // we have a disjoint, need to start a new section
2848 // break out of the inner while loop so the outer
2849 // one can process and restart
2850 break;
2851 }
2852 didAddEdge = false;
2853 }
2854
2855 continue;
2856 }
2857
2858 // we have an exterior edge, grow our current linear piece
2859 if (seenFirst && (e.first == firstId)) {
2860 linearAirway.push_front(e.second);
2861 firstId = e.second;
2862 seen.insert(e.second);
2863 } else if (seenSecond && (e.second == firstId)) {
2864 linearAirway.push_front(e.first);
2865 firstId = e.first;
2866 seen.insert(e.first);
2867 } else if (seenFirst && (e.first == lastId)) {
2868 linearAirway.push_back(e.second);
2869 lastId = e.second;
2870 seen.insert(e.second);
2871 } else if (seenSecond && (e.second == lastId)) {
2872 linearAirway.push_back(e.first);
2873 lastId = e.first;
2874 seen.insert(e.first);
2875 }
2876 didAddEdge = true;
2877
2878 if (rawEdges.empty()) {
2879 rawEdges = nextDeque;
2880 nextDeque.clear();
2881 }
2882 }
2883
2884 if (!result.empty())
2885 result.push_back(0);
2886 result.insert(result.end(), linearAirway.begin(), linearAirway.end());
2887 } // outer loop
2888
2889 SG_LOG(SG_AUTOPILOT, SG_WARN, "Airway:" << id);
2890 for (unsigned int i=0; i<result.size(); ++i) {
2891 if (result.at(i) == 0) {
2892 SG_LOG(SG_AUTOPILOT, SG_WARN, i << " <break>");
2893 } else {
2894 SG_LOG(SG_AUTOPILOT, SG_WARN, i << " " << loadById(result.at(i))->ident());
2895
2896 }
2897 }
2898
2899 return result;
2900}
2901
2903{
2904 sqlite3_bind_int64(d->findNavaidForRunway, 1, runway);
2905 sqlite3_bind_int(d->findNavaidForRunway, 2, ty);
2906
2907 PositionedID result = 0;
2908 if (d->execSelect(d->findNavaidForRunway)) {
2909 result = sqlite3_column_int64(d->findNavaidForRunway, 0);
2910 }
2911
2912 d->reset(d->findNavaidForRunway);
2913 return result;
2914}
2915
2917{
2918 return d->readOnly;
2919}
2920
2922{
2923 return d->path;
2924}
2925
2927{
2928 return d->nextTransientId--;
2929}
2930
2932// Transaction RAII object
2933
2935 _instance(cache),
2936 _committed(false)
2937{
2938 assert(cache);
2939 if (!cache->isReadOnly()) {
2940 _instance->beginTransaction();
2941 }
2942}
2943
2945{
2946 if (_instance->d->abandonCache || _instance->isReadOnly()) {
2947 return;
2948 }
2949
2950 if (!_committed) {
2951 SG_LOG(SG_NAVCACHE, SG_INFO, "aborting cache transaction!");
2952 _instance->abortTransaction();
2953 }
2954}
2955
2957{
2958 if (_instance->isReadOnly()) {
2959 return;
2960 }
2961
2962 assert(!_committed);
2963 _committed = true;
2964 _instance->commitTransaction();
2965}
2966
2968
2970{
2971public:
2973 db(NULL),
2974 isComplete(false),
2975 quit(false)
2976 {}
2977
2978 virtual void run()
2979 {
2980 while (!quit) {
2981 int err = sqlite3_step(query);
2982 if (err == SQLITE_DONE) {
2983 break;
2984 } else if (err == SQLITE_ROW) {
2985 PositionedID r = sqlite3_column_int64(query, 0);
2986 std::lock_guard<std::mutex> g(lock);
2987 results.push_back(r);
2988 } else if (err == SQLITE_BUSY) {
2989 // sleep a tiny amount
2990 SGTimeStamp::sleepForMSec(1);
2991 } else {
2992 std::string errMsg = sqlite3_errmsg(db);
2993 SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " running threaded search query");
2994 }
2995 }
2996
2997 std::lock_guard<std::mutex> g(lock);
2998 isComplete = true;
2999 }
3000
3001 std::mutex lock;
3002 sqlite3* db;
3003 sqlite3_stmt_ptr query;
3006 bool quit;
3007};
3008
3009NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term, bool onlyAirports) :
3011{
3012 SGPath p = NavDataCache::instance()->path();
3013 int openFlags = SQLITE_OPEN_READONLY;
3014 std::string pathUtf8 = p.utf8Str();
3015 sqlite3_open_v2(pathUtf8.c_str(), &d->db, openFlags, NULL);
3016
3017 std::string sql;
3018 if (onlyAirports) {
3019 sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
3020 + "%' AND (type >= 1 AND type <= 3)";
3021 } else {
3022 // types are hard-coded here becuase this is only used by NavaidSearchModel
3023 // in ther launcher. We would ideally use a TypeFilter but that would
3024 // mean loading each positioned to filter them, which is inefficient.
3025 sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
3026 + "%' AND ((type >= 1 AND type <= 3) OR ((type >= 9 AND type <= 11)) OR (type=18 AND name LIKE '% TACAN') ) ";
3027 }
3028 sqlite3_prepare_v2(d->db, sql.c_str(), sql.length(), &d->query, NULL);
3029
3030 d->start();
3031}
3032
3034{
3035 {
3036 std::lock_guard<std::mutex> g(d->lock);
3037 d->quit = true;
3038 }
3039
3040 d->join();
3041 sqlite3_finalize(d->query);
3042 sqlite3_close_v2(d->db);
3043}
3044
3046{
3048 {
3049 std::lock_guard<std::mutex> g(d->lock);
3050 r = std::move(d->results);
3051 }
3052 return r;
3053}
3054
3056{
3057 std::lock_guard<std::mutex> g(d->lock);
3058 return d->isComplete;
3059}
3060
3061} // of namespace flightgear
const double rec
#define SCHEMA_SQL
Definition CacheSchema.h:5
const int SCHEMA_VERSION
Definition CacheSchema.h:3
#define TEMPORARY_SCHEMA_SQL
Definition CacheSchema.h:38
#define p(x)
#define AND_TYPED
#define SG_NAVCACHE
#define POSITIONED_COLS
#define i(x)
void validateILSData()
reload the ILS data from XML if required.
Definition airport.cxx:795
static void clearAirportsCache()
Definition airport.cxx:483
Definition fix.hxx:34
const PathList & get_fg_scenery() const
Definition globals.hxx:234
const SGPath & get_fg_home() const
Definition globals.hxx:213
A mobile navaid, aka.
void setColocatedDME(PositionedID other)
Predicate class to support custom filtering of FGPositioned queries Default implementation of this pa...
virtual Type maxType() const
virtual Type minType() const
virtual bool pass(FGPositioned *aPos) const
Over-rideable filter method.
FGPositioned(PositionedID aGuid, Type ty, const std::string &aIdent, const SGGeod &aPos)
PositionedID guid() const
static SGSharedPtr< T > loadById(PositionedID id)
@ TOWER
an actual airport tower - not a radio comms facility!
@ DME
important that DME & TACAN are adjacent to keep the TacanFilter efficient - DMEs are proxies for TACA...
const std::string & ident() const
void setReciprocalRunway(PositionedID other)
Definition runways.cxx:109
void setILS(PositionedID nav)
Definition runways.hxx:83
void readAptDatFile(const NavDataCache::SceneryLocation &sceneryLocation, std::size_t bytesReadSoFar, std::size_t totalSizeOfAllAptDatFiles)
AirportTower(PositionedID &guid, PositionedID airport, const string &ident, const SGGeod &pos)
static void loadAWYDat(const SGPath &path)
Definition airways.cxx:130
void setAirport(PositionedID apt)
void loadFixes(const NavDataCache::SceneryLocation &sceneryLocation, std::size_t bytesReadSoFar, std::size_t totalSizeOfAllDatFiles)
Definition fixlist.cxx:68
FGPositionedList findAllByString(const string &s, const string &column, FGPositioned::Filter *filter, bool exact)
void writeIntProperty(const string &key, int value)
std::vector< sqlite3_stmt_ptr > StmtVec
PositionedIDVec selectIds(sqlite3_stmt_ptr query)
std::map< string, sqlite3_stmt_ptr > findByStringDict
FGRunwayBase * loadRunway(sqlite3_int64 rowId, FGPositioned::Type ty, const string &id, const SGGeod &pos, PositionedID apt)
FGPositioned * loadNav(sqlite3_int64 rowId, FGPositioned::Type ty, const string &id, const string &name, const SGGeod &pos)
NavDataCachePrivate(const SGPath &p, NavDataCache *o)
PositionedID insertTemporaryPositioned(PositionedID guid, FGPositioned::Type ty, const string &ident, const string &name, const SGGeod &pos, bool spatialIndex)
PositionedID insertPositioned(FGPositioned::Type ty, const string &ident, const string &name, const SGGeod &pos, PositionedID apt, bool spatialIndex)
bool isCachedFileModified(const SGPath &path, bool verbose)
sqlite3_stmt_ptr prepareSQL(const std::string &sql)
std::set< Octree::Branch * > deferredOctreeUpdates
unsigned int transactionLevel
record the levels of open transaction objects we have
FGPositioned * loadById(sqlite_int64 rowId, sqlite3_int64 &aptId)
std::map< DatFileType, NavDataCache::DatFilesGroupInfo > datFilesInfo
bool areDatFilesModified(NavDataCache::DatFileType datFileType, bool verbose)
FGAirport * loadAirport(sqlite_int64 rowId, FGPositioned::Type ty, const string &id, const string &name, const SGGeod &pos)
std::unique_ptr< RebuildThread > rebuilder
PositionedCache cache
the actual cache of ID -> instances.
sqlite3_int64 execInsert(sqlite3_stmt_ptr stmt)
void findDatFiles(NavDataCache::DatFileType datFileType)
sqlite3_stmt_ptr prepare(const string &sql)
CommStation * loadComm(sqlite3_int64 rowId, FGPositioned::Type ty, const string &id, const string &name, const SGGeod &pos, PositionedID airport)
ThreadedGUISearch(const std::string &term, bool onlyAirports)
PositionedID findILS(PositionedID airport, const std::string &runway, const std::string &navIdent)
Given an airport, and runway and ILS identifier, find the corresponding cache entry.
PositionedID insertNavaid(FGPositioned::Type ty, const std::string &ident, const std::string &name, const SGGeod &pos, int freq, int range, double multiuse, PositionedID apt, PositionedID runway)
void stampCacheFile(const SGPath &path, const std::string &sha={})
void writeStringProperty(const std::string &key, const std::string &value)
PositionedID insertRunway(FGPositioned::Type ty, const std::string &ident, const SGGeod &pos, PositionedID apt, double heading, double length, double width, double displacedThreshold, double stopway, int markings, int approach, int tdz, int reil, int surfaceCode, int shoulder_code, float smoothness, int center_lights, int edge_lights, int distance_remaining)
PositionedID createPOI(FGPositioned::Type ty, const std::string &ident, const SGGeod &aPos, const std::string &aName, bool transient)
static const std::string defaultDatFile[]
PositionedID insertCommStation(FGPositioned::Type ty, const std::string &name, const SGGeod &pos, int freq, int range, PositionedID apt)
const DatFilesGroupInfo & getDatFilesInfo(DatFileType datFileType) const
void writeIntProperty(const std::string &key, int value)
std::string readStringProperty(const std::string &key)
void defineOctreeNode(Octree::Branch *pr, Octree::Node *nd)
int readIntProperty(const std::string &key)
void writeDoubleProperty(const std::string &key, const double &value)
static bool isAnotherProcessRebuilding()
string_list readStringListProperty(const std::string &key)
FGPositionedRef findCommByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt)
Find the closest matching comm-station on a frequency, to a position.
static NavDataCache * createInstance()
PositionedID createTransientID()
void writeOrderedStringListProperty(const std::string &key, const string_list &values, const char *separator)
TypedPositionedVec getOctreeLeafChildren(int64_t octreeNodeId)
given an octree leaf, return all its child positioned items and their types
void setRunwayReciprocal(PositionedID runway, PositionedID recip)
unsigned int rebuildPhaseCompletionPercentage() const
PositionedIDVec findCommsByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt)
Find all on a frequency, sorted by distance from a position The filter with be used for both type ran...
PositionedID airportItemWithIdent(PositionedID apt, FGPositioned::Type ty, const std::string &ident)
find the first match item of the specified type and ident, at an airport
FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, FGPositioned::Filter *aFilter)
double readDoubleProperty(const std::string &key)
FGPositionedList findAllWithIdent(const std::string &ident, FGPositioned::Filter *filter, bool exact)
PositionedIDVec airwayWaypts(int id)
Waypoints on the airway.
void setAirportMetar(const std::string &icao, bool hasMetar)
update the metar flag associated with an airport
void writeStringListProperty(const std::string &key, const string_list &values)
static const std::string datTypeStr[]
PositionedID insertAirport(FGPositioned::Type ty, const std::string &ident, const std::string &name, const SGPath &sceneryPath)
int getOctreeBranchChildren(int64_t octreeNodeId)
Given an Octree node ID, return a bit-mask defining which of the child nodes exist.
PositionedID findNavaidForRunway(PositionedID runway, FGPositioned::Type ty)
Given a runway and type, find the corresponding navaid (ILS / GS / OM)
void updatePosition(PositionedID item, const SGGeod &pos)
Modify the position of an existing item.
AirwayRef loadAirway(int airwayID)
bool removePOI(FGPositionedRef wpt)
FGPositionedRef loadById(PositionedID guid)
retrieve an FGPositioned from the cache.
void setRunwayILS(PositionedID runway, PositionedID ils)
static NavDataCache * instance()
void insertTower(PositionedID airportId, const SGGeod &pos)
char ** searchAirportNamesAndIdents(const std::string &aFilter)
Helper to implement the AirportSearch widget.
bool isCachedFileModified(const SGPath &path) const
void insertEdge(int network, int airwayID, PositionedID from, PositionedID to)
insert an edge between two positioned nodes, into the network.
string_list readOrderedStringListProperty(const std::string &key, const char *separator)
bool isRebuildRequired()
predicate - check if the cache needs to be rebuilt.
RebuildPhase rebuild()
run the cache rebuild - returns the current phase or 'done'
FGPositionedList findAllWithName(const std::string &ident, FGPositioned::Filter *filter, bool exact)
PositionedIDVec findNavaidsByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt)
Find all navaids matching a particular frequency, sorted by range from the supplied position.
void setRebuildPhaseProgress(RebuildPhase ph, unsigned int percent=0)
bool isInAirwayNetwork(int network, PositionedID pos)
is the specified positioned a node on the network?
void setNavaidColocated(PositionedID navaid, PositionedID colocatedDME)
AirwayEdgeVec airwayEdgesFrom(int network, PositionedID pos)
retrive all the destination points reachable from a positioned in an airway
AirportRunwayPair findAirportRunway(const std::string &name)
given a navaid name (or similar) from apt.dat / nav.dat, find the corresponding airport and runway ID...
std::vector< SceneryLocation > SceneryLocationList
PositionedIDVec airportItemsOfType(PositionedID apt, FGPositioned::Type ty, FGPositioned::Type maxTy=FGPositioned::INVALID)
find all items of a specified type (or range of types) at an airport
int findAirway(int network, const std::string &aName, bool create)
void loadNav(const NavDataCache::SceneryLocation &sceneryLocation, std::size_t bytesReadSoFar, std::size_t totalSizeOfAllDatFiles)
Definition navdb.cxx:411
void removeChild(PositionedID id)
void insertChild(FGPositioned::Type ty, PositionedID id)
Octree node base class, tracks its bounding box and provides various queries relating to it.
const SGBoxd & bbox() const
virtual Leaf * findLeafForPos(const SGVec3d &aPos) const =0
static Options * sharedInstance()
Definition options.cxx:2345
void loadPOIs(const NavDataCache::SceneryLocation &sceneryLocation, std::size_t bytesReadSoFar, std::size_t totalSizeOfAllDatFiles)
Definition poidb.cxx:63
unsigned int completionPercent() const
RebuildThread(NavDataCache *cache)
void setProgress(NavDataCache::RebuildPhase ph, unsigned int percent)
NavDataCache::RebuildPhase currentPhase() const
FGGlobals * globals
Definition globals.cxx:142
std::vector< SGPath > PathList
Definition globals.hxx:37
std::vector< std::string > string_list
Definition globals.hxx:36
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
std::vector< AirwayEdge > AirwayEdgeVec
static NavDataCache * static_instance
bool metarDataLoad(const SGPath &metar_file)
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
RebuildLockStatus accquireRebuildLock()
static void releaseRebuildLock()
SGSharedPtr< Airway > AirwayRef
Definition airways.hxx:40
static int static_rebuildLockFileFd
const char * name
void addSentryBreadcrumb(const std::string &, const std::string &)
const std::string static_rebuildLockFile
std::pair< int, PositionedID > AirwayEdge
std::map< PositionedID, FGPositionedRef > PositionedCache
void sentryReportException(const std::string &, const std::string &)
void fatalMessageBoxThenExit(const std::string &caption, const std::string &msg, const std::string &moreText, int exitStatus, bool reportToSentry)
std::pair< PositionedID, PositionedID > AirportRunwayPair
a pair of airport ID, runway ID
std::vector< TypedPositioned > TypedPositionedVec
std::vector< PositionedID > PositionedIDVec
std::vector< FGPositionedRef > FGPositionedList
int64_t PositionedID
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25