1 // terrasync.cxx -- scenery fetcher
3 // Started by Curtis Olson, November 2002.
5 // Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
6 // Copyright (C) 2008 Alexander R. Perry <alex.perry@ieee.org>
7 // Copyright (C) 2011 Thorsten Brehm <brehmt@gmail.com>
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 # include <simgear_config.h>
29 #include <simgear/compiler.h>
38 #elif defined(_MSC_VER)
44 #include <stdlib.h> // atoi() atof() abs() system()
45 #include <signal.h> // signal()
53 #include <simgear/compiler.h>
55 #include "terrasync.hxx"
57 #include <simgear/bucket/newbucket.hxx>
58 #include <simgear/misc/sg_path.hxx>
59 #include <simgear/misc/strutils.hxx>
60 #include <simgear/threads/SGQueue.hxx>
61 #include <simgear/misc/sg_dir.hxx>
62 #include <simgear/debug/BufferedLogCallback.hxx>
63 #include <simgear/props/props_io.hxx>
64 #include <simgear/io/HTTPClient.hxx>
65 #include <simgear/io/SVNRepository.hxx>
66 #include <simgear/structure/exception.hxx>
68 static const bool svn_built_in_available = true;
70 using namespace simgear;
73 const char* rsync_cmd =
74 "rsync --verbose --archive --delete --perms --owner --group";
76 const char* svn_options =
79 namespace UpdateInterval
81 // interval in seconds to allow an update to repeat after a successful update (=daily)
82 static const double SuccessfulAttempt = 24*60*60;
83 // interval in seconds to allow another update after a failed attempt (10 minutes)
84 static const double FailedAttempt = 10*60;
87 typedef map<string,time_t> CompletedTiles;
89 ///////////////////////////////////////////////////////////////////////////////
90 // helper functions ///////////////////////////////////////////////////////////
91 ///////////////////////////////////////////////////////////////////////////////
92 string stripPath(string path)
94 // svn doesn't like trailing white-spaces or path separators - strip them!
95 path = simgear::strutils::strip(path);
96 size_t slen = path.length();
98 ((path[slen-1]=='/')||(path[slen-1]=='\\')))
102 return path.substr(0,slen);
105 bool hasWhitespace(string path)
107 return path.find(' ')!=string::npos;
110 ///////////////////////////////////////////////////////////////////////////////
111 // WaitingSyncItem ////////////////////////////////////////////////////////////
112 ///////////////////////////////////////////////////////////////////////////////
113 class WaitingSyncItem
118 Stop = 0, ///< special item indicating to stop the SVNThread
127 _refreshScenery(false)
131 WaitingSyncItem(string dir, Type ty) :
134 _refreshScenery(false)
138 { _refreshScenery = true; }
142 bool _refreshScenery;
145 ///////////////////////////////////////////////////////////////////////////////
148 * @brief SyncSlot encapsulates a queue of sync items we will fetch
149 * serially. Multiple slots exist to sync different types of item in
156 isNewDirectory(false),
160 WaitingSyncItem currentItem;
162 std::queue<WaitingSyncItem> queue;
163 std::auto_ptr<SVNRepository> repository;
165 bool busy; ///< is the slot working or idle
168 static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
169 static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
170 static const int NUM_SYNC_SLOTS = 2;
173 * @brief translate a sync item type into one of the available slots.
174 * This provides the scheduling / balancing / prioritising between slots.
176 static unsigned int syncSlotForType(WaitingSyncItem::Type ty)
179 case WaitingSyncItem::Tile: return SYNC_SLOT_TILES;
180 case WaitingSyncItem::SharedModels:
181 case WaitingSyncItem::AirportData:
182 return SYNC_SLOT_SHARED_DATA;
184 return SYNC_SLOT_SHARED_DATA;
189 ///////////////////////////////////////////////////////////////////////////////
190 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
191 ///////////////////////////////////////////////////////////////////////////////
192 class SGTerraSync::SvnThread : public SGThread
196 virtual ~SvnThread( ) { stop(); }
201 bool isIdle() {return waitingTiles.empty();}
202 void request(const WaitingSyncItem& dir) {waitingTiles.push_front(dir);}
203 bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
204 bool hasNewTiles() { return !_freshTiles.empty();}
205 WaitingSyncItem getNewTile() { return _freshTiles.pop_front();}
207 void setSvnServer(string server) { _svn_server = stripPath(server);}
208 void setExtSvnUtility(string svn_util) { _svn_command = simgear::strutils::strip(svn_util);}
209 void setRsyncServer(string server) { _rsync_server = simgear::strutils::strip(server);}
210 void setLocalDir(string dir) { _local_dir = stripPath(dir);}
211 string getLocalDir() { return _local_dir;}
212 void setUseSvn(bool use_svn) { _use_svn = use_svn;}
213 void setAllowedErrorCount(int errors) {_allowed_errors = errors;}
215 void setCachePath(const SGPath& p) {_persistentCachePath = p;}
216 void setCacheHits(unsigned int hits) {_cache_hits = hits;}
217 void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
219 volatile bool _active;
220 volatile bool _running;
222 volatile bool _stalled;
223 volatile int _fail_count;
224 volatile int _updated_tile_count;
225 volatile int _success_count;
226 volatile int _consecutive_errors;
227 volatile int _allowed_errors;
228 volatile int _cache_hits;
232 // external model run and helpers
234 void syncPathExternal(const WaitingSyncItem& next);
235 bool runExternalSyncCommand(const char* dir);
237 // internal mode run and helpers
239 void updateSyncSlot(SyncSlot& slot);
241 // commond helpers between both internal and external models
243 bool isPathCached(const WaitingSyncItem& next) const;
244 void initCompletedTilesPersistentCache();
245 void writeCompletedTilesPersistentCache() const;
246 void updated(const WaitingSyncItem& item, bool isNewDirectory);
247 void fail(const WaitingSyncItem& failedItem);
251 SyncSlot _syncSlots[NUM_SYNC_SLOTS];
253 volatile bool _is_dirty;
255 SGBlockingDeque <WaitingSyncItem> waitingTiles;
256 CompletedTiles _completedTiles;
257 SGBlockingDeque <WaitingSyncItem> _freshTiles;
261 string _rsync_server;
263 SGPath _persistentCachePath;
266 SGTerraSync::SvnThread::SvnThread() :
272 _updated_tile_count(0),
274 _consecutive_errors(0),
284 void SGTerraSync::SvnThread::stop()
286 // drop any pending requests
287 waitingTiles.clear();
292 // set stop flag and wake up the thread with an empty request
294 WaitingSyncItem w(string(), WaitingSyncItem::Stop);
300 bool SGTerraSync::SvnThread::start()
307 SG_LOG(SG_TERRAIN,SG_ALERT,
308 "Cannot start scenery download. Local cache directory is undefined.");
314 SGPath path(_local_dir);
317 SG_LOG(SG_TERRAIN,SG_ALERT,
318 "Cannot start scenery download. Directory '" << _local_dir <<
319 "' does not exist. Set correct directory path or create directory folder.");
325 path.append("version");
328 SG_LOG(SG_TERRAIN,SG_ALERT,
329 "Cannot start scenery download. Directory '" << _local_dir <<
330 "' contains the base package. Use a separate directory.");
336 _use_svn |= _use_built_in;
338 if ((_use_svn)&&(_svn_server==""))
340 SG_LOG(SG_TERRAIN,SG_ALERT,
341 "Cannot start scenery download. Subversion scenery server is undefined.");
346 if ((!_use_svn)&&(_rsync_server==""))
348 SG_LOG(SG_TERRAIN,SG_ALERT,
349 "Cannot start scenery download. Rsync scenery server is undefined.");
356 _updated_tile_count = 0;
358 _consecutive_errors = 0;
365 if (_use_svn && _use_built_in)
366 status = "Using built-in SVN support. ";
369 status = "Using external SVN utility '";
370 status += _svn_command;
375 status = "Using RSYNC. ";
378 // not really an alert - but we want to (always) see this message, so user is
379 // aware we're downloading scenery (and using bandwidth).
380 SG_LOG(SG_TERRAIN,SG_ALERT,
381 "Starting automatic scenery download/synchronization. "
383 << "Directory: '" << _local_dir << "'.");
389 bool SGTerraSync::SvnThread::runExternalSyncCommand(const char* dir)
392 SGPath localPath( _local_dir );
393 localPath.append( dir );
397 buf << "\"" << _svn_command << "\" "
398 << svn_options << " "
399 << "\"" << _svn_server << "/" << dir << "\" "
400 << "\"" << localPath.str_native() << "\"";
402 buf << rsync_cmd << " "
403 << "\"" << _rsync_server << "/" << dir << "/\" "
404 << "\"" << localPath.str_native() << "/\"";
409 // windows command line parsing is just lovely...
410 // to allow white spaces, the system call needs this:
411 // ""C:\Program Files\something.exe" somearg "some other arg""
412 // Note: whitespace strings quoted by a pair of "" _and_ the
413 // entire string needs to be wrapped by "" too.
414 // The svn url needs forward slashes (/) as a path separator while
415 // the local path needs windows-native backslash as a path separator.
416 command = "\"" + buf.str() + "\"";
420 SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
423 // tbd: does Windows support "popen"?
424 int rc = system( command.c_str() );
426 FILE* pipe = popen( command.c_str(), "r");
428 // wait for external process to finish
435 SG_LOG(SG_TERRAIN,SG_ALERT,
436 "Failed to synchronize directory '" << dir << "', " <<
437 "error code= " << rc);
443 void SGTerraSync::SvnThread::run()
446 initCompletedTilesPersistentCache();
460 void SGTerraSync::SvnThread::runExternal()
464 WaitingSyncItem next = waitingTiles.pop_front();
468 if (isPathCached(next)) {
470 SG_LOG(SG_TERRAIN, SG_DEBUG,
471 "Cache hit for: '" << next._dir << "'");
475 syncPathExternal(next);
477 if ((_allowed_errors >= 0)&&
478 (_consecutive_errors >= _allowed_errors))
483 } // of thread running loop
486 void SGTerraSync::SvnThread::syncPathExternal(const WaitingSyncItem& next)
489 SGPath path( _local_dir );
490 path.append( next._dir );
491 bool isNewDirectory = !path.exists();
494 if (isNewDirectory) {
495 int rc = path.create_dir( 0755 );
497 SG_LOG(SG_TERRAIN,SG_ALERT,
498 "Cannot create directory '" << path << "', return code = " << rc );
499 throw sg_exception("Cannot create directory for terrasync", path.str());
503 if (!runExternalSyncCommand(next._dir.c_str())) {
504 throw sg_exception("Running external sync command failed");
506 } catch (sg_exception& e) {
512 updated(next, isNewDirectory);
516 void SGTerraSync::SvnThread::updateSyncSlot(SyncSlot &slot)
518 if (slot.repository.get()) {
519 if (slot.repository->isDoingSync()) {
520 return; // easy, still working
524 SVNRepository::ResultCode res = slot.repository->failure();
525 if (res == SVNRepository::SVN_ERROR_NOT_FOUND) {
526 // this is fine, but maybe we should use a different return code
527 // in the future to higher layers can distinguish this case
528 } else if (res != SVNRepository::SVN_NO_ERROR) {
529 fail(slot.currentItem);
531 updated(slot.currentItem, slot.isNewDirectory);
532 SG_LOG(SG_IO, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " finished ("
533 << slot.stamp.elapsedMSec() << " msec");
536 // whatever happened, we're done with this repository instance
538 slot.repository.reset();
541 // init and start sync of the next repository
542 if (!slot.queue.empty()) {
543 slot.currentItem = slot.queue.front();
546 SGPath path(_local_dir);
547 path.append(slot.currentItem._dir);
548 slot.isNewDirectory = !path.exists();
549 if (slot.isNewDirectory) {
550 int rc = path.create_dir( 0755 );
552 SG_LOG(SG_TERRAIN,SG_ALERT,
553 "Cannot create directory '" << path << "', return code = " << rc );
554 fail(slot.currentItem);
557 } // of creating directory step
559 slot.repository.reset(new SVNRepository(path, &_http));
560 slot.repository->setBaseUrl(_svn_server + "/" + slot.currentItem._dir);
561 slot.repository->update();
565 SG_LOG(SG_IO, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " started");
569 void SGTerraSync::SvnThread::runInternal()
576 // drain the waiting tiles queue into the sync slot queues.
577 while (!waitingTiles.empty()) {
578 WaitingSyncItem next = waitingTiles.pop_front();
579 if (isPathCached(next)) {
581 SG_LOG(SG_TERRAIN, SG_DEBUG,
582 "Cache hit for: '" << next._dir << "'");
586 unsigned int slot = syncSlotForType(next._type);
587 _syncSlots[slot].queue.push(next);
590 bool anySlotBusy = false;
591 // update each sync slot in turn
592 for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
593 updateSyncSlot(_syncSlots[slot]);
594 anySlotBusy |= _syncSlots[slot].busy;
598 } // of thread running loop
601 bool SGTerraSync::SvnThread::isPathCached(const WaitingSyncItem& next) const
603 CompletedTiles::const_iterator ii = _completedTiles.find( next._dir );
604 if (ii == _completedTiles.end()) {
608 // check if the path still physically exists
609 SGPath p(_local_dir);
615 time_t now = time(0);
616 return (ii->second > now);
619 void SGTerraSync::SvnThread::fail(const WaitingSyncItem& failedItem)
621 time_t now = time(0);
622 _consecutive_errors++;
624 _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
627 void SGTerraSync::SvnThread::updated(const WaitingSyncItem& item, bool isNewDirectory)
629 time_t now = time(0);
630 _consecutive_errors = 0;
632 SG_LOG(SG_TERRAIN,SG_INFO,
633 "Successfully synchronized directory '" << item._dir << "'");
634 if (item._refreshScenery) {
636 _updated_tile_count++;
639 // for now only report new directories to refresh display
640 // (i.e. only when ocean needs to be replaced with actual data)
641 _freshTiles.push_back(item);
646 _completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
647 writeCompletedTilesPersistentCache();
650 void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
652 if (!_persistentCachePath.exists()) {
656 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
657 time_t now = time(0);
659 readProperties(_persistentCachePath.str(), cacheRoot);
660 for (int i=0; i<cacheRoot->nChildren(); ++i) {
661 SGPropertyNode* entry = cacheRoot->getChild(i);
662 string tileName = entry->getStringValue("path");
663 time_t stamp = entry->getIntValue("stamp");
668 _completedTiles[tileName] = stamp;
672 void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
675 if (_persistentCachePath.isNull()) {
679 std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
684 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
685 CompletedTiles::const_iterator it = _completedTiles.begin();
686 for (; it != _completedTiles.end(); ++it) {
687 SGPropertyNode* entry = cacheRoot->addChild("entry");
688 entry->setStringValue("path", it->first);
689 entry->setIntValue("stamp", it->second);
692 writeProperties(f, cacheRoot, true /* write_all */);
696 ///////////////////////////////////////////////////////////////////////////////
697 // SGTerraSync ////////////////////////////////////////////////////////////////
698 ///////////////////////////////////////////////////////////////////////////////
699 SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
703 _terraRoot(root->getNode("/sim/terrasync",true)),
709 _svnThread = new SvnThread();
710 _log = new BufferedLogCallback(SG_TERRAIN, SG_INFO);
711 _log->truncateAt(255);
713 sglog().addCallback(_log);
716 SGTerraSync::~SGTerraSync()
718 _tiedProperties.Untie();
721 sglog().removeCallback(_log);
725 void SGTerraSync::init()
732 _refreshDisplay = _terraRoot->getNode("refresh-display",true);
733 _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available);
737 void SGTerraSync::reinit()
739 // do not reinit when enabled and we're already up and running
740 if ((_terraRoot->getBoolValue("enabled",false))&&
741 (_svnThread->_active && _svnThread->_running))
748 if (_terraRoot->getBoolValue("enabled",false))
750 _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
751 _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
752 _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
753 _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5));
754 _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
755 _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
756 _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
757 _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
758 _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
760 if (_svnThread->start())
762 syncAirportsModels();
763 if (last_lat != NOWHERE && last_lon != NOWHERE)
765 // reschedule most recent position
770 schedulePosition(lat, lon);
775 _stalledNode->setBoolValue(_svnThread->_stalled);
778 void SGTerraSync::bind()
785 _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
786 _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
787 _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
788 _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
789 _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
790 _tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_svnThread->_cache_hits );
792 _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
793 _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
794 _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
795 _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
796 _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
797 _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
798 _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
799 // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
800 _stalledNode = _terraRoot->getNode("stalled", true);
801 _stalledNode->setBoolValue(_svnThread->_stalled);
802 _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
805 void SGTerraSync::unbind()
808 _tiedProperties.Untie();
813 void SGTerraSync::update(double)
815 static SGBucket bucket;
816 if (_svnThread->isDirty())
818 if (!_svnThread->_active)
820 if (_svnThread->_stalled)
822 SG_LOG(SG_TERRAIN,SG_ALERT,
823 "Automatic scenery download/synchronization stalled. Too many errors.");
827 // not really an alert - just always show this message
828 SG_LOG(SG_TERRAIN,SG_ALERT,
829 "Automatic scenery download/synchronization has stopped.");
831 _stalledNode->setBoolValue(_svnThread->_stalled);
834 if (!_refreshDisplay->getBoolValue())
837 while (_svnThread->hasNewTiles())
839 WaitingSyncItem next = _svnThread->getNewTile();
840 if (next._refreshScenery)
842 refreshScenery(_svnThread->getLocalDir(),next._dir);
848 void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
850 // find tiles to be refreshed
853 path.append(relativeDir);
856 simgear::Dir dir(path);
857 //TODO need to be smarter here. only update tiles which actually
858 // changed recently. May also be possible to use information from the
859 // built-in SVN client directly (instead of checking directory contents).
860 PathList tileList = dir.children(simgear::Dir::TYPE_FILE, ".stg");
861 for (unsigned int i=0; i<tileList.size(); ++i)
863 // reload scenery tile
864 long index = atoi(tileList[i].file().c_str());
865 _refreshCb(_userCbData, index);
871 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
873 void SGTerraSync::setTileRefreshCb(SGTerraSyncCallback refreshCb, void* userCbData)
875 _refreshCb = refreshCb;
876 _userCbData = userCbData;
879 void SGTerraSync::syncAirportsModels()
881 static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
882 // note "request" method uses LIFO order, i.e. processes most recent request first
883 for( unsigned i = 0; i < strlen(bounds)/2; i++ )
885 for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
888 dir << "Airports/" << synced_other;
889 WaitingSyncItem w(dir.str(), WaitingSyncItem::AirportData);
890 _svnThread->request( w );
894 WaitingSyncItem w("Models", WaitingSyncItem::SharedModels);
895 _svnThread->request( w );
899 void SGTerraSync::syncArea( int lat, int lon )
901 if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
904 int baselat, baselon;
907 int base = (int)(lat / 10);
908 if ( lat == base * 10 ) {
911 baselat = (base - 1) * 10;
915 baselat = (int)(lat / 10) * 10;
919 int base = (int)(lon / 10);
920 if ( lon == base * 10 ) {
923 baselon = (base - 1) * 10;
927 baselon = (int)(lon / 10) * 10;
931 const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
933 for (const char** tree = &terrainobjects[0]; *tree; tree++)
936 dir << *tree << "/" << setfill('0')
937 << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
938 << EW << setw(3) << abs(lon) << NS << setw(2) << abs(lat);
940 WaitingSyncItem w(dir.str(), WaitingSyncItem::Tile);
945 _svnThread->request( w );
951 void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
953 if ( lat_dir == 0 && lon_dir == 0 ) {
954 // do surrounding 8 1x1 degree areas.
955 for ( int i = lat - 1; i <= lat + 1; ++i ) {
956 for ( int j = lon - 1; j <= lon + 1; ++j ) {
957 if ( i != lat || j != lon ) {
963 if ( lat_dir != 0 ) {
964 syncArea( lat + lat_dir, lon - 1 );
965 syncArea( lat + lat_dir, lon + 1 );
966 syncArea( lat + lat_dir, lon );
968 if ( lon_dir != 0 ) {
969 syncArea( lat - 1, lon + lon_dir );
970 syncArea( lat + 1, lon + lon_dir );
971 syncArea( lat, lon + lon_dir );
975 // do current 1x1 degree area first
976 syncArea( lat, lon );
980 bool SGTerraSync::schedulePosition(int lat, int lon)
984 // Ignore messages where the location does not change
985 if ( lat != last_lat || lon != last_lon )
987 if (_svnThread->_running)
989 SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " <<
993 if ( last_lat != NOWHERE && last_lon != NOWHERE )
995 int dist = lat - last_lat;
998 lat_dir = dist / abs(dist);
1004 dist = lon - last_lon;
1007 lon_dir = dist / abs(dist);
1014 SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " <<
1015 "lat = " << lat << ", lon = " << lon <<
1016 ", lat_dir = " << lat_dir << ", " <<
1017 "lon_dir = " << lon_dir);
1019 syncAreas( lat, lon, lat_dir, lon_dir );