]> git.mxchange.org Git - flightgear.git/blob - utils/TerraSync/terrasync.cxx
merged
[flightgear.git] / utils / TerraSync / terrasync.cxx
1 // terrasync.cxx -- "JIT" scenery fetcher
2 //
3 // Written by Curtis Olson, started November 2002.
4 //
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>
8 //
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.
13 //
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.
18 //
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.
22 //
23 // $Id$
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <simgear/simgear_config.h>
30
31 #ifdef HAVE_WINDOWS_H
32 #include <windows.h>
33 #endif
34
35 #ifdef HAVE_UNISTD_H
36 #include "unistd.h"
37 #endif
38
39 #ifdef __MINGW32__
40 #   include <time.h>
41 #elif defined(_MSC_VER)
42 #   include <io.h>
43 #endif
44
45 #include <stdlib.h>             // atoi() atof() abs() system()
46 #include <signal.h>             // signal()
47
48 #include <simgear/compiler.h>
49
50 #include <iostream>
51 #include <fstream>
52 #include <string>
53
54 #include <simgear/io/raw_socket.hxx>
55 #include <simgear/scene/tsync/terrasync.hxx>
56
57 #if defined(_MSC_VER) || defined(__MINGW32__)
58     typedef void (__cdecl * sighandler_t)(int);
59 #elif defined( __APPLE__ ) || defined (__FreeBSD__)
60     typedef sig_t sighandler_t;
61 #endif
62
63 int termination_triggering_signals[] = {
64 #if defined(_MSC_VER) || defined(__MINGW32__)
65     SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, SIGBREAK, SIGABRT,
66 #else
67     SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGTERM,
68 #endif
69     0};  // zero terminated
70
71 using namespace std;
72
73 const char* svn_base = "http://terrascenery.googlecode.com/svn/trunk/data/Scenery";
74 const char* rsync_base = "flightgear.mxchange.org::Scenery";
75
76 bool terminating = false;
77 sighandler_t prior_signal_handlers[32];
78
79 simgear::Socket theSocket;
80 simgear::SGTerraSync* pTerraSync = NULL;
81
82 /** display usage information */
83 static void
84 usage( const string& prog ) {
85     cout << "Usage:  terrasync [options]\n"
86             "Options:\n"
87             " -d <dest>       destination directory [required]\n"
88             " -R              transport using pipe to rsync\n"
89             " -S              transport using built-in svn\n"
90             " -e              transport using external svn client\n"
91             " -p <port>       listen on UDP port [default: 5501]\n"
92             " -s <source>     source base [default: '']\n"
93 #ifndef _MSC_VER
94             " -pid <pidfile>  write PID to file\n"
95 #endif
96             " -v              be more verbose\n";
97
98 #ifndef _MSC_VER
99     cout << "\n"
100             "Example:\n"
101             "  pid=$(cat $pidfile 2>/dev/null)\n"
102             "  if test -n \"$pid\" && kill -0 $pid ; then\n"
103             "      echo \"terrasync already running: $pid\"\n"
104             "  else\n"
105             "      nice /games/sport/fgs/utils/TerraSync/terrasync         \\\n"
106             "        -v -pid $pidfile -S -p 5500 -d /games/orig/terrasync &\n"
107             "  fi\n";
108 #endif
109 }
110
111 /** Signal handler for termination requests (Ctrl-C) */
112 void
113 terminate_request_handler(int param)
114 {
115     char msg[] = "\nReceived signal XX, intend to exit soon.\n"
116          "Repeat the signal to force immediate termination.\n";
117     msg[17] = '0' + param / 10;
118     msg[18] = '0' + param % 10;
119     if (write(1, msg, sizeof(msg) - 1) == -1)
120     {
121         // need to act write's return value to avoid GCC compiler warning
122         // "'write' declared with attribute warn_unused_result"
123         terminating = true; // dummy operation
124     }
125     terminating = true;
126     signal(param, prior_signal_handlers[param]);
127     theSocket.close();
128     if (pTerraSync)
129         pTerraSync->unbind();
130 }
131
132 /** Parse command-line options. */
133 void
134 parseOptions(int argc, char** argv, SGPropertyNode_ptr config,
135              bool& testing, int& verbose, int& port, const char* &pidfn)
136 {
137     // parse arguments
138     for (int i=1; i < argc; ++i )
139     {
140         string arg = argv[i];
141         if ( arg == "-p" ) {
142             ++i;
143             port = atoi( argv[i] );
144         } else if ( arg.find("-pid") == 0 ) {
145             ++i;
146             pidfn = argv[i];
147             cout << "pidfn: " << pidfn << endl;
148         } else if ( arg == "-s" ) {
149             ++i;
150             config->setStringValue("svn-server", argv[i]);
151         } else if ( arg == "-d" ) {
152             ++i;
153             config->setStringValue("scenery-dir", argv[i]);
154         } else if ( arg == "-R" ) {
155             config->setBoolValue("use-svn", false);
156         } else if ( arg == "-S" ) {
157             config->setBoolValue("use-built-in-svn", true);
158         } else if ( arg == "-e" ) {
159             config->setBoolValue("use-built-in-svn", false);
160         } else if ( arg == "-v" ) {
161             verbose++;
162         } else if ( arg == "-T" ) {
163             testing = true;
164         } else if ( arg == "-h" ) {
165             usage( argv[0] );
166             exit(0);
167         } else {
168             cerr << "Unrecognized command-line option '" << arg << "'" << endl;
169             usage( argv[0] );
170             exit(-1);
171         }
172     }
173 }
174
175 /** parse a single NMEA-0183 position message */
176 static void
177 parse_message( int verbose, const string &msg, int *lat, int *lon )
178 {
179     double dlat, dlon;
180     string text = msg;
181
182     if (verbose>=3)
183         cout << "message: '" << text << "'" << endl;
184
185     // find GGA string and advance to start of lat (see NMEA-0183 for protocol specs)
186     string::size_type pos = text.find( "$GPGGA" );
187     if ( pos == string::npos )
188     {
189         *lat = simgear::NOWHERE;
190         *lon = simgear::NOWHERE;
191         return;
192     }
193     string tmp = text.substr( pos + 7 );
194     pos = tmp.find( "," );
195     tmp = tmp.substr( pos + 1 );
196     // cout << "-> " << tmp << endl;
197
198     // find lat then advance to start of hemisphere
199     pos = tmp.find( "," );
200     string lats = tmp.substr( 0, pos );
201     dlat = atof( lats.c_str() ) / 100.0;
202     tmp = tmp.substr( pos + 1 );
203
204     // find N/S hemisphere and advance to start of lon
205     if ( tmp.substr( 0, 1 ) == "S" ) {
206         dlat = -dlat;
207     }
208     pos = tmp.find( "," );
209     tmp = tmp.substr( pos + 1 );
210
211     // find lon
212     pos = tmp.find( "," );
213     string lons = tmp.substr( 0, pos );
214     dlon = atof( lons.c_str() ) / 100.0;
215     tmp = tmp.substr( pos + 1 );
216
217     // find E/W hemisphere and advance to start of lon
218     if ( tmp.substr( 0, 1 ) == "W" ) {
219         dlon = -dlon;
220     }
221
222     if ( dlat < 0 ) {
223         *lat = (int)dlat - 1;
224     } else {
225         *lat = (int)dlat;
226     }
227
228     if ( dlon < 0 ) {
229         *lon = (int)dlon - 1;
230     } else {
231         *lon = (int)dlon;
232     }
233
234     if ((dlon == 0) && (dlat == 0)) {
235         *lon = simgear::NOWHERE;
236         *lat = simgear::NOWHERE;
237     }
238 }
239
240 void
241 syncArea( int lat, int lon )
242 {
243     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
244         return;
245     char NS, EW;
246     int baselat, baselon;
247
248     if ( lat < 0 ) {
249         int base = (int)(lat / 10);
250         if ( lat == base * 10 ) {
251             baselat = base * 10;
252         } else {
253             baselat = (base - 1) * 10;
254         }
255         NS = 's';
256     } else {
257         baselat = (int)(lat / 10) * 10;
258         NS = 'n';
259     }
260     if ( lon < 0 ) {
261         int base = (int)(lon / 10);
262         if ( lon == base * 10 ) {
263             baselon = base * 10;
264         } else {
265             baselon = (base - 1) * 10;
266         }
267         EW = 'w';
268     } else {
269         baselon = (int)(lon / 10) * 10;
270         EW = 'e';
271     }
272
273     ostringstream dir;
274     dir << setfill('0')
275     << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
276     << EW << setw(3) << abs(lon)     << NS << setw(2) << abs(lat);
277     
278     pTerraSync->syncAreaByPath(dir.str());
279 }
280
281 void 
282 syncAreas( int lat, int lon, int lat_dir, int lon_dir )
283 {
284     if ( lat_dir == 0 && lon_dir == 0 ) {
285         
286         // do surrounding 8 1x1 degree areas.
287         for ( int i = lat - 1; i <= lat + 1; ++i ) {
288             for ( int j = lon - 1; j <= lon + 1; ++j ) {
289                 if ( i != lat || j != lon ) {
290                     syncArea( i, j );
291                 }
292             }
293         }
294     } else {
295         if ( lat_dir != 0 ) {
296             syncArea( lat + lat_dir, lon - 1 );
297             syncArea( lat + lat_dir, lon + 1 );
298             syncArea( lat + lat_dir, lon );
299         }
300         if ( lon_dir != 0 ) {
301             syncArea( lat - 1, lon + lon_dir );
302             syncArea( lat + 1, lon + lon_dir );
303             syncArea( lat, lon + lon_dir );
304         }
305     }
306
307     // do current 1x1 degree area first
308     syncArea( lat, lon );
309 }
310
311 bool 
312 schedulePosition(int lat, int lon)
313 {
314     bool Ok = false;
315     if ((lat == simgear::NOWHERE) || (lon == simgear::NOWHERE)) {
316         return Ok;
317     }
318     
319     static int last_lat = simgear::NOWHERE;
320     static int last_lon = simgear::NOWHERE;
321     if ((lat == last_lat) && (lon == last_lon)) {
322         Ok = true;
323         return Ok;
324     }
325
326     int lat_dir=0;
327     int lon_dir=0;
328     
329     if ( last_lat != simgear::NOWHERE && last_lon != simgear::NOWHERE )
330     {
331         int dist = lat - last_lat;
332         if ( dist != 0 )
333         {
334             lat_dir = dist / abs(dist);
335         }
336         else
337         {
338             lat_dir = 0;
339         }
340         dist = lon - last_lon;
341         if ( dist != 0 )
342         {
343             lon_dir = dist / abs(dist);
344         } else
345         {
346             lon_dir = 0;
347         }
348     }
349
350     cout << "Scenery update for " <<
351            "lat = " << lat << ", lon = " << lon <<
352            ", lat_dir = " << lat_dir << ",  " <<
353            "lon_dir = " << lon_dir << endl;
354
355     syncAreas( lat, lon, lat_dir, lon_dir );
356     Ok = true;
357
358     last_lat = lat;
359     last_lon = lon;
360
361     return Ok;
362 }
363     
364 /** Monitor UDP socket and process NMEA position updates. */
365 int
366 processRequests(SGPropertyNode_ptr config, bool testing, int verbose, int port)
367 {
368     const char* host = "localhost";
369     char msg[256];
370     int len;
371     int lat, lon;
372     bool connected = false;
373
374     // Must call this before any other net stuff
375     simgear::Socket::initSockets();
376
377     // open UDP socket
378     if ( !theSocket.open( false ) )
379     {
380         cerr << "error opening socket" << endl;
381         return -1;
382     }
383
384     if ( theSocket.bind( host, port ) == -1 )
385     {
386         cerr << "error binding to port " << port << endl;
387         return -1;
388     }
389
390     theSocket.setBlocking(true);
391
392     while ( (!terminating)&&
393             (!config->getBoolValue("stalled", false)) )
394     {
395         if (verbose >= 4)
396             cout << "main loop" << endl;
397         // main loop
398         bool recv_msg = false;
399         if ( testing )
400         {
401             // Testing without FGFS communication
402             lat = 37;
403             lon = -123;
404             recv_msg = true;
405         } else
406         {
407             if (verbose && pTerraSync->isIdle())
408             {
409                 cout << "Idle; waiting for FlightGear position data" << endl;
410             }
411             len = theSocket.recv(msg, sizeof(msg)-1, 0);
412             if (len >= 0)
413             {
414                 msg[len] = '\0';
415                 recv_msg = true;
416                 if (verbose>=2)
417                     cout << "recv length: " << len << endl;
418                 parse_message( verbose, msg, &lat, &lon );
419                 if ((!connected)&&
420                     (lat != simgear::NOWHERE)&&
421                     (lon != simgear::NOWHERE))
422                 {
423                     cout << "Valid position data received. Connected successfully." << endl;
424                     connected = true;
425                 }
426             }
427         }
428
429         if ( recv_msg )
430         {
431             schedulePosition(lat, lon);
432         }
433
434         if ( testing )
435         {
436             if (pTerraSync->isIdle())
437                 terminating = true;
438             else
439                 SGTimeStamp::sleepForMSec(1000);
440         }
441     } // while !terminating
442
443     return 0;
444 }
445
446
447 int main( int argc, char **argv )
448 {
449     int port = 5501;
450     int verbose = 0;
451     int exit_code = 0;
452     bool testing = false;
453     const char* pidfn = "";
454
455     // default configuration
456     sglog().setLogLevels( SG_ALL, SG_ALERT);
457     SGPropertyNode_ptr root = new SGPropertyNode();
458     SGPropertyNode_ptr config = root->getNode("/sim/terrasync", true);
459     config->setStringValue("scenery-dir", "terrasyncdir");
460     config->setStringValue("svn-server", svn_base);
461     config->setStringValue("rsync-server", rsync_base);
462     config->setBoolValue("use-built-in-svn", true);
463     config->setBoolValue("use-svn", true);
464     config->setBoolValue("enabled", true);
465     config->setIntValue("max-errors", -1); // -1 = infinite
466
467     // parse command-line arguments
468     parseOptions(argc, argv, config, testing, verbose, port, pidfn);
469
470     if (verbose)
471         sglog().setLogLevels( SG_ALL, SG_INFO);
472
473 #ifndef _MSC_VER
474     // create PID file
475     if (*pidfn)
476     {
477         ofstream pidstream;
478         pidstream.open(pidfn);
479         if (!pidstream.good())
480         {
481             cerr << "Cannot open pid file '" << pidfn << "': ";
482             perror(0);
483             exit(2);
484         }
485         pidstream << getpid() << endl;
486         pidstream.close();
487     }
488 #endif
489
490     // install signal handlers
491     for (int* sigp=termination_triggering_signals; *sigp; sigp++)
492     {
493         prior_signal_handlers[*sigp] =
494             signal(*sigp, *terminate_request_handler);
495         if (verbose>=2)
496             cout << "Intercepting signal " << *sigp << endl;
497     }
498
499     {
500         pTerraSync = new simgear::SGTerraSync;
501         pTerraSync->setRoot(root);
502         pTerraSync->bind();
503         pTerraSync->init();
504
505         // now monitor and process position updates
506         exit_code = processRequests(config, testing, verbose, port);
507
508         pTerraSync->unbind();
509         delete pTerraSync;
510         pTerraSync = NULL;
511     }
512
513     return exit_code;
514 }